diff --git a/lib/dyndnsd.rb b/lib/dyndnsd.rb index 1563ee3..67c7c3d 100644 --- a/lib/dyndnsd.rb +++ b/lib/dyndnsd.rb @@ -55,21 +55,25 @@ module Dyndnsd end def call(env) - return @responder.response_for(:method_forbidden) if env["REQUEST_METHOD"] != "GET" - return @responder.response_for(:not_found) if env["PATH_INFO"] != "/nic/update" + return @responder.response_for_error(:method_forbidden) if env["REQUEST_METHOD"] != "GET" + return @responder.response_for_error(:not_found) if env["PATH_INFO"] != "/nic/update" params = Rack::Utils.parse_query(env["QUERY_STRING"]) - return @responder.response_for(:hostname_missing) if not params["hostname"] + return @responder.response_for_error(:hostname_missing) if not params["hostname"] - hostname = params["hostname"] + hostnames = params["hostname"].split(',') # Check if hostname match rules - return @responder.response_for(:hostname_malformed) if not is_fqdn_valid?(hostname) + hostnames.each do |hostname| + return @responder.response_for_error(:hostname_malformed) if not is_fqdn_valid?(hostname) + end user = env["REMOTE_USER"] - return @responder.response_for(:host_forbidden) if not @users[user]['hosts'].include? hostname + hostnames.each do |hostname| + return @responder.response_for_error(:host_forbidden) if not @users[user]['hosts'].include? hostname + end # no myip? if not params["myip"] @@ -85,16 +89,23 @@ module Dyndnsd myip = params["myip"] - @db['hosts'][hostname] = myip + changes = [] + hostnames.each do |hostname| + if (not @db['hosts'].include? hostname) or (@db['hosts'][hostname] != myip) + changes << :good + @db['hosts'][hostname] = myip + else + changes << :nochg + end + end if @db.changed? @db['serial'] += 1 @db.save update - return @responder.response_for(:good, myip) end - @responder.response_for(:nochg, myip) + @responder.response_for_changes(changes, myip) end def self.run! diff --git a/lib/dyndnsd/responder/dyndns_style.rb b/lib/dyndnsd/responder/dyndns_style.rb index 584f73a..2bef220 100644 --- a/lib/dyndnsd/responder/dyndns_style.rb +++ b/lib/dyndnsd/responder/dyndns_style.rb @@ -2,7 +2,7 @@ module Dyndnsd module Responder class DynDNSStyle - def response_for(state, ip = nil) + def response_for_error(state) # general http errors return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found @@ -10,9 +10,11 @@ module Dyndnsd return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_missing return [200, {"Content-Type" => "text/plain"}, ["nohost"]] if state == :host_forbidden return [200, {"Content-Type" => "text/plain"}, ["notfqdn"]] if state == :hostname_malformed - # OKs - return [200, {"Content-Type" => "text/plain"}, ["good #{ip}"]] if state == :good - return [200, {"Content-Type" => "text/plain"}, ["nochg #{ip}"]] if state == :nochg + end + + def response_for_changes(states, ip) + body = states.map { |state| "#{state} #{ip}" }.join("\n") + return [200, {"Content-Type" => "text/plain"}, [body]] end end end diff --git a/lib/dyndnsd/responder/rest_style.rb b/lib/dyndnsd/responder/rest_style.rb index 6fd0f32..f349a61 100644 --- a/lib/dyndnsd/responder/rest_style.rb +++ b/lib/dyndnsd/responder/rest_style.rb @@ -2,7 +2,7 @@ module Dyndnsd module Responder class RestStyle - def response_for(state, ip = nil) + def response_for_error(state) # general http errors return [405, {"Content-Type" => "text/plain"}, ["Method Not Allowed"]] if state == :method_forbidden return [404, {"Content-Type" => "text/plain"}, ["Not Found"]] if state == :not_found @@ -10,9 +10,11 @@ module Dyndnsd return [422, {"Content-Type" => "text/plain"}, ["Hostname missing"]] if state == :hostname_missing return [403, {"Content-Type" => "text/plain"}, ["Forbidden"]] if state == :host_forbidden return [422, {"Content-Type" => "text/plain"}, ["Hostname malformed"]] if state == :hostname_malformed - # OKs - return [200, {"Content-Type" => "text/plain"}, ["Changed to #{ip}"]] if state == :good - return [200, {"Content-Type" => "text/plain"}, ["No change needed for #{ip}"]] if state == :nochg + end + + def response_for_changes(states, ip) + body = states.map { |state| state == :good ? "Changed to #{ip}" : "No change needed for #{ip}" }.join("\n") + return [200, {"Content-Type" => "text/plain"}, [body]] end end end diff --git a/spec/daemon_spec.rb b/spec/daemon_spec.rb index 2e2c6f0..a44909b 100644 --- a/spec/daemon_spec.rb +++ b/spec/daemon_spec.rb @@ -49,12 +49,51 @@ describe Dyndnsd::Daemon do last_response.should be_ok last_response.body.should == 'notfqdn' end + + it 'supports multiple hostnames in request' do + authorize 'test', 'secret' + get '/nic/update?hostname=foo.example.org,bar.example.org&myip=1.2.3.4' + last_response.should be_ok + last_response.body.should == "good 1.2.3.4\ngood 1.2.3.4" + end + + it 'rejects request if one hostname is invalid' do + authorize 'test', 'secret' + + get '/nic/update?hostname=test' + last_response.should be_ok + last_response.body.should == 'notfqdn' + + get '/nic/update?hostname=test.example.com' + last_response.should be_ok + last_response.body.should == 'notfqdn' + + get '/nic/update?hostname=test.example.org.me' + last_response.should be_ok + last_response.body.should == 'notfqdn' + + get '/nic/update?hostname=foo.test.example.org' + last_response.should be_ok + last_response.body.should == 'notfqdn' + + get '/nic/update?hostname=in%20valid.example.org' + last_response.should be_ok + last_response.body.should == 'notfqdn' + + get '/nic/update?hostname=valid.example.org,in.valid.example.org' + last_response.should be_ok + last_response.body.should == 'notfqdn' + end - it 'forbids changing hosts a user does not own' do + it 'rejects request if user does not own one hostname' do authorize 'test', 'secret' get '/nic/update?hostname=notmyhost.example.org' last_response.should be_ok last_response.body.should == 'nohost' + + get '/nic/update?hostname=foo.example.org,notmyhost.example.org' + last_response.should be_ok + last_response.body.should == 'nohost' end it 'updates a host on change' do @@ -79,45 +118,15 @@ describe Dyndnsd::Daemon do last_response.body.should == 'nochg 1.2.3.4' end - it 'forbids invalid hostnames' do - authorize 'test', 'secret' - - get '/nic/update?hostname=test' - last_response.should be_ok - last_response.body.should == 'notfqdn' - - get '/nic/update?hostname=test.example.com' - last_response.should be_ok - last_response.body.should == 'notfqdn' - - get '/nic/update?hostname=test.example.org.me' - last_response.should be_ok - last_response.body.should == 'notfqdn' - - get '/nic/update?hostname=foo.test.example.org' - last_response.should be_ok - last_response.body.should == 'notfqdn' - - get '/nic/update?hostname=in%20valid.example.org.me' - last_response.should be_ok - last_response.body.should == 'notfqdn' - end - - it 'outputs status for hostname' do + it 'outputs status per hostname' do authorize 'test', 'secret' get '/nic/update?hostname=foo.example.org&myip=1.2.3.4' last_response.should be_ok last_response.body.should == 'good 1.2.3.4' - end - - it 'supports multiple hostnames in request' do - authorize 'test', 'secret' - - pending get '/nic/update?hostname=foo.example.org,bar.example.org&myip=1.2.3.4' last_response.should be_ok - last_response.body.should == "good 1.2.3.4\ngood 1.2.3.4" + last_response.body.should == "nochg 1.2.3.4\ngood 1.2.3.4" end end