dyndnsd 0.0.1 → 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/lib/dyndnsd/generator/bind.rb +4 -4
- data/lib/dyndnsd/responder/dyndns_style.rb +21 -0
- data/lib/dyndnsd/responder/rest_style.rb +6 -4
- data/lib/dyndnsd/updater/command_with_bind_zone.rb +2 -2
- data/lib/dyndnsd/version.rb +1 -1
- data/lib/dyndnsd.rb +36 -14
- data/spec/daemon_spec.rb +67 -15
- metadata +12 -11
@@ -2,9 +2,9 @@
|
|
2
2
|
module Dyndnsd
|
3
3
|
module Generator
|
4
4
|
class Bind
|
5
|
-
def initialize(config)
|
5
|
+
def initialize(domain, config)
|
6
|
+
@domain = domain
|
6
7
|
@ttl = config['ttl']
|
7
|
-
@origin = config['origin']
|
8
8
|
@dns = config['dns']
|
9
9
|
@email_addr = config['email_addr']
|
10
10
|
end
|
@@ -12,13 +12,13 @@ module Dyndnsd
|
|
12
12
|
def generate(zone)
|
13
13
|
out = []
|
14
14
|
out << "$TTL #{@ttl}"
|
15
|
-
out << "$ORIGIN #{@
|
15
|
+
out << "$ORIGIN #{@domain}."
|
16
16
|
out << ""
|
17
17
|
out << "@ IN SOA #{@dns} #{@email_addr} ( #{zone['serial']} 3h 5m 1w 1h )"
|
18
18
|
out << "@ IN NS #{@dns}"
|
19
19
|
out << ""
|
20
20
|
zone['hosts'].each do |hostname,ip|
|
21
|
-
name = hostname.chomp('.' + @
|
21
|
+
name = hostname.chomp('.' + @domain)
|
22
22
|
out << "#{name} IN A #{ip}"
|
23
23
|
end
|
24
24
|
out << ""
|
@@ -0,0 +1,21 @@
|
|
1
|
+
|
2
|
+
module Dyndnsd
|
3
|
+
module Responder
|
4
|
+
class DynDNSStyle
|
5
|
+
def response_for_error(state)
|
6
|
+
# general http errors
|
7
|
+
return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden
|
8
|
+
return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found
|
9
|
+
# specific errors
|
10
|
+
return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_missing
|
11
|
+
return [200, {"Content-Type" => "text/plain"}, ["nohost"]] if state == :host_forbidden
|
12
|
+
return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_malformed
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_for_changes(states, ip)
|
16
|
+
body = states.map { |state| "#{state} #{ip}" }.join("\n")
|
17
|
+
return [200, {"Content-Type" => "text/plain"}, [body]]
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
module Dyndnsd
|
3
3
|
module Responder
|
4
4
|
class RestStyle
|
5
|
-
def
|
5
|
+
def response_for_error(state)
|
6
6
|
# general http errors
|
7
7
|
return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden
|
8
8
|
return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found
|
@@ -10,9 +10,11 @@ module Dyndnsd
|
|
10
10
|
return [422, {"Content-Type" => "text/plain"}, ["Hostname missing"]] if state == :hostname_missing
|
11
11
|
return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] if state == :host_forbidden
|
12
12
|
return [422, {"Content-Type" => "text/plain"}, ["Hostname malformed"]] if state == :hostname_malformed
|
13
|
-
|
14
|
-
|
15
|
-
|
13
|
+
end
|
14
|
+
|
15
|
+
def response_for_changes(states, ip)
|
16
|
+
body = states.map { |state| state == :good ? "Changed to #{ip}" : "No change needed for #{ip}" }.join("\n")
|
17
|
+
return [200, {"Content-Type" => "text/plain"}, [body]]
|
16
18
|
end
|
17
19
|
end
|
18
20
|
end
|
@@ -2,10 +2,10 @@
|
|
2
2
|
module Dyndnsd
|
3
3
|
module Updater
|
4
4
|
class CommandWithBindZone
|
5
|
-
def initialize(config)
|
5
|
+
def initialize(domain, config)
|
6
6
|
@zone_file = config['zone_file']
|
7
7
|
@command = config['command']
|
8
|
-
@generator = Generator::Bind.new(config)
|
8
|
+
@generator = Generator::Bind.new(domain, config)
|
9
9
|
end
|
10
10
|
|
11
11
|
def update(zone)
|
data/lib/dyndnsd/version.rb
CHANGED
data/lib/dyndnsd.rb
CHANGED
@@ -3,10 +3,12 @@
|
|
3
3
|
require 'logger'
|
4
4
|
require 'ipaddr'
|
5
5
|
require 'json'
|
6
|
+
require 'yaml'
|
6
7
|
require 'rack'
|
7
8
|
|
8
9
|
require 'dyndnsd/generator/bind'
|
9
10
|
require 'dyndnsd/updater/command_with_bind_zone'
|
11
|
+
require 'dyndnsd/responder/dyndns_style'
|
10
12
|
require 'dyndnsd/responder/rest_style'
|
11
13
|
require 'dyndnsd/database'
|
12
14
|
require 'dyndnsd/version'
|
@@ -29,6 +31,7 @@ module Dyndnsd
|
|
29
31
|
class Daemon
|
30
32
|
def initialize(config, db, updater, responder)
|
31
33
|
@users = config['users']
|
34
|
+
@domain = config['domain']
|
32
35
|
@db = db
|
33
36
|
@updater = updater
|
34
37
|
@responder = responder
|
@@ -43,22 +46,34 @@ module Dyndnsd
|
|
43
46
|
@updater.update(@db)
|
44
47
|
end
|
45
48
|
|
49
|
+
def is_fqdn_valid?(hostname)
|
50
|
+
return false if hostname.length < @domain.length + 2
|
51
|
+
return false if not hostname.end_with?(@domain)
|
52
|
+
name = hostname.chomp(@domain)
|
53
|
+
return false if not name.match(/^[a-zA-Z0-9_-]+\.$/)
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
46
57
|
def call(env)
|
47
|
-
return @responder.
|
48
|
-
return @responder.
|
58
|
+
return @responder.response_for_error(:method_forbidden) if env["REQUEST_METHOD"] != "GET"
|
59
|
+
return @responder.response_for_error(:not_found) if env["PATH_INFO"] != "/nic/update"
|
49
60
|
|
50
61
|
params = Rack::Utils.parse_query(env["QUERY_STRING"])
|
51
62
|
|
52
|
-
return @responder.
|
63
|
+
return @responder.response_for_error(:hostname_missing) if not params["hostname"]
|
53
64
|
|
54
|
-
|
65
|
+
hostnames = params["hostname"].split(',')
|
55
66
|
|
56
|
-
# Check if hostname
|
57
|
-
|
67
|
+
# Check if hostname match rules
|
68
|
+
hostnames.each do |hostname|
|
69
|
+
return @responder.response_for_error(:hostname_malformed) if not is_fqdn_valid?(hostname)
|
70
|
+
end
|
58
71
|
|
59
72
|
user = env["REMOTE_USER"]
|
60
73
|
|
61
|
-
|
74
|
+
hostnames.each do |hostname|
|
75
|
+
return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname
|
76
|
+
end
|
62
77
|
|
63
78
|
# no myip?
|
64
79
|
if not params["myip"]
|
@@ -74,16 +89,23 @@ module Dyndnsd
|
|
74
89
|
|
75
90
|
myip = params["myip"]
|
76
91
|
|
77
|
-
|
92
|
+
changes = []
|
93
|
+
hostnames.each do |hostname|
|
94
|
+
if (not @db['hosts'].include? hostname) or (@db['hosts'][hostname] != myip)
|
95
|
+
changes << :good
|
96
|
+
@db['hosts'][hostname] = myip
|
97
|
+
else
|
98
|
+
changes << :nochg
|
99
|
+
end
|
100
|
+
end
|
78
101
|
|
79
102
|
if @db.changed?
|
80
103
|
@db['serial'] += 1
|
81
104
|
@db.save
|
82
105
|
update
|
83
|
-
return @responder.response_for(:good)
|
84
106
|
end
|
85
107
|
|
86
|
-
@responder.
|
108
|
+
@responder.response_for_changes(changes, myip)
|
87
109
|
end
|
88
110
|
|
89
111
|
def self.run!
|
@@ -105,11 +127,11 @@ module Dyndnsd
|
|
105
127
|
Dyndnsd.logger.info "DynDNSd version #{Dyndnsd::VERSION}"
|
106
128
|
Dyndnsd.logger.info "Using config file #{config_file}"
|
107
129
|
|
108
|
-
config =
|
130
|
+
config = YAML::load(File.open(config_file, 'r') { |f| f.read })
|
109
131
|
|
110
|
-
db = Database.new(config['
|
111
|
-
updater = Updater::CommandWithBindZone.new(config['
|
112
|
-
responder = Responder::
|
132
|
+
db = Database.new(config['db'])
|
133
|
+
updater = Updater::CommandWithBindZone.new(config['domain'], config['updater']['params']) if config['updater']['name'] == 'command_with_bind_zone'
|
134
|
+
responder = Responder::DynDNSStyle.new
|
113
135
|
|
114
136
|
app = Daemon.new(config, db, updater, responder)
|
115
137
|
app = Rack::Auth::Basic.new(app, "DynDNS") do |user,pass|
|
data/spec/daemon_spec.rb
CHANGED
@@ -5,16 +5,17 @@ describe Dyndnsd::Daemon do
|
|
5
5
|
|
6
6
|
def app
|
7
7
|
config = {
|
8
|
+
'domain' => 'example.org',
|
8
9
|
'users' => {
|
9
10
|
'test' => {
|
10
11
|
'password' => 'secret',
|
11
|
-
'hosts' => ['foo.example.org']
|
12
|
+
'hosts' => ['foo.example.org', 'bar.example.org']
|
12
13
|
}
|
13
14
|
}
|
14
15
|
}
|
15
16
|
db = Dyndnsd::DummyDatabase.new({})
|
16
17
|
updater = Dyndnsd::Updater::Dummy.new
|
17
|
-
responder = Dyndnsd::Responder::
|
18
|
+
responder = Dyndnsd::Responder::DynDNSStyle.new
|
18
19
|
app = Dyndnsd::Daemon.new(config, db, updater, responder)
|
19
20
|
|
20
21
|
Rack::Auth::Basic.new(app, "DynDNS") do |user,pass|
|
@@ -25,6 +26,9 @@ describe Dyndnsd::Daemon do
|
|
25
26
|
it 'requires authentication' do
|
26
27
|
get '/'
|
27
28
|
last_response.status.should == 401
|
29
|
+
|
30
|
+
pending 'Need to find a way to add custom body on 401 responses'
|
31
|
+
last_response.should be_ok 'badauth'
|
28
32
|
end
|
29
33
|
|
30
34
|
it 'only supports GET requests' do
|
@@ -42,13 +46,54 @@ describe Dyndnsd::Daemon do
|
|
42
46
|
it 'requires the hostname query parameter' do
|
43
47
|
authorize 'test', 'secret'
|
44
48
|
get '/nic/update'
|
45
|
-
last_response.
|
49
|
+
last_response.should be_ok
|
50
|
+
last_response.body.should == 'notfqdn'
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'supports multiple hostnames in request' do
|
54
|
+
authorize 'test', 'secret'
|
55
|
+
get '/nic/update?hostname=foo.example.org,bar.example.org&myip=1.2.3.4'
|
56
|
+
last_response.should be_ok
|
57
|
+
last_response.body.should == "good 1.2.3.4\ngood 1.2.3.4"
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'rejects request if one hostname is invalid' do
|
61
|
+
authorize 'test', 'secret'
|
62
|
+
|
63
|
+
get '/nic/update?hostname=test'
|
64
|
+
last_response.should be_ok
|
65
|
+
last_response.body.should == 'notfqdn'
|
66
|
+
|
67
|
+
get '/nic/update?hostname=test.example.com'
|
68
|
+
last_response.should be_ok
|
69
|
+
last_response.body.should == 'notfqdn'
|
70
|
+
|
71
|
+
get '/nic/update?hostname=test.example.org.me'
|
72
|
+
last_response.should be_ok
|
73
|
+
last_response.body.should == 'notfqdn'
|
74
|
+
|
75
|
+
get '/nic/update?hostname=foo.test.example.org'
|
76
|
+
last_response.should be_ok
|
77
|
+
last_response.body.should == 'notfqdn'
|
78
|
+
|
79
|
+
get '/nic/update?hostname=in%20valid.example.org'
|
80
|
+
last_response.should be_ok
|
81
|
+
last_response.body.should == 'notfqdn'
|
82
|
+
|
83
|
+
get '/nic/update?hostname=valid.example.org,in.valid.example.org'
|
84
|
+
last_response.should be_ok
|
85
|
+
last_response.body.should == 'notfqdn'
|
46
86
|
end
|
47
87
|
|
48
|
-
it '
|
88
|
+
it 'rejects request if user does not own one hostname' do
|
49
89
|
authorize 'test', 'secret'
|
50
90
|
get '/nic/update?hostname=notmyhost.example.org'
|
51
|
-
last_response.
|
91
|
+
last_response.should be_ok
|
92
|
+
last_response.body.should == 'nohost'
|
93
|
+
|
94
|
+
get '/nic/update?hostname=foo.example.org,notmyhost.example.org'
|
95
|
+
last_response.should be_ok
|
96
|
+
last_response.body.should == 'nohost'
|
52
97
|
end
|
53
98
|
|
54
99
|
it 'updates a host on change' do
|
@@ -57,9 +102,9 @@ describe Dyndnsd::Daemon do
|
|
57
102
|
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4'
|
58
103
|
last_response.should be_ok
|
59
104
|
|
60
|
-
get '/nic/update?hostname=foo.example.org&myip=1.2.3.
|
105
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.40'
|
61
106
|
last_response.should be_ok
|
62
|
-
last_response.body.should == '
|
107
|
+
last_response.body.should == 'good 1.2.3.40'
|
63
108
|
end
|
64
109
|
|
65
110
|
it 'returns no change' do
|
@@ -70,18 +115,25 @@ describe Dyndnsd::Daemon do
|
|
70
115
|
|
71
116
|
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4'
|
72
117
|
last_response.should be_ok
|
73
|
-
last_response.body.should == '
|
74
|
-
end
|
75
|
-
|
76
|
-
it 'forbids invalid hostnames' do
|
77
|
-
pending
|
118
|
+
last_response.body.should == 'nochg 1.2.3.4'
|
78
119
|
end
|
79
120
|
|
80
121
|
it 'outputs status per hostname' do
|
81
|
-
|
122
|
+
authorize 'test', 'secret'
|
123
|
+
|
124
|
+
get '/nic/update?hostname=foo.example.org&myip=1.2.3.4'
|
125
|
+
last_response.should be_ok
|
126
|
+
last_response.body.should == 'good 1.2.3.4'
|
127
|
+
|
128
|
+
get '/nic/update?hostname=foo.example.org,bar.example.org&myip=1.2.3.4'
|
129
|
+
last_response.should be_ok
|
130
|
+
last_response.body.should == "nochg 1.2.3.4\ngood 1.2.3.4"
|
82
131
|
end
|
83
132
|
|
84
|
-
it '
|
85
|
-
|
133
|
+
it 'uses clients remote address if myip not specified' do
|
134
|
+
authorize 'test', 'secret'
|
135
|
+
get '/nic/update?hostname=foo.example.org'
|
136
|
+
last_response.should be_ok
|
137
|
+
last_response.body.should == 'good 127.0.0.1'
|
86
138
|
end
|
87
139
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dyndnsd
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2013-04-27 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rack
|
16
|
-
requirement: &
|
16
|
+
requirement: &70281418775580 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70281418775580
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: bundler
|
27
|
-
requirement: &
|
27
|
+
requirement: &70281418775080 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ~>
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '1.3'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70281418775080
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: rake
|
38
|
-
requirement: &
|
38
|
+
requirement: &70281418774660 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :development
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70281418774660
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: rspec
|
49
|
-
requirement: &
|
49
|
+
requirement: &70281418774200 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :development
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70281418774200
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rack-test
|
60
|
-
requirement: &
|
60
|
+
requirement: &70281418773780 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,7 +65,7 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70281418773780
|
69
69
|
description: A small, lightweight and extensible DynDNS server written with Ruby and
|
70
70
|
Rack.
|
71
71
|
email: chrnicolai@gmail.com
|
@@ -85,6 +85,7 @@ files:
|
|
85
85
|
- lib/dyndnsd.rb
|
86
86
|
- lib/dyndnsd/database.rb
|
87
87
|
- lib/dyndnsd/generator/bind.rb
|
88
|
+
- lib/dyndnsd/responder/dyndns_style.rb
|
88
89
|
- lib/dyndnsd/responder/rest_style.rb
|
89
90
|
- lib/dyndnsd/updater/command_with_bind_zone.rb
|
90
91
|
- lib/dyndnsd/version.rb
|