dyndyndong 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/dyndyndong ADDED
@@ -0,0 +1,219 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Copyleft shura. [shura1991@gmail.com]
4
+ #
5
+ # This file is part of DynDynDong.
6
+ #
7
+ # DynDynDong is free software: you can redistribute it and/or modify
8
+ # it under the terms of the GNU Affero General Public License as published
9
+ # by the Free Software Foundation, either version 3 of the License, or
10
+ # (at your option) any later version.
11
+ #
12
+ # DynDynDong is distributed in the hope that it will be useful,
13
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
14
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
+ # GNU Affero General Public License for more details.
16
+ #
17
+ # You should have received a copy of the GNU Affero General Public License
18
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
19
+
20
+ require 'dyndyndong'
21
+
22
+ def die(str)
23
+ STDERR.puts str
24
+ exit 1
25
+ end
26
+
27
+ def help
28
+ puts <<HELP
29
+ DynDynDong v#{DynDynDong::VERSION} is distributed under the GNU Affero General Public License.
30
+
31
+ USAGE: #{$0} [-d|-h|-1] [-c <config file>] [-D <delay>] [-s <service> <service args>]
32
+ -d|--daemon Run DynDynDong as a daemon (default no).
33
+ -h|--help Show this help.
34
+ -D|--delay Set time to wait between two cycles (default 600).
35
+ -1|--oneshot Do only a cycle (default no).
36
+ -c|--config Set alternative configuration file (default /etc/dyndyndong.conf).
37
+ -s|--service Start to parse a service, no defaults, you have to choose between:
38
+ - afraid For freedns.afraid.org
39
+ AVAILABLE ARGUMENTS:
40
+ -H|-host Set a single host or more hosts separated with ',' (comma), it can be repeated
41
+ - dyndns Fro dyndns.com
42
+ AVAILABLE ARGUMENTS:
43
+ -u|--username Set username for login, it can't be repeated
44
+ -p|--password Set password for login, it can't be repeated
45
+ -H|--host Set a single host or more hosts separated with ',' (comma), it can be repeated
46
+ -o|--offline Set a single host or more hosts separated with ',' (comma) to make offline, it
47
+ can be repeated
48
+ Parsing of services' arguments stop when an unrelated argument is given.
49
+ HELP
50
+ exit 0
51
+ end
52
+
53
+ CONFIG = '/etc/dyndyndong.conf'
54
+ DEFAULT_CONFIG = CONFIG.dup
55
+ oneshot = daemon = false
56
+
57
+ i, SERVICES = 0, {
58
+ :dyndns => [],
59
+ :afraid => []
60
+ }
61
+
62
+
63
+ while i < ARGV.size
64
+ parsed = false
65
+ case ARGV[i]
66
+ when '-d', '--daemon'
67
+ daemon = true
68
+ when '-h', '--help'
69
+ help
70
+ when /^-D/, '--delay'
71
+ if %w{-D --delay}.include?(ARGV[i])
72
+ delay = ARGV[i += 1]
73
+ else
74
+ delay = ARGV[i].gsub(/^-D/, '')
75
+ end
76
+ parsed = true
77
+ when '-1', '--oneshot'
78
+ parsed = oneshot = true
79
+ when /^-c/, '--config'
80
+ if %w{-c --config}.include?(ARGV[i])
81
+ CONFIG.replace(ARGV[i += 1])
82
+ else
83
+ CONFIG.replace(ARGV[i].gsub(/^-c/, ''))
84
+ end
85
+ parsed = true
86
+ when /^-s/, '--service'
87
+ if %w{-s --service}.include?(ARGV[i])
88
+ service = ARGV[i += 1]
89
+ else
90
+ service = ARGV[i].gsub(/^-s/, '')
91
+ end
92
+
93
+ case service
94
+ when 'dyndns'
95
+ srv = {:hosts => [], :offline => []}
96
+ while (i += 1) < ARGV.size
97
+ parsed = true
98
+ case ARGV[i]
99
+ when /^-u/, '--username'
100
+ die "Can't add more than one username" if srv[:username]
101
+
102
+ if %w{-u --username}.include?(ARGV[1])
103
+ srv[:username] = ARGV[i += 1]
104
+ else
105
+ srv[:username] = ARGV[i].gsub(/^-u/, '')
106
+ end
107
+ when /^-p/, '--password'
108
+ die "Can't add more than one password" if srv[:password]
109
+
110
+ if %w{-p --password}.include?(ARGV[1])
111
+ srv[:password] = ARGV[i += 1]
112
+ else
113
+ srv[:password] = ARGV[i].gsub(/^-p/, '')
114
+ end
115
+ when /^-H/, '--host'
116
+ if %w{-H --host}.include?(ARGV[i])
117
+ srv[:hosts] << ARGV[i += 1].split(',')
118
+ else
119
+ srv[:hosts] << ARGV[i].gsub(/^-H/, '').split(',')
120
+ end
121
+ when /^-o/, '--offline'
122
+ if %w{-o --offline}.include?(ARGV[i])
123
+ srv[:offline] << ARGV[i += 1].split(',')
124
+ else
125
+ srv[:offline] << ARGV[i].gsub(/^-o/).split(',')
126
+ end
127
+ else
128
+ parsed = false
129
+ break
130
+ end
131
+ end
132
+ srv[:hosts].flatten!
133
+ srv[:offline].flatten!
134
+ when 'afraid'
135
+ srv = []
136
+
137
+ while (i += 1) < ARGV.size
138
+ parsed = true
139
+ if ARGV[i] == '--host' or ARGV[i] == '-H'
140
+ srv += ARGV[i += 1].split(',').map {|h|
141
+ h.split(':')
142
+ }
143
+ elsif ARGV[i] =~ /^-H/
144
+ srv += ARGV[i].gsub(/^-H/, '').split(',').map {|h|
145
+ h.split(':')
146
+ }
147
+ else
148
+ parsed = false
149
+ break
150
+ end
151
+ end
152
+ else
153
+ die "Service #{service} doesn't exist"
154
+ end
155
+
156
+ i -= parsed ? 0 : 1
157
+ parsed = true
158
+
159
+ case service
160
+ when 'dyndns'
161
+ SERVICES[service.to_sym] << srv
162
+ when 'afraid'
163
+ SERVICES[service.to_sym] += srv
164
+ end
165
+ end
166
+
167
+ die "Unrecognized option #{ARGV[i].inspect}" if !parsed
168
+
169
+ i += 1
170
+ end
171
+
172
+ DAEMONIZE = daemon
173
+
174
+ DynDynDong.delay = delay if delay
175
+
176
+ die "You can't run client with --oneshot and --daemon simultaneously" if oneshot and DAEMONIZE
177
+
178
+ if SERVICES != {:dyndns=>[],:afraid=>[]}
179
+ if CONFIG == DEFAULT_CONFIG
180
+ SERVICES[:dyndns].each {|dyndns|
181
+ d = DynDynDong::DynDNS.new
182
+ d.username = dyndns[:username]
183
+ d.password = dyndns[:password]
184
+ dyndns[:hosts].each {|host|
185
+ d.host host
186
+ }
187
+ dyndns[:offline].each {|offline|
188
+ d.offline offline
189
+ }
190
+ }
191
+
192
+ SERVICES[:afraid].each {|afraid|
193
+ a = DynDynDong::Afraid.new
194
+ begin
195
+ a.host(*afraid)
196
+ rescue ArgumentError
197
+ die "Respect the syntax of afraid please."
198
+ end
199
+ }
200
+ else
201
+ die "You can't use a configure file with custom services"
202
+ end
203
+ else
204
+ begin
205
+ eval(File.read(CONFIG))
206
+ rescue Exception => e
207
+ die "Config error: #{e}"
208
+ end
209
+ end
210
+
211
+ if DAEMONIZE
212
+ DynDynDong::Daemon.start
213
+ else
214
+ if oneshot
215
+ DynDynDong::Client.start
216
+ else
217
+ DynDynDong::Client.start_loop
218
+ end
219
+ end
@@ -0,0 +1,38 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'dyndyndong/services'
19
+ require 'dyndyndong/dyndyndong'
20
+
21
+ module DynDynDong
22
+
23
+ class Client
24
+ def self.start
25
+ DynDynDong::Service.each {|s|
26
+ s.fetch
27
+ }
28
+ end
29
+
30
+ def self.start_loop
31
+ while true
32
+ self.start
33
+ sleep DynDynDong.delay
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,31 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'dyndyndong/client'
19
+ require 'daemons'
20
+
21
+ module DynDynDong
22
+
23
+ class Daemon
24
+ def self.start
25
+ Daemons.daemonize
26
+
27
+ DynDynDong::Client.start_loop
28
+ end
29
+ end
30
+
31
+ end
@@ -0,0 +1,27 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module DynDynDong
19
+ def self.delay
20
+ @delay || 600
21
+ end
22
+
23
+ def self.delay=(n)
24
+ raise Exception, "Insert a numeric value for delay" if !n.is_a?(Numeric)
25
+ @delay = n
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'net/http'
19
+
20
+ module DynDynDong
21
+
22
+ class Afraid < Service
23
+ def initialize(*args)
24
+ super(*args)
25
+ end
26
+
27
+ def host(domain, hash)
28
+ @hosts << [domain, hash]
29
+ end
30
+
31
+ def alias_host(domain, hash)
32
+ begin
33
+ res = Net::HTTP.get(URI.parse("http://freedns.afraid.org/dynamic/update.php?#{hash}"))
34
+ rescue Exception => e
35
+ return "#{e} -- skipping..."
36
+ end
37
+ res
38
+ end
39
+ end
40
+
41
+ end
@@ -0,0 +1,110 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'net/http'
19
+
20
+ module DynDynDong
21
+
22
+ class DynDNS < Service
23
+ MSGTABLE = {
24
+ 'badauth' => 'The username and password pair do not match a real user.',
25
+ '!donator' => 'Option available only to credited users.',
26
+ 'good' => 'The update was successful, and the hostname is now updated.',
27
+ 'nochg' => 'The update changed no settings, and is considered abusive.',
28
+ 'notfqdn' => 'The hostname specified is not a fully-qualified domain name.',
29
+ 'nohost' => 'The hostname specified does not exist in this user account.',
30
+ 'numhost' => 'Too many hosts specified in an update.',
31
+ 'abuse' => 'The hostname specified is blocked for update abuse.',
32
+ 'badagent' => 'The user agent was not sent or HTTP method is not permitted.',
33
+ 'good 127.0.0.1' => 'Request was ignored because of agent that does not follow our specifications.',
34
+ 'dnserr' => 'DNS error encountered.',
35
+ '911' => 'There is a problem or scheduled maintenance on our side.'
36
+ }
37
+
38
+ def initialize(*args)
39
+ @user = nil
40
+ @pass = nil
41
+ @ip = getip
42
+ super(*args)
43
+ end
44
+
45
+ def username(u)
46
+ @user = u.to_s
47
+ end
48
+
49
+ def password(p)
50
+ @pass = p.to_s
51
+ end
52
+
53
+ alias username= username
54
+ alias password= password
55
+
56
+ def host(h)
57
+ begin
58
+ @hosts << [h.to_s, getip(h)]
59
+ rescue Exception => e
60
+ STDERR.puts "Ip detection error: #{e}, skipping..."
61
+ end
62
+ end
63
+
64
+ def offline(h)
65
+ begin
66
+ @hosts << [h.to_s, getip(h), true]
67
+ rescue Exception => e
68
+ STDERR.puts "Ip detection error: #{e}, skipping..."
69
+ end
70
+ end
71
+
72
+ def prefetch
73
+ if !(@user and @pass)
74
+ raise "username or password ungiven"
75
+ end
76
+ end
77
+
78
+ def alias_host(h, ip, offline = false)
79
+ return "Nothing to update." if ip == @ip
80
+
81
+ Net::HTTP.start('members.dyndns.org') {|http|
82
+ req = Net::HTTP::Get.new('/nic/update?hostname=%s&myip=%s&offline=%s' % [
83
+ URI.escape(h), @ip, (offline ? 'YES' : 'NOCHG')])
84
+ req.basic_auth @user, @pass
85
+ x = http.request(req).body.gsub(/#{Regexp.escape(@ip)}/, '').strip
86
+ MSGTABLE[x]
87
+ }
88
+ end
89
+
90
+ private
91
+ def getip(h = nil)
92
+ if h
93
+ addr = Socket::getaddrinfo(h, 'www', nil, Socket::SOCK_STREAM)[0][3]
94
+ if addr == Socket::getaddrinfo(Socket::gethostname, 'www', nil, Socket::SOCK_STREAM)[0][3]
95
+ return @ip
96
+ else
97
+ return addr
98
+ end
99
+ end
100
+ str = Net::HTTP.get(URI.parse('http://checkip.dyndns.org/')).
101
+ match(/<body>(.+?)<\/body>/)[1] rescue ""
102
+ if str =~ /^Current IP Address: \d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/
103
+ return str.gsub(/^Current IP Address: /, '')
104
+ else
105
+ raise "Couldn't retrieve ip."
106
+ end
107
+ end
108
+ end
109
+
110
+ end
@@ -0,0 +1,91 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module DynDynDong
19
+
20
+ class Service
21
+
22
+ def initialize(&block)
23
+ @@instances ||= []
24
+ @@instances << self
25
+ @hosts = []
26
+ self.instance_eval(&block) if block
27
+ end
28
+
29
+ def fetch
30
+ begin
31
+ prefetch if (self.method(:prefetch) rescue nil)
32
+ rescue Exception => e
33
+ STDERR.puts("Prefetch error: #{e}, skipping...")
34
+ return
35
+ end
36
+
37
+ @hosts.each {|args|
38
+ STDOUT.write("Aliasing #{args.first}...")
39
+ begin
40
+ res = alias_host(*args)
41
+ STDOUT.write("\r--- #{args.first} \n\t#{res.gsub(/\n/, "\n\t")}\n")
42
+ rescue Exception => e
43
+ STDERR.puts("\tFetch error: #{e}, skipping...")
44
+ end
45
+ }
46
+
47
+ begin
48
+ postfetch if self.method(:postfetch) rescue nil
49
+ rescue Exception => e
50
+ STDERR.puts("Postfetch error: #{e}")
51
+ end
52
+ end
53
+
54
+ def method_missing(sym, *args, &block)
55
+ STDERR.puts("#{sym.to_s.inspect} missed for #{self.class}, skipping...")
56
+ end
57
+
58
+ def self.instances
59
+ @@instances ||=[]
60
+ @@instances
61
+ end
62
+
63
+ def self.inherited(obj)
64
+ @@services ||= []
65
+ @@services << obj
66
+ end
67
+
68
+ def self.services
69
+ @@services ||= []
70
+ @@services
71
+ end
72
+
73
+ def self.each
74
+ objs = []
75
+ self.instances.each {|i|
76
+ objs << i
77
+ yield i if block_given?
78
+ }
79
+ if block_given?
80
+ objs
81
+ else
82
+ Enumerator.new(objs)
83
+ end
84
+ end
85
+
86
+ end
87
+
88
+ end
89
+
90
+ require 'dyndyndong/services/afraid'
91
+ require 'dyndyndong/services/dyndns'
@@ -0,0 +1,22 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ module DynDynDong
19
+
20
+ VERSION = '0.0.1'
21
+
22
+ end
data/lib/dyndyndong.rb ADDED
@@ -0,0 +1,19 @@
1
+ # Copyleft shura. [shura1991@gmail.com]
2
+ #
3
+ # This file is part of DynDynDong.
4
+ #
5
+ # DynDynDong is free software: you can redistribute it and/or modify
6
+ # it under the terms of the GNU Affero General Public License as published
7
+ # by the Free Software Foundation, either version 3 of the License, or
8
+ # (at your option) any later version.
9
+ #
10
+ # DynDynDong is distributed in the hope that it will be useful,
11
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ # GNU Affero General Public License for more details.
14
+ #
15
+ # You should have received a copy of the GNU Affero General Public License
16
+ # along with failirc. If not, see <http://www.gnu.org/licenses/>.
17
+
18
+ require 'dyndyndong/client'
19
+ require 'dyndyndong/daemon'
metadata ADDED
@@ -0,0 +1,84 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dyndyndong
3
+ version: !ruby/object:Gem::Version
4
+ prerelease: false
5
+ segments:
6
+ - 0
7
+ - 0
8
+ - 1
9
+ version: 0.0.1
10
+ platform: ruby
11
+ authors:
12
+ - shura
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+
17
+ date: 2010-11-02 00:00:00 +01:00
18
+ default_executable:
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: daemons
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ segments:
29
+ - 0
30
+ version: "0"
31
+ type: :runtime
32
+ version_requirements: *id001
33
+ description: Ruby written dyndns client.
34
+ email: shura1991@gmail.com
35
+ executables:
36
+ - dyndyndong
37
+ extensions: []
38
+
39
+ extra_rdoc_files: []
40
+
41
+ files:
42
+ - lib/dyndyndong.rb
43
+ - lib/dyndyndong/services.rb
44
+ - lib/dyndyndong/version.rb
45
+ - lib/dyndyndong/services/dyndns.rb
46
+ - lib/dyndyndong/services/afraid.rb
47
+ - lib/dyndyndong/dyndyndong.rb
48
+ - lib/dyndyndong/daemon.rb
49
+ - lib/dyndyndong/client.rb
50
+ - bin/dyndyndong
51
+ has_rdoc: true
52
+ homepage: http://github.com/shurizzle/DynDynDong
53
+ licenses: []
54
+
55
+ post_install_message:
56
+ rdoc_options: []
57
+
58
+ require_paths:
59
+ - lib
60
+ required_ruby_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ segments:
66
+ - 0
67
+ version: "0"
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ">="
72
+ - !ruby/object:Gem::Version
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ requirements: []
77
+
78
+ rubyforge_project:
79
+ rubygems_version: 1.3.7
80
+ signing_key:
81
+ specification_version: 3
82
+ summary: Ruby written client to update dynamic dns, it supports many services.
83
+ test_files: []
84
+