dnsruby-1.61.3/0000755000004100000410000000000013607400362013321 5ustar www-datawww-datadnsruby-1.61.3/EVENTMACHINE0000644000004100000410000000013513607400362015111 0ustar www-datawww-dataDnsruby no longer supports EventMachine - the inbuilt select loop now works on all platforms.dnsruby-1.61.3/.travis.yml0000644000004100000410000000023713607400362015434 0ustar www-datawww-datalanguage: ruby cache: bundler sudo: false before_install: gem install bundler script: "bundle exec rake test" rvm: - 2.6.3 - 2.4.1 - 2.3.0 - 2.5.1 dnsruby-1.61.3/test/0000755000004100000410000000000013607400362014300 5ustar www-datawww-datadnsruby-1.61.3/test/tc_tsig.rb0000644000004100000410000002106013607400362016260 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require "digest/md5" class TestTSig < Minitest::Test include Dnsruby KEY_NAME="rubytsig" KEY = "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==" def is_empty(string) return (string == "; no data" || string == "; rdlength = 0") end def test_signed_update # Dnsruby::Resolver::use_eventmachine(false) run_test_client_signs run_test_resolver_signs end # def test_signed_update_em # begin # Dnsruby::Resolver::use_eventmachine(true) # rescue RuntimeError # Dnsruby.log.error("EventMachine not installed - not running tsig EM tests") # return # end # run_test_client_signs # run_test_resolver_signs # Dnsruby::Resolver::use_eventmachine(false) # end def run_test_client_signs # NOTE - client signing is only appropriate if DNSSEC and EDNS are switched # off. Otherwise, the resolver will attempt to alter the flags and add an # EDNS OPT psuedo-record to the query message, invalidating the signing. tsig = Dnsruby::RR.create({ :name => KEY_NAME, :type => "TSIG", :ttl => 0, :klass => "ANY", :algorithm => "hmac-md5", :fudge => 300, :key => KEY, :error => 0 }) update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") # Generate update record name, and test it has been made. Then delete it and check it has been deleted update_name = generate_update_name update.absent(update_name) update.add(update_name, 'TXT', 100, "test signed update") tsig.apply(update) assert(update.signed?, "Update has not been signed") res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") res.udp_size=512 # Or else we needed to add OPT record already res.dnssec=false res.recurse=false res.query_timeout = 20 response = res.send_message(update) assert_equal( Dnsruby::RCode.NOERROR, response.rcode) assert(response.verified?, "Response has not been verified") # Now check the record exists rr = res.query(update_name, 'TXT') assert_equal("test signed update", rr.answer()[0].strings.join(" "), "TXT record has not been created in zone") # Now delete the record update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") update.present(update_name, 'TXT') update.delete(update_name) tsig.apply(update) assert(update.signed?, "Update has not been signed") response = res.send_message(update) assert_equal( Dnsruby::RCode.NOERROR, response.rcode) assert(response.verified?, "Response has not been verified") # Now check the record does not exist Dnsruby::PacketSender.clear_caches # Or else the cache will tell us it still deos! begin rr = res.query(update_name, 'TXT') assert(false) rescue Dnsruby::NXDomain end end @@fudge = 0 def generate_update_name update_name = Time.now.to_i.to_s + @@fudge.to_s @@fudge+=1 update_name += ".update.validation-test-servers.nominet.org.uk" return update_name end def run_test_resolver_signs res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") res.query_timeout=20 res.tsig=KEY_NAME, KEY update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") # Generate update record name, and test it has been made. Then delete it and check it has been deleted update_name = generate_update_name update.absent(update_name) update.add(update_name, 'TXT', 100, "test signed update") assert(!update.signed?, "Update has been signed") response = res.send_message(update) assert_equal( Dnsruby::RCode.NOERROR, response.rcode) assert(response.verified?, "Response has not been verified") # Now check the record exists rr = res.query(update_name, 'TXT') assert_equal("test signed update", rr.answer()[0].strings.join(" "), "TXT record has not been created in zone") # Now delete the record update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") update.present(update_name, 'TXT') update.delete(update_name) tsig = Dnsruby::RR.create({ :type => 'TSIG', :klass => 'ANY', :name => KEY_NAME, :key => KEY }) tsig.apply(update) assert(update.signed?, "Update has not been signed") res.dnssec=false # Or else we needed to add OPT record already res.udp_size = 512 response = res.send_message(update) assert_equal( Dnsruby::RCode.NOERROR, response.rcode) assert(response.verified?, "Response has not been verified") # Now check the record does not exist Dnsruby::PacketSender.clear_caches # Make sure the cache doesn't have an old copy! begin rr = res.query(update_name, 'TXT') assert(false) rescue Dnsruby::NXDomain end end def test_message_signing m = Dnsruby::Message.new("example.com") m.set_tsig("name", "key") assert(!m.signed?) m.encode assert(m.signed?) m = Dnsruby::Message.new("example.com") m.set_tsig("name", "key") assert(!m.signed?) m.sign! assert(m.signed?) m = Dnsruby::Message.new("example.com") assert(!m.signed?) m.sign!("name", "key") assert(m.signed?) end def test_signed_zone_transfer # test TSIG over TCP session axfr ixfr end def axfr zt = Dnsruby::ZoneTransfer.new zt.transfer_type = Dnsruby::Types.AXFR zt.tsig=KEY_NAME, KEY zt.server = "ns0.validation-test-servers.nominet.org.uk" zone = zt.transfer("validation-test-servers.nominet.org.uk") assert(zone.length > 0) assert(zt.last_tsigstate==:Verified) end # We also test IXFR here - this is because we need to update a record (using # TSIG) before we can test ixfr... def ixfr # Check the SOA serial, do an update, check that the IXFR for that soa serial gives us the update we did, # then delete the updated record start_soa_serial = get_soa_serial("validation-test-servers.nominet.org.uk") # Now do an update res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") res.query_timeout=10 res.tsig=KEY_NAME, KEY update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") # Generate update record name, and test it has been made. Then delete it and check it has been deleted update_name = Time.now.to_i.to_s + rand(100).to_s + ".update.validation-test-servers.nominet.org.uk" update.absent(update_name) update.add(update_name, 'TXT', 100, "test zone transfer") assert(!update.signed?, "Update has been signed") response = res.send_message(update) assert(response.rcode == Dnsruby::RCode.NOERROR) end_soa_serial = get_soa_serial("validation-test-servers.nominet.org.uk") zt = Dnsruby::ZoneTransfer.new zt.transfer_type = Dnsruby::Types.IXFR zt.server = "ns0.validation-test-servers.nominet.org.uk" zt.serial = start_soa_serial # 2007090401 deltas = zt.transfer("validation-test-servers.nominet.org.uk") assert(deltas.length > 0) assert(deltas.last.class == Dnsruby::ZoneTransfer::Delta) assert_equal("test zone transfer", deltas.last.adds.last.strings.join(" ")) assert(zt.last_tsigstate==nil) # Now delete the updated record update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") update.present(update_name, 'TXT') update.delete(update_name) response = res.send_message(update) assert_equal( Dnsruby::RCode.NOERROR, response.rcode) end def get_soa_serial(name) soa_serial = nil Dnsruby::DNS.open {|dns| soa_rr = dns.getresource(name, 'SOA') soa_serial = soa_rr.serial } return soa_serial end def test_bad_tsig res = Resolver.new res.query_timeout=10 res.tsig=KEY_NAME, KEY begin ret = res.query("example.com") assert(false, "Should not have got TSIG response from non-TSIG server!\n #{ret}\n") rescue TsigError => e end end end dnsruby-1.61.3/test/tc_soak_base.rb0000644000004100000410000001101313607400362017236 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestSoakBase # < Minitest::Test include Dnsruby Rrs = [ { :type => Types.A, :name => 'ns1.google.com.', :address => '10.0.1.128' }, { :type => Types::MX, :name => 'ns1.google.com.', :exchange => 'ns1.google.com.', :preference => 10 }, { :type => 'CNAME', :name => 'ns1.google.com.', :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk' }, { :type => Types.TXT, :name => 'ns1.google.com.', :strings => ['Net-DNS'] } ] def TestSoakBase.test_continuous_queries_asynch_single_res # Have two threads looping, with one sending, and one receiving queries. # Never exceed more than 200 concurrent queries, but make sure they're always running. outstanding_limit = 1 num_loops = 2000 num_sent = 0 q = Queue.new timed_out = 0 mutex = Mutex.new start = Time.now num_in_progress = 0 sender = Thread.new{ res = SingleResolver.new res.packet_timeout=5 num_loops.times do |i| rr_count = 0 Rrs.each do |data| rr_count+=1 while (mutex.synchronize{num_in_progress> outstanding_limit}) do sleep(0.01) end res.send_async(Message.new(data[:name], data[:type]), q, [i,rr_count]) puts num_sent num_sent+=1 mutex.synchronize { num_in_progress+=1 } end end } receiver = Thread.new{ (num_loops*4).times do |i| id,ret, error = q.pop mutex.synchronize { num_in_progress-=1 } if (error.class == ResolvTimeout) timed_out+=1 # p "Number #{i} timed out!" elsif (ret.class != Message) Dnsruby.log.debug("tc_single_resolver : Query #{i} ERROR RETURNED : #{error.class}, #{error}") end end } sender.join receiver.join assert(num_in_progress==0) stop=Time.now time_taken=stop-start puts "Query count : #{num_sent}, #{timed_out} timed out. #{time_taken} time taken" assert(timed_out < num_sent * 0.1, "#{timed_out} of #{num_sent} timed out!") end def TestSoakBase.test_continuous_queries_asynch_resolver # Have two threads looping, with one sending, and one receiving queries. # Never exceed more than 250 concurrent queries, but make sure they're always running. num_loops = 1000 num_sent = 0 q = Queue.new timed_out = 0 mutex = Mutex.new start = Time.now num_in_progress = 0 sender = Thread.new{ res = Resolver.new # On windows, MAX_FILES is 256. This means that we have to limit # this test while we're not using single sockets. # We run four queries per iteration, so we're limited to 64 runs. num_loops.times do |i| while (mutex.synchronize{num_in_progress> 50}) do # One query has several sockets in Resolver sleep(0.01) end res.send_async(Message.new("example.com", Types.A), q, [i,1]) num_sent+=1 mutex.synchronize { num_in_progress+=1 } end } error_count=0 receiver = Thread.new{ (num_loops).times do |i| id,ret, error = q.pop mutex.synchronize { num_in_progress-=1 } if (error.class == ResolvTimeout) timed_out+=1 # p "Number #{i} timed out!" elsif (ret.class != Message) error_count+=1 Dnsruby.log.error("tc_single_resolver : Query #{i} ERROR RETURNED : #{error.class}, #{error}") end end } sender.join receiver.join assert(num_in_progress==0) stop=Time.now time_taken=stop-start puts "Query count : #{num_sent}, #{timed_out} timed out, #{error_count} other errors. #{time_taken} time taken" assert(timed_out < num_sent * 0.1, "#{timed_out} of #{num_sent} timed out!") assert(error_count == 0) end end dnsruby-1.61.3/test/tc_res_opt.rb0000644000004100000410000001460313607400362016772 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestResOpt < Minitest::Test include Dnsruby def test_dns_file # .txt because this test will run under windows, unlike the other file # configuration tests. res = Dnsruby::DNS.new('test/custom.txt') assert(res, 'new() returned something') assert_instance_of(DNS, res, 'new() returns an object of the correct class.') assert(res.config.nameserver, 'nameservers() works') servers = res.config.nameserver assert_equal('10.0.1.42', servers[0], 'Nameserver set correctly') assert_equal('10.0.2.42', servers[1], 'Nameserver set correctly') search = res.config.search assert(search.include?('alt.dnsruby.validation-test-servers.nominet.org.uk'), 'Search set correctly' ) assert(search.include?('ext.dnsruby.validation-test-servers.nominet.org.uk'), 'Search set correctly' ) assert(res.config.domain == 't2.dnsruby.validation-test-servers.nominet.org.uk', 'Local domain works' ) end def test_resolver_file res = Dnsruby::Resolver.new({:config_info => 'test/custom.txt'}) assert(res.config.nameserver==['10.0.1.42', '10.0.2.42'], res.config.nameserver.to_s) end def test_no_file Dnsruby.log.level=Logger::FATAL res=nil begin res = DNS.new('nosuch.txt') assert_equal(["0.0.0.0"], res.nameserver,"No nameservers should be set for #{test} = #{input}") rescue Exception end begin res = Resolver.new('nosuch.txt') assert_equal(["0.0.0.0"], res.nameserver,"No nameservers should be set for #{test} = #{input}") rescue Exception end # Dnsruby.log.level=Logger::ERROR end def test_config_hash_singleresolver # Resolver interface gives us : port, TCP, IgnoreTruncation, TSIGkey, timeout # SR : server, local_address, udp_size test_config = { :server => '10.0.0.1', :port => 54, # SingleResolver and Multi-Resolver :src_address => '10.1.0.1', # SingleResolver and Multi-Resolver :src_address6 => 'fc00::1:2:3', # SingleResolver and Multi-Resolver :src_port => 56353, # SingleResolver and Multi-Resolver :use_tcp => true, # SingleResolver and Multi-Resolver :ignore_truncation => true, # SingleResolver and Multi-Resolver :recurse => false, :packet_timeout => 60, # SingleResolver and Multi-Resolver # Only have one timeout for both UDP and TCP :dnssec => true, } res = SingleResolver.new(test_config) test_config.keys.each do |item| assert_equal(test_config[item], res.send(item), "#{item} is correct") end end def test_config_hash_multiresolver # Resolver interface gives us : port, TCP, IgnoreTruncation, TSIGkey, timeout # ER : retries, load_balance. Also loads servers from Config and configures SRs to point to them # Also implements Resolver interface - but iterates this through *all* SRs test_config = { :nameserver => ['10.0.0.1', '10.0.0.2'], # for Multi-Resolver & DNS :port => 54, # SingleResolver and Multi-Resolver :src_address => '10.1.0.1', # SingleResolver and Multi-Resolver :src_address6 => 'fc00::1:2:3', # SingleResolver and Multi-Resolver :src_port => 56753, # SingleResolver and Multi-Resolver :retry_delay => 6, # DNS and Multi-Resolver :retry_times => 5, # DNSand Multi-Resolver :use_tcp => true, # SingleResolver and Multi-Resolver :ignore_truncation => true, # SingleResolver and Multi-Resolver :recurse => false, :packet_timeout => 60, # SingleResolver and Multi-Resolver # Only have one timeout for both UDP and TCP :query_timeout => 60, # Multi-Resolver only :dnssec => true, } res = Resolver.new(test_config) test_config.keys.each do |item| if (item==:nameserver) assert_equal(res.config.nameserver, test_config[item], "#{item} is correct") else assert_equal(res.send(item), test_config[item], "#{item} is correct") end end end def test_config_hash_lookup # Lookup : can specify resolver, searchpath # # Check that we can set things in new() # res=nil test_config = { :nameserver => ['10.0.0.1', '10.0.0.2'], # for Multi-Resolver & DNS :domain => 'dnsruby.validation-test-servers.nominet.org.uk', # one for DNS only? :search => ['dnsruby.validation-test-servers.nominet.org.uk', 't.dnsruby.validation-test-servers.nominet.org.uk'], # one for DNS :ndots => 2, # DNS only :apply_search_list => false, # DNS only :apply_domain => false, # DNS only } res = DNS.new(test_config) test_config.keys.each do |item| assert_equal(res.config.send(item), test_config[item], "#{item} is correct") end end def test_bad_config res=nil Dnsruby.log.level=Logger::FATAL bad_input = { :tsig_rr => 'set', :errorstring => 'set', :answerfrom => 'set', :answersize => 'set', :querytime => 'set', :axfr_sel => 'set', :axfr_rr => 'set', :axfr_soa_count => 'set', :udppacketsize => 'set', :cdflag => 'set', } res=nil begin res = Resolver.new(bad_input) rescue Exception end if (res) bad_input.keys.each do |key| begin assert_not_equal(res.send(key), 'set', "#{key} is not set") rescue Exception end end end res=nil begin res = DNS.new(bad_input) rescue Exception end if (res) bad_input.keys.each do |key| begin assert_not_equal(res.send(key), 'set', "#{key} is not set") rescue Exception end end # Dnsruby.log.level=Logger::ERROR end end enddnsruby-1.61.3/test/tc_message.rb0000644000004100000410000000665713607400362016755 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestMessage < Minitest::Test include Dnsruby # Creates and returns sample message: # # ;; QUESTION SECTION (1 record) # ;; cnn.com. IN A # ;; Security Level : UNCHECKED # ;; ->>HEADER<<- opcode: QUERY, status: NOERROR, id: 7195 # ;; flags: ; QUERY: 1, ANSWER: 0, AUTHORITY: 0, ADDITIONAL: 0 def sample_message Message.new('cnn.com', 'A') end def test_question_section_formatted_ok multiline_regex = /QUESTION SECTION.+record.+cnn.com.\s+IN\s+A/m assert multiline_regex.match(sample_message.to_s) end def test_has_security_level_line line_regex = /^;; Security Level : .+/ assert line_regex.match(sample_message.to_s) end def test_has_flags_and_section_count line_regex = /^;; flags:.+QUERY: \d+, ANSWER: \d+, AUTHORITY: \d+, ADDITIONAL: \d+/ assert line_regex.match(sample_message.to_s) end def test_rd_flag_displayed_when_true message = sample_message message.header.instance_variable_set(:@rd, true) assert /;; flags(.+)rd/.match(message.to_s), message end def test_header_line_contains_opcode_and_status_and_id message = sample_message header_line = message.to_s.split("\n").grep(/->>HEADER<<-/).first line_regex = /->>HEADER<<- opcode: .+, status: .+, id: \d+/ assert line_regex.match(header_line) end def test_getopt message = sample_message assert message.get_opt.nil? # Add an OPT record opt = RR::OPT.new(4096, 32768) message.additional << opt opt = message.get_opt assert opt.is_a?(Dnsruby::RR::OPT), "Expected get_opt to return a Dnsruby::RR::OPT, but it returned a #{opt.class}" end def test_2eq test = ->(msg1, msg2, expected_result) do assert (msg1 == msg2) == expected_result end msg_a = sample_message msg_b = sample_message; msg_b.header.rd = (! msg_b.header.rd) test.(msg_a, msg_a, true) test.(msg_a, msg_b, false) test.(msg_a, msg_a.to_s, false) test.(msg_a, nil, false) # TODO: Add more tests. end def test_equals response_as_string = "\x10\a\x81\x90\x00\x01\x00\x04\x00\x00\x00\x06\x03cnn\x03com\x00\x00\x02\x00\x01\xC0\f\x00\x02\x00\x01\x00\x01QC\x00\x14\x03ns3\ntimewarner\x03net\x00\xC0\f\x00\x02\x00\x01\x00\x01QC\x00\x11\x03ns2\x03p42\x06dynect\xC04\xC0\f\x00\x02\x00\x01\x00\x01QC\x00\x06\x03ns1\xC0)\xC0\f\x00\x02\x00\x01\x00\x01QC\x00\x06\x03ns1\xC0I\xC0%\x00\x01\x00\x01\x00\x001\xA2\x00\x04\xC7\aD\xEE\xC0E\x00\x01\x00\x01\x00\x00\xB1\x0E\x00\x04\xCC\r\xFA*\xC0b\x00\x01\x00\x01\x00\x009`\x00\x04\xCCJl\xEE\xC0t\x00\x01\x00\x01\x00\x00\xBDg\x00\x04\xD0NF*\xC0t\x00\x1C\x00\x01\x00\x00\x00\xBB\x00\x10 \x01\x05\x00\x00\x90\x00\x01\x00\x00\x00\x00\x00\x00\x00B\x00\x00)\x0F\xA0\x00\x00\x80\x00\x00\x00".force_encoding("ASCII-8BIT") message = Message.decode(response_as_string) assert(message == message, message.to_s) end end dnsruby-1.61.3/test/tc_encoding.rb0000644000004100000410000000171413607400362017104 0ustar www-datawww-datarequire_relative 'spec_helper' require 'socket' # @TODO@ We also need a test server so we can control behaviour of server to test # different aspects of retry strategy. # Of course, with Ruby's limit of 256 open sockets per process, we'd need to run # the server in a different Ruby process. class TestEncoding < Minitest::Test include Dnsruby Thread::abort_on_exception = true Dnsruby::TheLog.level = Logger::DEBUG def test_cdnskey rrString = "tjeb.nl.\t3600\tIN\tCDNSKEY\t256 3 RSASHA1-NSEC3-SHA1 ( AwEAAcglEOS7bECRK5fqTuGTMJycmDhTzmUu/EQbAhKJOYJxDb5SG/RYqsJgzG7wgtGy0W1aP7I4k6SPtHmwcqjLaZLVUwRNWCGr2adjb9JTFyBR7F99Ngi11lEGM6Uiw/eDRk66lhoSGzohjj/rmhRTV6gN2+0ADPnafv3MBkPgryA3 ) ; key_tag=53177" rr = RR.create(rrString) puts rr puts rrString assert(rrString.to_s == rr.to_s) m = Dnsruby::Message.new m.add_additional(rr) m2 = Message.decode(m.encode) rr2 = m2.additional()[0] assert(rr.to_s == rr2.to_s) end end dnsruby-1.61.3/test/tc_res_file.rb0000644000004100000410000000263513607400362017111 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestAResolverFile < Minitest::Test def setup Dnsruby::Config.reset end def test_resFile res = Dnsruby::DNS.new("test/resolv.conf") assert(res, "new() returned something") assert(res.config.nameserver, "nameservers() works") servers = res.config.nameserver assert_equal(servers[0], '10.0.1.128', 'Nameserver set correctly') assert_equal(servers[1], '10.0.2.128', 'Nameserver set correctly') search = res.config.search assert(search.include?('dnsruby.validation-test-servers.nominet.org.uk'), 'Search set correctly' ) assert(search.include?('lib.dnsruby.validation-test-servers.nominet.org.uk'), 'Search set correctly' ) assert(res.config.domain=='t.dnsruby.validation-test-servers.nominet.org.uk', 'Local domain works' ) end end dnsruby-1.61.3/test/tc_sshfp.rb0000644000004100000410000000250413607400362016437 0ustar www-datawww-data # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestSSHFP < Minitest::Test include Dnsruby def test_sshfp txt = "apt-blade6.nominet.org.uk. 85826 IN SSHFP 1 1 6D4CF7C68E3A959990855099E15D6E0D4DEA4FFF" sshfp = RR.create(txt) assert(sshfp.type == Types.SSHFP) assert(sshfp.alg == RR::SSHFP::Algorithms.RSA) assert(sshfp.fptype == RR::SSHFP::FpTypes.SHA1) assert(sshfp.fp.unpack("H*")[0].upcase == "6D4CF7C68E3A959990855099E15D6E0D4DEA4FFF") m = Dnsruby::Message.new m.add_additional(sshfp) data = m.encode m2 = Dnsruby::Message.decode(data) sshfp2 = m2.additional()[0] assert(sshfp.fptype == sshfp2.fptype) assert(sshfp.alg == sshfp2.alg) assert(sshfp.fp == sshfp2.fp) assert(sshfp == sshfp2) end end dnsruby-1.61.3/test/custom.txt0000644000004100000410000000036713607400362016361 0ustar www-datawww-data# $Id: custom.txt 264 2005-04-06 09:16:15Z olaf $ domain t2.dnsruby.validation-test-servers.nominet.org.uk search alt.dnsruby.validation-test-servers.nominet.org.uk ext.dnsruby.validation-test-servers.nominet.org.uk nameserver 10.0.1.42 10.0.2.42 dnsruby-1.61.3/test/tc_resolv.rb0000644000004100000410000000441213607400362016626 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require_relative '../lib/dnsruby/resolv' class TestResolv < Minitest::Test RELATIVE_NAME = 'google-public-dns-a.google.com' SHORT_RELATIVE_NAME = 'dns.google' ABSOLUTE_NAME = RELATIVE_NAME + '.' IPV4_ADDR = '8.8.8.8' IPV6_ADDR = '2001:4860:4860::8888' ADDRESSES = [IPV4_ADDR, IPV6_ADDR] def test_resolv_name_to_addresses assert_equal(IPV4_ADDR, Dnsruby::Resolv.getaddress(ABSOLUTE_NAME).to_s) addresses = Dnsruby::Resolv.getaddresses(ABSOLUTE_NAME) case addresses.length when 1 assert_equal IPV4_ADDR, addresses.first.to_s Dnsruby::Resolv.each_address(ABSOLUTE_NAME) do |address| assert_equal IPV4_ADDR, address.to_s end when 2 assert_equal ADDRESSES.sort, addresses.map(&:to_s).sort addresses_from_each = [] Dnsruby::Resolv.each_address(ABSOLUTE_NAME) do |address| addresses_from_each << address.to_s end assert_equal ADDRESSES.sort, addresses_from_each.sort else raise "Addresses length must be 1 or 2 but was #{addresses.length}" end end def test_resolv_address_to_name assert_equal(SHORT_RELATIVE_NAME, Dnsruby::Resolv.getname(IPV4_ADDR).to_s) assert_raises(Dnsruby::ResolvError) do Dnsruby::Resolv.getname(SHORT_RELATIVE_NAME) end names = Dnsruby::Resolv.getnames(IPV4_ADDR) assert_equal(1, names.size) assert_equal(SHORT_RELATIVE_NAME, names.first.to_s) Dnsruby::Resolv.each_name(IPV4_ADDR) { |name| assert_equal(SHORT_RELATIVE_NAME, name.to_s)} end def test_resolv_address_to_address local = '127.0.0.1' assert_equal(local, Dnsruby::Resolv.new.getaddress(local)) end end dnsruby-1.61.3/test/tc_long_labels.rb0000644000004100000410000001071713607400362017602 0ustar www-datawww-datarequire_relative 'spec_helper' include Dnsruby class TestPacket < Minitest::Test def test_labels wirePacket = %w{0 68 5b 35 91 3a f7 00 0f 94 22 d9 51 08 00 45 00 05 12 71 65 40 00 3d 06 46 1f 2e e3 90 33 c0 a8 01 a3 00 35 e3 cf 94 d5 49 0a 88 da e7 1e 80 18 00 1d 6f 52 00 00 01 01 08 0a 8e 3a a6 b1 1f 4d ce 28 c5 b1 c0 0c 00 0c 00 01 00 00 0b 49 00 12 0f 69 6d 70 72 6f 76 65 61 6e 61 6c 79 73 69 73 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 0e 0b 77 69 6e 64 6f 77 73 72 75 62 79 d3 12 c0 0c 00 0c 00 01 00 00 0b 49 00 15 12 6d 69 63 72 6f 73 6f 66 74 66 6f 72 65 66 72 6f 6e 74 d3 bd c0 0c 00 0c 00 01 00 00 0b 49 00 11 09 6d 69 63 72 6f 73 6f 66 74 02 63 6f 02 6d 7a 00 c0 0c 00 0c 00 01 00 00 0b 49 00 12 09 77 69 6e 64 6f 77 73 78 70 03 6f 72 67 02 70 65 00 c0 0c 00 0c 00 01 00 00 0b 49 00 16 0f 65 75 67 72 61 6e 74 73 61 64 76 69 73 6f 72 03 63 6f 6d ca 49 c0 0c 00 0c 00 01 00 00 0b 49 00 14 11 64 65 66 79 61 6c 6c 63 68 61 6c 6c 65 6e 67 65 73 c5 97 c0 0c 00 0c 00 01 00 00 0b 49 00 18 15 63 6f 6e 73 6f 6c 69 64 61 74 65 64 6d 65 73 73 65 6e 67 65 72 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 19 16 72 65 74 61 69 6c 65 78 65 63 75 74 69 76 65 73 65 6d 69 6e 61 72 c0 a7 c0 0c 00 0c 00 01 00 00 0b 49 00 0e 0b 63 74 72 6f 70 65 6e 6f 72 6d 65 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 0e 0b 77 69 6e 64 6f 77 73 32 30 30 30 cc 6f c0 0c 00 0c 00 01 00 00 0b 49 00 1a 17 77 69 6e 64 6f 77 73 6d 6f 62 69 6c 65 63 6f 6d 6d 6d 75 6e 69 74 79 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 11 0e 72 69 73 65 6f 66 70 65 72 61 74 68 69 61 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 11 0e 72 65 6e 63 6f 6e 74 72 65 73 2d 33 36 30 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 11 0e 66 75 74 75 72 65 70 6f 73 74 6d 61 69 6c c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 10 0d 72 65 73 70 6f 6e 73 65 70 6f 69 6e 74 cc 4d c0 0c 00 0c 00 01 00 00 0b 49 00 10 0d 74 61 76 75 74 61 74 72 6f 6e 63 68 65 c0 a7 c0 0c 00 0c 00 01 00 00 0b 49 00 09 06 66 6c 65 78 67 6f df 34 c0 0c 00 0c 00 01 00 00 0b 49 00 0e 09 77 69 6e 64 6f 77 73 78 70 02 73 68 00 c0 0c 00 0c 00 01 00 00 0b 49 00 16 13 73 6d 61 72 74 70 68 6f 6e 65 63 6f 6d 6d 75 6e 69 74 79 c0 a7 c0 0c 00 0c 00 01 00 00 0b 49 00 12 0f 63 65 6e 74 72 65 64 65 73 75 73 61 67 65 73 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 0c 09 77 69 6e 64 6f 77 73 6e 74 fc 07 c0 0c 00 0c 00 01 00 00 0b 49 00 19 16 6c 65 73 2d 64 6f 69 67 74 73 2d 64 61 6e 73 2d 6c 65 2d 6e 65 7a c0 a7 c0 0c 00 0c 00 01 00 00 0b 49 00 13 10 74 65 63 68 6e 65 74 63 68 61 6c 6c 65 6e 67 65 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 15 12 6d 69 63 72 6f 73 6f 66 74 66 6f 72 65 66 72 6f 6e 74 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 10 0d 6c 65 73 62 6f 6e 73 6f 75 74 69 6c 73 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 0d 0a 77 69 6e 74 65 72 6e 61 6c 73 c3 d6 c0 0c 00 0c 00 01 00 00 0b 49 00 23 0e 64 65 73 69 67 6e 65 64 66 6f 72 62 69 67 02 64 65 0e 64 65 73 69 67 6e 65 64 66 6f 72 62 69 67 c2 b2 c0 0c 00 0c 00 01 00 00 0b 49 00 13 10 77 69 6e 64 6f 77 73 76 69 73 74 61 62 6c 6f 67 c9 42 c0 0c 00 0c 00 01 00 00 0b 49 00 12 09 77 69 6e 64 6f 77 73 6e 74 03 6f 72 67 02 66 6a 00 c0 0c 00 0c 00 01 00 00 0b 49 00 0c 09 77 69 6e 64 6f 77 73 6e 74 c1 b6 c0 0c 00 0c 00 01 00 00 0b 49 00 0f 0c 6f 66 66 69 63 65 73 79 73 74 65 6d c2 18 c0 0c 00 0c 00 01 00 00 0b 49 00 0f 0c 74 72 65 79 72 65 73 65 61 72 63 68 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 09 06 63 70 61 6e 64 6c c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 16 13 6f 66 66 72 65 2d 65 62 6c 6f 75 69 73 73 61 6e 74 65 73 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 0d 0a 63 6f 68 6f 77 69 6e 65 72 79 c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 13 10 74 6f 64 6f 73 6c 6f 65 6e 74 69 65 6e 64 65 6e c0 41 c0 0c 00 0c 00 01 00 00 0b 49 00 0f 09 77 69 6e 64 6f 77 73 78 70 02 63 6f f1 03 c0 0c 00 0c 00 01 00 00 0b 49 00 0b 08 74 65 63 68 65 64 30 36 c0 65 c0 0c 00 0c 00 01 00 00 0b 49 00 09 06 66 6c 65 78 67 6f dd 7f c0 0c 00 0c 00 01 00 00 0b 49 00 0c 09 66 6f 72 65 66 72 6f 6e 74 cb 21 c0 0c 00 0c 00 01 00 00 0b 49 00 14 11 64 65 66 79 61 6c 6c 63 68 61 6c 6c 65 6e 67 65 73 cb 44 00 00 29 05 78 00 00 80 00 00 00 } wirePacket.map!{|e| e.hex} packetdata = wirePacket.pack('c*') packet = Message.decode(packetdata) assert(packet, 'new data returned something'); #28 end def test_live resolver = Dnsruby::Resolver.new query = resolver.query('207.46.197.32', 'PTR', 'IN') end enddnsruby-1.61.3/test/tc_gpos.rb0000644000004100000410000001115713607400362016270 0ustar www-datawww-datarequire_relative 'spec_helper' require_relative '../lib/dnsruby/resource/GPOS.rb' # Tests GPOS resource record. See bottom of file for sample zone file. class TestGPOS < Minitest::Test include Dnsruby EXAMPLE_LONGITUDE = '10.0' EXAMPLE_LATITUDE = '20.0' EXAMPLE_ALTITUDE = '30.0' EXAMPLE_HOSTNAME = 'a.dnsruby.com.' EXAMPLE_TTL = 3 * 60 * 60 # 10,800 seconds, or 3 hours EXAMPLE_GPOS_STRING = 'a.dnsruby.com. 10800 IN GPOS 10.0 20.0 30.0' EXAMPLE_GPOS_HASH = { name: EXAMPLE_HOSTNAME, type: Types::GPOS, ttl: EXAMPLE_TTL, longitude: EXAMPLE_LONGITUDE, latitude: EXAMPLE_LATITUDE, altitude: EXAMPLE_ALTITUDE, } EXAMPLE_GPOS_DATA = begin rdata = RR::GPOS.build_rdata(EXAMPLE_LONGITUDE, EXAMPLE_LATITUDE, EXAMPLE_ALTITUDE) [EXAMPLE_HOSTNAME, Types::GPOS, Classes::IN, EXAMPLE_TTL, rdata.length, rdata, 0] end # Returns a GPOS record returned by a BIND server configured with the zone file # shown at the bottom of this file. I (keithrbennett) was unable to find a GPOS # record on the public Internet to use for live testing. def gpos_from_response # query = Message.new(EXAMPLE_HOSTNAME, 'GPOS') # query_binary = "E0\u0000\u0000\u0000\u0001\u0000\u0000\u0000\u0000\u0000\u0000\u0001a\adnsruby\u0003com\u0000\u0000\e\u0000\u0001" # response, _error = Resolver.new('127.0.0.1').query_raw(query) response_binary = "E0\x84\x80\x00\x01\x00\x01\x00\x01\x00\x01\x01a\adnsruby\x03com\x00\x00\e\x00\x01\xC0\f\x00\e\x00\x01\x00\x00*0\x00\x0F\x0410.0\x0420.0\x0430.0\xC0\x0E\x00\x02\x00\x01\x00\x00*0\x00\x06\x03ns1\xC0\x0E\xC0F\x00\x01\x00\x01\x00\x00*0\x00\x04\x7F\x00\x00\x01" response = Message.decode(response_binary) # response_binary = "\xE7\x01\x85\x90\x00\x01\x00\x01\x00\x01\x00\x01\x01g\adnsruby\x03com" + # "\x00\x00\e\x00\x01\xC0\f\x00\e\x00\x01\x00\t:\x80\x00\x0F\x0420.0\x0430.0\x0410.0" + # "\xC0\x0E\x00\x02\x00\x01\x00\t:\x80\x00\x05\x02ns\xC0\x0E\xC0F\x00\x01\x00\x01\x00" + # "\t:\x80\x00\x04\xC0\xA8\x01\n"; nil # # response = Message.decode(response_binary) response.answer[0] end def test_answer answer = gpos_from_response assert answer.is_a?(RR::GPOS), "Expected RR::GPOS but got a #{answer.class}: #{answer}" assert_equal(EXAMPLE_LONGITUDE, answer.longitude) assert_equal(EXAMPLE_LATITUDE, answer.latitude) assert_equal(EXAMPLE_ALTITUDE, answer.altitude) assert_equal(EXAMPLE_TTL, answer.ttl) end # should be: GPOS def test_to_s actual = gpos_from_response.to_s.split expected = %w(a.dnsruby.com. 10800 IN GPOS 10.0 20.0 30.0) assert_equal(expected, actual) end def test_creation_approaches ans_from_data = RR::GPOS.new_from_data(*EXAMPLE_GPOS_DATA) ans_from_string = RR::GPOS.new_from_string(EXAMPLE_GPOS_STRING) ans_from_hash = RR::GPOS.new_from_hash(EXAMPLE_GPOS_HASH) fails_to_populate_rdata = [] fails_to_populate_rdata << 'data' if ans_from_data.rdata.nil? fails_to_populate_rdata << 'string' if ans_from_string.rdata.nil? fails_to_populate_rdata << 'hash' if ans_from_hash.rdata.nil? assert_equal([], fails_to_populate_rdata, "Populate modes failing to populate rdata: #{fails_to_populate_rdata.join(', ')}") assert_equal(ans_from_data.rdata, ans_from_hash.rdata) assert_equal(ans_from_data.rdata, ans_from_string.rdata) assert_equal(ans_from_data, ans_from_hash) assert_equal(ans_from_data, ans_from_string) end def test_decode_encode response_binary = "E0\x84\x80\x00\x01\x00\x01\x00\x01\x00\x01\x01a\adnsruby\x03com\x00\x00\e\x00\x01\xC0\f\x00\e\x00\x01\x00\x00*0\x00\x0F\x0410.0\x0420.0\x0430.0\xC0\x0E\x00\x02\x00\x01\x00\x00*0\x00\x06\x03ns1\xC0\x0E\xC0F\x00\x01\x00\x01\x00\x00*0\x00\x04\x7F\x00\x00\x01" message_object = Message.decode(response_binary) reconstructed_binary = message_object.encode assert_equal response_binary.force_encoding('ASCII-8BIT'), reconstructed_binary end end # Sample zone file for setting up BIND to serve GPOS records: =begin $TTL 3h @ IN SOA dnsruby.com. foo.dnsruby.com. ( 1 ; serial 3H ; refresh after 3 hours 1H ; retry after 1 hour 1W ; expire after 1 week 1H) ; negative caching TTL of 1 hour dnsruby.com. IN NS ns1 ; Addresses for canonical names ns1.dnsruby.com. IN A 127.0.0.1 a.dnsruby.com. IN A 2.4.6.8 IN GPOS 10.0 20.0 30.0 b.dnsruby.com. IN A 2.4.6.9 IN GPOS 40 50 60 =end dnsruby-1.61.3/test/tc_dnskey.rb0000644000004100000410000000575113607400362016620 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class DnskeyTest < Minitest::Test INPUT = "example.com. 86400 IN DNSKEY 256 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3" + "Cbl+BBZH4b/0PY1kxkmvHjcZc8no" + "kfzj31GajIQKY+5CptLr3buXA10h" + "WqTkF7H6RfoRqXQeogmMHfpftf6z" + "Mv1LyBUgia7za6ZEzOJBOztyvhjL" + "742iU/TpPSEDhm2SNKLijfUppn1U" + "aNvv4w== )" BADINPUT = "example.com. 86400 IN DNSKEY 384 3 5 ( AQPSKmynfzW4kyBv015MUG2DeIQ3" + "Cbl+BBZH4b/0PY1kxkmvHjcZc8no" + "kfzj31GajIQKY+5CptLr3buXA10h" + "WqTkF7H6RfoRqXQeogmMHfpftf6z" + "Mv1LyBUgia7za6ZEzOJBOztyvhjL" + "742iU/TpPSEDhm2SNKLijfUppn1U" + "aNvv4w== )" # def test_bad_flag # dnskey = Dnsruby::RR.create(BADINPUT) # assert_equal(384, dnskey.flags) # assert(dnskey.bad_flags?) # end def test_dnskey_from_string dnskey = Dnsruby::RR.create(INPUT) # assert(!dnskey.bad_flags?) assert_equal(3, dnskey.protocol) assert_equal(256, dnskey.flags) assert_equal(Dnsruby::Algorithms::RSASHA1, dnskey.algorithm) assert_equal(Dnsruby::RR::DNSKEY::ZONE_KEY, dnskey.flags & Dnsruby::RR::DNSKEY::ZONE_KEY) assert_equal(0, dnskey.flags & Dnsruby::RR::DNSKEY::SEP_KEY) dnskey2 = Dnsruby::RR.create(dnskey.to_s) assert(dnskey2.to_s == dnskey.to_s, "#{dnskey.to_s} not equal to \n#{dnskey2.to_s}") end def test_from_string_with_comments k = Dnsruby::RR.create("tjeb.nl. 3600 IN DNSKEY 256 3 7 AwEAAcglEOS7bECRK5fqTuGTMJycmDhTzmUu/EQbAhKJOYJxDb5SG/RYqsJgzG7wgtGy0W1aP7I4k6SPtHmwcqjLaZLVUwRNWCGr2adjb9JTFyBR7F99Ngi11lEGM6Uiw/eDRk66lhoSGzohjj/rmhRTV6gN2+0ADPnafv3MBkPgryA3 ;{id = 53177 (zsk), size = 1024b}") assert_equal(53177, k.key_tag) end def test_dnskey_from_data dnskey = Dnsruby::RR.create(INPUT) m = Dnsruby::Message.new m.add_additional(dnskey) data = m.encode m2 = Dnsruby::Message.decode(data) dnskey3 = m2.additional()[0] assert_equal(dnskey.to_s, dnskey3.to_s) end def test_bad_values dnskey = Dnsruby::RR.create(INPUT) begin dnskey.protocol=4 fail() rescue Dnsruby::DecodeError end dnskey.flags=4 assert_equal(4, dnskey.flags) assert(dnskey.flags == 4) dnskey.flags=256 assert_equal(256, dnskey.flags) # assert(!dnskey.bad_flags?) dnskey.flags=257 assert_equal(257, dnskey.flags) # assert(!dnskey.bad_flags?) dnskey.flags=1 assert_equal(1, dnskey.flags) dnskey.protocol=3 end enddnsruby-1.61.3/test/tc_nxt.rb0000644000004100000410000001500513607400362016125 0ustar www-datawww-datarequire_relative 'spec_helper' require_relative '../lib/dnsruby/resource/NXT' require_relative '../lib/dnsruby/code_mappers' # Tests NXT resource record. See bottom of file for sample zone file. class TestNXT < Minitest::Test include Dnsruby # Get this by running the following script: # require 'dnsruby' # include Dnsruby # query = Message.new('a.dnsruby.com', 'NXT') # resolver = Resolver.new('127.0.0.1') # response, error = resolver.query_raw(query) # puts response.encode.inspect # to get a quoted string to be inserted in source code EXAMPLE_NXT_RESPONSE_AS_BINARY = \ "\xC2\xE0\x84\x80\x00\x01\x00\x01\x00\x01" + "\x00\x01\x01a\adnsruby\x03com\x00" + "\x00\x1E\x00\x01\xC0\f\x00\x1E\x00\x01\x00\x00*0\x00\x13\x01b\adnsruby\x03com\x00" + "@\x00\x00\n\xC0\x0E\x00\x02\x00\x01\x00\x00*0\x00\x06\x03ns1\xC0\x0E\xC0J\x00\x01\x00" + "\x01\x00\x00*0\x00\x04\x7F\x00\x00\x01" def test_type_val_to_string assert_equal 'SOA', RR::NXT::NxtTypes.code_to_name(6) assert_equal 'AXFR', RR::NXT::NxtTypes.code_to_name(252) assert_equal 'TYPE9999', RR::NXT::NxtTypes.code_to_name(9999) end def test_type_name_to_code assert_equal 6, RR::NXT::NxtTypes.name_to_code('SOA') assert_equal 252, RR::NXT::NxtTypes.name_to_code('AXFR') assert_equal 9999, RR::NXT::NxtTypes.name_to_code('TYPE9999') end def test_type_names_to_codes strings = %w(TYPE9999 SOA AXFR) assert_equal [9999, 6, 252], RR::NXT::NxtTypes.names_to_codes(strings) end def test_type_name_to_codes assert_equal [9999, 6, 252], RR::NXT::NxtTypes.names_string_to_codes("TYPE9999 SOA AXFR") end def test_codes_to_names assert_equal %w(TYPE9999 SOA AXFR), RR::NXT::NxtTypes.codes_to_names([9999, 6, 252]) end def test_codes_to_string assert_equal 'SOA AXFR TYPE9999', RR::NXT::NxtTypes.codes_to_string([6, 252, 9999]) end def test_codes_to_name_sorts_by_code assert_equal 'SOA AXFR TYPE9999', RR::NXT::NxtTypes.codes_to_string([9999, 6, 252]) end def test_binary_string_to_codes test_type_codes_as_code_array = [1, 6, 28, 100] test_type_codes_as_name_array = %w(A SOA AAAA UINFO) test_type_codes_as_number = 1267650600228229401496971640898 # (2 ** 1) + (2 ** 6) + (2 ** 28) + (2 ** 100) test_type_codes_as_binary_string = "\x10\x0\x0\x0\x0\x0\x0\x0\x0\x10\x0\x0\x42" assert_equal(test_type_codes_as_code_array, RR::NXT::NxtTypes.binary_string_to_codes(test_type_codes_as_binary_string)) assert_equal(test_type_codes_as_name_array, RR::NXT::NxtTypes.binary_string_to_names(test_type_codes_as_binary_string)) assert_equal(test_type_codes_as_binary_string, RR::NXT::NxtTypes.codes_to_binary_string(test_type_codes_as_code_array)) end def test_that_codes_are_in_range_1_to_127 TestUtils.assert_not_raised(ArgumentError) { RR::NXT::NxtTypes.codes_to_binary_string([1]) } TestUtils.assert_not_raised(ArgumentError) { RR::NXT::NxtTypes.codes_to_binary_string([127]) } assert_raises(ArgumentError) { RR::NXT::NxtTypes.codes_to_binary_string([0]) } assert_raises(ArgumentError) { RR::NXT::NxtTypes.codes_to_binary_string([128]) } end def test_that_zero_bit_set_raises_error assert_raises(ArgumentError) { RR::NXT::NxtTypes.codes_to_binary_string([]) } end def test_A_AAAA_NXT assert_equal([1, 28, 30], RR::NXT::NxtTypes.names_string_to_codes('A AAAA NXT')) assert_equal("P\x00\x00\x02", RR::NXT::NxtTypes.codes_to_binary_string([1, 28, 30])) end def test_type_bitmap_ctor_is_private assert_raises(NoMethodError) { RR::NXT::TypeBitmap.new('') } end def test_type_bitmap_to_s type_bitmap = RR::NXT::TypeBitmap.from_type_codes([1, 16, 30]) assert_equal('A TXT NXT', type_bitmap.to_s) end def test_parse_response_correctly response = Message.decode(EXAMPLE_NXT_RESPONSE_AS_BINARY) answer = response.answer nxt_record = answer[0] # Note: Although the NXT class is defined as Dnsruby::RR::NXT and not # Dnsruby::RR::IN::NXT, the IN module (in IN.rb) creates new classes # in the IN module for all class-insensitive resource record classes. # When the binary record is parsed, it is a Dnsruby::RR::IN::NXT # that is created. assert_equal(Dnsruby::RR::IN::NXT, nxt_record.class) actual_tokens = nxt_record.to_s.split expected_tokens = 'a.dnsruby.com. 10800 IN NXT b.dnsruby.com A AAAA NXT'.split assert_equal(actual_tokens, expected_tokens) end def assert_rr_content(rr) assert_equal(rr.type, 'NXT') # TODO: Should this be a string or a number? assert_equal(rr.name, Name.create('b.dnsruby.com.')) assert_equal(rr.ttl, 10800) assert_equal(rr.klass, 'IN') assert_equal(rr.next_domain, Name.create('a.dnsruby.com.')) end def test_new_from_string rr = RR::NXT.new_from_string('b.dnsruby.com. 10800 IN NXT a.dnsruby.com. SOA NXT') assert_rr_content(rr) end def test_new_from_hash assert_rr_content(sample_nxt_rr) end def test_new_from_data rdata = RR::NXT.build_rdata('a.dnsruby.com.', [Types::SOA, Types::NXT]) rr = RR::NXT.new_from_data('b.dnsruby.com.', Types::NXT, Classes::IN, 10800, rdata.size, rdata, 0) assert_rr_content(rr) end def test_owner_alias rr = sample_nxt_rr assert_equal('b.dnsruby.com', rr.owner.to_s) assert_equal('b.dnsruby.com', rr.name.to_s) new_name = Name.create('z.com') rr.owner = new_name assert_equal(new_name, rr.owner) assert_equal(new_name, rr.name) end def test_encode_decode_message nxt_rr = sample_nxt_rr message = Message.new message.add_answer(nxt_rr) binary_message = message.encode reconstructed_message = Message.decode(binary_message) reconstructed_nxt_rr = reconstructed_message.answer[0] assert_equal(nxt_rr, reconstructed_nxt_rr) end def sample_nxt_rr RR::NXT.new_from_hash( name: 'b.dnsruby.com.', ttl: 10800, klass: Classes::IN, next_domain: 'a.dnsruby.com.', types: [Types::SOA, Types::NXT]) end end # Sample zone file for setting up BIND to serve a NXT record: =begin $TTL 3h @ IN SOA dnsruby.com. foo.dnsruby.com. ( 1 ; serial 3H ; refresh after 3 hours 1H ; retry after 1 hour 1W ; expire after 1 week 1H) ; negative caching TTL of 1 hour dnsruby.com. IN NS ns1 ; Addresses for canonical names ns1.dnsruby.com. IN A 127.0.0.1 a.dnsruby.com. IN A 2.4.6.8 IN NXT b A AAAA NXT b.dnsruby.com. IN A 2.4.6.9 IN GPOS 40 50 60 =end dnsruby-1.61.3/test/tc_ipseckey.rb0000644000004100000410000000542713607400362017137 0ustar www-datawww-data # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestIPSECKEY < Minitest::Test include Dnsruby def test_ipseckey [{"38.1.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 3 2 mygateway.example.com. AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )" => ["mygateway.example.com", "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==", 10, 3, 2]}, {"38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 1 2 192.0.2.38 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )" => ["192.0.2.38", "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==", 10, 1, 2]}, {"38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 0 2 . AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )" => ["", "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==", 10, 0, 2]}, {"38.2.0.192.in-addr.arpa. 7200 IN IPSECKEY ( 10 1 2 192.0.2.3 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )" => ["192.0.2.3", "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==", 10, 1, 2]}, {"0.d.4.0.3.0.e.f.f.f.3.f.0.1.2.01.0.0.0.0.0.2.8.B.D.0.1.0.0.2.ip6.arpa. 7200 IN IPSECKEY ( 10 2 2 2001:0DB8:0:8002::2000:1 AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ== )" => ["2001:DB8:0:8002::2000:1", "AQNRU3mG7TVTO2BkR47usntb102uFJtugbo6BSGvgqt4AQ==", 10, 2, 2]} ].each {|hash| hash.each {|txt, data| ipseckey = RR.create(txt) assert(ipseckey.precedence == data[2]) assert(ipseckey.gateway_type == data[3]) assert(ipseckey.algorithm == data[4]) assert(ipseckey.gateway.to_s == data[0]) assert(ipseckey.public_key_string == data[1]) m = Dnsruby::Message.new m.add_additional(ipseckey) data = m.encode m2 = Dnsruby::Message.decode(data) ipseckey2 = m2.additional()[0] assert(ipseckey.gateway_type == ipseckey2.gateway_type) assert(ipseckey.algorithm == ipseckey2.algorithm) assert(ipseckey.gateway == ipseckey2.gateway) assert(ipseckey.klass == ipseckey2.klass) assert(ipseckey == ipseckey2) } } end end dnsruby-1.61.3/test/tc_hip.rb0000644000004100000410000000644613607400362016105 0ustar www-datawww-data # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestHIP < Minitest::Test include Dnsruby def test_hip [{"www.example.com. IN HIP ( 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D )" => [2, "200100107B1A74DF365639CC39F1D578", "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D", []]}, {"www.example.com. IN HIP ( 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs.example.com. )" => [2, "200100107B1A74DF365639CC39F1D578", "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D", ["rvs.example.com"]]}, {"www.example.com. IN HIP ( 2 200100107B1A74DF365639CC39F1D578 AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D rvs1.example.com. rvs2.example.com. )" => [2, "200100107B1A74DF365639CC39F1D578", "AwEAAbdxyhNuSutc5EMzxTs9LBPCIkOFH8cIvM4p9+LrV4e19WzK00+CI6zBCQTdtWsuxKbWIy87UOoJTwkUs7lBu+Upr1gsNrut79ryra+bSRGQb1slImA8YVJyuIDsj7kwzG7jnERNqnWxZ48AWkskmdHaVDP4BcelrTI3rMXdXF5D", ["rvs1.example.com", "rvs2.example.com"]]}, ].each {|hash| hash.each {|txt, data| hip = RR.create(txt) assert(hip.pk_algorithm == data[0]) assert(hip.hit_string == data[1]) assert(hip.public_key_string == data[2]) hip.rsvs.each {|in_rsv| assert(data[3].include?in_rsv.to_s) } assert(data[3].length == hip.rsvs.length) m = Dnsruby::Message.new m.add_additional(hip) data = m.encode m2 = Dnsruby::Message.decode(data) hip2 = m2.additional()[0] assert(hip.pk_algorithm == hip2.pk_algorithm) assert(hip.hit_string == hip2.hit_string) assert(hip.public_key_string == hip2.public_key_string) hip.rsvs.each {|in_rsv| assert(hip2.rsvs.include?in_rsv) } assert(hip2.rsvs.length == hip.rsvs.length) assert(hip == hip2) } } end end dnsruby-1.61.3/test/tc_rr-txt.rb0000644000004100000410000001127713607400362016563 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestRrTest < Minitest::Test include Dnsruby # Stimulus, expected response, and test name: TESTLIST = [ { # 2-5 :stim => %<"">, :rdatastr => %<"">, :char_str_list_r => ['',], :descr => 'Double-quoted null string', }, { # 6-9 :stim => %<''>, :rdatastr => %<"">, :char_str_list_r => ['',], :descr => 'Single-quoted null string', }, { # 10-13 :stim => %<" \t">, :rdatastr => %<" \t">, :char_str_list_r => [ %< \t>, ], :descr => 'Double-quoted whitespace string', }, { # 14-17 :stim => %, :rdatastr => %<"noquotes">, :char_str_list_r => [ %, ], :descr => 'unquoted single string', }, { # 18-21 :stim => %<"yes_quotes">, :rdatastr => %<"yes_quotes">, :char_str_list_r => [ %, ], :descr => 'Double-quoted single string', }, { # 26-29 :stim => %, :rdatastr => %<"two" "tokens">, :char_str_list_r => [ %q|two|, %q|tokens|, ], :descr => 'Two unquoted strings', }, # @TODO@ Why don't escaped quotes work? # { # 22-25 # :stim => %<"escaped \" quote">, # :rdatastr => %<"escaped \" quote">, # :char_str_list_r => [ %, ], # :descr => 'Quoted, escaped double-quote', # }, # { # 30-33 # :stim => %<"missing quote>, # :rdatastr => %<>, # :char_str_list_r => [], # :descr => 'Unbalanced quotes work', # } ] def test_RrTest # ------------------------------------------------------------------------------ # Canned data. # ------------------------------------------------------------------------------ name = 'foo.example.com'; klass = 'IN'; type = 'TXT'; ttl = 43201; rr_base = [name, ttl, klass, type, " " ].join(' ') # ------------------------------------------------------------------------------ # Run the tests # ------------------------------------------------------------------------------ TESTLIST.each do |test_hr| assert( uut = RR.create(rr_base + test_hr[:stim]), test_hr[:descr] + " -- Stimulus " ) assert_equal(test_hr[:rdatastr], uut.rdata_to_string(), test_hr[:descr] + " -- Response ( rdatastr ) " ) list = uut.strings assert_equal(test_hr[:char_str_list_r], list, test_hr[:descr] + " -- char_str_list equality" ) end string1 = % string2 = % rdata = [string1.length].pack("C") + string1 rdata += [string2.length].pack("C") + string2 work_hash = { :name => name, :ttl => ttl, :class => klass, :type => type, } # Don't break RR.new_from_hash (e.i. "See the manual pages for each RR # type to see what fields the type requires."). work_hash[:strings] = % uut = RR.create(work_hash) assert( uut , # 30 "RR.new_from_hash with txtdata -- Stimulus") assert_equal( uut.rdata_to_string() , %<"no" "quotes">, # 31 "RR.new_from_hash with txtdata -- Response (rdatastr())") rr_rdata = MessageEncoder.new {|msg| uut.encode_rdata(msg) }.to_s assert( rr_rdata == rdata , "TXT.rr_rdata" ) # 32 end def test_nasty_txt t = RR.create('txt2.t.net-dns.org. 60 IN TXT "Net-DNS\; complicated $tuff" "sort of \" text\; and binary \000 data"') assert(t.rdata.to_s == '"Net-DNS\; complicated $tuff" "sort of \" text\; and binary \000 data"', t.to_s) r1 = RR.create("auto._domainkey.cacert.org. 43200 IN TXT \"v=DKIM1\;g=*\;k=rsa\;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDNFxiNr+NHJwih3OPhGr4iwLE+BBDu72YrMSzUnU1FF50CW7iOtuhg796UZ6xrZ5VuhAix6YmmzcvF2UxYzoD/XpfZ4MzBu0ND4/nkt9/YOTyIBzwQqn9uMNve0Y76Zsel89dIJtOI+y+lfnFExV0jKwe53gzmxMVpMSSCcZPGwIDAQAB\" ; ----- DKIM auto for cacert.org") r2 = RR.create("auto._domainkey.cacert.org. 43200 IN TXT \"v=DKIM1;g=*;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDDNFxiNr+NHJwih3OPhGr4iwLE+BBDu72YrMSzUnU1FF50CW7iOtuhg796UZ6xrZ5VuhAix6YmmzcvF2UxYzoD/XpfZ4MzBu0ND4/nkt9/YOTyIBzwQqn9uMNve0Y76Zsel89dIJtOI+y+lfnFExV0jKwe53gzmxMVpMSSCcZPGwIDAQAB\"") assert(r1.to_s == r2.to_s) end end dnsruby-1.61.3/test/tc_resolver.rb0000644000004100000410000003165213607400362017163 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either tmexpress or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'socket' # @TODO@ We also need a test server so we can control behaviour of server to test # different aspects of retry strategy. # Of course, with Ruby's limit of 256 open sockets per process, we'd need to run # the server in a different Ruby process. class TestResolver < Minitest::Test include Dnsruby Thread::abort_on_exception = true GOOD_DOMAIN_NAME = 'example.com' BAD_DOMAIN_NAME = 'dnsruby-test-of-bad-domain-name.blah' PORT = 42138 @@port = PORT def setup Dnsruby::Config.reset end def assert_valid_response(response) assert(response.kind_of?(Message), "Expected response to be a message but was a #{response.class}") end def assert_nil_response(response) assert(response.nil?, "Expected no response but got a #{response.class}:\n#{response}") end def assert_error_is_exception(error, error_class = Exception) assert(error.is_a?(error_class), "Expected error to be an #{error_class}, but was a #{error.class}:\n#{error}") end def assert_nil_error(error) assert(error.nil?, "Expected no error but got a #{error.class}:\n#{error}") end def test_send_message response = Resolver.new.send_message(Message.new("example.com", Types.A)) assert_valid_response(response) end def test_send_message_bang_noerror response, error = Resolver.new.send_message!(Message.new(GOOD_DOMAIN_NAME, Types.A)) assert_nil_error(error) assert_valid_response(response) end def test_send_message_bang_error message = Message.new(BAD_DOMAIN_NAME, Types.A) response, error = Resolver.new.send_message!(message) assert_nil_response(response) assert_error_is_exception(error) end def test_send_plain_message resolver = Resolver.new response, error = resolver.send_plain_message(Message.new("cnn.com")) assert_nil_error(error) assert_valid_response(response) m = Message.new(BAD_DOMAIN_NAME) m.header.rd = true response, error = resolver.send_plain_message(m) assert_valid_response(response) assert_error_is_exception(error, NXDomain) end def test_query response = Resolver.new.query("example.com") assert_valid_response(response) end def test_query_bang_noerror response, error = Resolver.new.query!(GOOD_DOMAIN_NAME) assert_nil_error(error) assert_valid_response(response) end def test_query_bang_error response, error = Resolver.new.query!(BAD_DOMAIN_NAME) assert_nil_response(response) assert_error_is_exception(error) end def test_query_async q = Queue.new Resolver.new.send_async(Message.new("example.com", Types.A),q,q) id, response, error = q.pop assert_equal(id, q, "Id wrong!") assert_valid_response(response) assert_nil_error(error) end def test_query_one_duff_server_one_good res = Resolver.new({:nameserver => ["8.8.8.8", "8.8.8.7"]}) res.retry_delay=1 q = Queue.new res.send_async(Message.new("example.com", Types.A),q,q) id, response, error = q.pop assert_equal(id, q, "Id wrong!") assert_valid_response(response) assert_nil_error(error) end # @TODO@ Implement!! But then, why would anyone want to do this? # def test_many_threaded_clients # assert(false, "IMPLEMENT!") # end def test_reverse_lookup m = Message.new("8.8.8.8", Types.PTR) r = Resolver.new q=Queue.new r.send_async(m,q,q) id,ret, error=q.pop assert(ret.kind_of?(Message)) no_pointer=true ret.each_answer do |answer| if (answer.type==Types.PTR) no_pointer=false assert(answer.domainname.to_s=~/google/) end end assert(!no_pointer) end # def test_bad_host # res = Resolver.new({:nameserver => "localhost"}) # res.retry_times=1 # res.retry_delay=0 # res.query_timeout = 1 # q = Queue.new # res.send_async(Message.new("example.com", Types.A), q, q) # id, m, err = q.pop # assert(id==q) # assert(m == nil) # assert(err.kind_of?(OtherResolvError) || err.kind_of?(IOError), "OtherResolvError or IOError expected : got #{err.class}") # end # def test_nxdomain resolver = Resolver.new q = Queue.new resolver .send_async(Message.new(BAD_DOMAIN_NAME, Types.A), q, 1) id, m, error = q.pop assert(id==1, "Id should have been 1 but was #{id}") assert(m.rcode == RCode.NXDOMAIN, "Expected NXDOMAIN but got #{m.rcode} instead.") assert_error_is_exception(error, NXDomain) end def test_timeouts # test timeout behaviour for different retry, retrans, total timeout etc. # Problem here is that many sockets will be created for queries which time out. # Run a query which will not respond, and check that the timeout works if (!RUBY_PLATFORM=~/darwin/) start=stop=0 retry_times = 3 retry_delay=1 packet_timeout=2 # Work out what time should be, then time it to check expected = ((2**(retry_times-1))*retry_delay) + packet_timeout begin res = Dnsruby::Resolver.new({:nameserver => "10.0.1.128"}) # res = Resolver.new({:nameserver => "213.248.199.17"}) res.packet_timeout=packet_timeout res.retry_times=retry_times res.retry_delay=retry_delay start=Time.now m = res.send_message(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A)) fail rescue ResolvTimeout stop=Time.now time = stop-start assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}") end end end def test_packet_timeout res = Dnsruby::Resolver.new({:nameserver => []}) # res = Resolver.new({:nameserver => "10.0.1.128"}) start=stop=0 retry_times = retry_delay = packet_timeout= 10 query_timeout=2 begin res.packet_timeout=packet_timeout res.retry_times=retry_times res.retry_delay=retry_delay res.query_timeout=query_timeout # Work out what time should be, then time it to check expected = query_timeout start=Time.now m = res.send_message(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A)) fail rescue Dnsruby::ResolvTimeout stop=Time.now time = stop-start assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}") end # end def test_queue_packet_timeout # if (!RUBY_PLATFORM=~/darwin/) res = Dnsruby::Resolver.new({:nameserver => "10.0.1.128"}) # bad = SingleResolver.new("localhost") res.add_server("localhost") expected = 2 res.query_timeout=expected q = Queue.new start = Time.now m = res.send_async(Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk", Types.A), q, q) id,ret,err = q.pop stop = Time.now assert(id=q) assert(ret==nil) assert(err.class == ResolvTimeout, "#{err.class}, #{err}") time = stop-start assert(time <= expected * 1.3 && time >= expected * 0.9, "Wrong time take, expected #{expected}, took #{time}") # end end def test_illegal_src_port # Also test all singleresolver ports ok # Try to set src_port to an illegal value - make sure error raised, and port OK res = Dnsruby::Resolver.new res.port = 56789 tests = [53, 387, 1265, 3210, 48619] tests.each do |bad_port| begin res.src_port = bad_port fail("bad port #{bad_port}") rescue end end assert(res.single_resolvers[0].src_port = 56789) end def test_add_src_port # Try setting and adding port ranges, and invalid ports, and 0. # Also test all singleresolver ports ok res = Resolver.new res.src_port = [56789,56790, 56793] assert(res.src_port == [56789,56790, 56793]) res.src_port = 56889..56891 assert(res.src_port == [56889,56890,56891]) res.add_src_port(60000..60002) assert(res.src_port == [56889,56890,56891,60000,60001,60002]) res.add_src_port([60004,60005]) assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005]) res.add_src_port(60006) assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006]) # Now test invalid src_ports tests = [0, 53, [60007, 53], [60008, 0], 55..100] tests.each do |x| begin res.add_src_port(x) fail() rescue end end assert(res.src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006]) assert(res.single_resolvers[0].src_port == [56889,56890,56891,60000,60001,60002,60004,60005,60006]) end def test_eventtype_api # @TODO@ TEST THE Resolver::EventType interface! end end # Tests to see that query_raw handles send_plain_message's return values correctly. class TestRawQuery < Minitest::Test KEY_NAME = 'key-name' KEY = '0123456789' ALGO = 'hmac-md5' class CustomError < RuntimeError; end # Returns a new resolver whose send_plain_message method always returns # nil for the response, and a RuntimeError for the error. def resolver_returning_error resolver = Dnsruby::Resolver.new def resolver.send_plain_message(_message) [nil, CustomError.new] end resolver end # Returns a new resolver whose send_plain_message is overridden to return # :response_from_send_plain_message instead of a real Dnsruby::Message, # for easy comparison in the tests. def resolver_returning_response resolver = Dnsruby::Resolver.new def resolver.send_plain_message(_message) [:response_from_send_plain_message, nil] end resolver end # Test that when a strategy other than :raise or :return is passed, # an ArgumentError is raised. def test_bad_strategy assert_raises(ArgumentError) do resolver_returning_error.query_raw(Dnsruby::Message.new, :invalid_strategy) end end # Test that when send_plain_message returns an error, # and the error strategy is :raise, query_raw raises an error. def test_raise_error assert_raises(CustomError) do resolver_returning_error.query_raw(Dnsruby::Message.new, :raise) end end # Tests that if you don't specify an error strategy, an error will be # returned rather than raised (i.e. strategy defaults to :return). def test_return_error_is_default _response, error = resolver_returning_error.query_raw(Dnsruby::Message.new) assert error.is_a?(CustomError) end # Tests that when no error is returned, no error is raised. def test_raise_no_error response, _error = resolver_returning_response.query_raw(Dnsruby::Message.new, :raise) assert_equal :response_from_send_plain_message, response end # Test that when send_plain_message returns an error, and the error strategy # is set to :return, then an error is returned. def test_return_error _response, error = resolver_returning_error.query_raw(Dnsruby::Message.new, :return) assert error.is_a?(CustomError) end # Test that when send_plain_message returns a valid and response # and nil error, the same are returned by query_raw. def test_return_no_error response, error = resolver_returning_response.query_raw(Dnsruby::Message.new, :return) assert_nil error assert_equal :response_from_send_plain_message, response end def test_2_args_init options = Dnsruby::Resolver.create_tsig_options(KEY_NAME, KEY) assert_equal KEY_NAME, options[:name] assert_equal KEY, options[:key] assert_nil options[:algorithm] end def test_3_args_init options = Dnsruby::Resolver.create_tsig_options(KEY_NAME,KEY,ALGO) assert_equal KEY_NAME, options[:name] assert_equal KEY, options[:key] assert_equal ALGO, options[:algorithm] end def test_threads resolver = Dnsruby::Resolver.new(nameserver: ["8.8.8.8", "8.8.4.4"]) resolver.query("google.com", "MX") resolver.query("google.com", "MX") resolver.query("google.com", "MX") begin resolver.query("googlöe.com", "MX") rescue Dnsruby::ResolvError => e # fine end resolver.query("google.com", "MX") resolver.query("google.com", "MX") begin resolver.query("googlöe.com", "MX") rescue Dnsruby::ResolvError => e # fine end begin resolver.query("googlöe.com", "MX") rescue Dnsruby::ResolvError => e # fine end # Dnsruby::Cache.delete("googlöe.com", "MX") end end dnsruby-1.61.3/test/tc_ptrin.rb0000644000004100000410000017162113607400362016457 0ustar www-datawww-datarequire_relative 'spec_helper' class TestPtrIn < Minitest::Test include Dnsruby # Tests that message raises no error when decoded, encoded, and decoded again. def verify(message_data_as_hex_string, canonical = false) # Dnsruby.log.level = Logger::DEBUG message = Message.decode([message_data_as_hex_string].pack('H*').force_encoding("ASCII-8BIT")) Message.decode(message.encode(canonical)) end #example.com A IN (2-byte size removed at beginning) def test_non_canonical verify( 'f8f681900001000200030005076578616d706c6503636f6d0000010001c00c0001000100014f0c00045db8d822c00c002e000100014f0c00a20001080200015180580ec93f57f38df906a8076578616d706c6503636f6d006ae1882b1536a15c44f5813671af57bf9cae0366cff2ec085d6dedfddff0c469fa827ceec953de7cc1eee634f4cf695dc2caa2074f95199a5582e51e63b336d8f091d18c0c1a307ae3f5508ec650c4085a95e54e2c2451d9fc9ae04b4e62f3d1a1689e9507c3692fb84817a70afd3e9cdf066f73cc4ac11ed080a30d2af31510b457b5c04b0002000100014f0c001401620c69616e612d73657276657273036e657400c04b0002000100014f0c00040161c0e9c04b002e000100014f0c00a2000208020001518058109f4c57f56c1906a8076578616d706c6503636f6d006d8dd0fdbd0a0b0bfe7e4306a4a001bb7a13df2faedb1702a329243c326b915191335e99e16a236de99360547efa96ec6ee547a6dcfab94b57de6f7891bcaf99a2ef5d3c72d5bc18d1bf05ff4473f527bd8f2e6621489ab531dfb6a973e37e0f0be52740a362599058b204097a04c96492e527bfca6a22338eb865b51156c2ab0e6940c10700010001000004940004c72b8735c107001c00010001e209001020010500008f00000000000000000053c0e700010001000004940004c72b8535c0e7001c00010001e209001020010500008d000000000000000000530000291000000080000000') end #32.197.46.207.in-addr.arpa: type PTR, class IN (2-byte size removed at beginning) def test_canonical verify( 'bb7a81900001041200000001023332033139370234360332303707696e2d61646472046172706100000c0001c00c000c0001000008aa0017116270666f726772656174706c61696e733203636f6d00c00c000c0001000008aa00130e64657369676e6564666f7262696702636e00c00c000c0001000008aa000f0a6f66666963653230303702636800c00c000c0001000008aa000e0977696e646f77737870026b7a00c00c000c0001000008aa00150f77696e646f77733230303074657374036f726700c00c000c0001000008aa000e0b77696e646f77736e743938c0bfc00c000c0001000008aa0016117370726f7374616a77797a77616e696f6d02706c00c00c000c0001000008aa000f09697462727565636b65036e657400c00c000c0001000008aa001512626c7565796f6e6465726169726c696e6573c0bfc00c000c0001000008aa00140f6d73646e746563686e6574746f757202667200c00c000c0001000008aa000c0977696e646f77733938c085c00c000c0001000008aa00130a6f66666963653230303703636f6d026d7800c00c000c0001000008aa000906617a7572696bc116c00c000c0001000008aa00140f65756772616e747361647669736f7202697400c00c000c0001000008aa00171477696e646f7773787067616d6561647669736f72c0bfc00c000c0001000008aa000e09697462727565636b6502646500c00c000c0001000008aa00100b77696e646f77737275627902686b00c00c000c0001000008aa00120977696e646f7773787003636f6d02677400c00c000c0001000008aa000e0b77696e646f777332303037c0bfc00c000c0001000008aa00151268756d6f6e676f7573696e737572616e6365c04ac00c000c0001000008aa001411756b636f6d6d756e697479617761726473c116c00c000c0001000008aa00130a77696e64736f77737870036e657402646f00c00c000c0001000008aa001509646f776e6c6f616473086263656e7472616cc04ac00c000c0001000008aa000e09666f726566726f6e7402617400c00c000c0001000008aa00120d726573706f6e7365706f696e74026c7400c00c000c0001000008aa00130e65766572796f6e6567657473697402657500c00c000c0001000008aa000e09666f726566726f6e7402626500c00c000c0001000008aa00100977696e646f77737870036f7267c2b5c00c000c0001000008aa00120977696e646f777378700372656302726f00c00c000c0001000008aa00120d726573706f6e7365706f696e7402706800c00c000c0001000008aa0015126361707375726c6573756363657332303038c116c00c000c0001000008aa000e0977696e646f7773787002706e00c00c000c0001000008aa000e0b77696e646f777332303030c085c00c000c0001000008aa001815636f686f76696e6579617264616e6477696e657279c04ac00c000c0001000008aa00120977696e646f7773787003636f6d02746c00c00c000c0001000008aa00140f65756772616e747361647669736f72026c7600c00c000c0001000008aa00140f65756772616e747361647669736f7202636c00c00c000c0001000008aa00181164656679616c6c6368616c6c656e67657303636f6dc21dc00c000c0001000008aa00110e7374617274736f6d657468696e67c347c00c000c0001000008aa00100977696e646f77737870036f7267c23bc00c000c0001000008aa00100977696e646f77737870036f7267c0fcc00c000c0001000008aa0016136f666672652d65626c6f75697373616e746573c0bfc00c000c0001000008aa00140b77696e646f77737275627903636f6d02657300c00c000c0001000008aa000f0a6f66666963653230303702636100c00c000c0001000008aa000e0977696e646f7773787002616d00c00c000c0001000008aa001109626570636c6567616c02636f02756b00c00c000c0001000008aa000d0877696e646f77787002747600c00c000c0001000008aa00110977696e646f7773787004696e666fc381c00c000c0001000008aa001f1c786e2d2d66726465726d697474656c2d72617467656265722d333962c201c00c000c0001000008aa00110e65766572796f6e65676574736974c531c00c000c0001000008aa00151268756d6f6e676f7573696e737572616e6365c0bfc00c000c0001000008aa00110e696973646961676e6f7374696373c0bfc00c000c0001000008aa00160f65756772616e747361647669736f7203636f6dc0fcc00c000c0001000008aa000b06666c6578676f02696e00c00c000c0001000008aa00120f696e73696465647269766532303032c04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c18bc00c000c0001000008aa001815636f6e736f6c6964617465646d657373656e676572c116c00c000c0001000008aa000c09666f726566726f6e74c678c00c000c0001000008aa00140b77696e646f77737275627903636f6d02766500c00c000c0001000008aa0017146761676e657a2d656e2d65666669636163697465c0bfc00c000c0001000008aa000f0c636f686f76696e6579617264c0bfc00c000c0001000008aa001714636f6e73756d6572676f6f647373656d696e6172c04ac00c000c0001000008aa001310666f726f706572737065637469766173c0bfc00c000c0001000008aa00120977696e646f7773787003636f6d02616900c00c000c0001000008aa00170e65766572796f6e6567657473697403636f6d02617500c00c000c0001000008aa00120977696e646f77737870036e657402766900c00c000c0001000008aa00120f77696e646f77733230303074657374c116c00c000c0001000008aa00180f65756772616e747361647669736f7203636f6d02707400c00c000c0001000008aa00100b77696e646f777332303030026e6c00c00c000c0001000008aa000c0977696e646f77733935c0bfc00c000c0001000008aa0014117365727665757273616e736c696d697465c116c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c2f0c00c000c0001000008aa000e0977696e646f7773787002736e00c00c000c0001000008aa000e0977696e646f7773787002736300c00c000c0001000008aa0017146d6963726f736f667474696d65657870656e7365c04ac00c000c0001000008aa001c19647269766572646576656c6f706572636f6e666572656e6365c0bfc00c000c0001000008aa000b0877696e646f777870c82bc00c000c0001000008aa000b0661786170746102727500c00c000c0001000008aa000f09666f726566726f6e7402636fc678c00c000c0001000008aa000b0877696e646f777870c436c00c000c0001000008aa000c09626570636c6567616cc04ac00c000c0001000008aa000b0877696e646f777870c456c00c000c0001000008aa000b06666c6578676f02746d00c00c000c0001000008aa0017146761676e657a2d656e2d65666669636163697465c116c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c158c00c000c0001000008aa00110e636c7562736d61727470686f6e65c116c00c000c0001000008aa000c09666f726566726f6e74c158c00c000c0001000008aa00100d726573706f6e7365706f696e74c347c00c000c0001000008aa0015126361707375726c6573756363657332303038c0bfc00c000c0001000008aa000c09666f726566726f6e74c52dc00c000c0001000008aa000906666c6578676fc84bc00c000c0001000008aa00100977696e646f77737870036f7267c39fc00c000c0001000008aa00151273747265616d6c696e6566696e616e636573c04ac00c000c0001000008aa00130d726573706f6e7365706f696e740362697a00c00c000c0001000008aa00120d726573706f6e7365706f696e7402646b00c00c000c0001000008aa000f0c6d6f6e6e6f7576656c616d69c158c00c000c0001000008aa00120f77696e646f77733230303074657374c04ac00c000c0001000008aa000f0c666f75727468636f66666565c04ac00c000c0001000008aa001310666f726f706572737065637469766173c116c00c000c0001000008aa00110e6272757465666f72636567616d65c04ac00c000c0001000008aa000c09696e73656775726f73c116c00c000c0001000008aa00100d726573706f6e7365706f696e74c381c00c000c0001000008aa000f0c647269766572646576636f6ec04ac00c000c0001000008aa001611666f727265746e696e677373797374656d026e6f00c00c000c0001000008aa00140f65756772616e747361647669736f72026c7500c00c000c0001000008aa00100977696e646f77737870036e6574c436c00c000c0001000008aa00120977696e646f7773787003636f6d02666a00c00c000c0001000008aa00140d726573706f6e7365706f696e7403636f6dc06ac00c000c0001000008aa00100d726573706f6e7365706f696e74c580c00c000c0001000008aa000c09666f726566726f6e74c085c00c000c0001000008aa000e0977696e646f7773787002686e00c00c000c0001000008aa00161370736f65786563757469766573656d696e6172c04ac00c000c0001000008aa000906656e6779726fc04ac00c000c0001000008aa00110e6361736866696e616e6369616c73c04ac00c000c0001000008aa00151268756d6f6e676f7573696e737572616e6365c116c00c000c0001000008aa001512636f6e666f726d6974656c6963656e636573c0bfc00c000c0001000008aa0019166c65732d646f696774732d64616e732d6c652d6e657ac158c00c000c0001000008aa000e0977696e646f7773787002616700c00c000c0001000008aa000d0a676f746f746563686564c04ac00c000c0001000008aa00110e667574757265706f73746d61696cc580c00c000c0001000008aa0019166c7574746572636f6e7472656c657069726174616765c0bfc00c000c0001000008aa0014116c756365726e657075626c697368696e67c04ac00c000c0001000008aa000c09666f726566726f6e74cb26c00c000c0001000008aa000e0977696e646f7773787002757a00c00c000c0001000008aa000d0a636f686f77696e657279c116c00c000c0001000008aa001310657870657269656e6365746563686564c04ac00c000c0001000008aa00191677696e646f7773647269766572646576656c6f706572c0bfc00c000c0001000008aa000b0877696e646f777870cac6c00c000c0001000008aa00100d726573706f6e7365706f696e74c70ac00c000c0001000008aa000e0977696e646f7773787002666d00c00c000c0001000008aa00100d726573706f6e7365706f696e74c951c00c000c0001000008aa00130a6f66666963653230303703636f6d02736700c00c000c0001000008aa00110e64657369676e6564666f72626967c116c00c000c0001000008aa000b086370616475766f6cc0bfc00c000c0001000008aa000d0877696e646f777870026d6e00c00c000c0001000008aa000c09666f726566726f6e74c201c00c000c0001000008aa000e0977696e646f77737870026d7500c00c000c0001000008aa000a07666f7269656e74c04ac00c000c0001000008aa000c0977696e646f77737870cfa3c00c000c0001000008aa00100d6c6573626f6e736f7574696c73c0bfc00c000c0001000008aa0014117365727665757273616e736c696d697465c04ac00c000c0001000008aa000e0b6f66666963657265616479cb26c00c000c0001000008aa00120d726573706f6e7365706f696e7402626f00c00c000c0001000008aa000b086d6f6e727562616ec04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c085c00c000c0001000008aa00100d726573706f6e7365706f696e74c54cc00c000c0001000008aa00110e6a65646572686174736472617566c116c00c000c0001000008aa001e1b647269766572646576656c6f706d656e74636f6e666572656e6365c0bfc00c000c0001000008aa000e0b77696e646f777372756279c84bc00c000c0001000008aa001613636f6e666f726d6974652d6c6963656e636573c116c00c000c0001000008aa0015126361707375726c6573756363657332303038c04ac00c000c0001000008aa000d0877696e646f77787002677300c00c000c0001000008aa000d0a7374756f73626f726e65c116c00c000c0001000008aa000c09666f726566726f6e74c18bc00c000c0001000008aa0013106a656465722d686174732d6472617566c201c00c000c0001000008aa0014116d6f62696c657063646576656c6f706572c04ac00c000c0001000008aa00100977696e646f77737870036f7267c3dac00c000c0001000008aa00160d726573706f6e7365706f696e7403636f6d026d7900c00c000c0001000008aa000f0c636f686f76696e6579617264c04ac00c000c0001000008aa00120f73656d696e61697265732d6e617635c158c00c000c0001000008aa000f0c647269766572646576636f6ec116c00c000c0001000008aa000b0877696e646f777870c566c00c000c0001000008aa000f0c6469676974616c616e76696cc04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c158c00c000c0001000008aa000c0977696e646f77733938c04ac00c000c0001000008aa0018156e61762d636f6d706c69616e636577656263617374c04ac00c000c0001000008aa00110e70726f6a656374657870656e7365c04ac00c000c0001000008aa000906666c6578676fc0bfc00c000c0001000008aa000f0877696e646f77787003636f6dc8d8c00c000c0001000008aa000b086c69666563616d73c04ac00c000c0001000008aa00120f696d6167696e657a6c617375697465c0bfc00c000c0001000008aa00171474616b65636f6e74726f6c6f66706179726f6c6cc04ac00c000c0001000008aa0019166772617068696364657369676e696e73746974757465c04ac00c000c0001000008aa00130d726573706f6e7365706f696e7402636fc2f0c00c000c0001000008aa000e0b77696e646f777372756279c498c00c000c0001000008aa000c09697462727565636b65c0bfc00c000c0001000008aa001a17636c756270617274656e61697265737365727669636573c116c00c000c0001000008aa000d0a696d6167696e65637570c580c00c000c0001000008aa000e0b77696e646f777372756279cf52c00c000c0001000008aa001714617a7572696b726973656f667065726174686961c0bfc00c000c0001000008aa00100d726573706f6e7365706f696e74c7cbc00c000c0001000008aa001c19647269766572646576656c6f706572636f6e666572656e6365c04ac00c000c0001000008aa000f0c6d6f6e6e6f7576656c616d69c04ac00c000c0001000008aa000c09626c6f6f6477616b65c04ac00c000c0001000008aa00110e6a65646572686174736472617566c201c00c000c0001000008aa00120d726573706f6e7365706f696e7402646d00c00c000c0001000008aa001613636f6e666f726d6974652d6c6963656e636573c0bfc00c000c0001000008aa00120f696d6167696e657a6c617375697465c158c00c000c0001000008aa000c09666f726566726f6e74c951c00c000c0001000008aa000c09666f726566726f6e74c531c00c000c0001000008aa000e0b77696e646f777332303037cb07c00c000c0001000008aa00110e6d6f62696c6574656368746f7572c116c00c000c0001000008aa000e0b77696e646f777332303036c116c00c000c0001000008aa00120f63656e747265646573757361676573c0bfc00c000c0001000008aa001e1b786e2d2d66726465726d697474656c72617467656265722d713662c201c00c000c0001000008aa000b06666c6578676f02757300c00c000c0001000008aa00120d726573706f6e7365706f696e7402736500c00c000c0001000008aa0007046f727063cb26c00c000c0001000008aa001512696e666f726d61736a6f6e7373797374656dcc27c00c000c0001000008aa001109666f726566726f6e7402636f026b7200c00c000c0001000008aa00130a6f66666963653230303703636f6d02627200c00c000c0001000008aa000e0977696e646f7773787002626900c00c000c0001000008aa00252277696e646f7773647269766572646576656c6f706d656e74636f6e666572656e6365c04ac00c000c0001000008aa001310666f726f706572737065637469766173c04ac00c000c0001000008aa000a077061726c616e6fc04ac00c000c0001000008aa00110e696973646961676e6f7374696373c116c00c000c0001000008aa000906617a7572696bc0bfc00c000c0001000008aa000c09696e73656775726f73c04ac00c000c0001000008aa000e0977696e646f77737870026b6700c00c000c0001000008aa000e0977696e646f7773787002636700c00c000c0001000008aa00150e65766572796f6e65676574736974036e6574c7cfc00c000c0001000008aa000d0a636f686f77696e657279c04ac00c000c0001000008aa00120f6d797374617274757063656e746572c04ac00c000c0001000008aa001714706c75736465343030646966666572656e636573c54cc00c000c0001000008aa00100d726573706f6e7365706f696e74c0bfc00c000c0001000008aa0015126c6573646f6967747364616e736c656e657ac158c00c000c0001000008aa000c0970726f736577617265c116c00c000c0001000008aa000e0b6374726f70656e6f726d65c04ac00c000c0001000008aa000c0970726f736577617265c04ac00c000c0001000008aa000f0c77696e646f77732d32303030c84bc00c000c0001000008aa00120f696d70726f7665616e616c79736973c04ac00c000c0001000008aa000c09666f726566726f6e74c1c4c00c000c0001000008aa00191664656375706c657a766f747265706f74656e7469656cc04ac00c000c0001000008aa001a17686572617573666f72646572756e67736d656973746572c201c00c000c0001000008aa001411627033666f726772656174706c61696e73c04ac00c000c0001000008aa00120f696d6167696e65726c617375697465c04ac00c000c0001000008aa00120f696d6167696e65726c617375697465c158c00c000c0001000008aa00100d726573706f6e7365706f696e74c59cc00c000c0001000008aa000d0a77696e7465726e616c73c04ac00c000c0001000008aa0009046575676102677200c00c000c0001000008aa000e0b6374726f70656e6f726d65c116c00c000c0001000008aa001411756b636f6d6d756e697479617761726473c0bfc00c000c0001000008aa0015126c6573646f6967747364616e736c656e657ac0bfc00c000c0001000008aa0009066f6666726573d967c00c000c0001000008aa00100d6f70656e74797065666f72756dc04ac00c000c0001000008aa00100d726573706f6e7365706f696e74da48c00c000c0001000008aa00150d726573706f6e7365706f696e7402636f02696c00c00c000c0001000008aa001916656e68616e6365796f757270656f706c656173736574c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c951c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c21dc00c000c0001000008aa00100d63696f2d636f6d6d756e697479c085c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c580c00c000c0001000008aa0013106a656465722d686174732d6472617566c0bfc00c000c0001000008aa001815666f65726465726d697474656c7261746765626572c201c00c000c0001000008aa000c09626570636c6567616cc116c00c000c0001000008aa0019166d69677265727665727376697375616c73747564696fc0bfc00c000c0001000008aa000f0c6270666f72736f6c6f6d6f6ec04ac00c000c0001000008aa0019166f6666696365706f75726c65736574756469616e7473c0bfc00c000c0001000008aa00100d627033666f72736f6c6f6d6f6ec04ac00c000c0001000008aa00090669746865726fd6e4c00c000c0001000008aa0014116d6f62696c657063646576656c6f706572c0bfc00c000c0001000008aa000b08706f636b65747063c116c00c000c0001000008aa001512636f6e666f726d6974656c6963656e636573c116c00c000c0001000008aa000c096576726f706c616e6fda48c00c000c0001000008aa001a17636c756270617274656e61697265737365727669636573c0bfc00c000c0001000008aa001c19647269766572646576656c6f706572636f6e666572656e6365c116c00c000c0001000008aa00171467702d636f6d706c69616e636577656263617374c04ac00c000c0001000008aa000e0b7061636b746f7574656e31c0bfc00c000c0001000008aa0019166c65732d646f696774732d64616e732d6c652d6e657ac04ac00c000c0001000008aa001109686f77746f74656c6c02636f026e7a00c00c000c0001000008aa001e1b647269766572646576656c6f706d656e74636f6e666572656e6365c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74d696c00c000c0001000008aa000e0b7061636b746f7574656e31c116c00c000c0001000008aa001512636f6e666f726d6974656c6963656e636573c04ac00c000c0001000008aa000b0877696e646f777870c39fc00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c531c00c000c0001000008aa001613736d61727470686f6e65636f6d6d756e697479c04ac00c000c0001000008aa001e1b647269766572646576656c6f706d656e74636f6e666572656e6365c116c00c000c0001000008aa000f0c6d6f6e6e6f7576656c616d69c116c00c000c0001000008aa000f0c7061636b746f7574656e756ec0bfc00c000c0001000008aa00110e65766572796f6e65676574736974c54cc00c000c0001000008aa00100d77696e646f77736d6f62696c65d696c00c000c0001000008aa001b126d6963726f736f6674666f726566726f6e7403636f6d02747700c00c000c0001000008aa00120f6d73646e746563686e6574746f7572c04ac00c000c0001000008aa00120977696e646f77737870036f726702747000c00c000c0001000008aa00120f6f6e6c696e6573706f746c69676874c0bfc00c000c0001000008aa00100d6c6573626f6e736f7574696c73c116c00c000c0001000008aa000a076f6e6d79776179cb26c00c000c0001000008aa000c09696f2d6d6f64656c6cc201c00c000c0001000008aa000a076f6e6563617265c04ac00c000c0001000008aa00110e726973656f667065726174686961c04ac00c000c0001000008aa000f0c636c7562706f636b65747063c04ac00c000c0001000008aa001a176d6963726f736f66742d6272616e6368656e766964656fc201c00c000c0001000008aa0009066370616e646cc04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c0bfc00c000c0001000008aa0013106a656465722d686174732d6472617566c116c00c000c0001000008aa000c096f6e6d79776179756bc04ac00c000c0001000008aa000a07636f6e746f736fc04ac00c000c0001000008aa000b086e61766973696f6ec085c00c000c0001000008aa0014116361702d7375722d6c652d737563636573c0bfc00c000c0001000008aa000c09696e73656775726f73c0bfc00c000c0001000008aa00141164656679616c6c6368616c6c656e676573c1c4c00c000c0001000008aa000c096c6561726e32617370c116c00c000c0001000008aa00100d66696e656172747363686f6f6cc116c00c000c0001000008aa00110e646f776e6c6f616467726f6f7665c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74d702c00c000c0001000008aa000f0c7061636b746f7574656e756ec04ac00c000c0001000008aa0009066370616e646cc0bfc00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c52dc00c000c0001000008aa00120f646566726167636f6d6d616e646572c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c39fc00c000c0001000008aa001a17636c756270617274656e61697265737365727669636573c04ac00c000c0001000008aa0023206d6f6465726e65722d76657277616c74756e677361726265697473706c61747ac201c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c085c00c000c0001000008aa000c09666f726566726f6e74c39fc00c000c0001000008aa0013106a656465722d686174732d6472617566c04ac00c000c0001000008aa00100d77696e646f77736d6f62696c65c531c00c000c0001000008aa00110e65766572796f6e65676574736974c580c00c000c0001000008aa00131074686570686f6e652d636f6d70616e79c04ac00c000c0001000008aa00110e646f776e6c6f616467726f6f7665c116c00c000c0001000008aa0015126c6573646f6967747364616e736c656e657ac116c00c000c0001000008aa0014116d616e6167656669786564617373657473c04ac00c000c0001000008aa000c09707261786973746167c085c00c000c0001000008aa00150e65766572796f6e65676574736974036f7267c583c00c000c0001000008aa0015126f666672652d65626c6f75697373616e7465c04ac00c000c0001000008aa000c09697462727565636b65c04ac00c000c0001000008aa0014116d616e6167656669786564617373657473c0bfc00c000c0001000008aa000f0c6d6963726f736f6674646f70c116c00c000c0001000008aa000e0b77696e646f777332303030c2f0c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74cc27c00c000c0001000008aa0016136e6f74666f7270726f66697473656d696e6172c04ac00c000c0001000008aa00120f696d6167696e657a6c617375697465c116c00c000c0001000008aa00100d726573706f6e7365706f696e74d6e4c00c000c0001000008aa000c09666f726566726f6e74df83c00c000c0001000008aa0013106e6f72746877696e6474726164657273c04ac00c000c0001000008aa00100d77696e646f77736d6f62696c65c201c00c000c0001000008aa00131069697377656263617374736572696573c0bfc00c000c0001000008aa001815636f6e736f6c6964617465646d657373656e676572c04ac00c000c0001000008aa00120d726573706f6e7365706f696e7402636300c00c000c0001000008aa0014116d616e6167656669786564617373657473c116c00c000c0001000008aa000e0977696e646f7773787002766300c00c000c0001000008aa00100d646f746e657465787065727473c2f0c00c000c0001000008aa00110e6a65646572686174736472617566c0bfc00c000c0001000008aa000f0c6e636f6d706173736c616273c04ac00c000c0001000008aa00110e65766572796f6e65676574736974c201c00c000c0001000008aa000d0a6c697477617265696e63c116c00c000c0001000008aa0021066f666672657317656e7472657072656e6575722d73757065726865726f73c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74d6e4c00c000c0001000008aa00100d72656e636f6e7472652d333630c116c00c000c0001000008aa000e0b6d756e63686f6e74686973c0bfc00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74df83c00c000c0001000008aa00120f696d6167696e657a6c617375697465c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c0fcc00c000c0001000008aa00100d77696e646f77736e7432303030c0bfc00c000c0001000008aa000f0c6d6f6e2d7061636b2d70726fc04ac00c000c0001000008aa00110e646f776e6c6f616467726f6f7665c0bfc00c000c0001000008aa000c09646f742d7472757468c04ac00c000c0001000008aa00100d6465667261676d616e61676572c04ac00c000c0001000008aa000c0965727073797374656dcc27c00c000c0001000008aa00161372656c65766572746f75736c65736465666973c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c04ac00c000c0001000008aa0013106f66667265732d6d6963726f736f6674c0bfc00c000c0001000008aa000c076f6e6d7977617902666900c00c000c0001000008aa000f0c746563686461797332303038c0bfc00c000c0001000008aa000906637368617270c116c00c000c0001000008aa0015126f666672652d65626c6f75697373616e7465c116c00c000c0001000008aa000906666c6578676fc158c00c000c0001000008aa0015126d737368617265706f696e74666f72756d73c0bfc00c000c0001000008aa000e0b6c6f6f6b6f7574736f6674c04ac00c000c0001000008aa001b126d6963726f736f6674666f726566726f6e7403636f6d02617200c00c000c0001000008aa0009066370616e646cc116c00c000c0001000008aa00120f6d7977696e646f77736d6f62696c65df7fc00c000c0001000008aa000c09706f636b65746d736ec580c00c000c0001000008aa00120f6f6e6c696e6573706f746c69676874c04ac00c000c0001000008aa000d0a6f666669636532303037c96bc00c000c0001000008aa001815636f6e736f6c6964617465646d657373656e676572c0bfc00c000c0001000008aa001a1777696e646f77736d6f62696c65636f6d6d6d756e697479c0bfc00c000c0001000008aa00201d6f6666696365736d616c6c627573696e6573736163636f756e74696e67c04ac00c000c0001000008aa00120f696d6167696e65726c617375697465c0bfc00c000c0001000008aa000b086e636f6d70617373c04ac00c000c0001000008aa00181577696e646f7773616e7974696d6575706772616465c04ac00c000c0001000008aa000e0b77696e646f777332303036cb07c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74dde0c00c000c0001000008aa001411657874656e646572736d6172746c697374c04ac00c000c0001000008aa00110e646973636f766572746563686564c04ac00c000c0001000008aa0017126d6963726f736f6674666f726566726f6e74026a7000c00c000c0001000008aa0014116c756365726e657075626c697368696e67c0bfc00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74d22dc00c000c0001000008aa000b0872656d69782d3038c04ac00c000c0001000008aa00232077696e646f7773647269766572646576656c6f706572636f6e666572656e6365c116c00c000c0001000008aa00100d6f666669636572656164797063cb26c00c000c0001000008aa00110e6d6963726f736f66746174636573c04ac00c000c0001000008aa0017126d6963726f736f6674666f726566726f6e7402696500c00c000c0001000008aa000c09666f726566726f6e74cf52c00c000c0001000008aa000f0c666f75727468636f66666565c116c00c000c0001000008aa00120f6d7977696e646f77736d6f62696c65c06ac00c000c0001000008aa00100d6c6573626f6e736f7574696c73c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c1c4c00c000c0001000008aa00110e6d6963726f736f667468656c7073c04ac00c000c0001000008aa001c196d6963726f736f6674627573696e6573737365637572697479c085c00c000c0001000008aa00100d776f6f6467726f766562616e6bc0bfc00c000c0001000008aa0014116c756365726e657075626c697368696e67c116c00c000c0001000008aa000f0c666f75727468636f66666565c0bfc00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c7cbc00c000c0001000008aa000f0c636f686f76696e6579617264c116c00c000c0001000008aa000f0c6d6963726f736f6674646f70c0bfc00c000c0001000008aa000e0b77696e646f777372756279d696c00c000c0001000008aa000f086d736d6f62696c6504696e666f00c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c54cc00c000c0001000008aa00140d6d6f62696c65326d61726b6574046d6f626900c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74d678c00c000c0001000008aa000e0b6d756e63686f6e74686973c04ac00c000c0001000008aa000b086370616475766f6cc116c00c000c0001000008aa000e0b70726f746563746d797063dde0c00c000c0001000008aa00161372656c65766572746f75736c65736465666973c116c00c000c0001000008aa00191677696e646f77736d6f62696c65636f6d6d756e697479c0bfc00c000c0001000008aa00120f736572766572756e6c656173686564c0bfc00c000c0001000008aa000b08706f636b65747063c04ac00c000c0001000008aa000f0c746563686461797332303038c04ac00c000c0001000008aa000d0a64697265637462616e64c04ac00c000c0001000008aa001b1877696e646f7773647269766572646576656c6f706d656e74c04ac00c000c0001000008aa000f0c647269766572646576636f6ec0bfc00c000c0001000008aa00191672657461696c65786563757469766573656d696e6172c0bfc00c000c0001000008aa000d0a77696e7465726e616c73c0bfc00c000c0001000008aa0019166c7574746572636f6e7472656c657069726174616765c04ac00c000c0001000008aa000b0877696e646f777870c432c00c000c0001000008aa001613736d61727470686f6e65636f6d6d756e697479c116c00c000c0001000008aa000b086d6170706f696e74c116c00c000c0001000008aa000d0a6c697477617265696e63c0bfc00c000c0001000008aa0019166772617068696364657369676e696e73746974757465c116c00c000c0001000008aa000b086d6163746f706961c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74cf56c00c000c0001000008aa0015126d737368617265706f696e74666f72756d73c116c00c000c0001000008aa000e0b746f726b74686567616d65c04ac00c000c0001000008aa0019166c65732d646f696774732d64616e732d6c652d6e657ac0bfc00c000c0001000008aa0015126d737368617265706f696e74666f72756d73c04ac00c000c0001000008aa00100d70726f74656374796f75727063dde0c00c000c0001000008aa0015127465636e6f6c6f67696179656d7072657361c0bfc00c000c0001000008aa00120f6d73646e746563686e6574746f7572c116c00c000c0001000008aa00100d72657461696c77656263617374c04ac00c000c0001000008aa00120f736f7574687269646765766964656fc116c00c000c0001000008aa000e0b63616d70757367616d6573c54cc00c000c0001000008aa001613636f6e666f726d6974652d6c6963656e636573c04ac00c000c0001000008aa0015127465636e6f6c6f67696179656d7072657361c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c347c00c000c0001000008aa001a1777696e646f77736d6f62696c65636f6d6d6d756e697479c116c00c000c0001000008aa000e0b77696e67746970746f7973c116c00c000c0001000008aa000d0a6f666669636532303037c04ac00c000c0001000008aa0019166c7574746572636f6e7472656c657069726174616765c116c00c000c0001000008aa00100d72656e636f6e74726573333630c0bfc00c000c0001000008aa001512746865736572766572756e6c656173686564c116c00c000c0001000008aa00120f6d6963726f736f66746d6f62696c65cb07c00c000c0001000008aa00120f6d73646e746563686e6574746f7572c0bfc00c000c0001000008aa00100d77696e646f77736d6f62696c65d6e4c00c000c0001000008aa000f0c7061636b746f7574656e756ec116c00c000c0001000008aa00150c77696e646f7773766973746103636f6d02747200c00c000c0001000008aa00100d6d6f62696c65326d61726b6574c04ac00c000c0001000008aa00120f63656e747265646573757361676573c04ac00c000c0001000008aa00100b77696e646f77733230303002736b00c00c000c0001000008aa00100d77696e646f77736d6f62696c65c580c00c000c0001000008aa000b087465636865643036c04ac00c000c0001000008aa001815636f686f76696e6579617264616e6477696e657279c0bfc00c000c0001000008aa000f0c657264636f6d6d616e646572c04ac00c000c0001000008aa0015127465636e6f6c6f67696179656d7072657361c116c00c000c0001000008aa000f0c747265797265736561726368c0bfc00c000c0001000008aa00181170696374757265697470726f6475637473036d736ec04ac00c000c0001000008aa00100d776f6f6467726f766562616e6bc116c00c000c0001000008aa000805776d766864c04ac00c000c0001000008aa0013106f66667265732d6d6963726f736f6674c116c00c000c0001000008aa00120f63656e747265646573757361676573c116c00c000c0001000008aa00100977696e646f77736e74046e616d6500c00c000c0001000008aa000a0777696e646f7773c116c00c000c0001000008aa001310746f646f736c6f656e7469656e64656ec04ac00c000c0001000008aa00161372656c6576657a746f75736c65736465666973c158c00c000c0001000008aa00141164656679616c6c6368616c6c656e676573c347c00c000c0001000008aa000e0b6374726f70656e6f726d65c0bfc00c000c0001000008aa00100977696e646f77736e740367656ec678c00c000c0001000008aa000d0a636f686f77696e657279c0bfc00c000c0001000008aa00120f6c6f67697374696b6b73797374656dcc27c00c000c0001000008aa00110977696e646f7773787002707002617a00c00c000c0001000008aa000c0970726f736577617265c0bfc00c000c0001000008aa00110e6a65646572686174736472617566c04ac00c000c0001000008aa000d0a6c696e7465726e616c73c04ac00c000c0001000008aa0014116d6f62696c657063646576656c6f706572c116c00c000c0001000008aa00151277696e646f7773656d6265646465646b6974c04ac00c000c0001000008aa000f0c76697375616c73747564696fc116c00c000c0001000008aa000e0b77696e646f77736c6f676fc116c00c000c0001000008aa000d0a77696e7465726e616c73cb07c00c000c0001000008aa00252272656e636f6e747265732d636f6c6c6563746976697465732d6d6963726f736f6674c04ac00c000c0001000008aa000a0769742d6865726fd6e4c00c000c0001000008aa000a0777696e646f7773cc27c00c000c0001000008aa000d0a6c697477617265696e63c04ac00c000c0001000008aa000b086370616475766f6cc04ac00c000c0001000008aa00120f6d7977696e646f77736d6f62696c65cc9fc00c000c0001000008aa001c196d6963726f736f66746c6963656e736573746174656d656e74c04ac00c000c0001000008aa000a0772656d69783038c0bfc00c000c0001000008aa000d0a77696e7465726e616c73c201c00c000c0001000008aa001310746563686e65746368616c6c656e6765c04ac00c000c0001000008aa000a076e746673646f73c04ac00c000c0001000008aa001310746f646f736c6f656e7469656e64656ec0bfc00c000c0001000008aa00130e73707261766e6f75636573746f7502637a00c00c000c0001000008aa0019166c65732d646f696774732d64616e732d6c652d6e657ac116c00c000c0001000008aa00100d6d6963726f736f6674686f6d65c54cc00c000c0001000008aa000e0b7061636b746f7574656e31c04ac00c000c0001000008aa001916666f65726465726d697474656c2d7261746765626572c201c00c000c0001000008aa000d0a77696e7465726e616c73c580c00c000c0001000008aa00161372656c6576657a746f75736c65736465666973c54cc00c000c0001000008aa00100d6d6f62696c65326d61726b6574c32dc00c000c0001000008aa00252277696e646f7773647269766572646576656c6f706d656e74636f6e666572656e6365c0bfc00c000c0001000008aa00120977696e646f77736e74036f726702627a00c00c000c0001000008aa00100d72656e636f6e7472652d333630c04ac00c000c0001000008aa00110e6d6963726f736f667468656c7073c54cc00c000c0001000008aa00232077696e646f7773647269766572646576656c6f706572636f6e666572656e6365c04ac00c000c0001000008aa0019166772617068696364657369676e696e73746974757465c0bfc00c000c0001000008aa0019166d69677265727665727376697375616c73747564696fc04ac00c000c0001000008aa000f0c6d736f666669636532303037c1c4c00c000c0001000008aa00120f63656e747265646573757361676573c158c00c000c0001000008aa00252277696e646f7773647269766572646576656c6f706d656e74636f6e666572656e6365c116c00c000c0001000008aa00110c77696e646f7773766973746102626700c00c000c0001000008aa000c0977696e646f77733938f3fac00c000c0001000008aa00120f6d6963726f736f66746d6f62696c65edf7c00c000c0001000008aa00120f736f7574687269646765766964656fc0bfc00c000c0001000008aa0016136f666672652d65626c6f75697373616e746573c04ac00c000c0001000008aa000f0c746564736c656d6f6e616964c04ac00c000c0001000008aa000e0b6e69676874636173746572c04ac00c000c0001000008aa00110e6d6f62696c6574656368746f7572c0bfc00c000c0001000008aa00100977696e646f77736e74036e6574c0fcc00c000c0001000008aa00100d6f70656e74797065666f72756dc116c00c000c0001000008aa000c09776861636b65647476c04ac00c000c0001000008aa000f0c6d6963726f736f6674646f70c04ac00c000c0001000008aa000c0977696e646f77736e74c4edc00c000c0001000008aa00100d77696e646f77736d6f62696c65c1c4c00c000c0001000008aa000c0977696e646f77737870c436c00c000c0001000008aa001b1877696e646f7773647269766572646576656c6f706d656e74c116c00c000c0001000008aa000b086263656e7472616cc54cc00c000c0001000008aa00151277696465776f726c64696d706f7274657273c04ac00c000c0001000008aa000c097374756f73626f726ec0bfc00c000c0001000008aa000f0c7461696c7370696e746f7973c04ac00c000c0001000008aa0013106d616b656f7665726d796f6666696365c04ac00c000c0001000008aa000d0a6d7366746d6f62696c65c116c00c000c0001000008aa0015126c6573646f6967747364616e736c656e657ac04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c06ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c678c00c000c0001000008aa00161372656c6576657a746f75736c65736465666973c04ac00c000c0001000008aa00100d6d73646e6368616c6c656e6765c04ac00c000c0001000008aa0019166f6666696365706f75726c65736574756469616e7473c116c00c000c0001000008aa00120f736f7574687269646765766964656fc04ac00c000c0001000008aa00191672657461696c65786563757469766573656d696e6172c116c00c000c0001000008aa00110e6d6f62696c657365727669636573edf7c00c000c0001000008aa001307776562686f7374086e61766973696f6ec04ac00c000c0001000008aa00100d726573706f6e7365706f696e74f3a0c00c000c0001000008aa00120b7a65726f76697275736573036f7267dde3c00c000c0001000008aa00120f6d7977696e646f77736d6f62696c65c347c00c000c0001000008aa0016136f666672652d65626c6f75697373616e746573c116c00c000c0001000008aa00100d77696e646f77736d6f62696c65df83c00c000c0001000008aa0015126d6f62696c657063646576656c6f70657273c0bfc00c000c0001000008aa00100977696e646f77736e7403636f6df9cec00c000c0001000008aa000d0a77696e7465726e616c73c116c00c000c0001000008aa00100d747670686f746f766965776572c04ac00c000c0001000008aa00191677696e646f77736d6f62696c65636f6d6d756e697479c04ac00c000c0001000008aa00100b77696e646f77737275627902766e00c00c000c0001000008aa00100d6d6f62696c6564657669636573edf7c00c000c0001000008aa000f0c746563686564626f73746f6ec04ac00c000c0001000008aa00110e6d6f62696c6574656368746f7572c04ac00c000c0001000008aa00100d74617675746174726f6e636865c04ac00c000c0001000008aa00110e706179726f6c6c77656263617374c04ac00c000c0001000008aa00100d776577616e7474686562657374c04ac00c000c0001000008aa00151277696465776f726c64696d706f7274657273c0bfc00c000c0001000008aa001a1777696e646f77736d6f62696c65636f6d6d6d756e697479c04ac00c000c0001000008aa001c196d6963726f736f66746c6963656e736573746174656d656e74c54cc00c000c0001000008aa0015126d6963726f736f6674697461636164656d79c04ac00c000c0001000008aa00100d72656e636f6e7472652d333630c0bfc00c000c0001000008aa000c0977696e646f77737870c30ec00c000c0001000008aa000c0977696e646f77736e74c7a8c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74cb26c00c000c0001000008aa000e0b77696e67746970746f7973c04ac00c000c0001000008aa001613737570706f727477696e646f77737669737461c158c00c000c0001000008aa0013106e6f72746877696e6474726164657273c0bfc00c000c0001000008aa00232077696e646f7773647269766572646576656c6f706572636f6e666572656e6365c0bfc00c000c0001000008aa000e0b76796b6b6572736c616273c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c18bc00c000c0001000008aa000f0c747265797265736561726368c116c00c000c0001000008aa000c09746563686564766970c04ac00c000c0001000008aa00191677696e646f77736d6f62696c65636f6d6d756e697479c116c00c000c0001000008aa000e0b74696d6534746563686564c04ac00c000c0001000008aa000f0c72656e636f6e747265333630c0bfc00c000c0001000008aa0014116d6963726f736f6674736d616c6c62697ac04ac00c000c0001000008aa00191677696e646f7773647269766572646576656c6f706572c116c00c000c0001000008aa0014116d616e616765636f6c6c656374696f6e73c04ac00c000c0001000008aa000b086263656e7472616cc531c00c000c0001000008aa00100d706f776572746f676574686572c04ac00c000c0001000008aa00191677696e646f7773647269766572646576656c6f706572c04ac00c000c0001000008aa000a077265736b697473c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c201c00c000c0001000008aa00161377696e7465726e616c737265736f7572636573c04ac00c000c0001000008aa000c0977696e646f77736e74f3fac00c000c0001000008aa00100977696e646f77736e74036f7267cc81c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74e8acc00c000c0001000008aa00230e64657369676e6564666f726269670264650e64657369676e6564666f72626967c201c00c000c0001000008aa00141164656679616c6c6368616c6c656e676573c7cbc00c000c0001000008aa000e0977696e646f7773787002676d00c00c000c0001000008aa00151277696e7465726e616c73736f667477617265c04ac00c000c0001000008aa00151277696465776f726c64696d706f7274657273c116c00c000c0001000008aa000e0b77696e646f777372756279ec43c00c000c0001000008aa000a0772656d69783038c116c00c000c0001000008aa000f0c6d61696e66756e6374696f6ec04ac00c000c0001000008aa000d0a7374756f73626f726e65c04ac00c000c0001000008aa000f0c6f666669636573797374656ddde0c00c000c0001000008aa001e1b6d6963726f736f66742d627573696e6573732d7365637572697479c085c00c000c0001000008aa000e0b77696e646f777372756279c201c00c000c0001000008aa000b0872656d69782d3038c116c00c000c0001000008aa00120f7265616c6d656e74656772616e6465c04ac00c000c0001000008aa0008056d73657070c04ac00c000c0001000008aa00100d77696e646f77736d6f62696c65df7fc00c000c0001000008aa00110e72656e636f6e747265732d333630c116c00c000c0001000008aa00100977696e646f77736e74036e6574f656c00c000c0001000008aa00100d7374756f73626f726e73686f77c04ac00c000c0001000008aa0018156d6963726f736f66746269636f6e666572656e6365c04ac00c000c0001000008aa000b0877696e646f777870d804c00c000c0001000008aa000e0b696e6e6f766174652d756bc116c00c000c0001000008aa00141164656679616c6c6368616c6c656e676573d696c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c116c00c000c0001000008aa00100977696e646f77736e74036e6574f9cec00c000c0001000008aa00160f65756772616e747361647669736f7203636f6dda48c00c000c0001000008aa00120f696d6167696e65726c617375697465c116c00c000c0001000008aa00120f736572766572756e6c656173686564c116c00c000c0001000008aa00110e64657369676e6564666f72626967cc27c00c000c0001000008aa000e0b667278736f667477617265c04ac00c000c0001000008aa000f0c6d6f6e2d7061636b2d70726fc0bfc00c000c0001000008aa000d0a73686172656476696577c04ac00c000c0001000008aa00110977696e646f77736e7402636f02637200c00c000c0001000008aa000e0b77696e646f777372756279c085c00c000c0001000008aa00140c77696e646f7773766973746102636f02696400c00c000c0001000008aa00141164656679616c6c6368616c6c656e676573c2f0c00c000c0001000008aa00110e676174656b656570657274657374c2f0c00c000c0001000008aa000e0b77696e646f777372756279c18bc00c000c0001000008aa000e0b66616d696c797761766573c54cc00c000c0001000008aa00120f6f6e6c696e6573706f746c69676874c116c00c000c0001000008aa00120f65756772616e747361647669736f72f8a8c00c000c0001000008aa000b06666c6578676f026d7000c00c000c0001000008aa001613696d706f737369626c65637265617475726573c54cc00c000c0001000008aa00170f65756772616e747361647669736f7202636f02687500c00c000c0001000008aa000c0977696e646f77736e74c7e9c00c000c0001000008aa000906666c6578676fec43c00c000c0001000008aa00120f65756772616e747361647669736f72da48c00c000c0001000008aa000e0b77696e646f777372756279c951c00c000c0001000008aa00120f6d7977696e646f77736d6f62696c65c54cc00c000c0001000008aa00110e64657369676e6564666f72626967ec43c00c000c0001000008aa0015126f666672652d65626c6f75697373616e7465c0bfc00c000c0001000008aa00100977696e646f77737870036f7267c436c00c000c0001000008aa000b0872656d69782d3038c0bfc00c000c0001000008aa000e0977696e646f77737870026e6600c00c000c0001000008aa00191672657461696c65786563757469766573656d696e6172c04ac00c000c0001000008aa000b086d6163746f706961c116c00c000c0001000008aa00100d6d6f62696c6564657669636573cb07c00c000c0001000008aa000a0773776175646974f3fac00c000c0001000008aa00110e726973656f667065726174686961c0bfc00c000c0001000008aa00252272656e636f6e747265732d636f6c6c6563746976697465732d6d6963726f736f6674c116c00c000c0001000008aa00141177696e646f777373657276657232303038c0fcc00c000c0001000008aa000c096d73706172746e6572c04ac00c000c0001000008aa00110e6f66667265732d656e6f726d6573c04ac00c000c0001000008aa00100d74617675746174726f6e636865c0bfc00c000c0001000008aa00100d6d61726769657374726176656cc04ac00c000c0001000008aa00120f65756772616e747361647669736f72e429c00c000c0001000008aa00161372656c6576657a746f75736c65736465666973c0bfc00c000c0001000008aa000d0a74656368656474696d65c04ac00c000c0001000008aa0019166d6963726f736f667464656d616e64706c616e6e6572c04ac00c000c0001000008aa000e0b77696e646f77736c6f676fc04ac00c000c0001000008aa001a176d6963726f736f6674627573696e657373617761726473c04ac00c000c0001000008aa00130b77696e646f77737275627902636f027a6100c00c000c0001000008aa00110c77696e646f77737669737461026e7500c00c000c0001000008aa00171477696e646f7773787067616d6561647669736f72c04ac00c000c0001000008aa00100d747670686f746f766965776572c59cc00c000c0001000008aa00110e64657369676e6564666f72626967c32dc00c000c0001000008aa000f0c77696e7465726e616c736573c04ac00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c84bc00c000c0001000008aa000f0c6e666c666576657232303032c04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c65fc00c000c0001000008aa00140f65756772616e747361647669736f7202656500c00c000c0001000008aa000b086e636f6d70617373c54cc00c000c0001000008aa001512746865736572766572756e6c656173686564c04ac00c000c0001000008aa000b06666c6578676f02687400c00c000c0001000008aa00161372656c6576657a746f75736c65736465666973c116c00c000c0001000008aa00110e72657475726e746f746563686564c04ac00c000c0001000008aa00100977696e646f77736e7403636f6dc7edc00c000c0001000008aa000e0b77696e646f777372756279da48c00c000c0001000008aa000e0b77696e646f777372756279c82bc00c000c0001000008aa00130b77696e646f77737275627902636f02687500c00c000c0001000008aa000d0a65737469656d706f6465c0bfc00c000c0001000008aa00110e676174656b656570657274657374c085c00c000c0001000008aa00131074686570686f6e652d636f6d70616e79c0bfc00c000c0001000008aa00120f6865726f7368617070656e68657265dde0c00c000c0001000008aa00100b77696e646f77737275627902616500c00c000c0001000008aa00100977696e646f7773787003636f6dc456c00c000c0001000008aa00161372656c65766572746f75736c65736465666973c0bfc00c000c0001000008aa000f0c75732d6d6963726f736f6674c04ac00c000c0001000008aa0017147669737461666f72796f7572627573696e657373c04ac00c000c0001000008aa000f0c746563686d616b656f766572dde0c00c000c0001000008aa00100977696e646f777378700362697ac0fcc00c000c0001000008aa001b03777777146d6963726f736f6674737570706c79636861696ec04ac00c000c0001000008aa00120977696e646f777378700377656202746a00c00c000c0001000008aa000b0877696e646f777870cc61c00c000c0001000008aa00120f65756772616e747361647669736f72cc27c00c000c0001000008aa000906666c6578676fd6e4c00c000c0001000008aa00110e667574757265706f73746d61696cc116c00c000c0001000008aa000c097374756f73626f726ec116c00c000c0001000008aa0014116d6963726f736f667464796e616d696373c158c00c000c0001000008aa00110e72656e636f6e747265732d333630c0bfc00c000c0001000008aa00100d726973656f666e6174696f6e73c54cc00c000c0001000008aa0009067265736b6974c04ac00c000c0001000008aa00160e64657369676e6564666f7262696702636f027a6100c00c000c0001000008aa000a07737570706f7274e66dc00c000c0001000008aa000f0877696e646f777870036f7267c436c00c000c0001000008aa001a176d6963726f736f6674627573696e657373617761726473c116c00c000c0001000008aa00131074686570686f6e652d636f6d70616e79c116c00c000c0001000008aa0009066d7377776870c04ac00c000c0001000008aa000e0b77696e646f777372756279e429c00c000c0001000008aa00110e64657369676e6564666f72626967c085c00c000c0001000008aa000a0774656d70757269c0bfc00c000c0001000008aa00110e64657369676e6564666f72626967c347c00c000c0001000008aa000e0977696e646f77736e7402636600c00c000c0001000008aa001310746f646f736c6f656e7469656e64656ec531c00c000c0001000008aa000b06666c6578676f02706b00c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74cf52c00c000c0001000008aa000e0b77696e646f777372756279c96bc00c000c0001000008aa00151277656273746f72616765706172746e657273c04ac00c000c0001000008aa00120f65756772616e747361647669736f72c347c00c000c0001000008aa000d0a77696e7465726e616c73edf7c00c000c0001000008aa00120977696e646f7773787003636f6d02746a00c00c000c0001000008aa001411726576656e7565616e64657870656e7365c04ac00c000c0001000008aa00110e64657369676e6564666f72626967c21dc00c000c0001000008aa001411636f6e636572746d6f62696c656c697665c54cc00c000c0001000008aa00100d72656e636f6e74726573333630c116c00c000c0001000008aa00110e766973696f696e666f776f636865c201c00c000c0001000008aa000e0977696e646f77737870026d6400c00c000c0001000008aa00140f65756772616e747361647669736f7202736900c00c000c0001000008aa000e0b77696e646f777372756279d3f7c00c000c0001000008aa000f0c77696e696e7465726e616c73c04ac00c000c0001000008aa00100d68617a6d6173766976656d6173c04ac00c000c0001000008aa000b086263656e7472616cc201c00c000c0001000008aa001512746865736572766572756e6c656173686564c0bfc00c000c0001000008aa0013106865726f657368617070656e68657265dde0c00c000c0001000008aa000a0772656d69783038c04ac00c000c0001000008aa001310746f646f736c6f656e7469656e64656ec116c00c000c0001000008aa00100977696e646f77736e74036f7267f656c00c000c0001000008aa0019166d69677265727665727376697375616c73747564696fc116c00c000c0001000008aa0015126d6963726f736f6674666f726566726f6e74c498c00c000c0001000008aa000c0977696e646f77737870cf52c00c000c0001000008aa000e0b696e6e6f766174652d756bc0bfc00c000c0001000008aa00100977696e646f7773787003636f6dc381c00c000c0001000008aa000c09766973696f32303033c201c00c000c0001000008aa000d0a796f7572746563686564c04ac00c000c0001000008aa00120b77696e646f77737275627903636f6dc951c00c000c0001000008aa00120977696e646f77736e7403636f6d026a6d00c00c000c0001000008aa00110e64657369676e6564666f72626967cb07c00c000c0001000008aa000e0b6d756e63686f6e74686973c116c00c000c0001000008aa00120f65756772616e747361647669736f72c531c00c000c0001000008aa00110e64657369676e6564666f72626967cb26c00c000c0001000008aa000d0a6d7366746d6f62696c65c0bfc00c000c0001000008aa000f0c746563686461797332303038c116c00c000c0001000008aa000906666c6578676fdb0ec00c000c0001000008aa00110e667574757265706f73746d61696cc04ac00c000c0001000008aa00171464657361666961746f646f736c6f737265746f73c04ac00c000c0001000008aa00120f65756772616e747361647669736f72c201c00c000c0001000008aa00100977696e646f7773787003636f6df656c00c000c0001000008aa001815636f686f76696e6579617264616e6477696e657279c116c00c000c0001000008aa00120b77696e646f77737275627903636f6dc39fc00c000c0001000008aa000d0877696e646f77787002686d00c00c000c0001000008aa00100d676f746f7779646f7072616379c0fcc00c000c0001000008aa000d0a6f666669636532303037c201c00c000c0001000008aa00110e64657369676e6564666f72626967c54cc00c000c0001000008aa000e0b77696e646f777372756279f8a8c00c000c0001000008aa000c09726166616574617469d702c00c000c0001000008aa00180f65756772616e747361647669736f7203636f6d02637900c00c000c0001000008aa00120f6d736163726f7373616d6572696361c04ac00c000c0001000008aa001613736d61727470686f6e65636f6d6d756e697479c0bfc00c000c0001000008aa000e0977696e646f7773787002727700c00c000c0001000008aa00120977696e646f777378700362697a02706b00c00c000c0001000008aa00110e64657369676e6564666f72626967c0bfc00c000c0001000008aa000d0a6672787265706f727473c04ac00c000c0001000008aa00110e72656e636f6e747265732d333630c04ac00c000c0001000008aa001411636f6e73756c746f72696f6f6666696365c531c00c000c0001000008aa000d06666c6578676f03636f6dffdec00c000c0001000008aa000e0b77696e646f777372756279dde0c00c000c0001000008aa00110c77696e646f7773766973746102697300c00c000c0001000008aa00141164656679616c6c6368616c6c656e676573c580c00c000c0001000008aa00100d7374756f73626f726e73686f77c116c00c000c0001000008aa00100d726573706f6e7365706f696e74c0fcc00c000c0001000008aa000e0b696e6e6f766174652d756bc04ac00c000c0001000008aa00120f65756772616e747361647669736f72c580c00c000c0001000008aa000d0a7369646577696e646572edf7c00c000c0001000008aa00120f6d6963726f73667473757266616365c04ac00c000c0001000008aa000e0b77696e646f777372756279c476c00c000c0001000008aa000e0977696e646f7773787002627300c00c000c0001000008aa00120f65756772616e747361647669736f72cb07c00c000c0001000008aa00110e64657369676e6564666f72626967d3f7c00c000c0001000008aa000d0877696e646f77787002746f00c00c000c0001000008aa00120f65756772616e747361647669736f72f3fac00c000c0001000008aa00120f65756772616e747361647669736f72d696c00c000c0001000008aa00110e7374756f73626f726e6573686f77c04ac00c000c0001000008aa00120f65756772616e747361647669736f72c2f0c00c000c0001000008aa000906747279696973c04ac00c000c0001000008aa00110e7374756f73626f726e6573686f77c116c00c000c0001000008aa00151273636174656e61696c74756f736572766572c1c4c00c000c0001000008aa00100d76697375616c2d73747564696fc04ac00c000c0001000008aa0016137072657373746865677265656e627574746f6ec116c00c000c0001000008aa000e0b77696e646f777372756279c2f0c00c000c0001000008aa000c0977696e646f77737870d35ac00c000c0001000008aa000b06666c6578676f02617300c00c000c0001000008aa000b086263656e7472616cc1c4c00c000c0001000008aa0015126e6261696e73696465647269766532303033c04ac00c000c0001000008aa00120977696e646f777378700362697a02746a00c00c000c0001000008aa00100977696e646f7773787003636f6dc2b5c00c000c0001000008aa00120f736572766572756e6c656173686564c04ac00c000c0001000008aa000c0977696e646f77737870c9c9c00c000c0001000008aa00221f6d6f6465726e657276657277616c74756e677361726265697473706c61747ac201c00c000c0001000008aa00090661747461696ecc27c00c000c0001000008aa00120977696e646f7773787003636f6d02627300c00c000c0001000008aa000906666c6578676fffdec00c000c0001000008aa000f0c77696e646f77737669737461f9cec00c000c0001000008aa00120f65756772616e747361647669736f72f3a0c00c000c0001000008aa000e0b77696e646f777332303030c04ac00c000c0001000008aa00120f65756772616e747361647669736f72c085c00c000c0001000008aa00110977696e646f7773787002636f02696d00c00c000c0001000008aa000c0977696e646f77737870fbe9c00c000c0001000008aa000e0977696e646f7773787002696f00c00c000c0001000008aa000906666c6578676fc96bc00c000c0001000008aa000d0a6f666669636532303037c158c00c000c0001000008aa001411636f6e73756c746f72696f6f6666696365c116c00c000c0001000008aa00120d726573706f6e7365706f696e7402616500c00c000c0001000008aa000e0b77696e646f777372756279c678c00c000c0001000008aa000e0b77696e646f777372756279c7cbc00c000c0001000008aa001512746f646f732d6c6f2d656e7469656e64656ec531c00c000c0001000008aa000e0b77696e646f777372756279c158c00c000c0001000008aa00110c77696e646f77737669737461026c6100c00c000c0001000008aa00100977696e646f77737870036f7267c456c00c000c0001000008aa00110e726973656f667065726174686961c116c00c000c0001000008aa001a176d6963726f736f6674627573696e657373617761726473c0bfc00c000c0001000008aa000f0977696e646f7773787002636fce71c00c000c0001000008aa000906666c6578676fd702c00c000c0001000008aa000e0b7265616c69747966616d65c54cc00c000c0001000008aa00120f65756772616e747361647669736f72c158c00c000c0001000008aa00120977696e646f77737870036f7267026a6500c00c000c0001000008aa000906617a7572696bc04ac00c000c0001000008aa00120f73657276657273636174656e61746fc1c4c00c000c0001000008aa000e0b6e61766973696f6e78616ccc27c00c000c0001000008aa000f0c74687265652d646567726565c04ac00c000c0001000008aa000e0977696e646f7773787002616300c00c000c0001000008aa00100977696e646f77737870036e6574c456c00c000c0001000008aa000f0977696e646f7773787002636fc3dac00c000c0001000008aa00100977696e646f77737870036f7267c7edc00c000c0001000008aa000c0977696e646f77737870c04ac00c000c0001000008aa00120d726573706f6e7365706f696e7402687500c00c000c0001000008aa000f0c646972656374616363657373c0bfc00c000c0001000008aa00100d726573706f6e7365706f696e74c32dc00c000c0001000008aa00100977696e646f77737870036e6574c3dac00c000c0001000008aa00100d6d6963726f736f667473706c61c04ac00c000c0001000008aa00100d76657374706f636b657463666fc116c00c000c0001000008aa00120f65756772616e747361647669736f72d3f7c00c000c0001000008aa00100977696e646f7773787003696e74f656c00c000c0001000008aa000d0a6f666669636532303037dde0c00c000c0001000008aa00100d7374756f73626f726e73686f77c0bfc00c000c0001000008aa00100977696e646f7773787003636f6dcfd5c00c000c0001000008aa00110e7374756f73626f726e6573686f77c0bfc00c000c0001000008aa00110e64657369676e6564666f72626967c498c00c000c0001000008aa00150d726573706f6e7365706f696e7402636f027a6100c00c000c0001000008aa000c0977696e646f77737870d183c00c000c0001000008aa0014116d6963726f736f66746d6f6d656e74756dc04ac00c000c0001000008aa00120d726573706f6e7365706f696e7402777300c00c000c0001000008aa000c0977696e646f77737870ff66c00c000c0001000008aa000e0977696e646f7773787002617300c00c000c0001000008aa001b1877696e646f7773647269766572646576656c6f706d656e74c0bfc00c000c0001000008aa00110977696e646f7773787002636f02636b00c00c000c0001000008aa00120f686f6d652d7075626c697368696e67c04ac00c000c0001000008aa001a176d6963726f736f6674627573696e657373617761726473c531c00c000c0001000008aa00110e64657369676e6564666f72626967c2f0c00c000c0001000008aa00100977696e646f77736e7403696e74f656c00c000c0001000008aa000e0b77696e646f777372756279d6e4c00c000c0001000008aa000d0877696e646f777870026e6600c00c000c0001000008aa00100977696e646f77737870036f6666c7acc00c000c0001000008aa000f0c6d6f6e2d7061636b2d70726fc116c00c000c0001000008aa00100d747670686f746f766965776572c116c00c000c0001000008aa000e0b77696e646f777372756279c65fc00c000c0001000008aa00100d74617675746174726f6e636865c116c00c000c0001000008aa00110977696e646f77737870046669726dc381c00c000c0001000008aa000d0a64616e69736372617a79c04ac00c000c0001000008aa00171477696e646f7773787067616d6561647669736f72c116c00c000c0001000008aa000b086263656e7472616cc7cbc00c000c0001000008aa001a176265737365727769737365722d77657474626577657262c201c00c000c0001000008aa000e0b77696e646f777332303030c116c00c000c0001000008aa0011096d6963726f736f667402636f026d7a00c00c000c0001000008aa000906666c6578676fc580c00c000c0001000008aa000e06666c6578676f02636f02637200c00c000c0001000008aa00120f65756772616e747361647669736f72c0fcc00c000c0001000008aa00120f65756772616e747361647669736f72c30ec00c000c0001000008aa000f0c72656e636f6e747265333630c116c00c000c0001000008aa000b0877696e646f777870dfbfc00c000c0001000008aa00110e676174656b656570657274657374c0fcc00c000c0001000008aa000c0977696e646f77737870f656c00c000c0001000008aa000e0877696e646f77787002636fce71c00c000c0001000008aa00100977696e646f77737870036f7267f656c00c000c0001000008aa00120977696e646f77737870036e657402706b00c00c000c0001000008aa000c09666f726566726f6e74e8acc00c000c0001000008aa00120977696e646f7773787003636f6d02656300c00c000c0001000008aa001714617a7572696b726973656f667065726174686961c04ac00c000c0001000008aa0019166d616e616765796f757270656f706c65617373657473c04ac00c000c0001000008aa000f0877696e646f777870036e6574dfbfc00c000c0001000008aa001512626c7565796f6e6465726169726c696e6573c116c00c000c0001000008aa000b086263656e7472616cc84bc00c000c0001000008aa000e0977696e646f77737870026c6b00c00c000c0001000008aa000e0b77696e646f777372756279d702c00c000c0001000008aa000906666c6578676fd696c00c000c0001000008aa00131077696e646f77737669737461626c6f67c158c00c000c0001000008aa000d0a65737469656d706f6465c04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c201c00c000c0001000008aa00120f65756772616e747361647669736f72c84bc00c000c0001000008aa00141164656679616c6c6368616c6c656e676573e8acc00c000c0001000008aa00100d726573706f6e7365706f696e74ee38c00c000c0001000008aa000d0a65737469656d706f6465c116c00c000c0001000008aa00120977696e646f7773787003636f6d026e6600c00c000c0001000008aa00120977696e646f7773787003636f6d02756100c00c000c0001000008aa000f0977696e646f7773787002636fc7edc00c000c0001000008aa00100d737465776f73626f7273686f77c116c00c000c0001000008aa00120b77696e646f77737275627903636f6dc158c00c000c0001000008aa001714617a7572696b726973656f667065726174686961c116c00c000c0001000008aa00100977696e646f77737870036e6574dfbfc00c000c0001000008aa00100d737465776f73626f7273686f77c0bfc00c000c0001000008aa000c0977696e646f77737870f9cac00c000c0001000008aa00120977696e646f77737870036f726702706500c00c000c0001000008aa000c06666c6578676f02636fc7edc00c000c0001000008aa00100977696e646f77737870036f7267cc81c00c000c0001000008aa0013106865726f657368617070656e68657265c0fcc00c000c0001000008aa00100977696e646f77737870036e6574f9cec00c000c0001000008aa00100977696e646f7773787003636f6ddfbfc00c000c0001000008aa001411756b636f6d6d756e697479617761726473c04ac00c000c0001000008aa00252272656e636f6e747265732d636f6c6c6563746976697465732d6d6963726f736f6674c0bfc00c000c0001000008aa00100977696e646f77737870036e6574f656c00c000c0001000008aa00100977696e646f7773787003636f6dcdc4c00c000c0001000008aa00100d726573706f6e7365706f696e74c456c00c000c0001000008aa000d0a7374756f73626f726e65c0bfc00c000c0001000008aa000906666c6578676fcb26c00c000c0001000008aa00120d726573706f6e7365706f696e7402656300c00c000c0001000008aa000c09626570636c6567616cc0bfc00c000c0001000008aa000a0777696e32303030c84bc00c000c0001000008aa0014117365727665757273616e736c696d697465c0bfc00c000c0001000008aa000e037777770764657664617973dde0c00c000c0001000008aa000e0977696e646f77737870026c6900c00c000c0001000008aa000d0a6f666669636532303037c7cbc00c000c0001000008aa00100d726573706f6e7365706f696e74f8a8c00c000c0001000008aa00100977696e646f77737870036f7267cfd5c00c000c0001000008aa00160f65756772616e747361647669736f7203636f6dc381c00c000c0001000008aa00160d726573706f6e7365706f696e7403636f6d02706100c00c000c0001000008aa000e0977696e646f7773787002686d00c00c000c0001000008aa000e0977696e646f77737870026c6100c00c000c0001000008aa00120f65756772616e747361647669736f72ec43c00c000c0001000008aa000e0b77696e646f777372756279c0fcc00c000c0001000008aa000c097374756f73626f726ec04ac00c000c0001000008aa000e0977696e646f7773787002737400c00c000c0001000008aa00120f65756772616e747361647669736f72cb26c00c000c0001000008aa000b08676f6d656e74616cc54cc00c000c0001000008aa00100977696e646f77737870036e6574c23bc00c000c0001000008aa000b0877696e646f777870d720c00c000c0001000008aa00100d726573706f6e7365706f696e74edf7c00c000c0001000008aa00120977696e646f7773787003636f6d026a6d00c00c000c0001000008aa000f06666c6578676f03636f6d02756100c00c000c0001000008aa000b0877696e646f777870c8d8c00c000c0001000008aa0014116361702d7375722d6c652d737563636573c04ac00c000c0001000008aa000906666c6578676fc59cc00c000c0001000008aa000906617a7572696be429c00c000c0001000008aa000906666c6578676fc116c00c000c0001000008aa000f0c6d6f6e6e6f7576656c616d69c0bfc00c000c0001000008aa00120d726573706f6e7365706f696e7402627300c00c000c0001000008aa00160d726573706f6e7365706f696e7403636f6d02756100c00c000c0001000008aa00100977696e646f77737870036f7267c381c00c000c0001000008aa0014116361702d7375722d6c652d737563636573c116c00c000c0001000008aa00120b77696e646f77737275627903636f6dffdec00c000c0001000008aa00100d726573706f6e7365706f696e74c96bc00c000c0001000008aa00100977696e646f7773787003636f6dc7edc00c000c0001000008aa00110e64657369676e6564666f72626967cc9fc00c000c0001000008aa00110b77696e646f77737275627902636fc70ec00c000c0001000008aa000906666c6578676fdf83c00c000c0001000008aa00120977696e646f7773787003636f6d026e6900c00c000c0001000008aa000c096575726f706c616e6fda48c00c000c0001000008aa000f0977696e646f7773787002636fcfd5c00c000c0001000008aa000f06666c6578676f03636f6d02706b00c00c000c0001000008aa00120d726573706f6e7365706f696e7402656500c00c000c0001000008aa00120d726573706f6e7365706f696e74026d6100c00c000c0001000008aa000c0977696e646f77737870f9cec00c000c0001000008aa000e0b77696e646f777372756279eb60c00c000c0001000008aa00160f65756772616e747361647669736f7203636f6dc158c00c000c0001000008aa000c0977696e646f77737870dfbfc00c000c0001000008aa000c0977696e646f77737870c456c00c000c0001000008aa00100d726573706f6e7365706f696e74d678c00c000c0001000008aa000c0977696e646f77737870f3fac00c000c0001000008aa00100d726573706f6e7365706f696e74c06ac00c000c0001000008aa000e0b6270677765626361737433c04ac00c000c0001000008aa00100d726573706f6e7365706f696e74c2f0c00c000c0001000008aa000f0c66616d696c792d7761766573c54cc00c000c0001000008aa00120977696e646f77737870036e6574026a6500c00c000c0001000008aa00100b77696e646f77737275627902687500c00c000c0001000008aa000b06616d616c676102707300c00c000c0001000008aa00120977696e646f77737870036e657402706500c00c000c0001000008aa000f0c626c696e7874686567616d65c04ac00c000c0001000008aa00120977696e646f77737870036f726702687500c00c000c0001000008aa000d0a6f666669636532303037c84bc00c000c0001000008aa000f06616d616c676103636f6d02707300c00c000c0001000008aa000e0977696e646f7773787002736800c00c000c0001000008aa00120f77696e646f77737369646573686f77c04ac00c000c0001000008aa000b0877696e646f777870ce71c00c000c0001000008aa00070462657461f6f5c00c000c0001000008aa000e0b656169736f6c7574696f6ec085c00c000c0001000008aa0013106f66667265732d6d6963726f736f6674c04ac00c000c0001000008aa00100d726573706f6e7365706f696e74df7fc00c000c0001000008aa000d0a6f666669636532303037d3f7c00c000c0001000008aa00140d726573706f6e7365706f696e7403636f6dffdec00c000c0001000008aa00100d77696e646f77736e7432303030c116c00c000c0001000008aa00110e72657461696c7765626361737473c04ac00c000c0001000008aa000906666c6578676ff8a8c00c000c0001000008aa00120f65756772616e747361647669736f72c381c00c000c0001000008aa000c09666f726566726f6e74c0fcc00c000c0001000008aa00100977696e646f77737870036e6574cfd5c00c000c0001000008aa00120977696e646f777378700573746f7265c381c00c000c0001000008aa00110977696e646f77737870026d7902746a00c00c000c0001000008aa00120f65756772616e747361647669736f72c82fc00c000c0001000008aa00140d726573706f6e7365706f696e7403636f6dc456c00c000c0001000008aa00120977696e646f77737870036f726702706b00c00c000c0001000008aa00100977696e646f7773787003636f6dc09fc00c000c0001000008aa000d06666c6578676f03636f6dc158c00c000c0001000008aa00131069697377656263617374736572696573c116c00c000c0001000008aa000d0a756c74696d6174657063c04ac00c000c0001000008aa000e0b77696e646f777372756279e970c00c000c0001000008aa000b06616d616c676102707200c00c000c0001000008aa000e0977696e646f7773787002737200c00c000c0001000008aa00110e64657369676e6564666f72626967eb60c00c000c0001000008aa000b0866616272696b616dc04ac00c000c0001000008aa000c0977696e646f77737870f600c00c000c0001000008aa00110977696e646f7773787002636f02747400c00c000c0001000008aa000b0877696e646f777870dfbbc00c000c0001000008aa00110e667574757265706f73746d61696cc0bfc00c000c0001000008aa000d0a6f666669636532303037c580c00c000c0001000008aa00100d726573706f6e7365706f696e74dde0c00c000c0001000008aa000e0977696e646f7773787002676c00c00c000c0001000008aa00100d726573706f6e7365706f696e74d702c00c000c0001000008aa000906617a7572696bc580c00c000c0001000008aa0017146761676e657a2d656e2d65666669636163697465c04ac00c000c0001000008aa00120f686f6c6964617968656c70626f6f6bc04ac00c000c0001000008aa00100d726573706f6e7365706f696e74df83c00c000c0001000008aa00100d6469736b636f6d6d616e646572c04ac00c000c0001000008aa00100d72656164797365747368617265c54cc00c000c0001000008aa000b0877696e646f777870c237c00c000c0001000008aa00100d66696e656172747363686f6f6cc0bfc00c000c0001000008aa0014117461626c65747063646576656c6f706572c04a0000290200000080000000', true) end end dnsruby-1.61.3/test/tc_tcp_pipelining.rb0000644000004100000410000002001613607400362020316 0ustar www-datawww-data# -- # Copyright 2015 Verisign # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require_relative 'test_dnsserver' # The TCPPipeliningServer links our NioTcpPipeliningHandler on # the loopback interface. class TCPPipeliningServer < Async::DNS::Server PORT = 53937 IP = '127.0.0.1' DEFAULT_MAX_REQUESTS = 4 DEFAULT_TIMEOUT = 3 @@stats = Stats.new def self.stats @@stats end def initialize(**options) super(options) @handlers = [] @handlers << NioTcpPipeliningHandler.new(self, IP, PORT, DEFAULT_MAX_REQUESTS, DEFAULT_TIMEOUT) #4 max request end def process(name, resource_class, transaction) @logger.debug "name: #{name}" transaction.respond!("93.184.216.34", { resource_class: ::Resolv::DNS::Resource::IN::A }) end end class TestTCPPipelining < Minitest::Test class << self attr_accessor :query_id end def self.init unless @initialized @initialized = true @query_id = 0 end end @@server = nil def setup return self.class.init # Instantiate a new server that uses our tcp pipelining handler # For each query the server sends the query upstream (193.0.14.129) options = { server_class: TCPPipeliningServer, } #RubyDNS::run_server(options) || true if !@@server @@server = TCPPipeliningServer.new() Thread.new do @@server.run end end # Instantiate our resolver. The resolver will use the same pipeline as much as possible. # If a timeout occurs or max_request_per_connection a new connection should be initiated @@resolver ||= Dnsruby::Resolver.new( use_tcp: true, do_caching: false, tcp_pipelining: true, dnssec: false, packet_timeout: 10, tcp_pipelining_max_queries: 10, nameserver: TCPPipeliningServer::IP, port: TCPPipeliningServer::PORT) end # Send x number of queries asynchronously to our resolver def send_async_messages(number_of_messages, queue, wait_seconds = 0) Dnsruby.log.debug "Sending #{number_of_messages} messages" number_of_messages.times do name = "#{self.class.query_id}.com" Dnsruby.log.debug "Sending #{name}" message = Dnsruby::Message.new(name) # self.class.query_id identifies our query, must be different for each message @@resolver.send_async(message, queue, self.class.query_id) self.class.query_id += 1 # Note: For 0, we don't sleep at all instead of sleeping 0 since sleeping 0 # involves yielding the CPU. sleep wait_seconds unless wait_seconds == 0 end end # Verify x responses with no exception def verify_responses(number_of_messages, queue) number_of_messages.times do _response_id, response, exception = queue.pop assert_nil(exception) assert(response.is_a?(Dnsruby::Message)) end end def accept_wait(accept_count, max) i = 0 while TCPPipeliningServer.stats.accept_count < accept_count sleep 0.5 i+=0.5 assert(i connection_count sleep 0.5 i+=0.5 assert(i #{connection_count}") end end def timeout_wait(timeout_count, max) i = 0 while TCPPipeliningServer.stats.timeout_count < timeout_count sleep 0.5 i+=0.5 assert(i Dnsruby::Types.NSEC3, :name => name, :salt => "aabbccdd", :iterations => 12, :hash_alg => 1}) n = nsec3.calculate_hash assert_equal(n, hash, "Expected #{hash} but got #{n} for #{name}") c = Dnsruby::RR::NSEC3.calculate_hash(name, 12, Dnsruby::RR::NSEC3.decode_salt("aabbccdd"), 1) assert_equal(c, hash, "Expected #{hash} but got #{c} for #{name}") } # end def test_nsec_other_stuff nsec = Dnsruby::RR.create(INPUT) # begin # nsec.salt_length=256 # fail # rescue DecodeError # end # begin # nsec.hash_length=256 # fail # rescue DecodeError # end # Be liberal in what you accept... # begin # nsec.hash_alg = 8 # fail # rescue DecodeError # end begin nsec.flags = 2 fail rescue DecodeError end end def test_nsec_types # Test types in last section to 65536. # Test no zeros nsec = Dnsruby::RR.create(INPUT) nsec.add_type(Types.TYPE65534) assert(nsec.types.include?(Types.TYPE65534)) assert(nsec.to_s.include?(Types.TYPE65534.string)) end def test_types rr = RR.create("tfkha3ph6qs16qu3oqtmnfc5tbckpjl7.archi.amt. 1209600 IN NSEC3 1 1 5 - 1tmmto81uc71moj44cli3m6avs5l44l3 NSEC3 CNAME RRSIG ; flags: optout") assert(rr.types.include?(Types::NSEC3)) assert(rr.types.include?(Types::CNAME)) assert(rr.types.include?(Types::RRSIG)) rr = RR.create("929p027vb26s89h6fv5j7hmsis4tcr1p.tjeb.nl. 3600 IN NSEC3 1 0 5 beef 9rs4nbe7128ap5i6v196ge2iag5b7rcq A AAAA RRSIG ") end enddnsruby-1.61.3/test/tc_validator.rb0000644000004100000410000000544513607400362017310 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestValidator < Minitest::Test include Dnsruby def test_validation # Dnsruby::TheLog.level = Logger::DEBUG Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new("dnssec.nominet.org.uk") res.dnssec=true res.do_validation = true Dnsruby::Dnssec.do_validation_with_recursor(false) Dnsruby::Dnssec.default_resolver=(res) # This is a closed zone (not reachable by recursion) trusted_key = Dnsruby::RR.create({:name => "uk-dnssec.nic.uk.", :type => Dnsruby::Types.DNSKEY, :flags => RR::IN::DNSKEY::SEP_KEY | RR::IN::DNSKEY::ZONE_KEY, :key=> "AQPJO6LjrCHhzSF9PIVV7YoQ8iE31FXvghx+14E+jsv4uWJR9jLrxMYm sFOGAKWhiis832ISbPTYtF8sxbNVEotgf9eePruAFPIg6ZixG4yMO9XG LXmcKTQ/cVudqkU00V7M0cUzsYrhc4gPH/NKfQJBC5dbBkbIXJkksPLv Fe8lReKYqocYP6Bng1eBTtkA+N+6mSXzCwSApbNysFnm6yfQwtKlr75p m+pd0/Um+uBkR4nJQGYNt0mPuw4QVBu1TfF5mQYIFoDYASLiDQpvNRN3 US0U5DEG9mARulKSSw448urHvOBwT9Gx5qF2NE4H9ySjOdftjpj62kjb Lmc8/v+z" }) ret = Dnsruby::Dnssec.add_trust_anchor(trusted_key) r = res.query("aaa.bigzone.uk-dnssec.nic.uk", Dnsruby::Types.A) assert(r.security_level.code == Message::SecurityLevel::SECURE, "Level = #{r.security_level.string}") ret = Dnsruby::Dnssec.validate(r) assert(ret, "Dnssec validation failed") # @TODO@ Test other validation policies!! end def test_resolver_cd_validation_fails # Should be able to check Nominet test-zone here - no keys point to it res = Resolver.new res.dnssec=true r = res.query("uk-dnssec.nic.uk", Dnsruby::Types.A) assert(r.security_level = Message::SecurityLevel::INSECURE) end def test_eventtype_api # @TODO@ TEST THE Resolver::EventType interface! print "Test EventType API!\n" end def test_config_api # @TODO@ Test the different configuration options for the validator, # and their defaults # # Should be able to set : # o Whether or not validation happens # o The async API queue tuples etc. # o Whether to use authoritative nameservers for validation # o Whether to use authoritative nameservers generally # print "Test validation configuration options!\n" end end dnsruby-1.61.3/test/test_dnsserver.rb0000644000004100000410000001756013607400362017710 0ustar www-datawww-data# -- # Copyright 2015 Verisign # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'rubydns' require 'nio' require 'socket' require 'thread' module PipelineTest class BinaryStringIO < StringIO def initialize super set_encoding("BINARY") end end def self.read_chunk(socket) # The data buffer: buffer = BinaryStringIO.new # First we need to read in the length of the packet while buffer.size < 2 r = socket.read(1) return "" if r.nil? buffer.write r end # Read in the length, the first two bytes: length = buffer.string.byteslice(0, 2).unpack('n')[0] # Read data until we have the amount specified: while (buffer.size - 2) < length required = (2 + length) - buffer.size # Read precisely the required amount: r = socket.read(required) return "" if r.nil? buffer.write r end return buffer.string.byteslice(2, length) end end class TcpPipelineHandler < Async::DNS::GenericHandler def initialize(server, host, port) super(server) @socket = TCPServer.new(host, port) @selector = NIO::Selector.new monitor = @selector.register(@socket, :r) monitor.value = proc { accept } end def accept handle_connection(@socket.accept) end def handle_connection(socket) @logger.debug "New connection" @logger.debug "Add socket to @selector" monitor = @selector.register(socket, :r) monitor.value = proc { process_socket(socket) } end def process_socket(socket) @logger.debug "Processing socket" _, _remote_port, remote_host = socket.peeraddr options = { peer: remote_host } #we read all data until timeout input_data = PipelineTest.read_chunk(socket) if input_data == "" remove(socket) return end response = process_query(input_data, options) Async::DNS::StreamTransport.write_message(socket, response) rescue EOFError _, port, host = socket.peeraddr @logger.debug("*** #{host}:#{port} disconnected") remove(socket) end def remove(socket, update_connections=true) @logger.debug("Removing socket from selector") socket.close rescue nil @selector.deregister(socket) rescue nil end def run(reactor: Async::Task.current.reactor) Thread.new() do while true @selector.select() do |monitor| reactor.async(@socket) do |socket| monitor.value.call(monitor) end end end end end end class SimpleTimers def initialize @events = {} end def empty? @events.empty? end def after(seconds, &block) eventTime = Time.now + seconds @events[eventTime] ||= [] @events[eventTime] << block end def fire now = Time.now events = @events.select { |key, value| key <= now } (events || []).each do |key, blocks| blocks.each do |block| block.call end @events.delete(key) end end def wait_interval next_event = @events.keys.min next_event.nil? ? nil : next_event - Time.now end end # NioTcpPipeliningHandler accepts new tcp connection and reads data from the sockets until # either the client closes the connection, @max_requests_per_connection is reached # or @timeout is attained. class NioTcpPipeliningHandler < Async::DNS::GenericHandler DEFAULT_MAX_REQUESTS = 4 DEFAULT_TIMEOUT = 3 # TODO Add timeout def initialize(server, host, port, max_requests = DEFAULT_MAX_REQUESTS, timeout = DEFAULT_TIMEOUT) @socket = TCPServer.new(host, port) super(server, @socket) @max_requests_per_connection = max_requests @timeout = timeout @count = {} @server.class.stats.connections = @count.keys.count @timers = SimpleTimers.new @selector = NIO::Selector.new monitor = @selector.register(@socket, :r) monitor.value = proc { accept } end def run(reactor: Async::Task.current.reactor) @selector_threead = create_selector_thread end def accept handle_connection(@socket.accept) end def process_socket(socket) @logger.debug "Processing socket" _, _remote_port, remote_host = socket.peeraddr options = { peer: remote_host } new_connection = @count[socket].nil? @count[socket] ||= 0 @count[socket] += 1 @server.class.stats.connection_accept(new_connection, @count.keys.count) #we read all data until timeout input_data = PipelineTest.read_chunk(socket) if @count[socket] <= @max_requests_per_connection response = process_query(input_data, options) Async::DNS::StreamTransport.write_message(socket, response) end =begin if @count[socket] >= @max_requests_per_connection _, port, host = socket.peeraddr @logger.debug("*** max request for #{host}:#{port}") remove(socket) end =end rescue EOFError _, port, host = socket.peeraddr @logger.debug("*** #{host}:#{port} disconnected") remove(socket) end def remove(socket, update_connections=true) @logger.debug("Removing socket from selector") socket.close rescue nil @selector.deregister(socket) rescue nil socket_count = @count.delete(socket) @server.class.stats.connections = @count.keys.count if update_connections socket_count end def create_selector_thread Thread.new do loop do begin @timers.fire intervals = [@timers.wait_interval || 0.1, 0.1] @selector.select(intervals.min > 0 ? intervals.min : 0.1) do |monitor| monitor.value.call(monitor) end @logger.debug "Woke up" break if @selector.closed? rescue Exception => e @logger.debug "Exception #{e}" @logger.debug "Backtrace #{e.backtrace}" end end end end def handle_connection(socket) @logger.debug "New connection" @logger.debug "Add socket to @selector" monitor = @selector.register(socket, :r) monitor.value = proc { process_socket(socket) } @logger.debug "Add socket timer of #{@timeout}" @timers.after(@timeout) do @logger.debug "Timeout fired for socket #{socket}" count = remove(socket, false) unless count.nil? @logger.debug "Timeout for socket #{socket}" @logger.debug "Increasing timeout count" @server.class.stats.connection_timeout(@count.keys.count) end end end end # Stats collects statistics from our tcp handler class Stats def initialize() @mutex = Mutex.new @accept_count = 0 @timeout_count = 0 @max_count = 0 @connections = 0 end def increment_max; @mutex.synchronize { @max_count += 1 } end def increment_timeout; @mutex.synchronize { @timeout_count += 1 } end def increment_connection; @mutex.synchronize { @accept_count += 1 } end def connection_timeout(active_connections) @mutex.synchronize do @timeout_count += 1 @connections = active_connections end end def connection_accept(new_connection, active_connections) @mutex.synchronize { @connections = active_connections @accept_count += 1 if new_connection } end def connections=(active_connections) @mutex.synchronize { @connections = active_connections } end def connections @mutex.synchronize { @connections } end def accept_count @mutex.synchronize { @accept_count } end def timeout_count @mutex.synchronize { @timeout_count } end def max_count @mutex.synchronize { @max_count } end end dnsruby-1.61.3/test/tc_res_config.rb0000644000004100000410000000572113607400362017436 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestResolverConfig < Minitest::Test GoodInput = { "port" => 54, "src_address" => '10.1.0.1', "src_address6" => 'fc00::1:2:3', "src_port" => 56453, "use_tcp" => true, # "stayopen" => 1, "ignore_truncation" => true, "recurse" => false, "packet_timeout" => 5, # "dnssec" => 1, # "force_v4" => 1, }; ExtendedInput={ "query_timeout" => 30, "retry_delay" => 6, "retry_times" => 5, } LookupInput={ "domain" => 'dnsruby.rubyforge.org', "apply_search_list" => false, "ndots" => 4 , "apply_domain" => false } def setup Dnsruby::Config.reset end def test_multiple_resolver res = Dnsruby::Resolver.new({:nameserver => ["127.0.0.1", "::1"]}); assert(res, "new returned something"); assert_instance_of(Dnsruby::Resolver, res, "new() returns an object of the correct class."); # assert(res.config.nameserver, 'nameserver() works'); searchlist = ["t.dnsruby.validation-test-servers.nominet.org.uk", "t2.dnsruby.validation-test-servers.nominet.org.uk"]; assert_equal(res.config.search=searchlist, searchlist, 'setting searchlist returns correctly.'); assert_equal(res.config.search, searchlist, 'setting searchlist stickts.'); # ~ #diag "\n\nIf you do not have Net::DNS::SEC installed you will see a warning.\n"; # ~ #diag "It is safe to ignore this\n"; (GoodInput.merge(ExtendedInput)).each do | param, value | # puts("Setting " + param); res.send(param+"=", value) assert_equal(res.send(param), value, "setting #param sticks"); end; end def test_single_resolver [Dnsruby::SingleResolver.new()].each {|res| # [Dnsruby::SingleResolver.new({:nameserver => ["127.0.0.1"]}), # Dnsruby::SingleResolver.new({:nameserver => ["::1"]})].each {|res| GoodInput.each do | param, value | # puts("Setting " + param); res.send(param+"=", value) assert_equal(res.send(param), value, "setting #param sticks"); end; } end def test_dns res = Dnsruby::DNS.new LookupInput.each do | param, value | res.config.send(param+"=", value) assert_equal(res.config.send(param), value, "setting #param sticks"); end; end end dnsruby-1.61.3/test/tc_naptr.rb0000644000004100000410000000413213607400362016437 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestNAPTR < Minitest::Test include Dnsruby def test_naptr txt = "example.com. IN NAPTR 100 50 \"s\" \"z3950+I2L+I2C\" \"\" _z3950._tcp.gatech.edu." naptr = RR.create(txt) assert(naptr.type == Types.NAPTR) assert(naptr.order == 100) assert(naptr.preference == 50) assert(naptr.flags == 's') assert(naptr.service == "z3950+I2L+I2C") assert(naptr.regexp == "") assert(naptr.replacement == Name.create('_z3950._tcp.gatech.edu.')) m = Dnsruby::Message.new m.add_additional(naptr) data = m.encode m2 = Dnsruby::Message.decode(data) naptr2 = m2.additional()[0] assert(naptr2.type == Types.NAPTR) assert(naptr2.order == 100) assert(naptr2.preference == 50) assert(naptr2.flags == "s") assert(naptr2.service == "z3950+I2L+I2C") assert(naptr2.regexp == "") assert(naptr2.replacement == Name.create('_z3950._tcp.gatech.edu.')) naptr.flags = "u" end def test_string txt = 'all.rr.org. 7200 IN NAPTR 100 10 "" "" "/urn:cid:.+@([^\\\\.]+\\\\.)(.*)$/\\\\2/i" .' rr = RR.create(txt) assert(rr.to_s.index('"/urn:cid:.+@([^\\\\.]+\\\\.)(.*)$/\\\\2/i"'), '"/urn:cid:.+@([^\\\\.]+\\\\.)(.*)$/\\\\2/i"' + "\n" + rr.to_s) end def test_bad_string txt = 'all.rr.binary.org. IN NAPTR 100 10 "" "" "/urn:cid:.+@([^\\.]+\\.)(.*)$/\\\\2/i" .' rr = RR.create(txt) assert(rr.to_s.index('"/urn:cid:.+@([^.]+.)(.*)$/\\\\2/i"'), '"/urn:cid:.+@([^.]+.)(.*)$/\\\\2/i"' + "\n" + rr.to_s) end end dnsruby-1.61.3/test/tc_rrsig.rb0000644000004100000410000000600613607400362016443 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class RrsigTest < Minitest::Test INPUT = "host.example.com. 86400 IN RRSIG A 5 3 86400 20030322173103 ( " + "20030220173103 2642 example.com. " + "oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTr" + "PYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o" + "B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3t" + "GNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG" + "J5D6fwFm8nN+6pBzeDQfsS3Ap3o= )" def test_rrsig_from_string rrsig = Dnsruby::RR.create(INPUT) assert_equal(Dnsruby::Types.A, rrsig.type_covered) assert_equal(Dnsruby::Algorithms::RSASHA1, rrsig.algorithm) assert_equal(3, rrsig.labels) assert_equal(86400, rrsig.original_ttl) assert_equal(Time.gm(2003,03,22,17,31, 03).to_i, rrsig.expiration) assert_equal(Time.gm(2003,02,20,17,31,03).to_i, rrsig.inception) assert_equal(2642, rrsig.key_tag) assert_equal(Dnsruby::Name.create("example.com."), rrsig.signers_name) assert_equal("oJB1W6WNGv+ldvQ3WDG0MQkg5IEhjRip8WTr" + "PYGv07h108dUKGMeDPKijVCHX3DDKdfb+v6o" + "B9wfuh3DTJXUAfI/M0zmO/zz8bW0Rznl8O3t" + "GNazPwQKkRN20XPXV6nwwfoXmJQbsLNrLfkG" + "J5D6fwFm8nN+6pBzeDQfsS3Ap3o=", ([rrsig.signature].pack("m*")).gsub(/\n/,"").chomp) rrsig2 = Dnsruby::RR.create(rrsig.to_s) assert(rrsig2.to_s == rrsig.to_s) end def test_unknown_types rr = Dnsruby::RR.create("a.unknown.rr.org. 16070400 IN RRSIG TYPE731 7 4 16070400 20110220190432 20091112142325 59079 unknown.rr.org. a/iqriTleD/pkiXhH2HunBzbJ113JliHu8MrN30hwR5U8uR+FQ9UwoyqFVKmMFvhr66Q+Bn2leJhszJVLHM0GZpEP3yU9Kiux5z2sWxdNZY1phuVfe7vQhzPCG9a/gaNtOd/p42OaQRIvDpdp7Ey4m+2Lq/PfovuAa8jl1HBBSxYbt2sZ4Qh9IrP7qkabGzuF3iK8Kf+QTV+ty9enMRhv2zbGVJv0/KjfeOmLBpDnLxDtNN23ObqO2y31Ci434bWYbHRZJMofUWw/0cJHdw4qlnfraLHiXQSW/tT71mS/7CgHJcSZ89hdDFv8drAy/8py0MLT9nLrsvzH5F/knU/oA== ;{id = 59079}") assert(rr.type_covered == Dnsruby::Types.TYPE731) end def test_string_with_comments r = Dnsruby::RR.create("tjeb.nl. 3600 IN RRSIG NSEC3PARAM 7 2 3600 20090630164649 20090602164649 53177 tjeb.nl. Fw70WQMviRFGyeze3MUpfafaAcWIvHRpnq4ZK3lxexrR1p+rLxK5C4qVKU71XYrPYR7XEBxgUG1oyKNOhFOVyx31EjC462dz7Vxn6UDpD1LIwNnD28+oHfS9AFzGKcn4zUZqT+8IvOO1jiS9c3Y8WAkOloN9AwGIIKWU8zAp1n4= ;{id = 53177}") assert_equal("Fw70WQMviRFGyeze3MUpfafaAcWIvHRpnq4ZK3lxexrR1p+rLxK5C4qVKU71XYrPYR7XEBxgUG1oyKNOhFOVyx31EjC462dz7Vxn6UDpD1LIwNnD28+oHfS9AFzGKcn4zUZqT+8IvOO1jiS9c3Y8WAkOloN9AwGIIKWU8zAp1n4=", ([r.signature].pack("m*")).gsub(/\n/,"").chomp) end enddnsruby-1.61.3/test/tc_res_env.rb0000644000004100000410000000367713607400362016771 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestResolverEnv < Minitest::Test include Dnsruby # @todo@ Dnsruby does not provide this functionality def test_res_env ENV['RES_NAMESERVERS'] = '10.0.1.128 10.0.2.128'; ENV['RES_SEARCHLIST'] = 'dnsruby.validation-test-servers.nominet.org.uk lib.dnsruby.validation-test-servers.nominet.org.uk'; ENV['LOCALDOMAIN'] = 't.dnsruby.validation-test-servers.nominet.org.uk'; ENV['RES_OPTIONS'] = 'retrans:3 retry:2 debug'; res = DNS.new; assert(res, "new() returned something"); assert(res.config.nameserver, "nameservers() works"); servers = res.config.nameserver; assert_equal(servers[0], '10.0.1.128', 'Nameserver set correctly'); assert_equal(servers[1], '10.0.2.128', 'Nameserver set correctly'); search = res.searchlist; assert_equal(search[0], 'dnsruby.validation-test-servers.nominet.org.uk', 'Search set correctly' ); assert_equal(search[1], 'lib.dnsruby.validation-test-servers.nominet.org.uk', 'Search set correctly' ); assert_equal(res.domain, 't.dnsruby.validation-test-servers.nominet.org.uk', 'Local domain works' ); assert_equal(3, res.retrans, 'Retransmit works' ); assert_equal(2, res.retry, 'Retry works' ); assert(res.debug, 'Debug works' ); end end dnsruby-1.61.3/test/tc_question.rb0000644000004100000410000000273313607400362017167 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestQuestion < Minitest::Test include Dnsruby def test_question domain = "example.com" type = Types.MX klass = Classes.IN q = Question.new(domain, type, klass) assert(q, "new() returned something") assert_equal(domain, q.qname.to_s, "qName()") assert_equal(type, q.qtype, "qType()") assert_equal(klass, q.qclass, "qClass()") # # Check the aliases # assert_equal(q.zname.to_s, domain, 'zName()' ); assert_equal(q.ztype, type, 'zType()' ); assert_equal(q.zclass, klass, 'zClass()' ); # # Check that we can change stuff # q.qname=('example.net'); q.qtype=('A'); q.qclass=('CH'); assert_equal('example.net', q.qname.to_s, 'qName()' ); assert_equal(q.qtype, Types.A, 'qType()' ); assert_equal(q.qclass, Classes.CH, 'qClass()' ); end end dnsruby-1.61.3/test/tc_nsec3param.rb0000644000004100000410000000307213607400362017351 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class Nsec3ParamTest < Minitest::Test include Dnsruby INPUT = "example. 3600 IN NSEC3PARAM 1 0 12 aabbccdd" def test_nsec_from_string nsec = Dnsruby::RR.create(INPUT) assert_equal(Dnsruby::Nsec3HashAlgorithms.SHA_1, nsec.hash_alg) assert_equal(0, nsec.flags) assert_equal(12, nsec.iterations) assert_equal("aabbccdd", nsec.salt) nsec2 = Dnsruby::RR.create(nsec.to_s) assert(nsec2.to_s == nsec.to_s) end def test_nsec_from_data nsec = Dnsruby::RR.create(INPUT) m = Dnsruby::Message.new m.add_additional(nsec) data = m.encode m2 = Dnsruby::Message.decode(data) nsec3 = m2.additional()[0] assert_equal(nsec.to_s, nsec3.to_s) end def test_from_real_string r = Dnsruby::RR.create("tjeb.nl. 3600 IN NSEC3PARAM 1 0 5 beef") assert_equal(Dnsruby::Name.create("tjeb.nl."), r.name) assert_equal("beef", r.salt) assert_equal(Dnsruby::Nsec3HashAlgorithms.SHA_1, r.hash_alg) end enddnsruby-1.61.3/test/tc_caa.rb0000644000004100000410000000326013607400362016040 0ustar www-datawww-data # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'pry' class TestCAA < Minitest::Test include Dnsruby def test_caa {'foo.com. IN CAA 0 issue "ca.example.net"' => [0, 'issue', 'ca.example.net'], 'foo.com. IN CAA 1 issue "ca.example.net"' => [1, 'issue', 'ca.example.net'], 'foo.com. IN CAA 0 issuewild "ca.example.net"' => [0, 'issuewild', 'ca.example.net'], 'foo.com. IN CAA 0 iodef "mailto:security@example.com"' => [0, 'iodef', 'mailto:security@example.com'], 'foo.com. IN CAA 0 issue "ca.example.net; account=230123"' => [0, 'issue', 'ca.example.net; account=230123'] }.each do |text, data| caa = RR.create(text) assert_equal(data[0], caa.flag) assert_equal(data[1], caa.property_tag) assert_equal(data[2], caa.property_value) m = Dnsruby::Message.new m.add_additional(caa) data = m.encode m2 = Dnsruby::Message.decode(data) caa2 = m2.additional()[0] assert(caa.flag == caa2.flag) assert(caa.property_tag == caa2.property_tag) assert(caa.property_value == caa2.property_value) assert(caa == caa2) end end end dnsruby-1.61.3/test/tc_recur.rb0000644000004100000410000000217213607400362016435 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestRecur < Minitest::Test def test_recur Dnsruby::PacketSender.clear_caches r = Dnsruby::Recursor.new # Dnsruby::TheLog.level = Logger::DEBUG ret = r.query("uk", Dnsruby::Types.DNSKEY) # print ret assert ret, "Query result was nil." assert ret.answer.length > 0, "Answer length should > 0, but was #{ret.answer.length}." # ret = r.query_dorecursion("aaa.bigzone.uk-dnssec.nic.uk", Dnsruby::Types.DNSKEY) # ret = r.query_dorecursion("uk-dnssec.nic.uk", Dnsruby::Types.DNSKEY) end end dnsruby-1.61.3/test/tc_escapedchars.rb0000644000004100000410000003431613607400362017747 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestEscapedChars < Minitest::Test include Dnsruby def test_one Name::Label.set_max_length(150) # # We test al sorts of escaped non-ascii characters. # This is all to be protocol conform... so to speak. # # The collection of tests is somewhat of a hodgepodge that tried to # assess sensitivity to combinations of characters that the regular # expressions and perl itself are sensitive to. (like \\\\\.\..) # Development versions of the code tried to split a domain name in # invidual labels by a regular expression. It made no sense to remove # the more ackward tests as they have to pass anyway ... # Note that in perl the \\ in a presentation format can only be achieved # through \\\\ . # The hex codes are the names in wireformat: # length octet. content octets, length octet, content , NULL octet # Below are test combos, 1st and 2nd array elements are # representations of the name. The output of the perl functions should # yield the 2nd presentation (eg \037 gets presented as % ) # The 3rd element is a label count. # The 4th element represents the number of octets per label # The 5th element is a hexdump of the domain name in wireformat testcombos=[ ['bla.fo\.o.org', 'bla.fo\.o.org', 3, [3,4,3], # Wire: 3 b l a 4 f o . o 3 o r g 0 "03626c6104666f2e6f036f726700" ], [ 'bla\255.foo.org', 'bla\255.foo.org', 3, [4,3,3], # Wire: 4 b l a 0xff 3 f o o 3 o r g 0 "04626c61ff03666f6f036f726700" ], [ 'bla.f\xa9oo.org', 'bla.f\169oo.org', 3, [3,4,3] , # Wire: 3 b l a 4 f 0xa9 o o 3 o r g 0 "03626c610466a96f6f036f726700" ], # Note hex to decimal ['bla.fo\.o.org', 'bla.fo\.o.org', 3, [3,4,3], # Wire: 3 b l a 4 f o . o 3 o r g 0 "03626c6104666f2e6f036f726700" ], ['bla\0000.foo.org', 'bla\0000.foo.org', 3, [5,3,3], # Wire: 5 b l a 0x00 0 3 f o o 3 o r g 0 "05626c61003003666f6f036f726700" , ], ["bla.fo\o.org", "bla.foo.org", 3, [3,3,3], # Wire: 3 b l a 3 f o o 3 o r g 0 ignoring backslash on input "03626c6103666f6f036f726700", ], # drops the \ ['bla(*.foo.org', 'bla\(*.foo.org', 3, [5,3,3], # Wire: 5 b l a ( * 3 f o o 3 o r g 0 "05626c61282a03666f6f036f726700" ], [' .bla.foo.org', '\032.bla.foo.org', 4, [1,3,3,3], "012003626c6103666f6f036f726700", ], ["\\\\a.foo", "\\\\a.foo", 2, [2,3], # Wire: 2 \ a 3 f o o 0 "025c6103666f6f00" ], ['\\\\.foo', '\\\\.foo', 2, [1,3], # Wire: 1 \ 3 f o o 0 "015c03666f6f00", ], ['a\\..foo', 'a\\..foo', 2, [2,3], # Wire: 2 a . 3 f o o 0 "02612e03666f6f00" ], ['a\\.foo.org', 'a\\.foo.org', 2, [5,3], # Wire: 5 a . f o o 3 o r g 0 "05612e666f6f036f726700" , ], ['\..foo.org', '\..foo.org', 3, [1,3,3], # Wire: 1 . 3 f o o 3 o r g 0 "012e03666f6f036f726700" , ], [ '\046.\046', '\..\.', 2, [1,1], '012e012e00', ], [ # all non \w characters :-) '\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031\032.\033\034\035\036\037\038\039\040\041\042\043\044\045\046\047\048.\058\059\060\061\062\063\064\065.\091\092\093\094\095\096.\123\124\125\126\127\128\129', '\000\001\002\003\004\005\006\007\008\009\010\011\012\013\014\015\016\017\018\019\020\021\022\023\024\025\026\027\028\029\030\031\032.!\"#\$%&\'\(\)*+,-\./0.:\;<=>?\@a.[\\\\]^_`.{|}~\127\128\129', 5, [33,16,8,6,7], "21000102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20102122232425262728292a2b2c2d2e2f30083a3b3c3d3e3f4061065b5c5d5e5f60077b7c7d7e7f808100", ], ] # foreach my $testinput (@testcombos){ testcombos.each do |testinput| # test back and forth name = Name.create(testinput[0]) labels = Name.name2encodedlabels(testinput[0]) # assert_equal(testinput[1], Net::labels2name(labels), "consistent name2labels labels2name for " + testinput[0]) # name_from_labels = Name.encodedlabels2name(labels) name_from_labels = Name.new(labels) assert_equal(name.to_s, name_from_labels.to_s, "Name->Labels->Name for " + testinput[0]) # test number of labels assert_equal(testinput[2],labels.length(),"consistent labelcount (#{testinput[2]})") # test number of elements within label. i=0 # Test length of each individual label while i '\\e.eg.secret-wg.org', :type => 'TXT', :txtdata => '"WildCard Match"', :ttl => 10, :class => "IN" ) klass = "IN" ttl = 43200 name = 'def0au<.example.com' rrs = [ { #[0] :name => '\..bla\..example.com', :type => Types.A, :address => '10.0.0.1', }, { #[2] :name => name, :type => 'AFSDB', :subtype => 1, :hostname =>'afsdb-hostname.example.com', }, { #[3] :name => '\\.funny.example.com', :type => Types::CNAME, :domainname => 'cname-cn\244ame.example.com', }, { #[4] :name => name, :type => Types.DNAME, :domainname => 'dn\222ame.example.com', }, { #[9] :name => name, :type => Types.MINFO, :rmailbx => 'minfo\.rmailbx.example.com', :emailbx => 'minfo\007emailbx.example.com', }, { #[13] :name => name, :type => Types.NS, :domainname => '\001ns-nsdname.example.com', }, { #[19] :name => name, :type => Types.SOA, :mname => 'soa-mn\001ame.example.com', :rname => 'soa\.rname.example.com', :serial => 12345, :refresh => 7200, :retry => 3600, :expire => 2592000, :minimum => 86400, }, ] # ------------------------------------------------------------------------------ # Create the packet. # ------------------------------------------------------------------------------ packet = nil packet = Message.new(name) assert(packet, 'Packet created') rrs.each do |data| data.update({:ttl => ttl,}) rec = RR.create(data) packet.add_answer(rec) end # ------------------------------------------------------------------------------ # Re-create the packet from data. # ------------------------------------------------------------------------------ data = packet.encode assert(data, 'Packet has data after pushes') packet = nil packet = Message.decode(data) assert(packet, 'Packet reconstructed from data') answer = packet.answer # assert(answer && answer == rrs, 'Packet returned correct answer section') rrs.each do |rr| record = nil answer.each do |ansrec| if (ansrec.type == rr[:type]) record = ansrec break end end assert(record!=nil, "can't find answer record for #{rr}") rr.keys.each do |key| if (key == :type) assert_equal(Types.new(rr[key]).string, record.send(key).to_s, "value not right for key #{key} for rr #{rr}") else assert_equal(rr[key].to_s, record.send(key).to_s, "value not right for key #{key} for rr #{rr}") end end end while (answer.size>0 and rrs.size>0) data = rrs.shift rr = answer.shift type = data[:type] # foreach my $meth (keys %{$data}) { (data.keys.each do |meth| if (meth == :type) assert_equal(Types.new(data[meth]).to_s, rr.send(meth).to_s, "#{type} - #meth() correct") else assert_equal(data[meth].to_s, rr.send(meth).to_s, "#{type} - #meth() correct") end end) rr2 = RR.new_from_string(rr.to_s) assert_equal(rr.to_s, rr2.to_s, "#{type} - Parsing from string works") end Name::Label.set_max_length(Name::Label::MaxLabelLength) end end dnsruby-1.61.3/test/tc_ds.rb0000644000004100000410000000676513607400362015737 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'openssl' require 'digest/sha2' class DsTest < Minitest::Test include Dnsruby DLVINPUT = "dskey.example.com. 86400 IN DLV 60485 5 1 ( 2BB183AF5F22588179A53B0A" + "98631FAD1A292118 )" INPUT = "dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A" + "98631FAD1A292118 )" DNSKEY = "dskey.example.com. 86400 IN DNSKEY 256 3 5 ( AQOeiiR0GOMYkDshWoSKz9Xz" + "fwJr1AYtsmx3TGkJaNXVbfi/" + "2pHm822aJ5iI9BMzNXxeYCmZ"+ "DRD99WYwYqUSdjMmmAphXdvx"+ "egXd/M5+X7OrzKBaMbCVdFLU"+ "Uh6DhweJBjEVv5f2wwjM9Xzc"+ "nOf+EPbtG9DMBmADjFDc2w/r"+ "ljwvFw== )" # key id = 60485 DS1 = "dskey.example.com. 86400 IN DS 60485 5 1 ( 2BB183AF5F22588179A53B0A"+ "98631FAD1A292118 )" DS2 = "dskey.example.com. 86400 IN DS 60485 5 2 ( D4B7D520E7BB5F0F67674A0C"+ "CEB1E3E0614B93C4F9E99B83"+ "83F6A1E4469DA50A )" def test_ds_from_string ds = Dnsruby::RR.create(INPUT) assert_equal(60485, ds.key_tag) assert_equal(Algorithms.RSASHA1, ds.algorithm) assert_equal(1, ds.digest_type) assert_equal("2BB183AF5F22588179A53B0A98631FAD1A292118", ds.digest) ds2 = Dnsruby::RR.create(ds.to_s) assert(ds2.to_s == ds.to_s) end def test_ds_from_data ds = Dnsruby::RR.create(INPUT) m = Dnsruby::Message.new m.add_additional(ds) data = m.encode m2 = Dnsruby::Message.decode(data) ds3 = m2.additional()[0] assert_equal(ds.to_s, ds3.to_s) end def test_ds_values ds = Dnsruby::RR.create(INPUT) ds.digest_type = 2 # Be liberal in what you accept... # begin # ds.digest_type = 3 # fail # # rescue DecodeError # end end def test_ds_digest key = Dnsruby::RR.create(DNSKEY) # and check it is the same as DS right_ds = Dnsruby::RR.create(DS1) ds = Dnsruby::RR::DS.from_key(key, 1); assert_equal(ds.to_s, right_ds.to_s) end def test_sha2 # Create a new DS from the DNSKEY, key = Dnsruby::RR.create(DNSKEY) # and check it is the same as DS right_ds = Dnsruby::RR.create(DS2) ds = Dnsruby::RR::DS.from_key(key, 2); assert_equal(ds.to_s, right_ds.to_s) end def test_dlv_from_string dlv = Dnsruby::RR.create(DLVINPUT) assert_equal(60485, dlv.key_tag) assert_equal(Algorithms.RSASHA1, dlv.algorithm) assert_equal(1, dlv.digest_type) assert_equal("2BB183AF5F22588179A53B0A98631FAD1A292118", dlv.digest) dlv2 = Dnsruby::RR.create(dlv.to_s) assert(dlv2.to_s == dlv.to_s) end enddnsruby-1.61.3/test/tc_tkey.rb0000644000004100000410000000463613607400362016300 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require "digest/md5" class TestTKey < Minitest::Test def is_empty(string) return (string == "; no data" || string == "; rdlength = 0") end def test_tkey # ------------------------------------------------------------------------------ # Canned data. # ------------------------------------------------------------------------------ zone = "example.com" name = "123456789-test" klass = "IN" type = Dnsruby::Types.TKEY algorithm = "fake.algorithm.example.com" key = "fake key" inception = 100000 # use a strange fixed inception time to give a fixed # checksum expiration = inception + 24*60*60 rr = nil # ------------------------------------------------------------------------------ # Packet creation. # ------------------------------------------------------------------------------ rr = Dnsruby::RR.create( :name => name, :type => "TKEY", :ttl => 0, :klass => "ANY", :algorithm => algorithm, :inception => inception, :expiration => expiration, :mode => 3, # GSSAPI :key => "fake key", :other_data => "" ) packet = Dnsruby::Message.new(name, Dnsruby::Types.TKEY, "IN") packet.add_answer(rr) z = (packet.zone)[0] assert(packet, 'new() returned packet') #2 assert_equal(Dnsruby::OpCode.QUERY, packet.header.opcode, 'header opcode correct') #3 assert_equal(name, z.zname.to_s, 'zname correct') #4 assert_equal(Dnsruby::Classes.IN, z.zclass, 'zclass correct') #5 assert_equal(Dnsruby::Types.TKEY, z.ztype, 'ztype correct') #6 # @TODO@ Test TKEY against server! end end dnsruby-1.61.3/test/ts_dnsruby.rb0000644000004100000410000000121513607400362017020 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative "ts_online.rb" require_relative "ts_offline.rb" dnsruby-1.61.3/test/tc_verifier.rb0000644000004100000410000003575013607400362017140 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class VerifierTest < Minitest::Test def test_sha2 # Check if OpenSSL supports SHA2 have_sha2 = false begin OpenSSL::Digest::SHA256.new have_sha2 = true rescue Exception end if (have_sha2) # print "OpenSSL supports SHA2\n" do_test_sha256 do_test_sha512 do_test_nsec else print "OpenSSL doesn't support SHA2 - disabling SHA256/SHA512 tests. DNSSEC validation will not work with these type of signatures.\n" end end def do_test_sha256 key256 = Dnsruby::RR.create("example.net. 3600 IN DNSKEY (256 3 8 AwEAAcFcGsaxxdgiuuGmCkVI my4h99CqT7jwY3pexPGcnUFtR2Fh36BponcwtkZ4cAgtvd4Qs8P kxUdp6p/DlUmObdk= );{id = 9033 (zsk), size = 512b}") a = Dnsruby::RR.create("www.example.net. 3600 IN A 192.0.2.91") sig = Dnsruby::RR.create("www.example.net. 3600 IN RRSIG (A 8 3 3600 20300101000000 20000101000000 9033 example.net. kRCOH6u7l0QGy9qpC9 l1sLncJcOKFLJ7GhiUOibu4teYp5VE9RncriShZNz85mwlMgNEa cFYK/lPtPiVYP4bwg==) ;{id = 9033}") rrset = Dnsruby::RRSet.new(a) rrset.add(sig) verifier = Dnsruby::SingleVerifier.new(nil) verifier.verify_rrset(rrset, key256) end def do_test_sha512 key512 = Dnsruby::RR.create("example.net. 3600 IN DNSKEY (256 3 10 AwEAAdHoNTOW+et86KuJOWRD p1pndvwb6Y83nSVXXyLA3DLroROUkN6X0O6pnWnjJQujX/AyhqFD xj13tOnD9u/1kTg7cV6rklMrZDtJCQ5PCl/D7QNPsgVsMu1J2Q8g pMpztNFLpPBz1bWXjDtaR7ZQBlZ3PFY12ZTSncorffcGmhOL );{id = 3740 (zsk), size = 1024b}") a = Dnsruby::RR.create("www.example.net. 3600 IN A 192.0.2.91") sig = Dnsruby::RR.create("www.example.net. 3600 IN RRSIG (A 10 3 3600 20300101000000 20000101000000 3740 example.net. tsb4wnjRUDnB1BUi+t 6TMTXThjVnG+eCkWqjvvjhzQL1d0YRoOe0CbxrVDYd0xDtsuJRa eUw1ep94PzEWzr0iGYgZBWm/zpq+9fOuagYJRfDqfReKBzMweOL DiNa8iP5g9vMhpuv6OPlvpXwm9Sa9ZXIbNl1MBGk0fthPgxdDLw =);{id = 3740}") rrset = Dnsruby::RRSet.new(a) rrset.add(sig) verifier = Dnsruby::SingleVerifier.new(nil) verifier.verify_rrset(rrset, key512) end def test_se_query # Run some queries on the .se zone Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new(Dnsruby::Resolv.getaddress("a.ns.se")) res.dnssec = true r = res.query("se", Dnsruby::Types.ANY) # See comment below Dnsruby::Dnssec.anchor_verifier.add_trusted_key(r.answer.rrset("se", 'DNSKEY')) nss = r.answer.rrset("se", 'NS') ret = Dnsruby::Dnssec.verify_rrset(nss) assert(ret, "Dnssec verification failed") end def test_verify_message Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new(Dnsruby::Resolv.getaddress("a.ns.se")) res.udp_size = 5000 r = res.query("se", Dnsruby::Types.DNSKEY) # This shouldn't be in the code - but the key is rotated by the .se registry # so we can't keep up with it in the test code. # Oh, for a signed root... # print "Adding keys : #{r.answer.rrset("se", 'DNSKEY')}\n" Dnsruby::Dnssec.anchor_verifier.add_trusted_key(r.answer.rrset("se", 'DNSKEY')) ret = Dnsruby::Dnssec.verify(r) assert(ret, "Dnssec message verification failed : #{ret}") end def test_verify_message_fails Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new("a.ns.se") r = res.query("se", Dnsruby::Types.ANY) # Haven't configured key for this, so should fail begin ret = Dnsruby::Dnssec.verify(r) fail("Message shouldn't have verified") rescue (Dnsruby::VerifyError) end # assert(!ret, "Dnssec message verification failed") end def test_trusted_key Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new("dnssec.nominet.org.uk") res.dnssec = true bad_key = Dnsruby::RR.create( "uk-dnssec.nic.uk. 86400 IN DNSKEY 257 3 5 "+ "AwEAAbhThsjZqxZDyZLie1BYP+R/G1YRhmuIFCbmuQiF4NB86gpW8EVR l2s+gvNuQw6yh2YdDdyJBselE4znRP1XQbpOTC5UO5CDwge9NYja/jrX lvrX2N048vhIG8uk8yVxJDosxf6nmptsJBp3GAjF25soJs07Bailcr+5 vdZ7GibH") ret = Dnsruby::Dnssec.add_trust_anchor(bad_key) r = res.query("uk-dnssec.nic.uk", Dnsruby::Types.DNSKEY) begin ret = Dnsruby::Dnssec.verify(r) fail("Dnssec trusted key message verification should have failed with bad key") rescue (Dnsruby::VerifyError) # assert(!ret, "Dnssec trusted key message verification should have failed with bad key") end trusted_key = Dnsruby::RR.create({:name => "uk-dnssec.nic.uk.", :type => Dnsruby::Types.DNSKEY, :flags => 257, :protocol => 3, :algorithm => 5, :key=> "AQPJO6LjrCHhzSF9PIVV7YoQ8iE31FXvghx+14E+jsv4uWJR9jLrxMYm sFOGAKWhiis832ISbPTYtF8sxbNVEotgf9eePruAFPIg6ZixG4yMO9XG LXmcKTQ/cVudqkU00V7M0cUzsYrhc4gPH/NKfQJBC5dbBkbIXJkksPLv Fe8lReKYqocYP6Bng1eBTtkA+N+6mSXzCwSApbNysFnm6yfQwtKlr75p m+pd0/Um+uBkR4nJQGYNt0mPuw4QVBu1TfF5mQYIFoDYASLiDQpvNRN3 US0U5DEG9mARulKSSw448urHvOBwT9Gx5qF2NE4H9ySjOdftjpj62kjb Lmc8/v+z" }) ret = Dnsruby::Dnssec.add_trust_anchor(trusted_key) ret = Dnsruby::Dnssec.verify(r) assert(ret, "Dnssec trusted key message verification failed") # # Check that keys have been added to trusted key cache # ret = Dnsruby::Dnssec.verify(r) # assert(ret, "Dnssec trusted key cache failed") end def test_expired_keys # Add some keys with an expiration of 1 second. # Then wait a second or two, and check they are not available any more. Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors assert(Dnsruby::Dnssec.anchor_verifier.trusted_keys.length==0) trusted_key = Dnsruby::RR.create({:name => "uk-dnssec.nic.uk.", :type => Dnsruby::Types.DNSKEY, :key=> "AQPJO6LjrCHhzSF9PIVV7YoQ8iE31FXvghx+14E+jsv4uWJR9jLrxMYm sFOGAKWhiis832ISbPTYtF8sxbNVEotgf9eePruAFPIg6ZixG4yMO9XG LXmcKTQ/cVudqkU00V7M0cUzsYrhc4gPH/NKfQJBC5dbBkbIXJkksPLv Fe8lReKYqocYP6Bng1eBTtkA+N+6mSXzCwSApbNysFnm6yfQwtKlr75p m+pd0/Um+uBkR4nJQGYNt0mPuw4QVBu1TfF5mQYIFoDYASLiDQpvNRN3 US0U5DEG9mARulKSSw448urHvOBwT9Gx5qF2NE4H9ySjOdftjpj62kjb Lmc8/v+z" }) Dnsruby::Dnssec.add_trust_anchor_with_expiration(trusted_key, Time.now.to_i + 1) assert(Dnsruby::Dnssec.trust_anchors.length==1) sleep(2) assert(Dnsruby::Dnssec.trust_anchors.length==0) end def test_tcp # These queries work: # dig @194.0.1.13 isoc.lu dnskey # dig @194.0.1.13 isoc.lu dnskey +dnssec # dig @194.0.1.13 isoc.lu dnskey +tcp # This one does not # # dig @194.0.1.13 isoc.lu dnskey +dnssec +tcp r = Dnsruby::SingleResolver.new()# "194.0.1.13") r.dnssec = true r.use_tcp = true ret = r.query("isoc.lu", Dnsruby::Types.DNSKEY) # print ret.to_s+"\n" r = Dnsruby::SingleResolver.new("194.0.1.13") r.dnssec = true # r.use_tcp = true ret = r.query("isoc.lu", Dnsruby::Types.DNSKEY) # print ret.to_s+"\n" r.use_tcp = true r.dnssec = false ret = r.query("isoc.lu", Dnsruby::Types.DNSKEY) # print ret.to_s+"\n" r.dnssec = true begin ret = r.query("isoc.lu", Dnsruby::Types.DNSKEY) rescue (Dnsruby::OtherResolvError) end end def test_sendraw Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Resolver.new("a.ns.se") res.dnssec = true message = Dnsruby::Message.new("se", Dnsruby::Types.ANY) begin res.send_message(message) fail() rescue (Exception) end message.send_raw = true res.send_message(message) end def test_dsa # Let's check sources.org for DSA keys Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors res = Dnsruby::Recursor.new() ret = res.query("sources.org", Dnsruby::Types.DNSKEY) keys = ret.rrset("sources.org", "DNSKEY") assert(keys && keys.length > 0) dsa = nil keys.each {|key| if (key.algorithm == Dnsruby::Algorithms.DSA) dsa = key end } assert(dsa) # Now do something with it response = res.query("sources.org", Dnsruby::Types.ANY) verified = 0 # response.each_section {|sec| response.answer.rrsets.each {|rs| if (rs.sigs()[0].algorithm == Dnsruby::Algorithms.DSA && rs.sigs()[0].key_tag == dsa.key_tag) ret = Dnsruby::Dnssec.verify_rrset(rs, keys) assert(ret) verified+=1 end } # } assert(verified > 0) end def do_test_nsec begin begin require 'rubygems' rescue LoadError end require 'timecop' rescue LoadError return end Timecop.travel(2010, 03, 24, 0, 0, 0) { key = Dnsruby::RR.create("in-addr-servers.arpa. 3600 IN DNSKEY 256 3 8 AwEAAcoEdjN6PM57REYLqLCBNfjCbQQU8pSNOz/kRwP75YQzidnaQpCO4+rjOYSAPH5lAjtT+AxuUB33DkOhQHPDSO87JLt1pm65eNNsz10COEExfuokM98qiURN76kv3N1n/gRG2693tpkmVdvSTRCbReyq6BlzKuYABGLD3V3MUB4j ;{id = 12033 (zsk), size = 1024b}") verifier = Dnsruby::SingleVerifier.new(Dnsruby::SingleVerifier::VerifierType::ANCHOR) key_rrset = Dnsruby::RRSet.new(key) verifier.add_trusted_key(key_rrset); sig = Dnsruby::RR.create("b.in-addr-servers.arpa. 3600 IN RRSIG NSEC 8 3 3600 20100325113758 20100318052509 12033 in-addr-servers.arpa. uy5aUIhq3eKc24gcoyBoLYaR6kKtG957zpR0G2pf1XPCO2ESzwdIkXK0/XeUkRMmPRnKfGOhwNYIBK26kX3PYxaIPsDZVc5ZAC3uc/+EpCosMn3FJQQDiNx/gznEQZk0JRxTUMMMucCNW2HVU18NVtTQhT0MaAsLyG8OduWuMCI= ;{id = 12033}") nsec = Dnsruby::RR.create("B.in-addr-servers.arpa. 3600 IN NSEC C.in-addr-servers.arpa. A AAAA RRSIG NSEC") rrset = Dnsruby::RRSet.new(nsec) rrset.add(sig) verifier.verify_rrset(rrset, key_rrset) } end def test_naptr begin begin require 'rubygems' rescue LoadError end require 'timecop' rescue LoadError return end Timecop.travel(2010, 03, 24, 0, 0, 0) { key = Dnsruby::RR.create("all.rr.org. 2678400 IN DNSKEY 256 3 7 AwEAAcW1ZJxnMxZAAfsQ0JJQPHOlVNeGzs/AWVSGXiIYsg9UUSsvRTiK/Wy2wD7XC6osZpgy4Blhm846wktPbCwHpkxxbjxpaMABjbhH14gRol1Gpzf+gOr8vpdii8c2y6VMN9kIXZyaZUWcshLii19ysSGlqY1a1g2XZjogFtvzDHjH ;{id = 43068 (zsk), size = 1024b}") verifier = Dnsruby::SingleVerifier.new(Dnsruby::SingleVerifier::VerifierType::ANCHOR) key_rrset = Dnsruby::RRSet.new(key) verifier.add_trusted_key(key_rrset); sig = Dnsruby::RR.create("all.rr.org. 86400 IN RRSIG NAPTR 7 3 86400 20100727230632 20090919145743 43068 all.rr.org. RpyBsaLiaZ/OqX5twE0SoMhlVZVAHuAlS4FZqmnAg+udF3EwrY6N/POt3nPCtgwf7tczaxrMK6zWkOldfv37iyIgXIxDQvhoCb7IoffI5TsBL5CWl5n7pg8BNAMpLxd8HIu1DShWvlplpFbBWIaC6tZCR6ft/iP+uhU7dYcqTHg= ;{id = 43068}") naptr = Dnsruby::RR.create('all.rr.org. 86400 IN NAPTR 100 10 "" "" "!^urn:cid:.+@([^\\\\.]+\\\\.)(.*)$!\\\\2!i" .') rrset = Dnsruby::RRSet.new(naptr) rrset.add(sig) verifier.verify_rrset(rrset, key_rrset) } end def test_txt_rr begin begin require 'rubygems' rescue LoadError end require 'timecop' rescue LoadError return end Timecop.travel(2010, 03, 24, 0, 0, 0) { txt = 'txt2.all.rr.org. 86400 IN TXT "Net-DNS\\\\; complicated $tuff" "sort of \\" text\\\\; and binary \\000 data"' rr = Dnsruby::RR.create(txt) assert(rr.to_s.index('"Net-DNS\\\\; complicated $tuff" "sort of \\" text\\\\; and binary \\000 data"'), rr.to_s) key = Dnsruby::RR.create("all.rr.org. 2678400 IN DNSKEY 256 3 7 AwEAAcW1ZJxnMxZAAfsQ0JJQPHOlVNeGzs/AWVSGXiIYsg9UUSsvRTiK/Wy2wD7XC6osZpgy4Blhm846wktPbCwHpkxxbjxpaMABjbhH14gRol1Gpzf+gOr8vpdii8c2y6VMN9kIXZyaZUWcshLii19ysSGlqY1a1g2XZjogFtvzDHjH ;{id = 43068 (zsk), size = 1024b}") verifier = Dnsruby::SingleVerifier.new(Dnsruby::SingleVerifier::VerifierType::ANCHOR) key_rrset = Dnsruby::RRSet.new(key) verifier.add_trusted_key(key_rrset); sig = Dnsruby::RR.create("txt2.all.rr.org. 86400 IN RRSIG TXT 7 4 86400 20100813002344 20091006093439 43068 all.rr.org. LJv/ccd2JHyT6TK74Dtu/zH4jdeR4ScyrB8cGwaqeCjwxG4H5FY88Sk/U0JUQyxnUificnyZQwcyXAItn7QjBMHQO4ftVxl/gDCyt6MEXy9JKK/rfvXcAceo5prmlVrb8WxT5YnvPha3CxjK7f+YIs5cqppRVaZTQTxsAsJyJ20= ;{id = 43068}") txt = Dnsruby::RR.create('txt2.all.rr.org. 86400 IN TXT "Net-DNS\\\\; complicated $tuff" "sort of \\" text\\\\; and binary \\000 data"') rrset = Dnsruby::RRSet.new(txt) rrset.add(sig) verifier.verify_rrset(rrset, key_rrset) } end # def test_txt_zonefile # reader = Dnsruby::ZoneReader.new("cacert.org.") # zone = reader.process_file("cacert.txt") # reader2 = Dnsruby::ZoneReader.new("cacert.org.") # zone2 = reader.process_file("cacert.signed.txt") # assert(zone[1].to_s.index("DAQAB\"")) # assert(zone2[1].to_s.index("DAQAB\"")) # # assert(zone[1].to_s == zone2[1].to_s) # end # # def test_txt_from_zone # reader = Dnsruby::ZoneReader.new("all.rr.org.") # zone = reader.process_file("zone.txt") # rrset = Dnsruby::RRSet.new # key_rrset = Dnsruby::RRSet.new # zone.each {|rr| # if ( (rr.type == Dnsruby::Types.TXT) || ((rr.type == Dnsruby::Types.RRSIG) && (rr.type_covered == Dnsruby::Types.TXT))) # rrset.add(rr) # end # if (rr.type == Dnsruby::Types.DNSKEY) # key_rrset.add(rr) # end # } # verifier = Dnsruby::SingleVerifier.new(Dnsruby::SingleVerifier::VerifierType::ANCHOR) # verifier.verify_rrset(rrset, key_rrset) # end # def test_naptr_from_zone # reader = Dnsruby::ZoneReader.new("all.rr.org.") # zone = reader.process_file("zone.txt") # rrset = Dnsruby::RRSet.new # key_rrset = Dnsruby::RRSet.new # zone.each {|rr| # if ((rr.type == Dnsruby::Types.NAPTR) || ((rr.type == Dnsruby::Types.RRSIG) && (rr.type_covered == Dnsruby::Types.NAPTR))) # rrset.add(rr) # end # if (rr.type == Dnsruby::Types.DNSKEY) # key_rrset.add(rr) # end # } # verifier = Dnsruby::SingleVerifier.new(Dnsruby::SingleVerifier::VerifierType::ANCHOR) # verifier.verify_rrset(rrset, key_rrset) # end end dnsruby-1.61.3/test/tc_rr.rb0000644000004100000410000003377613607400362015756 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestRR < Minitest::Test include Dnsruby def test_rr # ------------------------------------------------------------------------------ # Canned data. # ------------------------------------------------------------------------------ name = "foo.example.com"; klass = "IN"; ttl = 43200; rrs = [ { #[0] :type => Types.A, :address => '10.0.0.1', }, { #[1] :type => Types::AAAA, :address => '102:304:506:708:90a:b0c:d0e:ff10', }, { #[2] :type => 'AFSDB', :subtype => 1, :hostname => 'afsdb-hostname.example.com', }, { #[3] :type => Types.CNAME, :domainname => 'cname-cname.example.com', }, { #[4] :type => Types.DNAME, :domainname => 'dname.example.com', }, { #[5] :type => Types.HINFO, :cpu => 'test-cpu', :os => 'test-os', }, { #[6] :type => Types.ISDN, :address => '987654321', :subaddress => '001', }, { #[7] :type => Types.MB, :domainname => 'mb-madname.example.com', }, { #[8] :type => Types.MG, :domainname => 'mg-mgmname.example.com', }, { #[9] :type => Types.MINFO, :rmailbx => 'minfo-rmailbx.example.com', :emailbx => 'minfo-emailbx.example.com', }, { #[10] :type => Types.MR, :domainname => 'mr-newname.example.com', }, { #[11] :type => Types.MX, :preference => 10, :exchange => 'mx-exchange.example.com', }, { #[12] :type => Types.NAPTR, :order => 100, :preference => 10, :flags => 'naptr-flags', :service => 'naptr-service', :regexp => 'naptr-regexp', :replacement => 'naptr-replacement.example.com', }, { #[13] :type => Types.NS, :domainname => 'ns-nsdname.example.com', }, { #[14] :type => Types.NSAP, :afi => '47', :idi => '0005', :dfi => '80', :aa => '005a00', :rd => '1000', :area => '0020', :id => '00800a123456', :sel => '00', # #:address => '4700580005a001000002000800a12345600' # :address => '47000580005a0000001000002000800a12345600' }, { #[15] :type => Types.PTR, :domainname => 'ptr-ptrdname.example.com', }, { #[16] :type => Types.PX, :preference => 10, :map822 => 'px-map822.example.com', :mapx400 => 'px-mapx400.example.com', }, { #[17] :type => Types.RP, :mailbox => 'rp-mbox.example.com', :txtdomain => 'rp-txtdname.example.com', }, { #[18] :type => Types.RT, :preference => 10, :intermediate => 'rt-intermediate.example.com', }, { #[19] :type => Types.SOA, :mname => 'soa-mname.example.com', :rname => 'soa-rname.example.com', :serial => 12345, :refresh => 7200, :retry => 3600, :expire => 2592000, :minimum => 86400, }, { #[20] :type => Types.SRV, :priority => 1, :weight => 2, :port => 3, :target => 'srv-target.example.com', }, { #[21] :type => Types.TXT, :strings => 'txt-txtdata', }, { #[22] :type => Types.X25, :address => '123456789', }, { #[23] :type => Types.LOC, :version => 0, :size => 3000, :horiz_pre => 500000, :vert_pre => 500, :latitude => 2001683648, :longitude => 1856783648, :altitude => 9997600, }, #[24] { :type => Types.CERT, :certtype => 3, :keytag => 1, :alg => 1, :cert => 'ffsayw1dvk7higuvhn56r26uwjx/', }, { #[25] :type => Types.SPF, :strings => 'txt-txtdata', }, { :type => Types.KX, :preference => 10, :exchange => 'kx-exchange.example.com', }, { # [26] :type => Types.APL, :prefixes => '1:10.0.0.0/8 !1:172.16.0.0/12 1:192.168.0.0/16 !1:192.168.0.0/24', }, { # [27] :type => Types.APL, :prefixes => '!2:fe80::/10 2:2001:db8::/32 2:2001:db8::/64', }, { # [28] :type => Types.APL, :prefixes => '1:0.0.0.0/0 1:255.255.255.255/32 2:::/0 2:::1/128', } ] # ------------------------------------------------------------------------------ # Create the packet # ------------------------------------------------------------------------------ message = Message.new assert(message, 'Message created'); rrs.each do |data| data.update({ :name => name, :ttl => ttl, }) rr=RR.create(data) message.add_answer(rr); end # ------------------------------------------------------------------------------ # Re-create the packet from data. # ------------------------------------------------------------------------------ data = message.encode; assert(data, 'Packet has data after pushes'); message=nil; message= Message.decode(data); assert(message, 'Packet reconstructed from data'); answer = message.answer; i = 0 rrs.each do |rec| ret_rr = answer[i] i += 1 rec.each do |key, value| # method = key+'=?' x = ret_rr.send(key) if (ret_rr.kind_of?RR::CERT and (key == :alg or key == :certtype)) assert_equal(value.to_s, x.code.to_s.downcase, "Packet returned wrong answer section for #{ret_rr.to_s}, #{key}") elsif (ret_rr.kind_of?RR::TXT and (key == :strings)) assert_equal(value.to_s.downcase, x[0].to_s.downcase, "TXT strings wrong") else if (key == :type) assert_equal(Types.new(value).to_s.downcase, x.to_s.downcase, "Packet returned wrong answer section for #{ret_rr.to_s}, #{key}") else assert_equal(value.to_s.downcase, x.to_s.downcase, "Packet returned wrong answer section for #{ret_rr.to_s}, #{key}") end end end end while (!answer.empty? and !rrs.empty?) data = rrs.shift; rr = answer.shift; type = data[:type]; assert(rr, "#{type} - RR defined"); assert_equal(name, rr.name.to_s, "#{type} - name() correct"); assert_equal(klass, rr.klass.to_s, "#{type} - class() correct"); assert_equal(ttl, rr.ttl, "#{type} - ttl() correct"); # foreach my $meth (keys %{data}) { data.keys.each do |meth| ret = rr.send(meth) if (rr.kind_of?RR::CERT and (meth == :alg or meth == :certtype)) assert_equal(data[meth].to_s, ret.code.to_s.downcase, "#{type} - #{meth}() correct") elsif (rr.kind_of?RR::TXT and (meth == :strings)) assert_equal(data[meth].to_s, ret[0].to_s.downcase, "TXT strings wrong") else if (meth == :type) assert_equal(Types.new(data[meth]).to_s.downcase, ret.to_s.downcase, "#{type} - #{meth}() correct"); else assert_equal(data[meth].to_s, ret.to_s.downcase, "#{type} - #{meth}() correct"); end end end rr2 = RR.new_from_string(rr.to_s) assert_equal(rr.to_s, rr2.to_s, "#{type} - Parsing from string works") end end def test_naptr update = Update.new update.add('example.com.','NAPTR', 3600, '1 0 "s" "SIP+D2T" "" _sip._tcp.example.com.') update.encode end def test_uri rrString = "_ftp._tcp.\t300\tIN\tURI\t10\ 1 \"ftp://ftp1.example.com/public\"" rr = RR.create(rrString) assert(rrString.to_s == rr.to_s) m = Dnsruby::Message.new m.add_additional(rr) m2 = Message.decode(m.encode) rr2 = m2.additional()[0] assert(rr == rr2) end def test_cds rrString = "dskey.example.com.\t86400\tIN\tCDS\t60485 RSASHA1 1 ( 2BB183AF5F22588179A53B0A98631FAD1A292118 )" rr = RR.create(rrString) assert(rrString.to_s == rr.to_s) m = Dnsruby::Message.new m.add_additional(rr) m2 = Message.decode(m.encode) rr2 = m2.additional()[0] assert(rr.to_s == rr2.to_s) end def test_cdnskey rrString = "tjeb.nl.\t3600\tIN\tCDNSKEY\t256 3 RSASHA1-NSEC3-SHA1 ( AwEAAcglEOS7bECRK5fqTuGTMJycmDhTzmUu/EQbAhKJOYJxDb5SG/RYqsJgzG7wgtGy0W1aP7I4k6SPtHmwcqjLaZLVUwRNWCGr2adjb9JTFyBR7F99Ngi11lEGM6Uiw/eDRk66lhoSGzohjj/rmhRTV6gN2+0ADPnafv3MBkPgryA3 ) ; key_tag=53177" rr = RR.create(rrString) assert(rrString.to_s == rr.to_s) m = Dnsruby::Message.new m.add_additional(rr) m2 = Message.decode(m.encode) rr2 = m2.additional()[0] assert(rr.to_s == rr2.to_s) end def test_cert rr = RR.create("test.kht.se. 60 IN CERT PGP 0 0 mQGiBDnY2vERBAD3cOxqoAYHYzS+xttvuyN9wZS8CrgwLIlT8Ewo/CCFI11PEO+gJyNPvWPRQsyt1SE60reaIsie2bQTg3DYIg0PmH+ZOlNkpKesPULzdlw4Rx3dD/M3Lkrm977h4Y70ZKC+tbvoYKCCOIkUVevny1PVZ+mB94rb0mMgawSTrct03QCg/w6aHNJFQV7O9ZQ1Fir85M3RS8cEAOo4/1ASVudz3qKZQEhU2Z9O2ydXqpEanHfGirjWYi5RelVsQ9IfBSPFaPAWzQ24nvQ18NU7TgdDQhP4meZXiVXcLBR5Mee2kByf2KAnBUF9aah5s8wZbSrC6u8xEZLuiauvWmCUIWe0Ylc1/L37XeDjrBI2pT+k183X119d6Fr1BACGfZVGsot5rxBUEFPPSrBqYXG/0hRYv9Eq8a4rJAHK2IUWYfivZgL4DtrJnHlha+H5EPQVYkIAN3nGjXoHmosY+J3Sk+GyR+dCBHEwCkoHMKph3igczCEfxAWgqKeYd5mf+QQq2JKrkn2jceiIO7s3CrepeEFAjDSGuxhZjPJVm7QoRGFuaWVsIFAuIE1haG9uZXkgPGRhbm1AcHJpbWUuZ3VzaGkub3JnPohOBBARAgAOBQI52NrxBAsDAQICGQEACgkQ+75aMGJLskn6LgCbBXUD7UmGla5e1zyhuY667hP3F+UAoJIeDZJyRFkQAmb+u8KekRyLD1MLtDJEYW5pZWwgTWFob25leSAoU2Vjb25kYXJ5IEVtYWlsKSA8Z3VzaGlAZ3VzaGkub3JnPohgBBMRAgAgBQJF1J/XAhsjBgsJCAcDAgQVAggDBBYCAwECHgECF4AACgkQ+75aMGJLskkVhACggsivQ9qLhfdA1rGm6f8LRJBSC4wAoI930h+/hshClj6AkNwGRtHdf5XJuQINBDnY2vQQCAD2Qle3CH8IF3KiutapQvMF6PlTETlPtvFuuUs4INoBp1ajFOmPQFXz0AfGy0OplK33TGSGSfgMg71l6RfUodNQ+PVZX9x2Uk89PY3bzpnhV5JZzf24rnRPxfx2vIPFRzBhznzJZv8V+bv9kV7HAarTW56NoKVyOtQa8L9GAFgr5fSI/VhOSdvNILSd5JEHNmszbDgNRR0PfIizHHxbLY7288kjwEPwpVsYjY67VYy4XTjTNP18F1dDox0YbN4zISy1Kv884bEpQBgRjXyEpwpy1obEAxnIByl6ypUM2Zafq9AKUJsCRtMIPWakXUGfnHy9iUsiGSa6q6Jew1XpMgs7AAICB/9eGjzF2gDh6U7I72x/6bSdlExx2LvIF92OZKc0S55IOS4Lgzs7Hbfm1aOL4oJt7wBg94xkF4cerxz7y8R9J+k3GNl14KOjbYaMAh1rdxdAzikYMH1p1hS78GMtwxky6jE5en87BGGMmnbC84JlxwN+MD7diu8D0Gkgjj/pxOp32D5jEe02wBPVjFTpFLJjpFniLUY6AohRDEdSuZwWPuoKVWhpeWkasNn5qgwGyDREbXpyPsU02BkwE4JiGs+JMMdOn9KMh5dxiuwsMM9gHiQZS3mSNBBKPWI5ZXsdStVFvapjf2FUFDXLUbTROPv1Xhqf0u7YYORFnWeVtvzKIxVaiEYEGBECAAYFAjnY2vQACgkQ+75aMGJLsklBWgCeN7z9xk52y/aoaCuF6hYb0d+3k98AoMRxvHuXI1Nc2FXY/x65PwHiUbaY") rr = RR.create("all.rr.org. IN CERT 6 0 0 FFsAyW1dVK7hIGuvhN56r26UwJx/") # rr = RR.create("all.rr.org. IN WKS 128.32.0.10 UDP who route timed domain") rr = RR.create('selector._domainkey.all.rr.org. IN TXT "v=DKIM1; n=Use=20DKIM; p=AwEAAZfbYw8SffZwsbrCLbC+JLErREIF6Yfe9aqsa1Pz6tpGWiLxm9rSL6/YoBvNP3UWX91YDF0JMo6lhu3UIZjITvIwDhx+RJYko9vLzaaJKXGf3ygy6z+deWoZJAV1lTY0Ltx9genboe88CSCHw9aSLkh0obN9Ck8R6zAMYR19ciM/; t=s"') end def test_dhcid rr = RR.create("all.rr.org. IN DHCID AAIBY2/AuCccgoJbsaxcQc9TUapptP69lOjxfNuVAA2kjEA=") m = Dnsruby::Message.new m.add_additional(rr) data = m.encode m2 = Dnsruby::Message.decode(data) rr2 = m2.additional()[0] assert(rr == rr2) end def test_loc rr = RR.create("all.rr.org. IN LOC 42 21 54 N 71 06 18 W -24m 30m") assert(rr.vert_pre == 1000) assert(rr.horiz_pre == 1000000) assert(rr.to_s.index("21")) assert(rr.to_s.index("71")) assert(rr.to_s.index("54")) assert(rr.to_s.index("71")) assert(rr.to_s.index("06")) assert(rr.to_s.index("18")) r2 = RR.create("helium IN LOC 51 49 17.9 N 4 39 22.9 E 0m") assert(r2.size == 100) assert(r2.to_s.index("17.9")) assert(r2.to_s.index("22.9")) end def test_hinfo rr = RR.create('helium IN HINFO "Shuttle-ST61G4 Intel PIV3000" "FreeBSD 7.0-STABLE"') assert rr.to_s.index('"Shuttle-ST61G4 Intel PIV3000"') assert rr.to_s.index('"FreeBSD 7.0-STABLE"') end def test_private_method_really_private begin RR._get_subclass(nil, nil, nil, nil, nil) raise "This should not have gotten here; the method should have been private" rescue NoMethodError # We should be here because the method should not have been found. end end # TTL should be ignored when calculating the hash of an RR. def test_hash_ignores_ttl a1 = RR.new_from_string 'techhumans.com. 1111 IN A 69.89.31.97' a2 = RR.new_from_string 'techhumans.com. 1111 IN A 69.89.31.97' a3 = RR.new_from_string 'techhumans.com. 2222 IN A 69.89.31.97' assert_equal a1.hash, a2.hash assert_equal a1.hash, a3.hash end def _test_duplicate_answer(method_as_symbol) expected_count = case method_as_symbol when :add_answer 1 when :add_answer! 2 end rr = RR.new_from_string 'techhumans.com. 1111 IN A 69.89.31.97' message = Message.new 2.times { message.send(method_as_symbol, rr) } assert_equal(expected_count, message.header.ancount) end def test_add_dup_answer_no_force _test_duplicate_answer(:add_answer) end def test_add_dup_answer_force _test_duplicate_answer(:add_answer!) end end dnsruby-1.61.3/test/tc_name.rb0000644000004100000410000000531213607400362016234 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestName < Minitest::Test include Dnsruby def test_label_length Name::Label.set_max_length(Name::Label::MaxLabelLength) # Other tests may have changed this # Test max label length = 63 begin name = Name.create("a.b.12345678901234567890123456789012345678901234567890123456789012345.com") assert(false, "Label of more than max=63 allowed") rescue ResolvError end end def test_name_length # Test max name length=255 begin name = Name.create("1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123.com") assert(false, "Name of length > 255 allowed") rescue ResolvError end end def test_absolute n = Name.create("example.com") assert(!n.absolute?) n = Name.create("example.com.") assert(n.absolute?) end def test_wild n = Name.create("example.com") assert(!n.wild?) n = Name.create("*.example.com.") assert(n.wild?) end def test_canonical_ordering names = [] names.push(Name.create("example")) names.push(Name.create("a.example")) names.push(Name.create("yljkjljk.a.example")) names.push(Name.create("Z.a.example")) names.push(Name.create("zABC.a.EXAMPLE")) names.push(Name.create("z.example")) names.push(Name.create("\001.z.example")) names.push(Name.create("*.z.example")) # names.push(Name.create("\200.z.example")) names.push(Name.create(["c8"].pack("H*")+".z.example")) names.each_index {|i| if (i < (names.length() - 1)) assert(names[i].canonically_before(names[i+1])) assert(!(names[i+1].canonically_before(names[i]))) end } assert(Name.create("x.w.example").canonically_before(Name.create("z.w.example"))) assert(Name.create("x.w.example").canonically_before(Name.create("a.z.w.example"))) end def test_escapes n1 = Name.create("\\nall.all.") n2 = Name.create("nall.all.") assert(n1 == n2, n1.to_s) end end dnsruby-1.61.3/test/tc_zone_reader.rb0000644000004100000410000000433713607400362017617 0ustar www-datawww-data require_relative 'spec_helper' class ZoneReaderTest < Minitest::Test include Dnsruby def setup @zone_data = < 'ancount', 'add_authority' => 'nscount', 'add_additional' => 'arcount', } tests.each do | try | count = try.shift; rrs = try; methods.each do |method, count_meth| packet = Message.new(domain) rrs.each do |rr| packet.send(method,rr) end assert_equal(count, packet.header.send(count_meth), "#{method} right for #{rrs.inspect}"); assert_equal(count, packet.header.send(count_meth), "#{method} right for #{rrs.inspect}"); end end end end dnsruby-1.61.3/test/test_utils.rb0000644000004100000410000000235013607400362017024 0ustar www-datawww-datarequire_relative 'spec_helper' # Use this in tests in the tests directory with: # require_relative 'test_utils' # include TestUtils module Dnsruby module TestUtils module_function # Asserts that all exceptions whose type are the specified exception class # or one of its subclasses are *not* raised. # # If any other kind of exception is raised, the test throws an exception # (rather than failing). # # The test passes if and only if no exceptions are raised. def assert_not_raised(exception_class, failure_message = nil) begin yield rescue => e if e.is_a?(exception_class) flunk(failure_message || "An exception was not expected but was raised: #{e}") else raise e end end end =begin # This should result in a test failure: def test_target_exception assert_not_raised(ArgumentError, 'ArgumentError') { raise ArgumentError.new } end # This should result in a test error: def test_other_exception assert_not_raised(ArgumentError, 'RuntimeError') { raise RuntimeError.new } end # This should result in a passed test: def test_no_exception assert_not_raised(ArgumentError, 'No Error') { } end =end end end dnsruby-1.61.3/test/tc_hash.rb0000644000004100000410000000230013607400362016231 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'set' module Dnsruby class TestHash < Minitest::Test def test_types_hash object1 = Types.new(Types::NSEC3) object2 = Types.new(Types::NSEC3) assert(object1 == object2) assert(object1.hash == object2.hash, "Hashes differed: #{object1.hash} != #{object2.hash}") end def test_types_set object1 = Types.new(Types::NSEC3) object2 = Types.new(Types::NSEC3) assert(object1 == object2) set = Set.new([object1, object2]) assert(set.size == 1, "Two equal objects should result in a set size of 1, but instead the size was #{set.size}.") end end end dnsruby-1.61.3/test/tc_dns.rb0000644000004100000410000002106313607400362016101 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestDNS < Minitest::Test include Dnsruby def setup Dnsruby::Config.reset end def test_ipv4_address Dnsruby::DNS.open { |dns| dns.getnames(Dnsruby::IPv4.create("221.186.184.68")) } end # def test_resolv_rb_api # DNS.open {|dns| # # dns.getresources("www.ruby-lang.org", Types.A).each {|r| assert_equal(r.address.to_s, "221.186.184.68")} # dns.getresources("www.ruby-lang.org", Types.A).each {|r| assert_equal(r.address.to_s, "54.163.249.195")} # r= dns.getresources("ruby-lang.org", Types.MX, Classes.IN).collect {|r| [r.exchange.to_s, r.preference]} # assert_equal(r, [["carbon.ruby-lang.org", 10]]) # } # d = DNS.open # # d.getresources("www.ruby-lang.org", Types.A, Classes.IN).each {|r| assert_equal(r.address.to_s, "221.186.184.68")} # d.getresources("www.ruby-lang.org", Types.A, Classes.IN).each {|r| assert_equal(r.address.to_s, "54.163.249.195")} # assert_equal(d.getaddress("www.ruby-lang.org").to_s, "54.163.249.195") # # assert_equal(d.getaddress("www.ruby-lang.org").to_s, "221.186.184.68") # r = d.getaddresses("www.ruby-lang.org") # assert_equal(r.length, 1) # assert_equal(r[0].to_s, "221.186.184.68") # d.each_address("www.ruby-lang.org") {|address| assert_equal(address.to_s, "54.163.249.195")} # # d.each_address("www.ruby-lang.org") {|address| assert_equal(address.to_s, "221.186.184.68")} # assert_equal(d.getname("210.251.121.214").to_s, "ci.ruby-lang.org") # r = d.getnames("210.251.121.214") # assert_equal(r.length, 1) # assert_equal(r[0].to_s, "ci.ruby-lang.org") # d.each_name("210.251.121.214") {|name| assert_equal(name.to_s, "ci.ruby-lang.org")} # r = d.getresource("www.ruby-lang.org", Types.A) # assert_equal(r.name.to_s, "carbon.ruby-lang.org") # assert_equal(r.address.to_s, "221.186.184.68") # assert_equal(r.klass, Classes.IN) # assert_equal(r.type, Types.A) # r = d.getresources("www.ruby-lang.org", Types.MX) # assert(r.length==1) # assert_equal(r[0].name.to_s, "carbon.ruby-lang.org") # assert_equal(r[0].preference, 10) # assert_equal(r[0].exchange.to_s, "carbon.ruby-lang.org") # assert_equal(r[0].klass, Classes.IN) # assert_equal(r[0].type, Types.MX) # r = d.each_resource("www.ruby-lang.org", Types.ANY) {|r| # assert_equal(r.name.to_s, "www.ruby-lang.org") # assert_equal(r.domainname.to_s, "carbon.ruby-lang.org") # assert_equal(r.klass, Classes.IN) # assert_equal(r.type, Types.CNAME) # } # d.close # end def test_async_api # @TODO@ Do we really want an async API for Resolv/DNS? # Or would users be better off with Resolver async API? end def test_concurrent # @TODO@ What kind of concurrent testing are we going to do on the top-level API? end def test_bad_input # # Check that new() is vetting things properly. # Dnsruby.log.level=Logger::FATAL [:nameserver].each do |test| # [{}, 'kjghdfkjhase',1,'\1',nil].each do |input| # Config now only checks that an IPv4, IPv6 or Name can be made with each input [{},1,nil].each do |input| res=nil begin res = Dnsruby::DNS.new({test => input}) assert(false, "Accepted invalid input") rescue assert(res==nil, "No resolver should be returned for #{test} = #{input}") end end end end def test_online res = DNS.new rrs = [ { :type => Types.A, :name => 'a.t.net-dns.org', # :name => 'a.t.dnsruby.validation-test-servers.nominet.org.uk', :address => '10.0.1.128' }, { :type => Types::MX, :name => 'mx.t.net-dns.org', :exchange => 'a.t.net-dns.org', # :name => 'mx.t.dnsruby.validation-test-servers.nominet.org.uk', # :exchange => 'a.t.dnsruby.validation-test-servers.nominet.org.uk', :preference => 10 }, { :type => 'CNAME', :name => 'cname.t.net-dns.org', :domainname => 'a.t.net-dns.org' # :name => 'cname.t.dnsruby.validation-test-servers.nominet.org.uk', # :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk' }, { :type => Types.TXT, :name => 'txt.t.net-dns.org', # :name => 'txt.t.dnsruby.validation-test-servers.nominet.org.uk', :strings => ['Net-DNS'] } ] rrs.each do |data| answer = res.getresource(data[:name], data[:type]) assert(answer) assert_equal(answer.klass, 'IN', 'Class correct' ) packet, queried_name = res.send_query(data[:name], data[:type]) assert(packet, "Got an answer for #{data[:name]} IN #{data[:type]}") assert_equal(1, packet.header.qdcount, 'Only one question') # assert_equal(1, answer.length, 'Got single answer') question = (packet.question)[0] answer = (packet.answer)[0] assert(question, 'Got question' ) assert_equal(data[:name], question.qname.to_s, 'Question has right name' ) assert_equal(data[:name], queried_name.to_s, 'queried_name has right name' ) assert_equal(Types.new(data[:type]), question.qtype, 'Question has right type' ) assert_equal('IN', question.qclass.string, 'Question has right class') assert(answer) assert_equal(answer.klass, 'IN', 'Class correct' ) data.keys.each do |meth| if (meth == :type) assert_equal(Types.new(data[meth]).to_s, answer.send(meth).to_s, "#{meth} correct (#{data[:name]})") else assert_equal(data[meth].to_s, answer.send(meth).to_s, "#{meth} correct (#{data[:name]})") end end end # do end # test_online def test_search_query_reverse # # test that getname() DTRT with reverse lookups # tests = [ { :ip => '198.41.0.4', :host => 'a.root-servers.net', }, { :ip => '2001:500:1::803f:235', :host => 'h.root-servers.net', }, ] res = DNS.new tests.each do |test| name = res.getname(test[:ip]) assert_instance_of(Name,name) next unless name assert_equal(name.to_s, test[:host], "getname(#{test[:ip]}) works") end # do end # test def test_searchlist res = DNS.new( :domain => 't.net-dns.org', :search => ["t.net-dns.org", "net-dns.org"] # :domain => 't.dnsruby.validation-test-servers.nominet.org.uk', # :search => ["t.dnsruby.validation-test-servers.nominet.org.uk", "dnsruby.validation-test-servers.nominet.org.uk"] ) # # test the send_query() appends the default domain and # searchlist correctly. # # @TODO@ Should really be done in Config test! tests = [ { :method => 'search', :name => 'a' }, { :method => 'search', :name => 'a.t' }, { :method => 'query', :name => 'a' } ] # res.send_query("a.t.dnsruby.validation-test-servers.nominet.org.uk", "A") res.send_query("a.t.net-dns.org", "A") res.config.ndots=2 tests.each do |test| method = test[:method] if (method=="query") res.config.apply_search_list=false else res.config.apply_search_list=true end ans, query = res.send_query(test[:name]) assert_instance_of(Message, ans) # assert_equal(2, ans.header.ancount, "Correct answer count (with persistent socket and #{method})") a = ans.answer assert_instance_of(RR::IN::A, a[0]) assert_equal(a[0].name.to_s, 'a.t.net-dns.org',"Correct name (with persistent socket and #{method})") # assert_equal(a[0].name.to_s, 'a.t.dnsruby.validation-test-servers.nominet.org.uk',"Correct name (with persistent socket and #{method})") end end def test_port d = DNS.new({:port => 5353}) assert(d.to_s.include?"5353") end def test_port_nil d = DNS.new({:port => nil}) assert(d.to_s.include? Dnsruby::Config::DEFAULT_PORT.to_s) end end dnsruby-1.61.3/test/tc_nsec.rb0000644000004100000410000002332613607400362016251 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class NsecTest < Minitest::Test include Dnsruby INPUT = "alfa.example.com. 86400 IN NSEC host.example.com. ( " + "A MX RRSIG NSEC TYPE1234 )" def test_nsec_from_string nsec = Dnsruby::RR.create(INPUT) assert_equal("host.example.com", nsec.next_domain.to_s) assert_equal([Types.A, Types.MX, Types.RRSIG, Types.NSEC, Types.TYPE1234], nsec.types) nsec2 = Dnsruby::RR.create(nsec.to_s) assert(nsec2.to_s == nsec.to_s) s = "tjeb.nl. 3600 IN NSEC dragon.tjeb.nl. A NS SOA MX AAAA RRSIG NSEC DNSKEY" nsec = Dnsruby::RR.create(s) assert(nsec.types.include?(Types.A)) assert(nsec.types.include?(Types.DNSKEY)) end def test_nsec_from_data nsec = Dnsruby::RR.create(INPUT) m = Dnsruby::Message.new m.add_additional(nsec) data = m.encode m2 = Dnsruby::Message.decode(data) nsec3 = m2.additional()[0] assert_equal(nsec.to_s, nsec3.to_s) end def test_nsec_types # Test types in last section to 65536. # Test no zeros nsec = Dnsruby::RR.create(INPUT) nsec.add_type(Types.TYPE65534) assert(nsec.types.include?(Types.TYPE65534)) assert(nsec.to_s.include?(Types.TYPE65534.string)) end def test_examples_from_rfc_4035_name_error # Grab the example responses from RFC4035 and make sure that they pass. # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # and make sure that they fail verification for that reason m = Message.new m.header.rcode = 3 m.add_question(Question.new("m1.example.")) m.add_authority(RR.create("example. 3600 IN SOA ns1.example. bugs.x.w.example. ( 1081539377 3600 300 3600000 3600 )")) m.add_authority(RR.create("m3.example. 3600 NSEC ns1.example. NS RRSIG NSEC")) m.add_authority(RR.create("example. 3600 NSEC a.example. NS SOA MX RRSIG NSEC DNSKEY")) m.add_authority(RR.create("example. 3600 RRSIG NSEC 5 1 3600 20040509183619 ( 20040409183619 38519 example. O0k558jHhyrC97ISHnislm4kLMW48C7U7cBm FTfhke5iVqNRVTB1STLMpgpbDIC9hcryoO0V Z9ME5xPzUEhbvGnHd5sfzgFVeGxr5Nyyq4tW SDBgIBiLQUv1ivy29vhXy7WgR62dPrZ0PWvm jfFJ5arXf4nPxp/kEowGgBRzY/U= )")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with bad NSECs") rescue VerifyError end m.authority.delete(RR.create("m3.example. 3600 NSEC ns1.example. NS RRSIG NSEC")) m.add_authority(RR.create("b.example. 3600 NSEC ns1.example. NS RRSIG NSEC")) Dnssec.anchor_verifier.verify_nsecs(m) m.authority.delete(RR.create("example. 3600 NSEC a.example. NS SOA MX RRSIG NSEC DNSKEY")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with no wildcard proof") rescue VerifyError end end def test_examples_from_rfc_4035_no_data # Grab the example responses from RFC4035 and make sure that they pass. # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # and make sure that they fail verification for that reason m = Message.new m.header.rcode = 0 m.add_question(Question.new("ns1.example.", Types.MX)) m.add_authority(RR.create("example. 3600 IN SOA ns1.example. bugs.x.w.example. ( 1081539377 3600 300 3600000 3600 )")) m.add_authority(RR.create("m3.example. 3600 NSEC n1.example. NS RRSIG NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with bad NSECs") rescue VerifyError end m.authority.delete(RR.create("m3.example. 3600 NSEC n1.example. NS RRSIG NSEC")) m.add_authority(RR.create("ns1.example. 3600 NSEC ns2.example. A RRSIG NSEC")) Dnssec.anchor_verifier.verify_nsecs(m) m.authority.delete(RR.create("ns1.example. 3600 NSEC ns2.example. A RRSIG NSEC")) m.add_authority(RR.create("ns1.example. 3600 NSEC ns2.example. A RRSIG MX NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed on type covered") rescue VerifyError end end def test_examples_from_rfc_4035_wildcard_expansion # Grab the example responses from RFC4035 and make sure that they pass. # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # and make sure that they fail verification for that reason m = Message.new m.header.rcode = m.add_question(Question.new("a.z.w.example.", Types.MX)) m.add_answer(RR.create("a.z.w.example. 3600 IN MX 1 ai.example.")) m.add_answer(RR.create("a.z.w.example. 3600 RRSIG MX 5 4 3600 20040509183619 ( 20040409183619 38519 example. OMK8rAZlepfzLWW75Dxd63jy2wswESzxDKG2 f9AMN1CytCd10cYISAxfAdvXSZ7xujKAtPbc tvOQ2ofO7AZJ+d01EeeQTVBPq4/6KCWhqe2X TjnkVLNvvhnc0u28aoSsG0+4InvkkOHknKxw 4kX18MMR34i8lC36SR5xBni8vHI= )")) m.add_authority(RR.create("x.y.w.example. 3600 NSEC xx.example. MX RRSIG NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with bad number of labels in RRSIG") rescue VerifyError end m.answer.delete(RR.create("a.z.w.example. 3600 RRSIG MX 5 4 3600 20040509183619 ( 20040409183619 38519 example. OMK8rAZlepfzLWW75Dxd63jy2wswESzxDKG2 f9AMN1CytCd10cYISAxfAdvXSZ7xujKAtPbc tvOQ2ofO7AZJ+d01EeeQTVBPq4/6KCWhqe2X TjnkVLNvvhnc0u28aoSsG0+4InvkkOHknKxw 4kX18MMR34i8lC36SR5xBni8vHI= )")) m.add_answer(RR.create("a.z.w.example. 3600 RRSIG MX 5 2 3600 20040509183619 ( 20040409183619 38519 example. OMK8rAZlepfzLWW75Dxd63jy2wswESzxDKG2 f9AMN1CytCd10cYISAxfAdvXSZ7xujKAtPbc tvOQ2ofO7AZJ+d01EeeQTVBPq4/6KCWhqe2X TjnkVLNvvhnc0u28aoSsG0+4InvkkOHknKxw 4kX18MMR34i8lC36SR5xBni8vHI= )")) Dnssec.anchor_verifier.verify_nsecs(m) m.authority.delete(RR.create("x.y.w.example. 3600 NSEC xx.example. MX RRSIG NSEC")) m.add_authority(RR.create("x.y.w.example. 3600 NSEC z.w.example. MX RRSIG NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with bad NSEC") rescue VerifyError end end def test_examples_from_rfc_4035_wildcard_no_data # Grab the example responses from RFC4035 and make sure that they pass. # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # and make sure that they fail verification for that reason m = Message.new m.header.rcode = 0 m.add_question(Question.new("a.z.w.example.", Types.AAAA)) m.add_authority(RR.create("example. 3600 IN SOA ns1.example. bugs.x.w.example. ( 1081539377 3600 300 3600000 3600 )")) m.add_authority(RR.create("x.y.w.example. 3600 NSEC xx.example. MX RRSIG NSEC")) m.add_authority(RR.create("*.w.example. 3600 NSEC x.y.example. MX RRSIG NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with bad wildcard expansion") rescue VerifyError end m.authority.delete(RR.create("*.w.example. 3600 NSEC x.y.example. MX RRSIG NSEC")) m.add_authority(RR.create("*.w.example. 3600 NSEC x.w.example. MX RRSIG NSEC")) # Test bad versions of wildcard no data Dnssec.anchor_verifier.verify_nsecs(m) m.authority.delete(RR.create("x.y.w.example. 3600 NSEC xx.example. MX RRSIG NSEC")) begin Dnssec.anchor_verifier.verify_nsecs(m) fail("Should have failed with no nsecs") rescue VerifyError end end # @TODO@ Test referrals # def test_examples_from_rfc_4035_referral_signed # # Grab the example responses from RFC4035 and make sure that they pass. # # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # # and make sure that they fail verification for that reason # m = Message.new # m.header.rcode = 3 # fail # end # # def test_examples_from_rfc_4035_referral_unsigned # # Grab the example responses from RFC4035 and make sure that they pass. # # Then, try changing some of the NSEC values (ignoring the RRSIGs for now) # # and make sure that they fail verification for that reason # m = Message.new # m.header.rcode = 3 # fail # end # enddnsruby-1.61.3/test/run-tests-individually0000755000004100000410000003006613607400362020672 0ustar www-datawww-data#!/usr/bin/env ruby # Runs each test individually in its own Ruby VM, # shows output from those that failed, and outputs separate lists # of the tests that succeeded and those that failed. # Suggest you use tee to display while running but save results to a file, e.g.: # test/run-tests-individually | tee run-tests-individually.out.txt test_files = Dir[File.join(File.dirname(__FILE__), 'tc_*.rb')] def run_file(filespec) output = `ruby #{filespec} 2>&1` return_code = $? if return_code == 0 puts "Ok: Test #{filespec} completed successfully" true else puts "Failed: Test #{filespec} failed with the following errors:\n#{output}" false end end successes, failures = test_files.partition { |filespec| run_file(filespec) } puts "Successes:\n\n"; puts successes; puts "\n\n" puts "Failures:\n\n"; puts failures =begin Sample output: Ok: Test test/tc_axfr.rb completed successfully Ok: Test test/tc_cache.rb completed successfully Failed: Test test/tc_dlv.rb failed with the following errors: Run options: --seed 19558 # Running: TestDlv | R | 0.00 s Slowest tests: 2.04 s TestDlv#test_dlv Slowest suites: 2.04 s TestDlv Finished in 2.040666s, 0.4900 runs/s, 0.4900 assertions/s. 1) Error: TestDlv#test_dlv: ArgumentError: Can't make sense of nameserver : ns2.nic.se, exception : Dnsruby::NXDomain /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:293:in `rescue in rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:256:in `rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:252:in `rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:248:in `resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/packet_sender.rb:230:in `initialize' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:569:in `new' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:569:in `add_server' test/tc_dlv.rb:42:in `test_dlv' 1 runs, 1 assertions, 0 failures, 1 errors, 0 skips Ok: Test test/tc_dns.rb completed successfully Ok: Test test/tc_dnskey.rb completed successfully Ok: Test test/tc_ds.rb completed successfully Ok: Test test/tc_escapedchars.rb completed successfully Ok: Test test/tc_gpos.rb completed successfully Ok: Test test/tc_hash.rb completed successfully Ok: Test test/tc_header.rb completed successfully Ok: Test test/tc_hip.rb completed successfully Ok: Test test/tc_hs.rb completed successfully Ok: Test test/tc_ipseckey.rb completed successfully Ok: Test test/tc_message.rb completed successfully Ok: Test test/tc_misc.rb completed successfully Ok: Test test/tc_name.rb completed successfully Ok: Test test/tc_naptr.rb completed successfully Ok: Test test/tc_nsec.rb completed successfully Ok: Test test/tc_nsec3.rb completed successfully Ok: Test test/tc_nsec3param.rb completed successfully Ok: Test test/tc_nxt.rb completed successfully Ok: Test test/tc_packet.rb completed successfully Ok: Test test/tc_packet_unique_push.rb completed successfully Ok: Test test/tc_ptrin.rb completed successfully Ok: Test test/tc_question.rb completed successfully Ok: Test test/tc_queue.rb completed successfully Ok: Test test/tc_recur.rb completed successfully Ok: Test test/tc_res_config.rb completed successfully Failed: Test test/tc_res_env.rb failed with the following errors: Run options: --seed 61787 # Running: TestResolverEnv | F | 0.00 s Slowest tests: 0.00 s TestResolverEnv#test_res_env Slowest suites: 0.00 s TestResolverEnv Finished in 0.002247s, 445.0378 runs/s, 1335.1135 assertions/s. 1) Failure: TestResolverEnv#test_res_env [test/tc_res_env.rb:38]: Nameserver set correctly. Expected: "10.128.128.128" Actual: "10.0.1.128" 1 runs, 3 assertions, 1 failures, 0 errors, 0 skips Ok: Test test/tc_res_file.rb completed successfully Ok: Test test/tc_res_opt.rb completed successfully Ok: Test test/tc_resolv.rb completed successfully Ok: Test test/tc_resolver.rb completed successfully Ok: Test test/tc_rr-opt.rb completed successfully Ok: Test test/tc_rr-txt.rb completed successfully Ok: Test test/tc_rr-unknown.rb completed successfully Ok: Test test/tc_rr.rb completed successfully Ok: Test test/tc_rrset.rb completed successfully Ok: Test test/tc_rrsig.rb completed successfully Ok: Test test/tc_single_resolver.rb completed successfully Failed: Test test/tc_soak.rb failed with the following errors: Run options: --seed 6029 # Running: TestSingleResolverSoak | RRRRRRRRtest/tc_soak.rb:283:in `create_default_single_resolver': uninitialized constant TestSingleResolverSoak::SingleResolver (NameError) Did you mean? SingleForwardable from test/tc_soak.rb:243:in `block (2 levels) in test_many_threads_on_many_single_resolvers' Ok: Test test/tc_soak_base.rb completed successfully Ok: Test test/tc_sshfp.rb completed successfully Ok: Test test/tc_tcp.rb completed successfully Ok: Test test/tc_tcp_pipelining.rb completed successfully Ok: Test test/tc_tkey.rb completed successfully Failed: Test test/tc_tsig.rb failed with the following errors: Run options: --seed 20864 # Running: TestTSig | R..R | 11.87 s Slowest tests: 11.87 s TestTSig#test_signed_update 10.74 s TestTSig#test_signed_zone_transfer 0.00 s TestTSig#test_bad_tsig 0.00 s TestTSig#test_message_signing Slowest suites: 22.62 s TestTSig Finished in 22.616872s, 0.1769 runs/s, 0.3095 assertions/s. 1) Error: TestTSig#test_signed_zone_transfer: ArgumentError: Can't make sense of nameserver : ns0.validation-test-servers.nominet.org.uk, exception : undefined method `answer' for nil:NilClass /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:293:in `rescue in rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:256:in `rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:252:in `rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:248:in `resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/zone_transfer.rb:94:in `block in transfer' /Users/kbennett/work/dnsruby/lib/dnsruby/zone_transfer.rb:92:in `each' /Users/kbennett/work/dnsruby/lib/dnsruby/zone_transfer.rb:92:in `transfer' test/tc_tsig.rb:189:in `axfr' test/tc_tsig.rb:180:in `test_signed_zone_transfer' 2) Error: TestTSig#test_signed_update: ArgumentError: Can't make sense of nameserver : , exception : Nameserver invalid! /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:293:in `rescue in rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:256:in `rescue in rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:252:in `rescue in resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/config.rb:248:in `resolve_server' /Users/kbennett/work/dnsruby/lib/dnsruby/packet_sender.rb:230:in `initialize' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:488:in `new' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:488:in `block (2 levels) in add_config_nameservers' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:487:in `each' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:487:in `block in add_config_nameservers' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:485:in `synchronize' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:485:in `add_config_nameservers' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:514:in `set_config_nameserver' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:469:in `initialize' test/tc_tsig.rb:69:in `new' test/tc_tsig.rb:69:in `run_test_client_signs' test/tc_tsig.rb:31:in `test_signed_update' 4 runs, 7 assertions, 0 failures, 2 errors, 0 skips Ok: Test test/tc_update.rb completed successfully Failed: Test test/tc_validator.rb failed with the following errors: Run options: --seed 12765 # Running: Test EventType API! TestValidator | .RRTest validation configuration options! . | 10.05 s Slowest tests: 5.05 s TestValidator#test_validation 5.01 s TestValidator#test_resolver_cd_validation_fails 0.00 s TestValidator#test_eventtype_api 0.00 s TestValidator#test_config_api Slowest suites: 10.05 s TestValidator Finished in 10.054978s, 0.3978 runs/s, 0.0000 assertions/s. 1) Error: TestValidator#test_resolver_cd_validation_fails: Dnsruby::ResolvTimeout: Query timed out /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:257:in `send_message' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:203:in `query' test/tc_validator.rb:52:in `test_resolver_cd_validation_fails' 2) Error: TestValidator#test_validation: Dnsruby::ResolvTimeout: Query timed out /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:257:in `send_message' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:203:in `query' test/tc_validator.rb:40:in `test_validation' 4 runs, 0 assertions, 0 failures, 2 errors, 0 skips Failed: Test test/tc_verifier.rb failed with the following errors: Run options: --seed 23316 # Running: VerifierTest | .R.R....RF. | 14.37 s Slowest tests: 5.40 s VerifierTest#test_tcp 5.01 s VerifierTest#test_trusted_key 2.01 s VerifierTest#test_expired_keys 1.54 s VerifierTest#test_verify_message_fails 1.21 s VerifierTest#test_dsa 0.41 s VerifierTest#test_sendraw Slowest suites: 15.91 s VerifierTest Finished in 15.914313s, 0.6912 runs/s, 0.3770 assertions/s. 1) Error: VerifierTest#test_tcp: Dnsruby::ResolvTimeout: Query timed out /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:257:in `send_message' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:203:in `query' test/tc_verifier.rb:183:in `test_tcp' 2) Error: VerifierTest#test_trusted_key: Dnsruby::ResolvTimeout: Query timed out /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:257:in `send_message' /Users/kbennett/work/dnsruby/lib/dnsruby/resolver.rb:203:in `query' test/tc_verifier.rb:125:in `test_trusted_key' 3) Error: VerifierTest#test_verify_message: Dnsruby::VerifyError: Failed to verify DNSKEY RRSet /Users/kbennett/work/dnsruby/lib/dnsruby/single_verifier.rb:277:in `block (2 levels) in verify' /Users/kbennett/work/dnsruby/lib/dnsruby/single_verifier.rb:275:in `each' /Users/kbennett/work/dnsruby/lib/dnsruby/single_verifier.rb:275:in `block in verify' /Users/kbennett/work/dnsruby/lib/dnsruby/message/message.rb:345:in `block in each_section' /Users/kbennett/work/dnsruby/lib/dnsruby/message/message.rb:345:in `each' /Users/kbennett/work/dnsruby/lib/dnsruby/message/message.rb:345:in `each_section' /Users/kbennett/work/dnsruby/lib/dnsruby/single_verifier.rb:261:in `verify' /Users/kbennett/work/dnsruby/lib/dnsruby/dnssec.rb:293:in `rescue in rescue in verify' /Users/kbennett/work/dnsruby/lib/dnsruby/dnssec.rb:290:in `rescue in verify' /Users/kbennett/work/dnsruby/lib/dnsruby/dnssec.rb:287:in `verify' test/tc_verifier.rb:98:in `test_verify_message' 4) Failure: VerifierTest#test_dsa [test/tc_verifier.rb:229]: Expected nil to be truthy. 11 runs, 6 assertions, 1 failures, 3 errors, 0 skips Ok: Test test/tc_zone_reader.rb completed successfully Successes: test/tc_axfr.rb test/tc_cache.rb test/tc_dns.rb test/tc_dnskey.rb test/tc_ds.rb test/tc_escapedchars.rb test/tc_gpos.rb test/tc_hash.rb test/tc_header.rb test/tc_hip.rb test/tc_hs.rb test/tc_ipseckey.rb test/tc_message.rb test/tc_misc.rb test/tc_name.rb test/tc_naptr.rb test/tc_nsec.rb test/tc_nsec3.rb test/tc_nsec3param.rb test/tc_nxt.rb test/tc_packet.rb test/tc_packet_unique_push.rb test/tc_ptrin.rb test/tc_question.rb test/tc_queue.rb test/tc_recur.rb test/tc_res_config.rb test/tc_res_file.rb test/tc_res_opt.rb test/tc_resolv.rb test/tc_resolver.rb test/tc_rr-opt.rb test/tc_rr-txt.rb test/tc_rr-unknown.rb test/tc_rr.rb test/tc_rrset.rb test/tc_rrsig.rb test/tc_single_resolver.rb test/tc_soak_base.rb test/tc_sshfp.rb test/tc_tcp.rb test/tc_tcp_pipelining.rb test/tc_tkey.rb test/tc_update.rb test/tc_zone_reader.rb Failures: test/tc_dlv.rb test/tc_res_env.rb test/tc_soak.rb test/tc_tsig.rb test/tc_validator.rb test/tc_verifier.rb =end dnsruby-1.61.3/test/ts_offline.rb0000644000004100000410000000324513607400362016761 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' Dnsruby.log.level = Logger::FATAL # We'll prepend 'tc_' and append '.rb' to these: TESTS = %w( dnskey escapedchars gpos hash header ipseckey message misc name naptr nsec nsec3 nsec3param nxt tlsa packet packet_unique_push ptrin question res_config res_file res_opt rr rr-txt rr-unknown rrset rrsig tkey update zone_reader ) # Omitted: # # tc_res_env TESTS.each { |test| require_relative "tc_#{test}.rb" } def have_open_ssl? have_open_ssl = true begin require "openssl" OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, "key", "data") key = OpenSSL::PKey::RSA.new key.e = 111 rescue have_open_ssl = false end have_open_ssl end if have_open_ssl? require_relative 'tc_ds.rb' else puts "-----------------------------------------------------------------------" puts "OpenSSL not present (with full functionality) - skipping DS digest test" puts "-----------------------------------------------------------------------" end dnsruby-1.61.3/test/tc_queue.rb0000644000004100000410000000205313607400362016437 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestQueue < Minitest::Test def test_queue q = Queue.new r = Dnsruby::Resolver.new # Dnsruby::TheLog.level=Logger::DEBUG timeout = 15 num_queries = 100 r.query_timeout = timeout num_queries.times do |i| r.send_async(Dnsruby::Message.new("example.com"), q, i) # print "Sent #{i}\n" end sleep(timeout * 2) assert(q.size == num_queries, "#{num_queries} expected, but got #{q.size}") end end dnsruby-1.61.3/test/tc_single_resolver.rb0000644000004100000410000002356113607400362020524 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestSingleResolver < Minitest::Test include Dnsruby Thread::abort_on_exception = true # Dnsruby.log.level=Logger::DEBUG def setup Dnsruby::Config.reset end Rrs = [ { :type => Types.A, :name => 'a.t.net-dns.org', :address => '10.0.1.128' }, { :type => Types::MX, :name => 'mx.t.net-dns.org', :exchange => 'a.t.net-dns.org', :preference => 10 }, { :type => 'CNAME', :name => 'cname.t.net-dns.org', :domainname => 'a.t.net-dns.org' }, { :type => Types.TXT, :name => 'txt.t.net-dns.org', :strings => ['Net-DNS'] } ] def test_simple res = SingleResolver.new() m = res.query("ns1.google.com.") end def test_timeout # if ((RUBY_PLATFORM=~/darwin/) == nil) # Run a query which will not respond, and check that the timeout works start_time = 0 begin udps = UDPSocket.new udps.bind("127.0.0.1", 0) port = *udps.addr.values_at(3, 1) begin Dnsruby::PacketSender.clear_caches res = SingleResolver.new("127.0.0.1") res.port = port res.packet_timeout=1 start_time = Time.now.to_i m = res.query("a.t.net-dns.org") fail "Got response when should have got none" rescue ResolvTimeout stop_time = Time.now.to_i assert((stop_time - start_time) <= (res.packet_timeout * 2), "UDP timeout too long : #{stop_time - start_time}" + ", should be #{res.packet_timeout}") end begin Dnsruby::PacketSender.clear_caches res = SingleResolver.new("127.0.0.1") res.port = port res.use_tcp = true res.packet_timeout=1 start_time = Time.now.to_i # TheLog.level = Logger::DEBUG m = res.query("a.t.net-dns.org") fail "TCP timeouts" rescue ResolvTimeout # print "Got Timeout for TCP\n" stop_time = Time.now.to_i assert((stop_time - start_time) <= (res.packet_timeout * 2), "TCP timeout too long : #{stop_time - start_time}, should be #{res.packet_timeout}") rescue Exception => e fail(e) end TheLog.level = Logger::ERROR rescue udps.close end end def test_queue_timeout port = 46129 # if (!RUBY_PLATFORM=~/darwin/) begin udps = UDPSocket.new udps.bind("127.0.0.1", 0) port = *udps.addr.values_at(3, 1) res = SingleResolver.new("127.0.0.1") res.dnssec = true res.port = port res.packet_timeout=1 q = Queue.new msg = Message.new("a.t.net-dns.org") res.send_async(msg, q, msg) id, ret, error = q.pop assert(id==msg) assert(ret==nil) assert(error.class == ResolvTimeout) rescue udps.close end # end end def test_queries res = SingleResolver.new Rrs.each do |data| packet=nil 2.times do begin packet = res.query(data[:name], data[:type]) rescue ResolvTimeout end break if packet end assert(packet) assert_equal(packet.question[0].qclass, 'IN', 'Class correct') assert(packet, "Got an answer for #{data[:name]} IN #{data[:type]}") assert_equal(1, packet.header.qdcount, 'Only one question') # assert_equal(1, answer.length, "Got single answer (for question #{data[:name]}") question = (packet.question)[0] answer = (packet.answer)[0] assert(question, 'Got question') assert_equal(data[:name], question.qname.to_s, 'Question has right name') assert_equal(Types.new(data[:type]), question.qtype, 'Question has right type') assert_equal('IN', question.qclass.string, 'Question has right class') assert(answer) assert_equal(answer.klass, 'IN', 'Class correct') data.keys.each do |meth| if (meth == :type) assert_equal(Types.new(data[meth]).to_s, answer.send(meth).to_s, "#{meth} correct (#{data[:name]})") else assert_equal(data[meth].to_s, answer.send(meth).to_s, "#{meth} correct (#{data[:name]})") end end end # do end # test_queries # @TODO@ Although the test_thread_stopped test runs in isolation, it won't run as part # of the whole test suite (ts_dnsruby.rb). Commented out until I can figure out how to # get Test::Unit to run this one sequentially... # def test_thread_stopped # res=SingleResolver.new # # Send a query, and check select_thread running. # m = res.query("example.com") # assert(Dnsruby::SelectThread.instance.select_thread_alive?) # # Wait a second, and check select_thread stopped. # sleep(2) # assert(!Dnsruby::SelectThread.instance.select_thread_alive?) # # Send another query, and check select_thread running. # m = res.query("example.com") # assert(Dnsruby::SelectThread.instance.select_thread_alive?) # end def test_res_config res = Dnsruby::SingleResolver.new res.server=('a.t.net-dns.org') ip = res.server assert_equal('10.0.1.128', ip.to_s, 'nameserver() looks up IP.') res.server=('cname.t.net-dns.org') ip = res.server assert_equal('10.0.1.128', ip.to_s, 'nameserver() looks up cname.') end # def test_truncated_response # res = SingleResolver.new # # print "Dnssec = #{res.dnssec}\n" # # res.server=('ns0.validation-test-servers.nominet.org.uk') # res.server=('ns.nlnetlabs.nl') # res.packet_timeout = 15 # begin # m = res.query("overflow.net-dns.org", 'txt') # assert(m.header.ancount == 62, "62 answer records expected, got #{m.header.ancount}") # assert(!m.header.tc, "Message was truncated!") # rescue ResolvTimeout => e # rescue ServFail => e # not sure why, but we get this on Travis... # end # end def test_illegal_src_port # Try to set src_port to an illegal value - make sure error raised, and port OK res = SingleResolver.new tests = [53, 387, 1265, 3210, 48619] tests.each do |bad_port| begin res.src_port = bad_port fail("bad port #{bad_port}") rescue end end end def test_add_src_port # Try setting and adding port ranges, and invalid ports, and 0. res = SingleResolver.new res.src_port = [56789, 56790, 56793] assert(res.src_port == [56789, 56790, 56793]) res.src_port = 56889..56891 assert(res.src_port == [56889, 56890, 56891]) res.add_src_port(60000..60002) assert(res.src_port == [56889, 56890, 56891, 60000, 60001, 60002]) res.add_src_port([60004, 60005]) assert(res.src_port == [56889, 56890, 56891, 60000, 60001, 60002, 60004, 60005]) res.add_src_port(60006) assert(res.src_port == [56889, 56890, 56891, 60000, 60001, 60002, 60004, 60005, 60006]) # Now test invalid src_ports tests = [0, 53, [60007, 53], [60008, 0], 55..100] tests.each do |x| begin res.add_src_port(x) fail() rescue end end assert(res.src_port == [56889, 56890, 56891, 60000, 60001, 60002, 60004, 60005, 60006]) end # TODO THIS TEST DOES NOT WORK ON TRAVIS # It works fine outside of Travis, so feel free to uncomment it and run it locally # Just don't check it in, as Travis will bork - not sure why, something to do with setting up localhost servers # def test_options_preserved_on_tcp_resend # # Send a very small EDNS message to trigger tcp resend. # # Can we do that without using send_raw and avoiding the case we want to test? # # Sure - just knock up a little server here, which simply returns the response with the # # TC bit set, and records both packets sent to it # # Need to listen once on UDP and once on TCP # udpPacket = nil # tcpPacket = nil # port = 59821 # thread = Thread.new { # u = UDPSocket.new() # u.bind("localhost", port) # # s = u.recvfrom(15000) # received_query = s[0] # udpPacket = Message.decode(received_query) # u.connect(s[1][2], s[1][1]) # udpPacket.header.tc = true # u.send(udpPacket.encode(), 0) # u.close # # ts = TCPServer.new(port) # t = ts.accept # packet = t.recvfrom(2)[0] # # len = (packet[0]<<8)+packet[1] # if (RUBY_VERSION >= "1.9") # len = (packet[0].getbyte(0)<<8)+packet[1].getbyte(0) # Ruby 1.9 # end # packet = t.recvfrom(len)[0] # tcpPacket = Message.decode(packet) # tcpPacket.header.tc = true # lenmsg = [tcpPacket.encode.length].pack('n') # t.send(lenmsg, 0) # t.write(tcpPacket.encode) # t.close # ts.close # } # ret = nil # done = true; # thread2 = Thread.new { # r = SingleResolver.new("localhost") # r.port = port # begin # ret = r.query("example.com") # rescue OtherResolvError => e # done = false # end # } # thread.join # thread2.join # if (done) # assert(tcpPacket && udpPacket) # assert(tcpPacket.header == udpPacket.header) # assert(tcpPacket.additional.rrsets('OPT', true)[0].rrs()[0].ttl == udpPacket.additional.rrsets('OPT', true)[0].rrs()[0].ttl, "UDP : #{udpPacket.additional.rrsets('OPT', true)[0].rrs()[0]}, TCP #{tcpPacket.additional.rrsets('OPT', true)[0].rrs()[0]}") # end # end end dnsruby-1.61.3/test/tc_hs.rb0000644000004100000410000000134613607400362015731 0ustar www-datawww-datarequire_relative 'spec_helper' class TestDNS < Minitest::Test def setup Dnsruby::Config.reset end # Illustrates that when a message whose class is 'HS' is sent to # a DNS server that does not support the HS class, using send_plain_message, # the response returns with an rcode of NOTIMP and a Dnsruby::NotImp error. def test_hs_class_returns_notimp_code_and_error resolver_host = 'a.gtld-servers.net' resolver = Dnsruby::Resolver.new(resolver_host) resolver.query_timeout = 20 message = Dnsruby::Message.new('test.com', 'A', 'HS') response, error = resolver.send_plain_message(message) assert_equal(Dnsruby::RCode::NOTIMP, response.rcode) assert_equal(Dnsruby::NotImp, error.class) end end dnsruby-1.61.3/test/ts_online.rb0000644000004100000410000001005513607400362016620 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' Dnsruby.log.level = Logger::FATAL require 'socket' # Tells whether or not we can connect to the Internet. def online? sock = UDPSocket.new() online = false begin sock.connect('193.0.14.129', 25) # that address is k.root-servers.net online = true sock.close rescue Exception => exception puts " ------------------------------------------------------------ Cannot bind to socket: #{exception} This is an indication you have network problems. No online tests will be run!! ------------------------------------------------------------ " end online end if online? online_tests = %w( axfr hs recur resolv resolver tcp tcp_pipelining single_resolver cache dns rr-opt res_config ) # Excluded are: # # inet6 # recurse # queue # soak # OK - online and ready to go puts ' Running online tests. These tests send UDP packets - some may be lost. If you get the odd timeout error with these tests, try running them again. It may just be that some UDP packets got lost the first time... ' online_tests.each { |test| require_relative("tc_#{test}.rb") } end # We have set server_up to unconditionally return false. # Therefore, to avoid any misconception that this code could run, # I'm commenting it out. =begin def server_up? false # Check if we can contact the server - if we can't, then abort the test # (but tell user that test has not been run due to connectivity problems) # Disabling the attempt to connect to Nominet servers... # begin # sock = UDPSocket.new # sock.connect('ns0.validation-test-servers.nominet.org.uk', # 25) # sock.close # server_up = true # rescue Exception # puts "----------------------------------------" # puts "Cannot connect to test server\n\t"+$!.to_s+"\n" # puts "\n\nNo tests targetting this server will be run!!\n\n" # puts "----------------------------------------" # end end if (server_up) require_relative "tc_single_resolver.rb" require_relative "tc_cache.rb" require_relative "tc_dns.rb" require_relative "tc_rr-opt.rb" require_relative "tc_res_config.rb" have_openssl = false begin require "openssl" OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, "key", "data") key = OpenSSL::PKey::RSA.new key.e = 111 have_openssl=true rescue Exception => e puts "-------------------------------------------------------------------------" puts "OpenSSL not present (with full functionality) - skipping TSIG/DNSSEC test" puts "-------------------------------------------------------------------------" end if (have_openssl) require_relative "tc_tsig.rb" puts "------------------------------------------------------" puts "Running DNSSEC test - may fail if OpenSSL not complete" puts "------------------------------------------------------" require_relative "tc_verifier.rb" require_relative "tc_dlv.rb" require_relative "tc_validator.rb" end =end # have_em = false # begin # require 'eventmachine' # have_em = true # rescue LoadError => e # puts "----------------------------------------" # puts "EventMachine not installed - skipping test" # puts "----------------------------------------" # end # if (have_em) # require 'test/tc_event_machine_single_res.rb' # require 'test/tc_event_machine_res.rb' # require 'test/tc_event_machine_deferrable.rb' # end dnsruby-1.61.3/test/tc_header.rb0000644000004100000410000000477013607400362016553 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestHeader < Minitest::Test include Dnsruby def test_header header = Header.new(); assert(header, "new() returned something") header.id=41 assert_equal(header.id, 41, "id() works") header.qr=true assert_equal(header.qr, true, "qr() works") header.opcode="QUERY" assert_equal(OpCode.Query, header.opcode, "opcode() works") header.opcode=OpCode::Query assert_equal(header.opcode.string, "Query", "opcode() works") header.aa=true assert_equal(header.aa, true, "aa() works") header.tc=false assert_equal(header.tc, false, "tc() works") header.rd=true assert_equal(header.rd, true, "rd() works") header.ad=true assert_equal(header.ad, true, "rd() works") header.cd=true assert_equal(header.cd, true, "rd() works") header.ra=true assert_equal(header.ra, true, "ra() works") header.qr=true assert_equal(header.qr, true, "qr() works") header.rcode="NOERROR" assert_equal(header.get_header_rcode, RCode::NOERROR, "rcode() works") header.rcode=RCode.NOERROR assert_equal(header.get_header_rcode.string, "NOERROR", "rcode() works") header.qdcount=1 header.ancount=2 header.nscount=3 header.arcount=3 # Reenable when support for CD is there # header.cd=0 # assert_equal(header.cd, 0, "cd() works") data = header.data header2 = Header.new_from_data(data); assert(header==(header2), 'Headers are the same'); header = Header.new; # # Check that the aliases work properly. # header.zocount=(0); header.prcount=(1); header.upcount=(2); header.adcount=(3); assert_equal(header.zocount, 0, 'zocount works'); assert_equal(header.prcount, 1, 'prcount works'); assert_equal(header.upcount, 2, 'upcount works'); assert_equal(header.adcount, 3, 'adcount works'); end end dnsruby-1.61.3/test/tc_rr-unknown.rb0000644000004100000410000000752213607400362017441 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestRrUnknown < Minitest::Test include Dnsruby def test_RrUnknown assert_equal(10226, Types::typesbyname('TYPE10226'), 'typesbyname(TYPE10226) returns 10226') assert_equal('TYPE10226', Types::typesbyval(10226), 'typesbyval(10226) returns TYPE10226') assert_equal(Types::typesbyval(1), "A", ' typesbyval(1) returns A') assert_equal(Types::typesbyval(Types.typesbyname('TYPE001')), 'A', 'typesbyval(typebyname(TYPE001)) returns A') begin Types.typesbyval(0xffff+1) flunk("Should fail on large TYPE code") rescue Exception end assert_equal(Classes::classesbyname('CLASS124'), 124, 'classesbyname(CLASS124) returns 124') assert_equal(Classes::classesbyval(125), 'CLASS125','classesbyval(125) returns CLASS125') assert_equal(Classes::classesbyval(1), 'IN', 'classesbyval(1) returns IN') assert_equal('HS', Classes::classesbyval(Classes::classesbyname('CLASS04')), 'classesbyval(typebyname(CLASS04)) returns HS') begin Classes::classesbyval(0xffff+1) flunk("Should fail on large CLASS code") rescue Exception end end def test_rr_new rr = RR.new_from_string('e.example CLASS01 TYPE01 10.0.0.2') assert_equal(RR::IN::A, rr.class, 'TYPE01 parsed OK') assert_equal('A', rr.type.string, 'TYPE01 parsed OK') assert_equal('IN', rr.klass.string,'CLASS01 parsed OK') assert_equal(1, rr.klass.code,'CLASS01 parsed OK') rr = RR.new_from_string('e.example IN A \# 4 0A0000 01 ') assert_equal('10.0.0.1', rr.address.to_s,'Unknown RR representation for A parsed OK') begin res=RR.new_from_string('e.example IN A \# 4 0A0000 01 11 ') flunk "Should fail on inconsistent length and hex presentation" rescue Exception # like($@, '/\\\# 4 0A0000 01 11 assert_equal inconsistent\ length does not match content/', 'Fails on inconsassert_equaltent length and hex presentation') end rr = RR.new_from_string('e.example IN TYPE4555 \# 4 0A0000 01 ') assert_equal('e.example 0 IN TYPE4555 \# 4 0a000001', rr.to_s, 'Fully unknown RR parsed correctly') rr4 = RR.new_from_string('e.example. CLASS122 TYPE4555 \# 4 0A0000 01 ') assert_equal('e.example. 0 CLASS122 TYPE4555 \# 4 0a000001', rr4.to_s, 'Fully unknown RR in unknown CLASS parsed correctly') end def test_real_data uuencodedPacket=%w{ 02 79 85 00 00 01 00 01 00 01 00 01 04 54 45 53 54 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 ff 00 01 c0 0c 30 39 00 01 00 00 00 7b 00 0a 11 22 33 44 55 aa bb cc dd ee c0 11 00 02 00 01 00 00 03 84 00 05 02 6e 73 c0 11 c0 44 00 01 00 01 00 00 03 84 00 04 7f 00 00 01} # packetdata = uuencodedPacket.pack('H*') # packetdata = packetdata.gsub("\s*", "") uuencodedPacket.map!{|e| e.hex} packetdata = uuencodedPacket.pack('c*') # packet = Net::Packet.new_from_binary(packetdata) packet = Message.decode(packetdata) string_representation = (packet.answer)[0].to_s # string_representation =~ s/\s+/ /g, string_representation = string_representation.gsub(/\s+/, " ") assert_equal( 'TEST.example.com. 123 IN TYPE12345 \# 10 1122334455aabbccddee', string_representation, 'Packet read from a packet dumped by bind...' ) end end dnsruby-1.61.3/test/localdns.rb0000755000004100000410000000120113607400362016421 0ustar www-datawww-data#!/usr/bin/env ruby require_relative 'spec_helper' require_relative "test_dnsserver" class SimpleTCPPipeliningUDPServer < Async::DNS::Server PORT = 53938 IP = '127.0.0.1' def initialize(**options) super(options) @handlers << TcpPipelineHandler.new(self, IP, PORT) @handlers << Async::DNS::UDPServerHandler.new(self, IP, PORT) end def process(name, resource_class, transaction) @logger.debug "name: #{name}" transaction.respond!("93.184.216.34", { resource_class: ::Resolv::DNS::Resource::IN::A }) end end if __FILE__ == $0 RubyDNS::run_server(server_class: SimpleTCPPipeliningUDPServer) end dnsruby-1.61.3/test/tc_rrset.rb0000644000004100000410000001072613607400362016460 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class RrsetTest < Minitest::Test def test_rrset rrset = Dnsruby::RRSet.new rr=Dnsruby::RR.create({ :name => "example.com", :ttl => 3600, :type => 'MX', :preference => 10, :exchange => 'mx-exchange.example.com', }) rrset.add(rr) rr.preference = 12 rrset.add(rr) rr.preference = 1 rrset.add(rr) canon = rrset.sort_canonical assert(1 == canon[0].preference) assert(10 == canon[1].preference) assert(12 == canon[2].preference) assert(rrset.sigs.length == 0) assert(rrset.num_sigs == 0) assert(rrset.rrs.length == 3) # Check RRSIG records (only of the right type) can be added to the RRSet sig = Dnsruby::RR.create({:name=>"example.com", :ttl => 3600, :type => 'RRSIG', :type_covered => 'A', :original_ttl => 3600, :algorithm => Dnsruby::Algorithms::RSASHA1, :labels => 3, :expiration => Time.mktime(2003,03,22,17,31, 03).to_i, :inception => Time.mktime(2003,02,20,17,31,03).to_i, :key_tag => 2642 }) assert(!rrset.add(sig)) assert(rrset.sigs.length == 0) assert(rrset.num_sigs == 0) assert(rrset.rrs.length == 3) sig.type_covered = Dnsruby::Types.MX assert(rrset.add(sig)) assert(rrset.sigs.length == 1) assert(rrset.num_sigs == 1) assert(rrset.rrs.length == 3) sig.name="example.co.uk" assert(!rrset.add(sig)) assert(rrset.sigs.length == 1) assert(rrset.num_sigs == 1) assert(rrset.rrs.length == 3) end def test_real_rrset uuencodedPacket = %w{ 7c 7d 81 80 00 01 00 02 00 0b 00 0d 03 6e 73 31 03 6e 69 63 02 75 6b 00 00 ff 00 01 c0 0c 00 01 00 01 00 02 a2 cc 00 04 c3 42 f0 82 c0 0c 00 1c 00 01 00 02 88 93 00 10 2a 01 00 40 10 01 00 35 00 00 00 00 00 00 00 02 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 33 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 35 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 02 c0 0c c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 32 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 62 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 64 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 34 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 36 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 61 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 37 c0 10 c0 10 00 02 00 01 00 02 a2 cc 00 06 03 6e 73 63 c0 10 c0 86 00 01 00 01 00 02 96 62 00 04 d9 4f a4 83 c0 54 00 01 00 01 00 02 96 8e 00 04 d5 db 0d 83 c0 bc 00 01 00 01 00 02 97 08 00 04 c2 53 f4 83 c0 bc 00 1c 00 01 00 02 96 62 00 10 20 01 06 30 01 81 00 35 00 00 00 00 00 00 00 83 c0 66 00 01 00 01 00 02 96 85 00 04 d5 f6 a7 83 c0 ce 00 01 00 01 00 02 96 85 00 04 d5 f8 fe 82 c0 f2 00 01 00 01 00 02 96 85 00 04 d4 79 28 82 c0 e0 00 01 00 01 00 02 97 08 00 04 cc 4a 70 2c c0 e0 00 1c 00 01 00 02 96 62 00 10 20 01 05 02 d3 99 00 00 00 00 00 00 00 00 00 44 c0 98 00 01 00 01 00 02 96 8e 00 04 cc 4a 71 2c c1 04 00 01 00 01 00 02 96 9b 00 04 c7 07 42 2c c0 aa 00 01 00 01 00 02 96 71 00 04 c7 07 43 2c c0 aa 00 1c 00 01 00 02 96 62 00 10 20 01 05 02 10 0e 00 00 00 00 00 00 00 00 00 44 } uuencodedPacket.map!{|e| e.hex} packetdata = uuencodedPacket.pack('c*') message = Dnsruby::Message.decode(packetdata) # message.additional.rrsets.each {|rr| print "RRSet : #{rr}\n"} sec_hash = message.section_rrsets(nil, true) # include the OPT record sec_hash.each {|section, rrsets| rrsets.each {|rrset| # print "#{section} rrset : #{rrset}\n" rrset.each { |rr| } } } sec_hash = message.section_rrsets(nil, true) # include the OPT record sec_hash.each {|section, rrsets| rrsets.each {|rrset| # print "#{section} rrset : #{rrset}\n" rrset.each { |rr| } } } end enddnsruby-1.61.3/test/tc_tlsa.rb0000644000004100000410000002532413607400362016264 0ustar www-datawww-datarequire_relative 'spec_helper' require 'openssl' require 'digest' class TLSATest < Minitest::Test include Dnsruby INPUT = ['_443._tcp.example.jp. IN TLSA 3 0 1 ( 6609173804b9e31895f550db027ef7c7fa6f1bc9326c99371b61f1ba5 '\ 'cb3595d )', '_443._tcp.example.jp. IN TLSA 255 255 255 ( 6609173804b9e31895f550db027ef7c7fa6f1bc9326c99371b61f1ba5 '\ 'cb3595d )', '_443._tcp.data.iana.org. IN TLSA 3 0 0 ( 308206833082056ba003020102021009cabbe2191c8f569dd4b6dd250 '\ 'f21d8300d06092a864886f70d01010b05003070310b30090603550406 '\ '1302555331153013060355040a130c446967694365727420496e63311 '\ '93017060355040b13107777772e64696769636572742e636f6d312f30 '\ '2d0603550403132644696769436572742053484132204869676820417 '\ '3737572616e636520536572766572204341301e170d31343130323730 '\ '30303030305a170d3138303130333132303030305a3081a3310b30090 '\ '60355040613025553311330110603550408130a43616c69666f726e69 '\ '61311430120603550407130b4c6f7320416e67656c6573313c303a060 '\ '355040a1333496e7465726e657420436f72706f726174696f6e20666f '\ '722041737369676e6564204e616d657320616e64204e756d626572733 '\ '1163014060355040b130d4954204f7065726174696f6e733113301106 '\ '035504030c0a2a2e69616e612e6f726730820222300d06092a864886f '\ '70d01010105000382020f003082020a02820201009dbdfddeb5cae53a '\ '559747e2fda63728e4aba60f18b79a69f03310bf0164e5ee7db6b15bf '\ '56df23fddbae6a1bb38449b8c883f18102bbd8bb655ac0e2dac2ee3ed '\ '5cf4315868d2c598068284854b24894dcd4bd37811f0ad3a282cd4b4e '\ '599ffd07d8d2d3f2478554f81020b320ee12f44948e2ea1edbc990b83 '\ '0ca5cca6b4a839fb27b51850c9847eac74f26609eb24365b9751fb1c3 '\ '208f56913bacbcae49201347c78b7e54a9d99979404c37f00fb65db84 '\ '9fd75e3a68770c30f2abe65b33256fb59b450050b00d8139d4d80d36f '\ '7bc46daf303e48f0f0791b2fdd72ec60b2cb3ad533c3f288c9c194e49 '\ '337a69c496731f086d4f1f9825900713e2a551d05cb6057567850d91e '\ '6001c4ce27176f0957873a95b880acbec19e7bd9bcf1286d0452b7378 '\ '9c41905dd470971cd73aea52c77b080cd779af58234f337225c26f87a '\ '8c13e2a65e9dd4e03a5b41d7e06b3353f38129b2327a531ec9627a21d '\ 'c423733aa029d4989448ba3322891c1a5690ddf2d25c8ec8aaa894b14 '\ 'aa92130c6b6d969a21ff671b60c4c923a94a93ea1dd0492c93393ca6e '\ 'dd61f33ca77e9208d01d6bd15107662ec088733df4c876a7e1608b829 '\ '73a0f7592e84ed15579d181e79024ae8a7e4b9f0078eb2005b23f9d09 '\ 'a1df1bbc7de2a5a6085a3646d9fadb0e9da273a5f403cdd42831ce6f0 '\ 'ca46889585602bb8bc36bb3be861ff6d1a62e350203010001a38201e3 '\ '308201df301f0603551d230418301680145168ff90af0207753cccd96 '\ '56462a212b859723b301d0603551d0e04160414c7d0acef898b20e4b9 '\ '14668933032394f6bf3a61301f0603551d1104183016820a2a2e69616 '\ 'e612e6f7267820869616e612e6f7267300e0603551d0f0101ff040403 '\ '0205a0301d0603551d250416301406082b0601050507030106082b060 '\ '1050507030230750603551d1f046e306c3034a032a030862e68747470 '\ '3a2f2f63726c332e64696769636572742e636f6d2f736861322d68612 '\ 'd7365727665722d67332e63726c3034a032a030862e687474703a2f2f '\ '63726c342e64696769636572742e636f6d2f736861322d68612d73657 '\ '27665722d67332e63726c30420603551d20043b303930370609608648 '\ '0186fd6c0101302a302806082b06010505070201161c68747470733a2 '\ 'f2f7777772e64696769636572742e636f6d2f43505330818306082b06 '\ '01050507010104773075302406082b060105050730018618687474703 '\ 'a2f2f6f6373702e64696769636572742e636f6d304d06082b06010505 '\ '0730028641687474703a2f2f636163657274732e64696769636572742 '\ 'e636f6d2f446967694365727453484132486967684173737572616e63 '\ '6553657276657243412e637274300c0603551d130101ff04023000300 '\ 'd06092a864886f70d01010b0500038201010070314c38e7c02fd80810 '\ '500b9df6dae85de9b23e29fbd68bfdb5f23411c89acfaf9ae05af9123 '\ 'a8aa6bce6954a4e68dc7cfc480a65d76f229c4bd5f5674b0c9ac6d06a '\ '37a1a1c145c3956120b8efe67c887ab4ff7d6aa950ff3698f27c4a19d '\ '59d93a39aca5a7b6d6c75e34974e50f5a590005b3cb665ddbd7074f9f '\ 'cbcbf9c50228d5e25596b64ada160b48f77a93aaced22617bfe005e00 '\ 'fe20a532a0adcb818c878dc5d6649277777ca1a814e21d0b53308af40 '\ '78be4554715e4ce4828b012f25ffa13a6ceb30d20a75deba8a344e41d '\ '627fa638feff38a3063a0187519b39b053f7134d9cd83e6091accf5d2 '\ 'e3a05edfa1dfbe181a87ad86ba24fe6b97fe )', '_443._tcp.data.iana.org. IN TLSA 3 0 1 ( 2760bc55bbb8cf398e4c90da21018b2eaafc9e375f7428cf0708e7c88 '\ '8261b49', '_443._tcp.data.iana.org. IN TLSA 3 0 2 ( e6f38e78b1c9f8e0969e81c555e2770eeccb3f120986558adfb2c48aa '\ 'dc6f85d3596f0cc7362a6a6cda7b6dea222a968fef5aeeaf6d334c8b9 '\ '725543f27683db )', '_443._tcp.data.iana.org. IN TLSA 3 1 0 (30820222300d06092a864886f70d01010105000382020f003082020a0 '\ '2820201009dbdfddeb5cae53a559747e2fda63728e4aba60f18b79a69 '\ 'f03310bf0164e5ee7db6b15bf56df23fddbae6a1bb38449b8c883f181 '\ '02bbd8bb655ac0e2dac2ee3ed5cf4315868d2c598068284854b24894d '\ 'cd4bd37811f0ad3a282cd4b4e599ffd07d8d2d3f2478554f81020b320 '\ 'ee12f44948e2ea1edbc990b830ca5cca6b4a839fb27b51850c9847eac '\ '74f26609eb24365b9751fb1c3208f56913bacbcae49201347c78b7e54 '\ 'a9d99979404c37f00fb65db849fd75e3a68770c30f2abe65b33256fb5 '\ '9b450050b00d8139d4d80d36f7bc46daf303e48f0f0791b2fdd72ec60 '\ 'b2cb3ad533c3f288c9c194e49337a69c496731f086d4f1f9825900713 '\ 'e2a551d05cb6057567850d91e6001c4ce27176f0957873a95b880acbe '\ 'c19e7bd9bcf1286d0452b73789c41905dd470971cd73aea52c77b080c '\ 'd779af58234f337225c26f87a8c13e2a65e9dd4e03a5b41d7e06b3353 '\ 'f38129b2327a531ec9627a21dc423733aa029d4989448ba3322891c1a '\ '5690ddf2d25c8ec8aaa894b14aa92130c6b6d969a21ff671b60c4c923 '\ 'a94a93ea1dd0492c93393ca6edd61f33ca77e9208d01d6bd15107662e '\ 'c088733df4c876a7e1608b82973a0f7592e84ed15579d181e79024ae8 '\ 'a7e4b9f0078eb2005b23f9d09a1df1bbc7de2a5a6085a3646d9fadb0e '\ '9da273a5f403cdd42831ce6f0ca46889585602bb8bc36bb3be861ff6d '\ '1a62e350203010001 )', '_443._tcp.data.iana.org. IN TLSA 3 1 1 ( d56f85824b6ed2ab15b9040c20b574515d9a0ab415ca253b42cbc915a '\ '11de18d )', '_443._tcp.data.iana.org. IN TLSA 3 1 2 ( ba8b1b6f74782cb681373c314cf7bf4d2468c6a9dee47909fae1381ca '\ '6447249c42cb2a4d6d808fa1486ba70b7c1bb70dd76657a281441110b '\ 'b4043007ee5ce3 )' ].freeze CERT = "-----BEGIN CERTIFICATE----- MIIGgzCCBWugAwIBAgIQCcq74hkcj1ad1LbdJQ8h2DANBgkqhkiG9w0BAQsFADBw MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3 d3cuZGlnaWNlcnQuY29tMS8wLQYDVQQDEyZEaWdpQ2VydCBTSEEyIEhpZ2ggQXNz dXJhbmNlIFNlcnZlciBDQTAeFw0xNDEwMjcwMDAwMDBaFw0xODAxMDMxMjAwMDBa MIGjMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEUMBIGA1UEBxML TG9zIEFuZ2VsZXMxPDA6BgNVBAoTM0ludGVybmV0IENvcnBvcmF0aW9uIGZvciBB c3NpZ25lZCBOYW1lcyBhbmQgTnVtYmVyczEWMBQGA1UECxMNSVQgT3BlcmF0aW9u czETMBEGA1UEAwwKKi5pYW5hLm9yZzCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCC AgoCggIBAJ29/d61yuU6VZdH4v2mNyjkq6YPGLeaafAzEL8BZOXufbaxW/Vt8j/d uuahuzhEm4yIPxgQK72LtlWsDi2sLuPtXPQxWGjSxZgGgoSFSySJTc1L03gR8K06 KCzUtOWZ/9B9jS0/JHhVT4ECCzIO4S9ElI4uoe28mQuDDKXMprSoOfsntRhQyYR+ rHTyZgnrJDZbl1H7HDII9WkTusvK5JIBNHx4t+VKnZmXlATDfwD7ZduEn9deOmh3 DDDyq+ZbMyVvtZtFAFCwDYE51NgNNve8RtrzA+SPDweRsv3XLsYLLLOtUzw/KIyc GU5JM3ppxJZzHwhtTx+YJZAHE+KlUdBctgV1Z4UNkeYAHEzicXbwlXhzqVuICsvs Gee9m88ShtBFK3N4nEGQXdRwlxzXOupSx3sIDNd5r1gjTzNyJcJvh6jBPipl6d1O A6W0HX4GszU/OBKbIyelMeyWJ6IdxCNzOqAp1JiUSLozIokcGlaQ3fLSXI7IqqiU sUqpITDGttlpoh/2cbYMTJI6lKk+od0Ekskzk8pu3WHzPKd+kgjQHWvRUQdmLsCI cz30yHan4WCLgpc6D3WS6E7RVXnRgeeQJK6KfkufAHjrIAWyP50Jod8bvH3ipaYI WjZG2frbDp2ic6X0A83UKDHObwykaIlYVgK7i8Nrs76GH/bRpi41AgMBAAGjggHj MIIB3zAfBgNVHSMEGDAWgBRRaP+QrwIHdTzM2WVkYqISuFlyOzAdBgNVHQ4EFgQU x9Cs74mLIOS5FGaJMwMjlPa/OmEwHwYDVR0RBBgwFoIKKi5pYW5hLm9yZ4IIaWFu YS5vcmcwDgYDVR0PAQH/BAQDAgWgMB0GA1UdJQQWMBQGCCsGAQUFBwMBBggrBgEF BQcDAjB1BgNVHR8EbjBsMDSgMqAwhi5odHRwOi8vY3JsMy5kaWdpY2VydC5jb20v c2hhMi1oYS1zZXJ2ZXItZzMuY3JsMDSgMqAwhi5odHRwOi8vY3JsNC5kaWdpY2Vy dC5jb20vc2hhMi1oYS1zZXJ2ZXItZzMuY3JsMEIGA1UdIAQ7MDkwNwYJYIZIAYb9 bAEBMCowKAYIKwYBBQUHAgEWHGh0dHBzOi8vd3d3LmRpZ2ljZXJ0LmNvbS9DUFMw gYMGCCsGAQUFBwEBBHcwdTAkBggrBgEFBQcwAYYYaHR0cDovL29jc3AuZGlnaWNl cnQuY29tME0GCCsGAQUFBzAChkFodHRwOi8vY2FjZXJ0cy5kaWdpY2VydC5jb20v RGlnaUNlcnRTSEEySGlnaEFzc3VyYW5jZVNlcnZlckNBLmNydDAMBgNVHRMBAf8E AjAAMA0GCSqGSIb3DQEBCwUAA4IBAQBwMUw458Av2AgQUAud9troXemyPin71ov9 tfI0Eciaz6+a4Fr5EjqKprzmlUpOaNx8/EgKZddvIpxL1fVnSwyaxtBqN6GhwUXD lWEguO/mfIh6tP99aqlQ/zaY8nxKGdWdk6Oaylp7bWx140l05Q9aWQAFs8tmXdvX B0+fy8v5xQIo1eJVlrZK2hYLSPd6k6rO0iYXv+AF4A/iClMqCty4GMh43F1mSSd3 d8oagU4h0LUzCK9AeL5FVHFeTOSCiwEvJf+hOmzrMNIKdd66ijROQdYn+mOP7/OK MGOgGHUZs5sFP3E02c2D5gkazPXS46Be36Hfvhgah62GuiT+a5f+ -----END CERTIFICATE-----".freeze def test_tlsa_from_string t1 = Dnsruby::RR.create(INPUT[0]) assert_equal(3, t1.usage) assert_equal(0, t1.selector) assert_equal(1, t1.matching_type) assert_equal('6609173804b9e31895f550db027ef7c7fa6f1bc9326c99371b61f1ba5 cb3595d', t1.data) t2 = Dnsruby::RR.create(INPUT[1]) assert_equal(255, t2.usage) assert_equal(255, t2.selector) assert_equal(255, t2.matching_type) assert_equal('6609173804b9e31895f550db027ef7c7fa6f1bc9326c99371b61f1ba5 cb3595d', t2.data) end def test_tlsa_from_data t1 = Dnsruby::RR.create(INPUT[0]) m = Dnsruby::Message.new m.add_additional(t1) data = m.encode m2 = Dnsruby::Message.decode(data) t3 = m2.additional[0] assert_equal(t1.to_s, t3.to_s) end def test_tlsa_verify_rsa_cert cert = OpenSSL::X509::Certificate.new(CERT) der = cert.to_der t4 = Dnsruby::RR.create(INPUT[2]) assert_equal(t4.databin, der) t5 = Dnsruby::RR.create(INPUT[3]) assert_equal(t5.databin, OpenSSL::Digest::SHA256.digest(der)) t6 = Dnsruby::RR.create(INPUT[4]) assert_equal(t6.databin, OpenSSL::Digest::SHA512.digest(der)) end def test_tlsa_verify_rsa_pkey cert = OpenSSL::X509::Certificate.new(CERT) pkey = cert.public_key.to_der t7 = Dnsruby::RR.create(INPUT[5]) assert_equal(t7.databin, pkey) t8 = Dnsruby::RR.create(INPUT[6]) assert_equal(t8.databin, OpenSSL::Digest::SHA256.digest(pkey)) t9 = Dnsruby::RR.create(INPUT[7]) assert_equal(t9.databin, OpenSSL::Digest::SHA512.digest(pkey)) end end dnsruby-1.61.3/test/spec_helper.rb0000644000004100000410000000157413607400362017125 0ustar www-datawww-dataif ENV['RUN_EXTRA_TASK'] == 'TRUE' require 'coveralls' Coveralls.wear! require 'simplecov' # SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter.new( # [SimpleCov::Formatter::HTMLFormatter, Coveralls::SimpleCov::Formatter]) SimpleCov.formatter = Coveralls::SimpleCov::Formatter SimpleCov.start do add_filter 'test/' end end require 'minitest' require 'minitest/autorun' require 'minitest/display' MiniTest::Display.options = { suite_names: true, color: true, print: { success: ".", failure: "F", error: "R" } } # This is in a self invoking anonymous lambda so local variables do not # leak to the outer scope. -> do load_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) $LOAD_PATH.unshift(load_dir) unless $LOAD_PATH.include?(load_dir) require_relative '../lib/dnsruby' require_relative 'test_utils' end.() dnsruby-1.61.3/test/tc_packet.rb0000644000004100000410000002734613607400362016576 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestPacket < Minitest::Test include Dnsruby def test_packet domain = "example.com." type = "MX" klass = "IN" packet = Message.new(domain, type, klass) assert(packet, 'new() returned something'); #2 assert(packet.header, 'header() method works'); #3 assert_instance_of(Header,packet.header,'header() returns right thing'); #4 question = packet.question; assert(question && question.length == 1, 'question() returned right number of items'); #5 # assert_instance_of(Net::DNS::Question,question[0], 'question() returned the right thing'); #6 answer = packet.answer; assert(answer.length == 0, 'answer() works when empty'); #7 authority = packet.authority; assert(authority.length == 0, 'authority() works when empty'); #8 additional = packet.additional; assert(additional.length == 0, 'additional() works when empty'); #9 packet.add_answer(RR.create( { :name => "a1.example.com.", :type => Types.A, :address => "10.0.0.1"})); assert_equal(1, packet.header.ancount, 'First push into answer section worked'); #10 ret = packet.answer.rrset("example.com.", 'NSEC') assert_equal(ret.rrs.length, 0, "#{ret.rrs.length}") ret = packet.answer.rrset("a1.example.com", 'A') assert_equal(ret.rrs.length, 1, "#{ret.rrs.length}") ret = packet.answer.rrsets() assert_equal(ret.length, 1, "#{ret.length}") packet.add_answer(RR.create({:name => "a2.example.com.", :type => "A", :address => "10.0.0.2"})); assert_equal(packet.header.ancount, 2, 'Second push into answer section worked'); #11 packet.add_authority(RR.create({:name => "a3.example.com.", :type => "A", :address => "10.0.0.3"})); assert_equal(1, packet.header.nscount, 'First push into authority section worked'); #12 packet.add_authority(RR.create( { :name => "a4.example.com.", :type => "A", :address => "10.0.0.4"})); assert_equal(2, packet.header.nscount, 'Second push into authority section worked'); #13 packet.add_additional(RR.create({ :name => "a5.example.com.", :type => "A", :address => "10.0.0.5"})); assert_equal(1, packet.header.adcount, 'First push into additional section worked'); #14 packet.add_additional(RR.create( { :name => "a6.example.com.", :type => Types.A, :address => "10.0.0.6"})); assert_equal(2, packet.header.adcount, 'Second push into additional section worked'); #15 data = packet.encode; packet2 = Message.decode(data); assert(packet2, 'new() from data buffer works'); #16 assert_equal(packet.to_s, packet2.to_s, 'inspect() works correctly'); #17 string = packet2.to_s 6.times do |count| ip = "10.0.0.#{count+1}"; assert(string =~ /#{ip}/, "Found #{ip} in packet"); # 18 though 23 end assert_equal(1, packet2.header.qdcount, 'header question count correct'); #24 assert_equal(2, packet2.header.ancount, 'header answer count correct'); #25 assert_equal(2, packet2.header.nscount, 'header authority count correct'); #26 assert_equal(2, packet2.header.adcount, 'header additional count correct'); #27 # Test using a predefined answer. This is an answer that was generated by a bind server. # # data=["22cc85000001000000010001056461636874036e657400001e0001c00c0006000100000e100025026e730472697065c012046f6c6166c02a7754e1ae0000a8c0000038400005460000001c2000002910000000800000050000000030"].pack("H*"); uuencodedPacket =%w{ 22 cc 85 00 00 01 00 00 00 01 00 01 05 64 61 63 68 74 03 6e 65 74 00 00 1e 00 01 c0 0c 00 06 00 01 00 00 0e 10 00 25 02 6e 73 04 72 69 70 65 c0 12 04 6f 6c 61 66 c0 2a 77 54 e1 ae 00 00 a8 c0 00 00 38 40 00 05 46 00 00 00 1c 20 00 00 29 10 00 00 00 80 00 00 05 00 00 00 00 30 } uuencodedPacket = %w{ ba 91 81 80 00 01 00 04 00 00 00 01 07 65 78 61 6d 70 6c 65 03 63 6f 6d 00 00 ff 00 01 c0 0c 00 02 00 01 00 02 9f f4 00 14 01 61 0c 69 61 6e 61 2d 73 65 72 76 65 72 73 03 6e 65 74 00 c0 0c 00 02 00 01 00 02 9f f4 00 04 01 62 c0 2b c0 0c 00 01 00 01 00 02 9f 7e 00 04 d0 4d bc a6 c0 0c 00 06 00 01 00 02 9f f4 00 31 04 64 6e 73 31 05 69 63 61 6e 6e 03 6f 72 67 00 0a 68 6f 73 74 6d 61 73 74 65 72 c0 6e 77 a1 2d b7 00 00 1c 20 00 00 0e 10 00 12 75 00 00 01 51 80 00 00 29 05 00 00 00 00 00 00 00 } uuencodedPacket.map!{|e| e.hex} packetdata = uuencodedPacket.pack('c*') packet3 = Message.decode(packetdata) assert(packet3, 'new data returned something'); #28 assert_equal(packet3.header.qdcount, 1, 'header question count in syntetic packet correct'); #29 assert_equal(packet3.header.ancount, 4, 'header answer count in syntetic packet correct'); #30 assert_equal(packet3.header.nscount, 0, 'header authority count in syntetic packet correct'); #31 assert_equal(packet3.header.adcount, 1, 'header additional in sytnetic packet correct'); #32 rr=packet3.additional; assert_equal(Types.OPT, rr[0].type, "Additional section packet is EDNS0 type"); #33 assert_equal(1280, rr[0].klass.code, "EDNS0 packet size correct"); #34 # In theory its valid to have multiple questions in the question section. # Not many servers digest it though. packet.add_question("bla.foo", Types::TXT, Classes.CH) question = packet.question assert_equal(2, question.length, 'question() returned right number of items poptest:2'); #36 end def get_test_packet packet=Message.new("254.9.11.10.in-addr.arpa.","PTR","IN") packet.add_answer(RR.create(%q[254.9.11.10.in-addr.arpa. 86400 IN PTR host-84-11-9-254.customer.example.com.])); packet.add_authority(RR.create("9.11.10.in-addr.arpa. 86400 IN NS autons1.example.com.")); packet.add_authority(RR.create("9.11.10.in-addr.arpa. 86400 IN NS autons2.example.com.")); packet.add_authority(RR.create("9.11.10.in-addr.arpa. 86400 IN NS autons3.example.com.")); return packet end def test_push packet = get_test_packet data=packet.encode packet2=Message.decode(data) assert_equal(packet.to_s,packet2.to_s,"Packet decode and encode"); #39 end def test_rrset packet = get_test_packet packet.each_section do |section| # print "#{section.rrsets}\n" end packet.section_rrsets.each do |section, rrsets| # print "section = #{section}, rrsets = #{rrsets.length}\n" end assert(packet.authority.rrsets.length == 1) assert(packet.question().length == 1) assert(packet.answer.rrsets.length == 1) assert(packet.additional.rrsets.length == 0) assert(packet.authority.rrsets[0].length == 3) # assert(packet.additional.rrsets[0].length == 0) assert(packet.answer.rrsets[0].length == 1) end def test_section packet = Message.new("ns2.nic.se") packet.add_answer(RR.create("ns2.nic.se. 3600 IN A 194.17.45.54")) packet.add_answer(RR.create("ns2.nic.se. 3600 IN RRSIG A 5 3 3600 20090329175503 ( 20090319175503 32532 nic.se. YFvEOPpVHgAmPwtM2Q0KD5x6UaZ5bMzINMyW4xXSXOxG /EYCTbmTfPpfZTnAUPAfNRIA4RS9etMgh5Zy3Wug4dKs 20+3vwlSz0Ge5jluOoowkWAK3YbLkqwSi1DeZg/HT1Ns zcBDHMJ9sxmB6d4nuRA6653w9RULVjpKng1gh0s= ) ")) packet.add_authority(RR.create("nic.se. 3600 IN NS ns2.nic.se.")) packet.add_authority(RR.create("nic.se. 3600 IN NS ns3.nic.se.")) packet.add_authority(RR.create("nic.se. 3600 IN NS ns.nic.se.")) packet.add_authority(RR.create("nic.se. 3600 IN RRSIG NS 5 2 3600 20090329175503 ( 20090319175503 32532 nic.se. ZExPKC9zDiyY0TuuPGDBtzYE119fiXWqihARO41l7uTT LBbYcCNg3ItJZW2y0o4iFYpqrp62l25uKhO4cMEZbgZs Gq9B6zZ4/2D0v4zFjlzCEZ0lTrGb6xgOrnQbZUiTbg46 x9iBai7Ud1w/hgV/TSxikP1SS0J1AillybPiMWQ= )")) packet.add_additional(RR.create("ns.nic.se. 3600 IN A 212.247.7.228")) packet.add_additional(RR.create("ns.nic.se. 3600 IN AAAA 2a00:801:f0:53::53")) packet.add_additional(RR.create("ns3.nic.se. 60 IN A 212.247.3.83")) packet.add_additional(RR.create("ns.nic.se. 3600 IN RRSIG A 5 3 3600 20090329175503 ( 20090319175503 32532 nic.se. opTtrYBF+Mm4BGK+5vvAvzxxgh4GUxa7YxflT1DybG7u uRdi+ZD6+DFXvaMKPcmVLRcMV2wEv7v1zBj+jaAkqPno ikOHMtd9g0FtmfxR//TLLzgjDsunee0MX6hLX/ApTUy8 hhcGB1pxk371tZKSBkNI7SN7gaSnknUUEp6eNN4= )")) packet.add_additional(RR.create("ns.nic.se. 3600 IN RRSIG AAAA 5 3 3600 20090329175503 ( 20090319175503 32532 nic.se. Qaj/eG9MPGF6QZUPpRq3LBxfxQiKki3J2myKy+OQuE65 juDBb+29YjteqQW1PrilxRjo4apX5Q4LNAhS+bEx+PNU dHr8x0u7z7fZMCAaZhQndnWTD5Wzf1J97bt0ml78yqDi PkYeqNTNeM0Y40VTu0aHsPPPZpQRR7MYcODUbl0= )")) packet.add_additional(RR.create("ns3.nic.se. 60 IN RRSIG A 5 3 60 20090329175503 ( 20090319175503 32532 nic.se. Ql7Msgt0HKDifPaCV8UYsiLj7hOEp6LPJJ5oaFrJhooU Nrp4gcwlX9QbrYXWQ8cgE0Z+bL2c07EX/f+n7+xfgCIu UtL1tXJPsujZBojMtpnkbZsCb5cQmUv0CjAVIdF82W7Q mUg/YzRLeIyl/wBm0u8/v7TZp/KbGbaKMWMXkjo= )")) packet.add_additional(RR::OPT.new(4096, 0x9e22)) packet.header.aa = true assert(packet.answer.length == 2) assert(packet.authority.length == 4) assert(packet.additional.length == 7) ns3_a_rrset = packet.additional.rrset("ns3.nic.se", "A") assert(ns3_a_rrset.length == 2) section_rrsets = packet.section_rrsets assert(section_rrsets["answer"].length == 1) assert(section_rrsets["authority"].length == 1) assert(section_rrsets["additional"].length == 3) add_count = 0 packet.each_additional {|rr| add_count += 1} assert(add_count == 7) packet.additional.remove_rrset(Name.create("ns.nic.se."), Types.AAAA) assert(packet.answer.length == 2) assert(packet.authority.length == 4) assert(packet.additional.length == 5) section_rrsets = packet.section_rrsets assert(section_rrsets["answer"].length == 1) assert(section_rrsets["authority"].length == 1) assert(section_rrsets["additional"].length == 2) add_count = 0 packet.each_additional {|rr| add_count += 1} assert(add_count == 5) packet.additional.remove_rrset(Name.create("ns.nic.se."), Types.A) assert(packet.answer.length == 2) assert(packet.authority.length == 4) assert(packet.additional.length == 3) section_rrsets = packet.section_rrsets assert(section_rrsets["answer"].length == 1) assert(section_rrsets["authority"].length == 1) assert(section_rrsets["additional"].length == 1) packet.additional.remove_rrset(Name.create("ns3.nic.se."), Types.A) assert(packet.answer.length == 2) assert(packet.authority.length == 4) assert(packet.additional.length == 1) section_rrsets = packet.section_rrsets assert(section_rrsets["answer"].length == 1) assert(section_rrsets["authority"].length == 1) assert(section_rrsets["additional"].length == 0) end def test_clone m = Message.new("blah.example.com", "DNSKEY", "IN") m.header.rcode=4 m2 = m.clone assert_equal(m.to_s, m2.to_s, "Clone to_s failed") assert_equal(m, m2, "Clone failed") end end dnsruby-1.61.3/test/tc_update.rb0000644000004100000410000003161513607400362016603 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestUpdate < Minitest::Test include Dnsruby def is_empty(string) return true if string == nil || string.length == 0 return (string == "; no data" || string == "; rdlength = 0"); end def test_update # ------------------------------------------------------------------------------ # Canned data. # ------------------------------------------------------------------------------ zone = "example.com"; name = "foo.example.com"; klass = Classes.CLASS32; klass2 = Classes.CH; type = Types.A; ttl = 43200; rdata = "10.1.2.3"; rr = nil; # ------------------------------------------------------------------------------ # Packet creation. # ------------------------------------------------------------------------------ update = Dnsruby::Update.new(zone, klass); z = (update.zone)[0]; assert(update, 'new() returned packet'); #2 assert_equal(update.header.opcode, OpCode.UPDATE, 'header opcode correct'); #3 assert_equal(z.zname.to_s, zone, 'zname correct'); #4 assert_equal(z.zclass.to_s, klass.to_s, 'zclass correct'); #5 assert_equal(z.ztype, Types.SOA, 'ztype correct'); #6 # ------------------------------------------------------------------------------ # RRset exists (value-independent). # ------------------------------------------------------------------------------ rr = update.present(name, type); assert(rr, 'yxrrset() returned RR'); #7 assert_equal(name, rr.name.to_s, 'yxrrset - right name'); #8 assert_equal(0, rr.ttl, 'yxrrset - right TTL'); #9 assert_equal('ANY', rr.klass.string, 'yxrrset - right class'); #10 assert_equal(type, rr.type, 'yxrrset - right type'); #11 assert(is_empty(rr.rdata), "yxrrset - data empty (#{rr.rdata})"); #12 rr = nil # ------------------------------------------------------------------------------ # RRset exists (value-dependent). # ------------------------------------------------------------------------------ rr = update.present(name, type, rdata, klass); assert(rr, 'yxrrset() returned RR'); #13 assert_equal(name, rr.name.to_s, 'yxrrset - right name'); #14 assert_equal(0, rr.ttl, 'yxrrset - right TTL'); #15 assert_equal(klass, rr.klass.string, 'yxrrset - right class'); #16 assert_equal(type, rr.type, 'yxrrset - right type'); #17 assert_equal(rdata, rr.rdata, 'yxrrset - right data'); #18 rr=nil # ------------------------------------------------------------------------------ # RRset does not exist. # ------------------------------------------------------------------------------ rr = update.absent(name, type); assert(rr, 'nxrrset() returned RR'); #19 assert_equal(name, rr.name.to_s, 'nxrrset - right name'); #20 assert_equal(0, rr.ttl, 'nxrrset - right ttl'); #21 assert_equal('NONE', rr.klass.string, 'nxrrset - right class'); #22 assert_equal(type, rr.type, 'nxrrset - right type'); #23 assert(is_empty(rr.rdata), 'nxrrset - data empty'); #24 rr = nil # ------------------------------------------------------------------------------ # Name is in use. # ------------------------------------------------------------------------------ rr = update.present(name); assert(rr, 'yxdomain() returned RR'); #25 assert_equal(rr.name.to_s, name, 'yxdomain - right name'); #26 assert_equal(rr.ttl, 0, 'yxdomain - right ttl'); #27 assert_equal(rr.klass.string, 'ANY', 'yxdomain - right class'); #28 assert_equal(rr.type.string, 'ANY', 'yxdomain - right type'); #29 assert(is_empty(rr.rdata), 'yxdomain - data empty'); #30 rr = nil # ------------------------------------------------------------------------------ # Name is not in use. (No Class) # ------------------------------------------------------------------------------ rr = update.absent(name); assert(rr, 'nxdomain() returned RR'); #31 assert_equal(rr.name.to_s, name, 'nxdomain - right name'); #32 assert_equal(rr.ttl, 0, 'nxdomain - right ttl'); #33 assert_equal(rr.klass.string, 'NONE', 'nxdomain - right class'); #34 assert_equal(rr.type.string, 'ANY', 'nxdomain - right type'); #35 assert(is_empty(rr.rdata), 'nxdomain - data empty'); #36 rr = nil # ------------------------------------------------------------------------------ # Add to an RRset. # ------------------------------------------------------------------------------ rr = update.add(name, type, ttl, rdata); assert(rr, 'rr_add() returned RR'); #37 assert_equal(rr.name.to_s, name, 'rr_add - right name'); #38 assert_equal(rr.ttl, ttl, 'rr_add - right ttl'); #39 assert_equal(rr.klass, klass, 'rr_add - right class'); #40 assert_equal(rr.type, type, 'rr_add - right type'); #41 assert_equal(rr.rdata, rdata, 'rr_add - right data'); #42 rr = nil # ------------------------------------------------------------------------------ # Delete an RRset. # ------------------------------------------------------------------------------ rr = update.delete(name, type); assert(rr, 'rr_del() returned RR'); #43 assert_equal(name, rr.name.to_s, 'rr_del - right name'); #44 assert_equal(0, rr.ttl, 'rr_del - right ttl'); #45 assert_equal('ANY', rr.klass.string, 'rr_del - right class'); #46 assert_equal(type, rr.type, 'rr_del - right type'); #47 assert(is_empty(rr.rdata), 'rr_del - data empty'); #48 rr = nil # ------------------------------------------------------------------------------ # Delete All RRsets From A Name. # ------------------------------------------------------------------------------ rr = update.delete(name); assert(rr, 'rr_del() returned RR'); #49 assert_equal(name, rr.name.to_s, 'rr_del - right name'); #50 assert_equal(0, rr.ttl, 'rr_del - right ttl'); #51 assert_equal(Classes.ANY, rr.klass, 'rr_del - right class'); #52 assert_equal(Classes.ANY, rr.type, 'rr_del - right type'); #53 assert(is_empty(rr.rdata), 'rr_del - data empty'); #54 rr = nil # ------------------------------------------------------------------------------ # Delete An RR From An RRset. # ------------------------------------------------------------------------------ rr = update.delete(name, type, rdata); assert(rr, 'rr_del() returned RR'); #55 assert_equal(name, rr.name.to_s, 'rr_del - right name'); #56 assert_equal(0, rr.ttl, 'rr_del - right ttl'); #57 assert_equal('NONE', rr.klass.string, 'rr_del - right class'); #58 assert_equal(type, rr.type, 'rr_del - right type'); #59 assert_equal(rdata, rr.rdata, 'rr_del - right data'); #60 rr = nil data = update.encode header = Header.new_from_data(data) assert(header.opcode == OpCode.Update) new_update = Message.decode(data) assert(new_update.header.opcode == OpCode.Update) # ------------------------------------------------------------------------------ # Make sure RRs in an update packet have the same class as the zone, unless # the class is NONE or ANY. # ------------------------------------------------------------------------------ update = Dnsruby::Update.new(zone, klass); assert(update, 'packet created'); #61 update.present(name, type, rdata); update.present(name, type, rdata); update.present(name, type); update.absent(name, type); pre = update.pre; assert_equal(3, pre.size, 'pushed inserted correctly'); #62 assert_equal(klass, pre[0].klass.string, 'first class right'); #63 assert_equal(Classes.ANY, pre[1].klass, 'third class right'); #65 assert_equal(Classes.NONE, pre[2].klass, 'forth class right'); #66 end def test_absent_cname update = Update.new() rr = update.absent("target_name", "CNAME") assert(rr, 'nxdomain() returned RR'); assert_equal(rr.name.to_s, "target_name", 'nxdomain - right name'); assert_equal(rr.ttl, 0, 'nxdomain - right ttl'); assert_equal(rr.klass.string, 'NONE', 'nxdomain - right class'); assert_equal(rr.type.string, 'CNAME', 'nxdomain - right type'); assert(is_empty(rr.rdata), 'nxdomain - data empty'); encoded_msg = Message.decode(update.encode) rr = encoded_msg.answer.first assert(rr, 'nxdomain() returned RR') assert_equal(rr.name.to_s, "target_name", 'nxdomain - right name') assert_equal(rr.ttl, 0, 'nxdomain - right ttl') assert_equal(rr.klass.string, 'NONE', 'nxdomain - right class') assert_equal(rr.type.string, 'CNAME', 'nxdomain - right type') # assert_nil(rr.rdata, 'nxdomain - data empty') assert(is_empty(rr.rdata), 'nxdomain - data empty') end def test_delete_specific_cname update = Update.new 'example.com' update.delete 'test.example.com', 'CNAME', 'target.example.com' encoded_msg = Message.decode update.encode rr = encoded_msg.authority.first assert_equal rr.name.to_s, 'test.example.com', 'delete_cname - right name' assert_equal 0, rr.ttl, 'delete_cname - right ttl' assert_equal 'NONE', rr.klass.string, 'delete_cname - right class' assert_equal 'CNAME', rr.type.string, 'delete_cname - right type' assert_equal 'target.example.com', rr.rdata.to_s, 'delete_cname - right target' end def test_delete_cname update = Update.new 'example.com' update.delete 'test.example.com', 'CNAME' encoded_msg = Message.decode update.encode rr = encoded_msg.authority.first assert_equal rr.name.to_s, 'test.example.com', 'delete_cname - right name' assert_equal 0, rr.ttl, 'delete_cname - right ttl' assert_equal 'ANY', rr.klass.string, 'delete_cname - right class' assert_equal 'CNAME', rr.type.string, 'delete_cname - right type' assert(is_empty(rr.rdata), 'delete_cname - right rdata') end def test_txt update = Update.new() update.add("target_name", "TXT", 100, "test signed update") assert(update.to_s.index("test signed update")) end def test_delete_txt update = Update.new 'example.com' update.delete 'test.example.com', 'TXT', 'foo bar' encoded_msg = Message.decode update.encode rr = encoded_msg.authority.first assert_equal rr.name.to_s, 'test.example.com', 'delete_txt - right name' assert_equal 0, rr.ttl, 'delete_txt - right ttl' assert_equal 'TXT', rr.type.string, 'delete_txt - right type' assert_equal ['foo bar'], rr.rdata, 'delete_txt - right rdata' end def test_array update = Update.new update.add("target_name", "TXT", 100, ['"test signed update"', 'item#2']) assert(update.to_s.index("item")) end end dnsruby-1.61.3/test/resolv.conf0000644000004100000410000000140013607400362016454 0ustar www-datawww-data#-- #Copyright 2007 Nominet UK # #Licensed under the Apache License, Version 2.0 (the "License"); #you may not use this file except in compliance with the License. #You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # #Unless required by applicable law or agreed to in writing, software #distributed under the License is distributed on an "AS IS" BASIS, #WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #See the License for the specific language governing permissions and #limitations under the License. #++ domain t.dnsruby.validation-test-servers.nominet.org.uk search dnsruby.validation-test-servers.nominet.org.uk lib.dnsruby.validation-test-servers.nominet.org.uk nameserver 10.0.1.128 10.0.2.128 dnsruby-1.61.3/test/tc_dlv.rb0000644000004100000410000000612513607400362016104 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestDlv < Minitest::Test include Dnsruby def test_dlv # Enable DLV (only) for validation. # Try to validate some records which can only be done through dlv # OK - if we don't configure trust anchors, and there is no signed root, then this is easy! Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors Dnsruby::PacketSender.clear_caches # Dnssec.do_validation_with_recursor(true) # @TODO@ Should use whole RRSet of authoritative NS for these resolvers, # not individual servers! res = Dnsruby::Resolver.new("a.ns.se") res.add_server("b.ns.se") res.dnssec=true ret = res.query("se.", Dnsruby::Types.ANY) # assert(ret.security_level == Dnsruby::Message::SecurityLevel::INSECURE) # With no keys configured, checking will not be performed assert(ret.security_level == Dnsruby::Message::SecurityLevel::UNCHECKED) res = Dnsruby::Resolver.new("ns3.nic.se") res.add_server("ns2.nic.se") res.dnssec = true ret = res.query("ns2.nic.se", Dnsruby::Types.A) assert(ret.security_level == Dnsruby::Message::SecurityLevel::UNCHECKED) # Load DLV key dlv_key = RR.create("dlv.isc.org. IN DNSKEY 257 3 5 BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh") Dnssec.add_dlv_key(dlv_key) Dnsruby::PacketSender.clear_caches # SE no longer in DLV # res = Dnsruby::Recursor.new() # ret = res.query("ns2.nic.se", Dnsruby::Types.A) # assert(ret.security_level == Dnsruby::Message::SecurityLevel::SECURE) # .cz no longer in dlv? # ret = res.query("b.ns.nic.cz", Dnsruby::Types.A) # assert(ret.security_level == Dnsruby::Message::SecurityLevel::SECURE) # Test .gov # Dnsruby::TheLog.level = Logger::DEBUG res = Resolver.new ret = res.query("nih.gov", "NS") assert(ret.security_level = Dnsruby::Message::SecurityLevel::SECURE) end # se no longer in dlv # def test_scrub_non_authoritative # # Dnssec.do_validation_with_recursor(true) # res = Dnsruby::Recursor.new() # ret = res.query("frobbit.se") # res.prune_rrsets_to_rfc5452(ret, "frobbit.se.") # Dnssec.validate(ret) # assert(ret.security_level == Dnsruby::Message::SecurityLevel::SECURE) # end end dnsruby-1.61.3/test/tc_misc.rb0000644000004100000410000001235513607400362016254 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestMisc < Minitest::Test def test_wildcard # test to make sure that wildcarding works. # rr = Dnsruby::RR.create('*.t.dnsruby.validation-test-servers.nominet.org.uk 60 IN A 10.0.0.1') assert(rr, 'RR got made') assert_equal('*.t.dnsruby.validation-test-servers.nominet.org.uk', rr.name.to_s, 'Name is correct' ) assert_equal(60, rr.ttl, 'TTL is correct' ) assert_equal(Dnsruby::Classes.IN, rr.klass, 'CLASS is correct' ) assert_equal(Dnsruby::Types.A, rr.type, 'TYPE is correct' ) assert_equal('10.0.0.1', rr.address.to_s, 'Address is correct') end def test_misc # # Make sure the underscore in SRV hostnames work. # srv = Dnsruby::RR.create('_rvp._tcp.t.dnsruby.validation-test-servers.nominet.org.uk. 60 IN SRV 0 0 80 im.bastardsinc.biz') assert(srv, 'SRV not created successfully') # ~ # Test that the 5.005 Use of uninitialized value at # ~ # /usr/local/lib/perl5/site_perl/5.005/Net/DNS/RR.pm line 639. bug is gone rr = Dnsruby::RR.create('mx.t.dnsruby.validation-test-servers.nominet.org.uk 60 IN MX 10 a.t.dnsruby.validation-test-servers.nominet.org.uk') assert(rr, 'RR created') assert_equal(rr.preference, 10, 'Preference works') mx = Dnsruby::RR.create('mx.t.dnsruby.validation-test-servers.nominet.org.uk 60 IN MX 0 mail.dnsruby.validation-test-servers.nominet.org.uk') assert(mx.to_s =~ /0 mail.dnsruby.validation-test-servers.nominet.org.uk/) # was 'like' assert_equal(mx.preference, 0) assert_equal(mx.exchange.to_s, 'mail.dnsruby.validation-test-servers.nominet.org.uk') srv = Dnsruby::RR.create('srv.t.dnsruby.validation-test-servers.nominet.org.uk 60 IN SRV 0 2 3 target.dnsruby.validation-test-servers.nominet.org.uk') # # @todo@ Absolute name issues # assert(srv.inspect =~ /0 2 3 target.dnsruby.validation-test-servers.nominet.org.uk\./) # assert_equal(srv.rdatastr, '0 2 3 target.dnsruby.validation-test-servers.nominet.org.uk.') end def test_TXT_RR # # # Below are some thests that have to do with TXT RRs # # # QUESTION SECTION: # txt2.t.net-dns.org. IN TXT # ANSWER SECTION: # txt2.t.net-dns.org. 60 IN TXT "Net-DNS\ complicated $tuff" "sort of \" text\ and binary \000 data" # AUTHORITY SECTION: # net-dns.org. 3600 IN NS ns1.net-dns.org. # net-dns.org. 3600 IN NS ns.ripe.net. # net-dns.org. 3600 IN NS ns.hactrn.net. # ADDITIONAL SECTION: # ns1.net-dns.org. 3600 IN A 193.0.4.49 # ns1.net-dns.org. 3600 IN AAAA uuencodedPacket=%w{ 11 99 85 00 00 01 00 01 00 03 00 02 04 74 78 74 32 01 74 07 6e 65 74 2d 64 6e 73 03 6f 72 67 00 00 10 00 01 c0 0c 00 10 00 01 00 00 00 3c 00 3d 1a 4e 65 74 2d 44 4e 53 3b 20 63 6f 6d 70 6c 69 63 61 74 65 64 20 24 74 75 66 66 21 73 6f 72 74 20 6f 66 20 22 20 74 65 78 74 3b 20 61 6e 64 20 62 69 6e 61 72 79 20 00 20 64 61 74 61 c0 13 00 02 00 01 00 00 0e 10 00 06 03 6e 73 31 c0 13 c0 13 00 02 00 01 00 00 0e 10 00 0d 02 6e 73 04 72 69 70 65 03 6e 65 74 00 c0 13 00 02 00 01 00 00 0e 10 00 0c 02 6e 73 06 68 61 63 74 72 6e c0 93 c0 79 00 01 00 01 00 00 0e 10 00 04 c1 00 04 31 c0 79 00 1c 00 01 00 00 0e 10 00 10 20 01 06 10 02 40 00 03 00 00 12 34 be 21 e3 1e } uuencodedPacket.map!{|e| e.hex} packetdata = uuencodedPacket.pack('c*') packetdata.gsub!("\s*", "") packet = Dnsruby::Message.decode(packetdata) txtRr=(packet.answer)[0] assert_equal('Net-DNS; complicated $tuff',txtRr.strings[0],"First Char string in TXT RR read from wireformat") # Compare the second char_str this contains a NULL byte (space NULL # space=200020 in hex) temp = (txtRr.strings)[1].unpack('H*')[0] # #assert_equal(unpack('H*',(TXTrr.char_str_list())[1]),"736f7274206f66202220746578743b20616e642062696e61727920002064617461", "Second Char string in TXT RR read from wireformat") assert_equal("736f7274206f66202220746578743b20616e642062696e61727920002064617461", temp,"Second Char string in TXT RR read from wireformat") txtRr2=Dnsruby::RR.create('txt2.t.dnsruby.validation-test-servers.nominet.org.uk. 60 IN TXT "Test1 \" \; more stuff" "Test2"') assert_equal((txtRr2.strings)[0],'Test1 " ; more stuff', "First arg string in TXT RR read from zonefileformat") assert_equal((txtRr2.strings)[1],'Test2',"Second Char string in TXT RR read from zonefileformat") # txtRr3 = RR.create("baz.example.com 3600 HS TXT '\"' 'Char Str2'") txtRr3 = Dnsruby::RR.create("baz.example.com 3600 IN TXT '\"' 'Char Str2'") assert_equal( (txtRr3.strings)[0],'"',"Escaped \" between the single quotes") end end dnsruby-1.61.3/test/tc_tcp.rb0000644000004100000410000001331713607400362016106 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'socket' class TestTcp < Minitest::Test def test_TCP res = Dnsruby::Resolver.new() res.use_tcp = true ret=res.query("example.com") assert(ret.is_a?(Dnsruby::Message)) end def test_TCP_port # Need a test server so we can tell what port this message was actually sent on! port = nil src_port = 57923 Dnsruby::PacketSender.clear_caches received_port = nil server_thread = Thread.new { ts = TCPServer.new(0) port = ts.addr[1] t = ts.accept # Check that the source port was src_port received_port = t.peeraddr()[1] packet = t.recvfrom(2)[0] len = (packet[0]<<8)+packet[1] if (RUBY_VERSION >= "1.9") len = (packet[0].getbyte(0)<<8)+packet[1].getbyte(0)# Ruby 1.9 end packet = t.recvfrom(len)[0] tcpPacket = Dnsruby::Message.decode(packet) tcpPacket.header.tc = true lenmsg = [tcpPacket.encode.length].pack('n') t.send(lenmsg, 0) t.write(tcpPacket.encode) t.close ts.close } ret = nil sleep(1) client_thread = Thread.new { # res = Dnsruby::SingleResolver.new("127.0.0.1") res = Dnsruby::SingleResolver.new("localhost") res.port = port res.use_tcp = true res.src_port=src_port ret=res.query("example.com") } server_thread.join client_thread.join assert(received_port == src_port) assert(ret.is_a?(Dnsruby::Message)) end # def test_no_tcp # # Try to get a long response (which is truncated) and check that we have # @TODO@ FIX THIS TEST!!! # # tc bit set # res = Dnsruby::Resolver.new() # res.udp_size = 512 # res.no_tcp = true # ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Dnsruby::Types.TXT) # assert(ret.header.tc, "Message should be truncated with no TCP") # end class HackMessage < Dnsruby::Message def wipe_additional @additional = Dnsruby::Section.new(self) end # Decode the encoded message def HackMessage.decode(m) o = HackMessage.new() begin Dnsruby::MessageDecoder.new(m) {|msg| o.header = Dnsruby::Header.new(msg) o.header.qdcount.times { question = msg.get_question o.question << question } o.header.ancount.times { rr = msg.get_rr o.answer << rr } o.header.nscount.times { rr = msg.get_rr o.authority << rr } o.header.arcount.times { |count| start = msg.index rr = msg.get_rr if (rr.type == Dnsruby::Types::TSIG) if (count!=o.header.arcount-1) Dnsruby.log.Error("Incoming message has TSIG record before last record") raise Dnsruby::DecodeError.new("TSIG record present before last record") end o.tsigstart = start # needed for TSIG verification end o.additional << rr } } rescue Dnsruby::DecodeError => e # So we got a decode error # However, we might have been able to fill in many parts of the message # So let's raise the DecodeError, but add the partially completed message e.partial_message = o raise e end return o end end def test_bad_truncation # Some servers don't do truncation properly. # Make a UDP server which returns large badly formatted packets (arcount > num_additional), with TC bit set #  And make a TCP server which returns large well formatted packets # Then make sure that Dnsruby recieves response correctly. Dnsruby::PacketSender.clear_caches socket = UDPSocket.new socket.bind("127.0.0.1", 0) port = socket.addr[1] Thread.new { s = socket.recvfrom(65536) received_query = s[0] socket.connect(s[1][2], s[1][1]) ans = HackMessage.decode(received_query) ans.wipe_additional 100.times {|i| ans.add_additional(Dnsruby::RR.create("example.com 3600 IN A 1.2.3.#{i}")) } ans.header.arcount = 110 ans.header.tc = true socket.send(ans.encode,0) } server_thread = Thread.new { ts = TCPServer.new(port) t = ts.accept packet = t.recvfrom(2)[0] len = (packet[0]<<8)+packet[1] if (RUBY_VERSION >= "1.9") len = (packet[0].getbyte(0)<<8)+packet[1].getbyte(0)# Ruby 1.9 end packet = t.recvfrom(len)[0] tcpPacket = HackMessage.decode(packet) tcpPacket.wipe_additional 110.times {|i| tcpPacket.add_additional(Dnsruby::RR.create("example.com 3600 IN A 1.2.3.#{i}")) } lenmsg = [tcpPacket.encode.length].pack('n') t.send(lenmsg, 0) t.write(tcpPacket.encode) t.close ts.close } # Now send query res = Dnsruby::Resolver.new("127.0.0.1") res.port = port res.udp_size = 4096 assert(res.udp_size == 4096) ret = res.query("example.com") assert(ret.header.arcount == 110) count = 0 ret.additional.each {|rr| count += 1} assert(count == 110) end # @TODO@ Check stuff like persistent sockets end dnsruby-1.61.3/test/tc_rr-opt.rb0000644000004100000410000001520113607400362016535 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' require 'socket' class TestRrOpt < Minitest::Test include Dnsruby # This test illustrates that when an OPT record specifying a maximum # UDP size is added to a query, the server will respect that setting # and limit the response's size to <= that maximum. # This works only with send_plain_message, not send_message, query, etc. def test_plain_respects_bufsize resolver = Resolver.new(['a.gtld-servers.net', 'b.gtld-servers.net', 'c.gtld-servers.net']) resolver.query_timeout=20 run_test = ->(bufsize) do create_test_query = ->(bufsize) do message = Message.new('com', Types.ANY, Classes.IN) message.add_additional(RR::OPT.new(bufsize)) message end query = create_test_query.(bufsize) response, _error = resolver.send_plain_message(query) if (_error != nil) then print "Error at #{bufsize} : #{_error}" end # puts "\nBufsize is #{bufsize}, binary message size is #{response.encode.size}" assert_equal(true, response.header.tc) assert(response.encode.size <= bufsize) end #run_test.(512) #run_test.(612) run_test.(4096) end def test_rropt size=2048; ednsflags=0x9e22; optrr = RR::OPT.new(size, ednsflags) assert(optrr.dnssec_ok,"DO bit set") optrr.dnssec_ok=false assert_equal(optrr.flags,0x1e22,"Clearing do, leaving the other bits "); assert(!optrr.dnssec_ok,"DO bit cleared") optrr.dnssec_ok=true assert_equal(optrr.flags,0x9e22,"Clearing do, leaving the other bits "); assert_equal(optrr.payloadsize,2048,"Size read") assert_equal(optrr.payloadsize=(1498),1498,"Size set") optrr.set_client_subnet("0.0.0.0/0") assert_equal(optrr.edns_client_subnet,"0.0.0.0/0/0","Wildcard Address") optrr.set_client_subnet("216.253.14.2/24") assert_equal(optrr.edns_client_subnet,"216.253.14.0/24/0","IPv4 subnet") optrr.set_client_subnet("216.253.14.2/1") assert_equal(optrr.edns_client_subnet,"216.0.0.0/1/0","IPv4 subnet <8 bits") optrr.set_client_subnet("2600:3c00:0:91fd:ab77:157e::/64") assert_equal(optrr.edns_client_subnet,"2600:3c00:0:91fd::/64/0","IPv6 subnet") optrr.set_client_subnet("2600:3c00:0:91fd:ab77:157e::/7") assert_equal(optrr.edns_client_subnet,"2600::/7/0","IPv6 subnet <8 bits") end def test_resolver_opt_application return if (/java/ =~ RUBY_PLATFORM) # @TODO@ Check if this is fixed with JRuby yet # Set up a server running on localhost. Get the resolver to send a # query to it with the UDP size set to 4096. Make sure that it is received # correctly. Dnsruby::PacketSender.clear_caches socket = UDPSocket.new socket.bind("127.0.0.1", 0) port = socket.addr[1] q = Queue.new Thread.new { s = socket.recvfrom(65536) received_query = s[0] socket.connect(s[1][2], s[1][1]) q.push(Message.decode(received_query)) socket.send(received_query,0) } # Now send query res = Resolver.new("127.0.0.1") res.dnssec = true res.port = port res.udp_size = 4096 assert(res.udp_size == 4096) res.query("example.com") # Now get received query from the server p = q.pop # Now check the query was what we expected assert(p.header.arcount == 1) assert(p.additional()[0].type = Types.OPT) assert(p.additional()[0].klass.code == 4096) end # Sadly Nominet no longer host these servers :-( # def test_large_packet # # Query TXT for overflow.dnsruby.validation-test-servers.nominet.org.uk # # with a large udp_size # res = SingleResolver.new # res.udp_size = 4096 # ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT) # assert(ret.rcode == RCode.NoError) # end def test_decode_opt # Create an OPT RR size=2048; ednsflags=0x9e22; optrr = RR::OPT.new(size, ednsflags) # Add it to a message m = Message.new m.add_additional(optrr) # Encode the message data = m.encode # Decode it m2 = Message.decode(data) # Make sure there is an OPT RR there assert(m2.rcode == RCode.NOERROR ) end def test_formerr_response # If we get a FORMERR back from the remote resolver, we should retry with no OPT record # So, we need a server which sends back FORMERR for OPT records, and is OK without them. # Then, we need to get a client to send a request to it (by default adorned with EDNS0), # and make sure that the response is returned to the client OK. # We should then check that the server only received one message with EDNS0, and one message # without. return if (/java/ =~ RUBY_PLATFORM) # @TODO@ Check if this is fixed with JRuby yet # Set up a server running on localhost. Get the resolver to send a # query to it with the UDP size set to 4096. Make sure that it is received # correctly. Dnsruby::PacketSender.clear_caches socket = UDPSocket.new socket.bind("127.0.0.1", 0) port = socket.addr[1] q = Queue.new Thread.new { 2.times { s = socket.recvfrom(65536) received_query = s[0] m = Message.decode(received_query) q.push(m) if (m.header.arcount > 0) # send back FORMERR m.header.rcode = RCode.FORMERR socket.send(m.encode,0,s[1][2], s[1][1]) else socket.send(received_query,0,s[1][2], s[1][1]) # @TODO@ FORMERR if edns end } } # Now send query res = Resolver.new("127.0.0.1") res.dnssec = true res.port = port res.udp_size = 4096 assert(res.udp_size == 4096) ret = res.query("example.com") assert(ret.header.get_header_rcode == RCode.NOERROR) assert(ret.header.arcount == 0) # Now get received query from the server p = q.pop # Now check the query was what we expected assert(p.header.arcount == 1) assert(p.additional()[0].type = Types.OPT) assert(p.additional()[0].klass.code == 4096) # Now check the second message assert (!(q.empty?)) p2 = q.pop assert (p2) assert(p2.header.arcount == 0) end end dnsruby-1.61.3/test/tc_cache.rb0000644000004100000410000001262613607400362016365 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestCache < Minitest::Test include Dnsruby def test_cache cache = Cache.new m1 = Message.new("example.com.", Types.A, Classes.IN) rr1 = RR.create("example.com. 3 IN A 208.77.188.166") m1.add_answer(rr1) m1.header.aa = true assert(!m1.cached) cache.add(m1) ret = cache.find("example.com", "A") assert(ret.cached) assert(ret.answer.rrset("example.com", "A").to_s == m1.answer.rrset("example.com", "A").to_s, "#{m1.answer.rrset("example.com", "A").to_s}end\n#{ret.answer.rrset("example.com", "A").to_s}end" ) assert(ret.header.aa == false) assert(ret.answer.rrsets()[0].ttl == 3) sleep(1) ret = cache.find("example.com", "A") assert(ret.cached) assert((ret.answer.rrsets()[0].ttl == 2) || (ret.answer.rrsets()[0].ttl == 1), "ttl = #{ret.answer.rrsets()[0].ttl}") assert(ret.answer != m1.answer, "ret.answer=#{ret.answer}\nm1.answer=#{m1.answer}" ) assert(ret.header.aa == false) sleep(2) # TTL of 3 should have timed out now ret = cache.find("example.com", "A") assert(!ret) cache.add(m1) m2 = Message.new("example.com.", Types.A, Classes.IN) rr2 = RR.create("example.com. 200 IN A 208.77.188.166") m2.add_answer(rr2) m2.header.aa = true cache.add(m2) ret = cache.find("example.com", "A") assert(ret.cached) assert(ret.answer.rrsets()[0].ttl == 200) end def test_opt_record # Create a very large message, encode it and decode it - there should be an opt record # test getting that in and out the cache # We should be able to do this in the online test by getting back a very big # record from the test zone end def test_negative end def test_cache_max_size Dnsruby::Cache.max_size=1 res = Resolver.new() Dnsruby::PacketSender.clear_caches() assert(Dnsruby::PacketSender.recursive_cache_length == 0) msg = res.query("example.com") assert(!msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 1) msg = res.query("example.com") assert(msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 1) msg = res.query("google.com") assert(!msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 1) msg = res.query("example.com") assert(!msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 1) Dnsruby::Cache.max_size=2 assert(Dnsruby::PacketSender.recursive_cache_length == 1) msg = res.query("example.com") assert(msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 1) msg = res.query("google.com") assert(!msg.cached) assert(Dnsruby::PacketSender.recursive_cache_length == 2) end def test_resolver_do_caching # Get the records back from the test zone Dnsruby::PacketSender.clear_caches res = Resolver.new() res.do_caching = false assert(!res.do_caching) ret = res.query("example.com") assert(!ret.cached) assert(ret.rcode == RCode.NoError) # Wait a while sleep(1) # Ask for the same records ret = res.query("example.com") assert(ret.rcode == RCode.NoError) assert(!ret.cached) end def test_online # Get the records back from the test zone Dnsruby::PacketSender.clear_caches Dnsruby::Recursor.clear_caches res = SingleResolver.new("ns.nlnetlabs.nl.") # res = SingleResolver.new("ns0.validation-test-servers.nominet.org.uk.") res.udp_size = 4096 query = Message.new("net-dns.org", Types.TXT) # query = Message.new("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT) ret = res.send_message(query) # print "#{ret}\n" assert(!ret.cached) assert(ret.rcode == RCode.NoError) assert(ret.header.aa) # Store the ttls first_ttls = ret.answer.rrset( "net-dns.org", Types.TXT).ttl # "overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT).ttl # Wait a while sleep(1) # Ask for the same records query = Message.new("net-dns.org", Types.TXT) # query = Message.new("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT) ret = res.send_message(query) # print "#{ret}\n" assert(ret.rcode == RCode.NoError) assert(ret.cached) second_ttls = ret.answer.rrset( "net-dns.org", Types.TXT).ttl # "overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT).ttl # make sure the ttl is less the time we waited assert((second_ttls == first_ttls - 1) || (second_ttls == first_ttls - 2), "First ttl = #{first_ttls}, second = #{second_ttls}\n") # make sure the header flags (and ID) are right assert(ret.header.id == query.header.id, "First id = #{query.header.id}, cached response was #{ret.header.id}\n") assert(!ret.header.aa) end def test_online_uncached # @TODO@ Check that wildcard queries are not cached end end dnsruby-1.61.3/test/tc_axfr.rb0000644000004100000410000000234013607400362016252 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require_relative 'spec_helper' class TestAxfr < Minitest::Test def test_axfr zt = Dnsruby::ZoneTransfer.new zt.transfer_type = Dnsruby::Types.AXFR zt.server = 'nsztm1.digi.ninja' if contactable?(zt.server) zone = zt.transfer('zonetransfer.me') assert(zone.length > 0) assert_nil(zt.last_tsigstate) end end def contactable?(server) begin sock = UDPSocket.new sock.connect(server, 25) sock.close true rescue Exception false end end # NB - test_ixfr is in tc_tsig.rg - this is becuase it requires # TSIG to make an update (which we can then test for with ixfr) end dnsruby-1.61.3/README.md0000644000004100000410000000623013607400362014601 0ustar www-datawww-data[![Build Status](https://travis-ci.org/alexdalitz/dnsruby.svg?branch=master)](https://travis-ci.org/alexdalitz/dnsruby) [![Coverage Status](https://img.shields.io/coveralls/alexdalitz/dnsruby.svg)](https://coveralls.io/r/alexdalitz/dnsruby?branch=master) Dnsruby ======= Dnsruby is a pure Ruby DNS client library which implements a stub resolver. It aims to comply with all DNS RFCs. Dnsruby presents an enhanced API for DNS. It is based on Ruby's core resolv.rb Resolv API, but has been much extended to provide a complete DNS implementation. Dnsruby runs a single I/O thread to handle all concurrent queries. It is therefore suitable for high volume DNS applications. The following is a (non-exhaustive) list of features : - Implemented RRs : A, AAAA, AFSDB, ANY, CAA, CERT, CNAME, DNAME, GPOS, HINFO, ISDN, LOC, MB, MG, MINFO, MR, MX, NAPTR, NS, NSAP, NXT, OPT, PTR, PX, RP, RT, SOA, SPF, SRV, TKEY, TSIG, TXT, WKS, X25, DNSKEY, RRSIG, NSEC, NSEC3, NSEC3PARAM, DS, DLV - Generic RR types supported (RFC3597) - (Signed) Zone transfer (AXFR and IXFR) supported - (Signed) Dynamic updates supported - DNSSEC validation supported Dependencies ------------ Dnsruby can run with no dependencies. However, if you wish to use TSIG or DNSSEC then the OpenSSL library must be available. This is a part of the Ruby standard library, but appears not to be present on all Ruby platforms. If it is not available, then the test code will not run the tests which require it. Code which attempts to use the library (if it is not present) will raise an exception. Demo Code --------- The demo folder contains some example programs using Dnsruby. These examples include a basic dig tool (rubydig) and a tool to concurrently resolve many names, amongst others. Unit Tests ---------- Tests require a current version of minitest (see the .gemspec file for which version is required). In order for the tests to run successfully you may need to have the bundler gem installed and run `bundle` or `bundle install` from the project root to install a suitable version of minitest. There are "online" and "offline" tests. You can use rake to conveniently run the tests. From the project root you can run: ``` rake test # run all tests rake test_offline # run only offline tests rake test_online # run only online tests ``` If you get the following error when running rake test tasks, then you may need to preface the command with bundle exec to ensure that the gem versions specified in Gemfile.lock are used at runtime: ``` bundle exec rake test ``` Usage Help ---------- There are a couple of blog articles that might be helpful in understanding how to use Dnsruby. These used to be hosted by Nominet UK, however the original content has been copied to the dnsruby github wiki at : https://github.com/alexdalitz/dnsruby/wiki Contact/Links ------- | Link Type | Link/Text | |-----|----- | Author Email | alex@caerkettontech.com | | Github | https://github.com/alexdalitz/dnsruby | | Google Group | https://groups.google.com/forum/#!forum/dnsruby | | Rubygems | http://rubygems.org/gems/dnsruby/ | dnsruby-1.61.3/EXAMPLES0000644000004100000410000001062113607400362014462 0ustar www-datawww-data# This file shows how to do common tasks with Dnsruby : require 'rubygems' require 'dnsruby' include Dnsruby # Use the system configured nameservers to run a query res = Resolver.new ret = res.query("example.com") # Defaults to A record a_recs = ret.answer.rrset("A") # Use a defined nameserver to run an asynchronous query # with no recursion res = Resolver.new({:nameserver => ["a.iana-servers.net", "b.iana-servers.net"]}) queue = Queue.new m = Message.new("example.com", Types.NS) m.header.rd = false res.send_async(m, queue, 1) # ... do some other stuff ... id, reply, error = queue.pop if (error) print "Error : #{error}\n" else # See where the answer came from print "Got response from : #{reply.answerfrom}, #{reply.answerip}\n" end # Use a Recursor to recursively query authoritative nameservers, # starting from the root. Note that a cache of authoritative servers # is built up for use by future queries by any Recursors. rec = Recursor.new ret = rec.query("uk-dnssec.nic.uk", "NS") # Ask Dnsruby to send the query without using the cache. m.do_caching = false ret = res.send_message(m) # Ask Dnsruby to send a Message without doing any pre- or post-processing ret = res.send_plain_message(Message.new("example.com")) # Send a TSIG signed dynamic update to a resolver # and verify the response res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") res.dnssec = false tsig = Dnsruby::RR.create({ :name => "rubytsig", :type => "TSIG", :ttl => 0, :klass => "ANY", :algorithm => "hmac-md5", :fudge => 300, :key => "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==", :error => 0 }) update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") # ... add stuff to the update update.absent("notthere.update.validation-test-servers.nominet.org.uk", 'TXT') tsig.apply(update) response = res.send_message(update) print "TSIG response was verified? : #{response.verified?}\n" # # DNSSEC stuff # # Load the ISC DLV key and query some signed zones dlv_key = RR.create("dlv.isc.org. IN DNSKEY 257 3 5 BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh") Dnssec.add_dlv_key(dlv_key) res = Recursor.new ret = res.query("frobbit.se", "NS") print "Security level for signed zone from DLV : #{ret.security_level}\n" frobbit_servers = ret.answer.rrset("frobbit.se", Types.NS) # and query for a zone which is not signed r = Resolver.new ret = r.query("ed.ac.uk") print "Security level of unsigned zone : #{ret.security_level}\n" res = Resolver.new frobbit_servers.rrs.each {|s| print "Adding nameserver : #{s.nsdname}\n"; res.add_server(s.nsdname)} # and some non-existent domains in signed ones res.send_async(Message.new("notthere.frobbit.se"), queue, 2) id, reply, error = queue.pop print "Error returned from non-existent name in signed zone : #{error}, security level : #{reply.security_level}\n" # Clear the keys and caches Dnsruby::Dnssec.clear_trusted_keys Dnsruby::Dnssec.clear_trust_anchors Dnsruby::PacketSender.clear_caches Dnsruby::Recursor.clear_caches # Load a specific trust anchor and query some signed zones trusted_key = Dnsruby::RR.create({:name => "uk-dnssec.nic.uk.", :type => Dnsruby::Types.DNSKEY, :flags => 257, :protocol => 3, :algorithm => 5, :key=> "AQPJO6LjrCHhzSF9PIVV7YoQ8iE31FXvghx+14E+jsv4uWJR9jLrxMYm sFOGAKWhiis832ISbPTYtF8sxbNVEotgf9eePruAFPIg6ZixG4yMO9XG LXmcKTQ/cVudqkU00V7M0cUzsYrhc4gPH/NKfQJBC5dbBkbIXJkksPLv Fe8lReKYqocYP6Bng1eBTtkA+N+6mSXzCwSApbNysFnm6yfQwtKlr75p m+pd0/Um+uBkR4nJQGYNt0mPuw4QVBu1TfF5mQYIFoDYASLiDQpvNRN3 US0U5DEG9mARulKSSw448urHvOBwT9Gx5qF2NE4H9ySjOdftjpj62kjb Lmc8/v+z" }) Dnssec.add_trust_anchor(trusted_key) res = Dnsruby::Resolver.new("dnssec.nominet.org.uk") r = res.query("aaa.bigzone.uk-dnssec.nic.uk", Dnsruby::Types.DNSKEY) print "Security level of signed zone under manually install trusted key : #{r.security_level}\n" # See if we are using a Recursor for DNSSEC queries print "Using recursion to validate DNSSEC responses? : #{Dnssec.do_validation_with_recursor?}\n" dnsruby-1.61.3/.gitignore0000644000004100000410000000027713607400362015317 0ustar www-datawww-data*.gem *.rbc .bundle .config .yardoc Gemfile.lock InstalledFiles _yardoc coverage doc/ .idea/ lib/bundler/man pkg rdoc spec/reports test/tmp test/version_tmp tmp *.bundle *.so *.o *.a mkmf.logdnsruby-1.61.3/LICENSE0000644000004100000410000000104213607400362014323 0ustar www-datawww-data# Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. dnsruby-1.61.3/dnsruby.gemspec0000644000004100000410000000311513607400362016354 0ustar www-datawww-datalib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) require 'dnsruby/version' SPEC = Gem::Specification.new do |s| s.name = "dnsruby" s.version = Dnsruby::VERSION s.authors = ["Alex Dalitz"] s.email = 'alex@caerkettontech.com' s.homepage = "https://github.com/alexdalitz/dnsruby" s.platform = Gem::Platform::RUBY s.summary = "Ruby DNS(SEC) implementation" s.description = \ 'Dnsruby is a pure Ruby DNS client library which implements a stub resolver. It aims to comply with all DNS RFCs, including DNSSEC NSEC3 support.' s.license = "Apache License, Version 2.0" s.files = `git ls-files -z`.split("\x0") s.post_install_message = \ "Installing dnsruby... For issues and source code: https://github.com/alexdalitz/dnsruby For general discussion (please tell us how you use dnsruby): https://groups.google.com/forum/#!forum/dnsruby" s.test_file = "test/ts_offline.rb" s.extra_rdoc_files = ["DNSSEC", "EXAMPLES", "README.md", "EVENTMACHINE"] unless /java/ === RUBY_PLATFORM s.add_development_dependency 'pry', '~> 0.10' s.add_development_dependency 'pry-byebug', '~> 2.0' if RUBY_VERSION >= '2' end s.add_development_dependency 'rake', '~> 10', '>= 10.3.2' s.add_development_dependency 'minitest', '~> 5.4' s.add_development_dependency 'rubydns', '~> 2.0.1' s.add_development_dependency 'nio4r', '~> 2.0' s.add_development_dependency 'minitest-display', '>= 0.3.0' if RUBY_VERSION >= "1.9.3" s.add_development_dependency 'coveralls', '~> 0.7' end s.add_runtime_dependency 'addressable', '~> 2.5' end dnsruby-1.61.3/RELEASE_NOTES.md0000644000004100000410000001431613607400362015700 0ustar www-datawww-data# Release Notes ##v1.61.2 * Add new root key ## v1.61.1 * Add Addressable as a gem runtime dependency ## v1.61.0 * Add URI, CDS and CDNSKEY records * Supply port to DNS.new as optiona parameter * Supply timeout to zone transfer connect * Fix multi-line strings * Try absolute name as candidate in DNS even if not dot supplied * Do not try to generate candidates if no domain is given * Handle new OpenSSL interface as well as old * Handle new DSA interface * fix encode error select thread issue * handle encoding errors * add punycode support * Make sure dnssec is enabled in verifier and also in digroot demo * Other minor fixes and changes to test code and infrastructure ## v1.60.2 * Fix deletion of TXT records with spaces in dynamic updates (thanks Sean Dilda) * Fix use of non-default ports in Dnsruby::Resolver (thanks Thomas Morgan) * Fix NAPTR encoding for null rdata dynamic update packets * Fix CAA resource record encoding * Avoid changing ruby global thread abort behavior (thanks Brent Cook) ## v1.60.1 * DNSSEC validation switched OFF by default (but can still be switched on) * Add APL RR support (thanks Manabu Sonoda) * Various test fixes (thanks Keith Bennett) * 'include' issues fixed (thanks Keith Bennett!) * Fixnum replacement (thanks Keith Bennett) * Zone transfer fixes (thanks Manabu Sonoda) * Name decoding fix * MX record passing error now raised * CAA RR support (thanks Richard Luther) * TLSA RR support (thanks Manabu Sonoda) ## v1.60.0 * TCP multi-packet support fixed * Response 'Message' now included with exception. * Docs added * CNAME dynamic update fix ## v1.59.3 * Output TXT record multiple strings correctly * NONE class encoding fix * only add name labels if there are any ## v1.59.2 * Timeout error fix ## v1.59.1 * Support for HMAC SHA512 TSIG keys * Fix TCP pipelining tests * IDN encoding error returned as Dnsruby::OtherResolvError ## v1.59.0 * Add LICENSE file * Add Cache max_size (gihub issue 64) * Disable caching for SOA lookups in demo check_soa.rb * Fix for invalid nameserver in config * Fix encoding for OPT data (thanks Craig Despeaux) * Various test system fixes * OPT fixes * DNSSEC verification failure handling wrt lack of DS chain * DNSSEC validation policy name constants * Fix for BOGUS DLV chains * demo upgrades * Resolver hints improvements ## v1.58.0 * Add TCP pipelining (reusing a single TCP connection for multiple requests). * Enhance zone reading, including reading data from a string. * Add add_answer! method for adding duplicate answers, as needed for an AXFR response. * Add support for GPOS and NXT resource records. * Test cleanup, including removal of use of Nominet servers, soak_test cleanup. * Refactorings: MessageDecoder, Resolv, Resolver (part). * Fix zone reader adding unwanted dot to relative hostnames being converted to absolute. * Fix default access for tsig options in Resolver. * Fix ZoneTransfer not to use deprecated SingleResolver. * Fix Resolver bug in parameter to create_tsig_options. * Fix tests to always use working copy and not gem. ## v1.57.0 * Add query_raw method as alias for send_plain_message, with option to raise or return error. * Fixed a bug in RR hash calculation where TTL should have been ignored but wasn't. * Add support for (obsolete) GPOS resource record type. * Tweak Travis CI configuration. * Fix zone reader for case where a line contains whitespace preceding a comment. * Add post install message. * Improve README. * Moved content of NEWS to RELEASE_NOTES.md. * Use git ls-files now to determine files for inclusion in gem. ## v1.56.0 * Drop support for Ruby 1.8, using lambda -> and hash 'key: value' notations. * First release since the move from Rubyforge to Github (https://github.com/alexdalitz/dnsruby). * Add EDNS client subnet support. * Relocate CodeMapper subclasses, Resolv, RR, and RRSet classes. * Add Travis CI and coveralls integration. * Improve Google IPV6 support. * Convert some file names to snake case. * Remove trailing whitespace from lines, and ensure that comments have space between '#' and text. * Restore test success when running under JRuby. * Disabled attempt to connect to Nominet servers, which are no longer available. * Convert from test/unit to minitest/autorun to support Ruby 2.1+. * Remove setup.rb. * Other minor refactoring and improvements to production code, test code, and documentation. ## v1.53 * Validation routine fixes * Ruby 1.9 fixes * Recursor fixes * IPv4 Regex fixes * Fixes for A/PTR lookups with IP-like domain name * TXT and SSHFP processing fixes * Default retry parameters in Resolver more sensible ## v1.48 * Fixed deadlock/performance issue seen on some platforms * DNSSEC validation now disabled by default * Signed root DS record can be added to validator * ITAR support removed * multi-line DS/RRSIG reading bug fixed (thanks Marco Davids!) * DS algorithms of more than one digit can now be read from string * LOC records now parsed correctly * HINFO records now parsed correctly ## v1.42 * Complicated TXT and NAPTR records now handled correctly * ZoneReader now handles odd escape characters correctly * Warns when immediate timeout occurs because no nameservers are configured * Easy hmac-sha1/256 options to Resolver#tsig= * ZoneReader fixed for "IN CNAME @" notations * ZoneReader supports wildcards * Dnsruby.version method added - currently returns 1.42 ## v1.41 * RFC3597 unknown classes (e.g. CLASS32) now handled correctly in RRSIGs * Resolver#do_caching flag added for Resolver-level caching * DNSKEY#key_tag now cached - only recalculated when key data changes * Bugfix where Resolver would not time queries out if no nameservers were configured * Recursor now performs A and AAAA queries in parallel * Fix for zero length salt * Fixing priming for signed root * Fixes for DLV verification * Other minor fixes ## v1.40 * Zone file reading support added (Dnsruby::ZoneReader) * Name and Label speed-ups * CodeMapper speed-ups * DHCID RR added * LOC presentation format parsing fixed * KX RR added * Quotations now allowed in text representation for ISDN, X25 and HINFO * AFSDB from_string fixes * Fixing CERT types and from_string * CERT now allows algorithm 0 * Fix for DS record comparison * HIP RR added * Minor bug fixes * IPSECKEY RR added * Clients can now manipulate Name::Labels dnsruby-1.61.3/SIGNED_UPDATES0000644000004100000410000000163013607400362015402 0ustar www-datawww-dataSigned updates with Dnsruby =========================== In order to use TSIG records to automatically perform TSIG signing/verification of messages : res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") # Now configure the resolver with the TSIG key for signing/verifying KEY_NAME="rubytsig" KEY = "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==" res.tsig=KEY_NAME, KEY # Now try sending/receiving some update messages update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") update_name = generate_update_name update.absent(update_name) update.add(update_name, 'TXT', 100, "test signed update") # Resolver will automatically sign message and verify response response = res.send_message(update) assert(response.verified?) # Check that the response has been verifieddnsruby-1.61.3/Rakefile0000644000004100000410000000173513607400362014774 0ustar www-datawww-datarequire 'rake/testtask' ENV['RUN_EXTRA_TASK'] = 'TRUE' if RUBY_VERSION >= "1.9.3" && defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ruby' if ENV['RUN_EXTRA_TASK'] == 'TRUE' require 'rdoc/task' Rake::RDocTask.new do |rd| rd.rdoc_files.include("lib/**/*.rb") rd.rdoc_files.exclude("lib/Dnsruby/iana_ports.rb") rd.main = "Dnsruby" # rd.options << "--ri" end require 'coveralls/rake/task' Coveralls::RakeTask.new end def create_task(task_name, test_suite_filespec) Rake::TestTask.new do |t| t.name = task_name t.test_files = FileList[test_suite_filespec] t.verbose = true t.warning = false end end create_task(:test, 'test/ts_dnsruby.rb') create_task(:test_offline, 'test/ts_offline.rb') create_task(:test_online, 'test/ts_online.rb') create_task(:soak, 'test/tc_soak.rb') create_task(:message, 'test/tc_message.rb') create_task(:cache, 'test/tc_cache.rb') create_task(:pipe, 'test/tc_tcp_pipelining.rb') dnsruby-1.61.3/lib/0000755000004100000410000000000013607400362014067 5ustar www-datawww-datadnsruby-1.61.3/lib/dnsruby/0000755000004100000410000000000013607400362015555 5ustar www-datawww-datadnsruby-1.61.3/lib/dnsruby/bit_mapping.rb0000644000004100000410000001036713607400362020402 0ustar www-datawww-data# This code is copied from the trick_bag gem (see https://github.com/keithrbennett/trick_bag). # It is copied a) to avoid adding a new dependency and b) because that gem is in # version 0 and is unstable. module Dnsruby # Provides methods for converting between the various representations # of a bitmap: number, binary encoded string, array, and sparse array. # # Where an array is used to represent bits, the first element (#0) will be the # low (1) bit and the last bit will be the high bit. module BitMapping module_function # Converts from a binary string to a number, e.g. "\x01\x00" => 256 def binary_string_to_number(string) string = string.clone.force_encoding(Encoding::ASCII_8BIT) string.bytes.inject(0) do |number, byte| number * 256 + byte.ord end end # Converts a number to a binary encoded string, e.g. 256 => "\x01\x00" def number_to_binary_string(number, min_length = 0) assert_non_negative(number) binary_string = ''.force_encoding(Encoding::ASCII_8BIT) while number > 0 byte_value = number & 0xFF binary_string << byte_value number >>= 8 end binary_string.reverse.rjust(min_length, "\x00") end # Converts a number to an array of place values, e.g. 9 => [8, 0, 0, 1] def number_to_place_value_array(number) assert_non_negative(number) array = [] bit_value = 1 while number > 0 array << ((number & 1 == 1) ? bit_value : 0) number >>= 1 bit_value <<= 1 end array.reverse end # Converts from a value array to a number, e.g. [8, 0, 0, 1] => 9 def place_value_array_to_number(place_value_array) place_value_array.inject(&:+) end # Converts a number to an array of bit values, e.g. 9 => [1, 0, 0, 1] def number_to_bit_array(number, minimum_binary_places = 0) assert_non_negative(number) array = [] while number > 0 array << (number & 1) number >>= 1 end array.reverse! zero_pad_count = minimum_binary_places - array.size zero_pad_count.times { array.unshift(0) } array end # Converts an array of bit values, e.g. [1, 0, 0, 1], to a number, e.g. 9 def bit_array_to_number(bit_array) return nil if bit_array.empty? multiplier = 1 bit_array.reverse.inject(0) do |result, n| result += n * multiplier multiplier *= 2 result end end # Converts a number to a sparse array containing bit positions that are set/true/1. # Note that these are bit positions, e.g. 76543210, and not bit column values # such as 128/64/32/16/8/4/2/1. def number_to_set_bit_positions_array(number) assert_non_negative(number) array = [] position = 0 while number > 0 array << position if number & 1 == 1 position += 1 number >>= 1 end array end # Converts an array of bit position numbers to a numeric value, e.g. [0, 2] => 5 def set_bit_position_array_to_number(position_array) return nil if position_array.empty? position_array.inject(0) do |result, n| result += 2 ** n end end # Converts a binary string to an array of bit values, e.g. "\x0C" => [1, 1, 0, 0] def binary_string_to_bit_array(string, minimum_binary_places = 0) number = binary_string_to_number(string) number_to_bit_array(number, minimum_binary_places) end # If number is negative, raises an ArgumentError; else does nothing. def assert_non_negative(number) unless number.is_a?(Integer) && number >= 0 raise ArgumentError.new( "Parameter must be a nonnegative Integer " + "but is #{number.inspect} (a #{number.class})") end end # Reverses a binary string. Note that it is not enough to reverse # the string itself because although the bytes would be reversed, # the bits within each byte would not. def reverse_binary_string_bits(binary_string) binary_place_count = binary_string.size * 8 reversed_bit_array = binary_string_to_bit_array(binary_string, binary_place_count).reverse number = bit_array_to_number(reversed_bit_array) number_to_binary_string(number) end end end dnsruby-1.61.3/lib/dnsruby/the_log.rb0000644000004100000410000000242013607400362017521 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'logger' require 'singleton' require 'thread' module Dnsruby # This class exists for backwards compatibility. # # It's Logger (which defaults to STDOUT, level FATAL) can be configured, or a new Logger can be supplied. # # Dnsruby::TheLog.level=Logger::DEBUG # Dnsruby::TheLog.debug("Debug message") # class TheLog # Set a new Logger for use by Dnsruby def set_logger(logger) Dnsruby.log = logger end # Change the Logger level. def level=(level) Dnsruby.log.level = level end def level return Dnsruby.log.level end def self.method_missing(symbol, *args) #:nodoc: all Dnsruby.log.send(symbol, *args) end end enddnsruby-1.61.3/lib/dnsruby/version.rb0000644000004100000410000000005013607400362017562 0ustar www-datawww-datamodule Dnsruby VERSION = '1.61.3' end dnsruby-1.61.3/lib/dnsruby/DNS.rb0000644000004100000410000002332413607400362016532 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby/hosts' require 'dnsruby/config' require "dnsruby/resolver" module Dnsruby # == Dnsruby::DNS class # Resolv::DNS performs DNS queries. # # === class methods # * Dnsruby::DNS.new(config_info=nil) # # ((|config_info|)) should be nil, a string or a hash. # If nil is given, /etc/resolv.conf and platform specific information is used. # If a string is given, it should be a filename which format is same as /etc/resolv.conf. # If a hash is given, it may contains information for nameserver, search and ndots as follows. # # Dnsruby::DNS.new({:nameserver=>["210.251.121.21"], :search=>["ruby-lang.org"], :ndots=>1}) # # * Dnsruby::DNS.open(config_info=nil) # * Dnsruby::Resolv::DNS.open(config_info=nil) {|dns| ...} # # === methods # * Dnsruby::DNS#close # # * Dnsruby::DNS#getaddress(name) # * Dnsruby::DNS#getaddresses(name) # * Dnsruby::DNS#each_address(name) {|address| ...} # address lookup methods. # # ((|name|)) must be an instance of Dnsruby::Name or String. Resultant # address is represented as an instance of Dnsruby::IPv4 or Dnsruby::IPv6. # # * Dnsruby::DNS#getname(address) # * Dnsruby::DNS#getnames(address) # * Dnsruby::DNS#each_name(address) {|name| ...} # These methods lookup hostnames . # # ((|address|)) must be an instance of Dnsruby::IPv4, Dnsruby::IPv6 or String. # Resultant name is represented as an instance of Dnsruby::Name. # # * Dnsruby::DNS#getresource(name, type, class) # * Dnsruby::DNS#getresources(name, type, class) # * Dnsruby::DNS#each_resource(name, type, class) {|resource| ...} # These methods lookup DNS resources of ((|name|)). # ((|name|)) must be a instance of Dnsruby::Name or String. # # ((|type|)) must be a member of Dnsruby::Types # ((|class|)) must be a member of Dnsruby::Classes # # Resultant resource is represented as an instance of (a subclass of) # Dnsruby::RR. # (Dnsruby::RR::IN::A, etc.) # # The searchlist and other Config info is applied to the domain name if appropriate. All the nameservers # are tried (if there is no timely answer from the first). # # This class uses Resolver to perform the queries. # # Information taken from the following places : # * STD0013 # * RFC 1035, etc. # * ftp://ftp.isi.edu/in-notes/iana/assignments/dns-parameters # * etc. class DNS attr_accessor :do_caching # Creates a new DNS resolver. See Resolv::DNS.new for argument details. # # Yields the created DNS resolver to the block, if given, otherwise returns it. def self.open(*args) dns = new(*args) return dns unless block_given? begin yield dns ensure dns.close end end # Closes the resolver def close @resolver.close end def to_s return "DNS : " + @config.to_s end # Creates a new DNS resolver # # +config_info+ can be: # # * nil:: Uses platform default (e.g. /etc/resolv.conf) # * String:: Path to a file using /etc/resolv.conf's format # * Hash:: Must contain :nameserver, :search and :ndots keys # example : # # Dnsruby::DNS.new({:nameserver => ['210.251.121.21'], # :search => ['ruby-lang.org'], # :ndots => 1}) def initialize(config_info=nil) @do_caching = true @config = Config.new() @config.set_config_info(config_info) @resolver = Resolver.new(@config) # if (@resolver.single_resolvers.length == 0) # raise ArgumentError.new("Must pass at least one valid resolver address") # end end attr_reader :config # Gets the first IP address of +name+ from the DNS resolver # # +name+ can be a Dnsruby::Name or a String. Retrieved address will be a # Dnsruby::IPv4 or a Dnsruby::IPv6 def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("DNS result has no information for #{name}") end # Gets all IP addresses of +name+ from the DNS resolver # # +name+ can be a Dnsruby::Name or a String. Retrieved address will be a # Dnsruby::IPv4 or a Dnsruby::IPv6 def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end # Iterates over all IP addresses of +name+ retrieved from the DNS resolver # # +name+ can be a Dnsruby::Name or a String. Retrieved address will be a # Dnsruby::IPv4 or a Dnsruby::IPv6 def each_address(name) each_resource(name) {|resource| yield resource.address} end # Gets the first hostname for +address+ from the DNS resolver # # +address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved # name will be a Dnsruby::Name. def getname(address) each_name(address) {|name| return name} raise ResolvError.new("DNS result has no information for #{address}") end # Gets all hostnames for +address+ from the DNS resolver # # +address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved # name will be a Dnsruby::Name. def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end # Iterates over all hostnames for +address+ retrieved from the DNS resolver # # +address+ must be a Dnsruby::IPv4, Dnsruby::IPv6 or a String. Retrieved # name will be a Dnsruby::Name. def each_name(address) case address when Name ptr = address when IPv4, IPv6 ptr = address.to_name when IPv4::Regex ptr = IPv4.create(address).to_name when IPv6::Regex ptr = IPv6.create(address).to_name else raise ResolvError.new("cannot interpret as address: #{address}") end each_resource(ptr, Types.PTR, Classes.IN) {|resource| yield resource.domainname} end # Look up the first +type+, +klass+ resource for +name+ # # +type+ defaults to Dnsruby::Types.A # +klass+ defaults to Dnsruby::Classes.IN # # Returned resource is represented as a Dnsruby::RR instance, e.g. # Dnsruby::RR::IN::A def getresource(name, type=Types.A, klass=Classes.IN) each_resource(name, type, klass) {|resource| return resource} raise ResolvError.new("DNS result has no information for #{name}") end # Look up all +type+, +klass+ resources for +name+ # # +type+ defaults to Dnsruby::Types.A # +klass+ defaults to Dnsruby::Classes.IN # # Returned resource is represented as a Dnsruby::RR instance, e.g. # Dnsruby::RR::IN::A def getresources(name, type=Types.A, klass=Classes.IN) ret = [] each_resource(name, type, klass) {|resource| ret << resource} return ret end # Iterates over all +type+, +klass+ resources for +name+ # # +type+ defaults to Dnsruby::Types.A # +klass+ defaults to Dnsruby::Classes.IN # # Yielded resource is represented as a Dnsruby::RR instance, e.g. # Dnsruby::RR::IN::A def each_resource(name, type=Types.A, klass=Classes.IN, &proc) type = Types.new(type) klass = Classes.new(klass) reply, reply_name = send_query(name, type, klass) case reply.rcode.code when RCode::NOERROR extract_resources(reply, reply_name, type, klass, &proc) return # when RCode::NXDomain # Dnsruby.log.debug("RCode::NXDomain returned - raising error") # raise Config::NXDomain.new(reply_name.to_s) else Dnsruby.log.error{"Unexpected rcode : #{reply.rcode.string}"} raise Config::OtherResolvError.new(reply_name.to_s) end end def extract_resources(msg, name, type, klass) # :nodoc: if type == Types.ANY n0 = Name.create(name) msg.each_answer {|rec| yield rec if n0 == rec.name } end yielded = false n0 = Name.create(name) msg.each_answer {|rec| if n0 == rec.name case rec.type when type if (rec.klass == klass) yield rec yielded = true end when Types.CNAME n0 = rec.domainname end end } return if yielded msg.each_answer {|rec| if n0 == rec.name case rec.type when type if (rec.klass == klass) yield rec end end end } end def send_query(name, type=Types.A, klass=Classes.IN) # :nodoc: candidates = @config.generate_candidates(name) exception = nil candidates.each do |candidate| q = Queue.new msg = Message.new msg.header.rd = 1 msg.add_question(candidate, type, klass) msg.do_validation = false msg.header.cd = false msg.do_caching = do_caching @resolver.do_validation = false @resolver.send_async(msg, q) id, ret, exception = q.pop if (exception == nil && ret && ret.rcode == RCode.NOERROR) return ret, ret.question[0].qname end end raise exception end end end # -- # @TODO@ Asynchronous interface. Some sort of Deferrable? # ++ dnsruby-1.61.3/lib/dnsruby/zone_transfer.rb0000644000004100000410000003110513607400362020761 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # This class performs zone transfers as per RFC1034 (AXFR) and RFC1995 (IXFR). class ZoneTransfer # The nameserver to use for the zone transfer - defaults to system config attr_accessor :server # What type of transfer to do (IXFR or AXFR) - defaults to AXFR attr_accessor :transfer_type # The class - defaults to IN attr_accessor :klass # The port to connect to - defaults to 53 attr_accessor :port # If using IXFR, this is the SOA serial number to start the incrementals from attr_accessor :serial # The source address to connect to attr_accessor :src_address # The TSIG record used to sign the transfer attr_reader :tsig # Returns the tsigstate of the last transfer (nil if no TSIG signed transfer has occurred) attr_reader :last_tsigstate # Sets the connect timeout in seconds attr_accessor :connect_timeout # Sets the TSIG to sign the zone transfer with. # Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key) # Pass in nil to stop tsig signing. # * res.tsig=(tsig_rr) # * res.tsig=(key_name, key) # * res.tsig=nil # Don't sign the transfer def tsig=(*args) @tsig = Resolver.get_tsig(args) end def initialize @server=Config.new.nameserver[0] @transfer_type = Types.AXFR @klass=Classes.IN @port=53 @serial=0 @tsig = nil @axfr = nil @src_address = nil @connect_timeout = 5 end # Perform a zone transfer (RFC1995) # If an IXFR query is unsuccessful, then AXFR is tried (and @transfer_type is set # to AXFR) # TCP is used as the only transport # # If AXFR is performed, then the zone will be returned as a set of records : # # zt = Dnsruby::ZoneTransfer.new # zt.transfer_type = Dnsruby::Types.AXFR # zt.server = "ns0.validation-test-servers.nominet.org.uk" # zone = zt.transfer("validation-test-servers.nominet.org.uk") # soa = zone[0] # rec1 = zone[1] # print zone.to_s # # # If IXFR is performed, then the incrementals will be returned as a set of Deltas. # Each Delta contains the start and end SOA serial number, as well as an array of # adds and deletes that occurred between the start and end. # # zt = Dnsruby::ZoneTransfer.new # zt.transfer_type = Dnsruby::Types.IXFR # zt.server = "ns0.validation-test-servers.nominet.org.uk" # zt.serial = 2007090401 # deltas = zt.transfer("validation-test-servers.nominet.org.uk") # assert_equal("Should show up in transfer", deltas[0].adds[1].data) def transfer(zone) servers = @server if (servers.class == String) servers=[servers] end xfr = nil exception = nil servers.each do |server| begin server=Config.resolve_server(server) xfr = do_transfer(zone, server) break rescue Exception => e exception = e end end if (xfr == nil && exception != nil) raise exception end return xfr end def do_transfer(zone, server) #:nodoc: all @transfer_type = Types.new(@transfer_type) @state = :InitialSoa socket = Socket.tcp(server, @port, @src_address, connect_timeout: @connect_timeout) # socket = TCPSocket.new(server, @port, @src_address) begin # Send an initial query msg = Message.new(zone, @transfer_type, @klass) if @transfer_type == Types.IXFR rr = RR.create("#{zone} 0 IN SOA" + '0 0 %u 0 0 0 0' % @serial) msg.add_authority(rr) end send_message(socket, msg) while (@state != :End) response = receive_message(socket) if (@state == :InitialSoa) rcode = response.rcode if (rcode != RCode.NOERROR) if (@transfer_type == Types.IXFR && rcode == RCode.NOTIMP) # IXFR didn't work - let's try AXFR Dnsruby.log.debug("IXFR DID NOT WORK (rcode = NOTIMP) - TRYING AXFR!!") @state = :InitialSoa @transfer_type=Types.AXFR # Send an initial AXFR query msg = Message.new(zone, @transfer_type, @klass) send_message(socket, msg) next end raise ResolvError.new(rcode.string); end if (response.question[0].qtype != @transfer_type) raise ResolvError.new("invalid question section") end if (response.header.ancount == 0 && @transfer_type == Types.IXFR) Dnsruby.log.debug("IXFR DID NOT WORK (ancount = 0) - TRYING AXFR!!") # IXFR didn't work - let's try AXFR @transfer_type=Types.AXFR # Send an initial AXFR query @state = :InitialSoa msg = Message.new(zone, @transfer_type, @klass) send_message(socket, msg) next end end response.each_answer { |rr| parseRR(rr) } if (@state == :End && response.tsigstate == :Intermediate) raise ResolvError.new("last message must be signed") end if (@state == :End && @tsig) if (response.tsigstate != :Verified) @last_tsigstate = :Failed raise ResolvError.new("Zone transfer not correctly signed") end @last_tsigstate = :Verified end end # This could return with an IXFR response, or an AXFR response. # If it fails completely, then try to send an AXFR query. # Once the query has been sent, then enter the main response loop. # Unless we know we're definitely AXFR, we should be prepared for either IXFR or AXFR # AXFR response : The first and the last RR of the response is the SOA record of the zone. # The whole zone is returned inbetween. # IXFR response : one or more difference sequences is returned. The list of difference # sequences is preceded and followed by a copy of the server's current # version of the SOA. # Each difference sequence represents one update to the zone (one SOA # serial change) consisting of deleted RRs and added RRs. The first RR # of the deleted RRs is the older SOA RR and the first RR of the added # RRs is the newer SOA RR. socket.close if (@axfr!=nil) return @axfr end return @ixfr rescue Exception => e socket.close raise e end end # All changes between two versions of a zone in an IXFR response. class Delta # The starting serial number of this delta. attr_accessor :start # The ending serial number of this delta. attr_accessor :end # A list of records added between the start and end versions attr_accessor :adds # A list of records deleted between the start and end versions attr_accessor :deletes def initialize() @adds = [] @deletes = [] end def to_s ret = "Adds : " + @adds.join(",") ret +=", Deletes : " + @deletes.join(",") end end # Compare two serials according to RFC 1982. Return 0 if equal, # -1 if s1 is bigger, 1 if s1 is smaller. def compare_serial(s1, s2) if s1 == s2 return 0 end if s1 < s2 and (s2 - s1) < (2**31) return 1 end if s1 > s2 and (s1 - s2) > (2**31) return 1 end if s1 < s2 and (s2 - s1) > (2**31) return -1 end if s1 > s2 and (s1 - s2) < (2**31) return -1 end return 0 end def parseRR(rec) #:nodoc: all name = rec.name type = rec.type delta = Delta.new case @state when :InitialSoa if (type != Types.SOA) raise ResolvError.new("missing initial SOA") end @initialsoa = rec # Remember the serial number in the initial SOA; we need it # to recognize the end of an IXFR. @end_serial = rec.serial # if ((@transfer_type == Types.IXFR) && (@end_serial <= @serial)) if ((@transfer_type == Types.IXFR) && (compare_serial(@end_serial, @serial) >= 0)) Dnsruby.log.debug("zone up to date") raise ZoneSerialError.new("IXFR up to date: expected serial " + @serial.to_s + " , got " + rec.serial.to_s); @state = :End else @state = :FirstData end when :FirstData # If the transfer begins with 1 SOA, it's an AXFR. # If it begins with 2 SOAs, it's an IXFR. if (@transfer_type == Types.IXFR && type == Types.SOA && rec.serial == @serial) Dnsruby.log.debug("IXFR response - using IXFR") @rtype = Types.IXFR @ixfr = [] @state = :Ixfr_DelSoa else Dnsruby.log.debug("AXFR response - using AXFR") @rtype = Types.AXFR @transfer_type = Types.AXFR @axfr = [] @axfr << @initialsoa @state = :Axfr end parseRR(rec) # Restart... return when :Ixfr_DelSoa delta = Delta.new @ixfr.push(delta) delta.start = rec.serial delta.deletes << rec @state = :Ixfr_Del when :Ixfr_Del if (type == Types.SOA) @current_serial = rec.serial @state = :Ixfr_AddSoa parseRR(rec); # Restart... return; end delta = @ixfr[@ixfr.length - 1] delta.deletes << rec when :Ixfr_AddSoa delta = @ixfr[@ixfr.length - 1] delta.end = rec.serial delta.adds << rec @state = :Ixfr_Add when :Ixfr_Add if (type == Types.SOA) soa_serial = rec.serial if (soa_serial == @end_serial) @state = :End return elsif (soa_serial != @current_serial) raise ZoneSerialError.new("IXFR out of sync: expected serial " + @current_serial.to_s + " , got " + soa_serial.to_s); else @state = :Ixfr_DelSoa parseRR(rec); # Restart... return; end end delta = @ixfr[@ixfr.length - 1] delta.adds << rec when :Axfr # Old BINDs sent cross class A records for non IN classes. if (type == Types.A && rec.klass() != @klass) else if (type == Types.SOA) @state = :End else @axfr << rec end end when :End raise ResolvError.new("extra data in zone transfer") else raise ResolvError.new("invalid state for zone transfer") end end def send_message(socket, msg) #:nodoc: all if (@tsig) @tsig.apply(msg) @tsig = msg.tsig end query_packet = msg.encode lenmsg = [query_packet.length].pack('n') socket.send(lenmsg, 0) socket.send(query_packet, 0) end def tcp_read(socket, len) #:nodoc: all buf="" while (buf.length < len) and not socket.eof? do buf += socket.read(len-buf.length) end return buf end def receive_message(socket) #:nodoc: all buf = tcp_read(socket, 2) answersize = buf.unpack('n')[0] # Some servers (e.g. dnscache) apparently hang up on some connections. # Thanks to Matt Palmer for the fix. raise ResolvError.new("Server did not send a valid answer") if answersize.nil? buf = tcp_read(socket, answersize) msg = Message.decode(buf) if (@tsig) if !@tsig.verify_envelope(msg, buf) Dnsruby.log.error("Bad signature on zone transfer - closing connection") raise ResolvError.new("Bad signature on zone transfer") end end return msg end end end dnsruby-1.61.3/lib/dnsruby/cache.rb0000644000004100000410000001101613607400362017144 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # # This class implements a cache. # It stores data under qname-qclass-qtype tuples. # Each tuple indexes a CacheData object (which # stores a Message, and an expiration). # If a new Message is stored to a tuple, it will # overwrite the previous Message. # When a Message is retrieved from the cache, the header # and ttls will be "fixed" - i.e. AA cleared, etc. # @TODO@ Max size for cache? module Dnsruby class Cache # :nodoc: all def initialize() @cache = Hash.new @@max_size = 16*1024 # Get this right... @mutex = Mutex.new end def cache @cache end def clear() @mutex.synchronize { @cache = Hash.new } end def length return @cache.length end def Cache.max_size=(length) @@max_size = length end def add(message) q = message.question[0] key = CacheKey.new(q.qname, q.qtype, q.qclass).to_s data = CacheData.new(message) @mutex.synchronize { if (@cache[key]) TheLog.debug("CACHE REPLACE : #{q.qname}, #{q.qtype}\n") else TheLog.debug("CACHE ADD : #{q.qname}, #{q.qtype}\n") end @cache[key] = data while @cache.size > @@max_size # keep the cache size reasonable @cache.shift end } end # This method "fixes up" the response, so that the header and ttls are OK # The resolver will still need to copy the flags and ID across from the query def find(qname, qtype, qclass = Classes.IN) # print "CACHE find : #{qname}, #{qtype}\n" qn = Name.create(qname) qn.absolute = true key = CacheKey.new(qn, qtype, qclass).to_s @mutex.synchronize { data = @cache[key] if (!data) # print "CACHE lookup failed\n" return nil end if (data.expiration <= Time.now.to_i) @cache.delete(key) TheLog.debug("CACHE lookup stale\n") return nil end m = data.message TheLog.debug("CACHE found\n") return m } end def Cache.delete(qname, qtype, qclass = Classes.IN) key = CacheKey.new(qname, qtype, qclass) @mutex.synchronize { @cache.delete(key) } end class CacheKey # :nodoc: all attr_accessor :qname, :qtype, :qclass def initialize(*args) self.qclass = Classes.IN if (args.length > 0) self.qname = Name.create(args[0]) self.qname.absolute = true if (args.length > 1) self.qtype = Types.new(args[1]) if (args.length > 2) self.qclass = Classes.new(args[2]) end end end end def to_s return "#{qname.inspect.downcase} #{qclass} #{qtype}" end end class CacheData # :nodoc: all attr_reader :expiration def message=(m) @expiration = get_expiration(m) @message = Message.decode(m.encode(true)) @message.cached = true end def message m = Message.decode(@message.encode) m.cached = true # @TODO@ What do we do about answerfrom, answersize, etc.? m.header.aa = false # Anything else to do here? # Fix up TTLs!! offset = (Time.now - @time_stored).to_i m.each_resource {|rr| next if rr.type == Types::OPT rr.ttl = rr.ttl - offset } return m end def get_expiration(m) # Find the minimum ttl of any of the rrsets min_ttl = 9999999 m.each_section {|section| section.rrsets.each {|rrset| if (rrset.ttl < min_ttl) min_ttl = rrset.ttl end } } if (min_ttl == 9999999) return 0 end return (Time.now.to_i + min_ttl) end def initialize(*args) @expiration = 0 @time_stored = Time.now.to_i self.message=(args[0]) end def to_s return "#{self.message}" end end end enddnsruby-1.61.3/lib/dnsruby/resolv.rb0000644000004100000410000000617113607400362017421 0ustar www-datawww-data# The Resolv class can be used to resolve addresses using /etc/hosts and /etc/resolv.conf, # # The DNS class may be used to perform more queries. If greater control over the sending # of packets is required, then the Resolver or SingleResolver classes may be used. module Dnsruby # NOTE! Beware, there is a Ruby library class named Resolv, and you may need to # explicitly specify Dnsruby::Resolv to use the Dnsruby Resolv class, # even if you have include'd Dnsruby. class Resolv # Address RegExp to use for matching IP addresses ADDRESS_REGEX = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ # Some class methods require the use of an instance to compute their result. # For this purpose we create a single instance that can be reused. def self.instance @instance ||= self.new end # Class methods that delegate to instance methods: # Looks up the first IP address for +name+ def self.getaddress(name) instance.getaddress(name) end # Looks up all IP addresses for +name+ def self.getaddresses(name) instance.getaddresses(name) end # Iterates over all IP addresses for +name+ def self.each_address(name, &block) instance.each_address(name, &block) end # Looks up the first hostname of +address+ def self.getname(address) instance.getname(address) end # Looks up all hostnames of +address+ def self.getnames(address) instance.getnames(address) end # Iterates over all hostnames of +address+ def self.each_name(address, &proc) instance.each_name(address, &proc) end # Instance Methods: # Creates a new Resolv using +resolvers+ def initialize(resolvers=[Hosts.new, DNS.new]) @resolvers = resolvers end # Looks up the first IP address for +name+ def getaddress(name) addresses = getaddresses(name) if addresses.empty? raise ResolvError.new("no address for #{name}") else addresses.first end end # Looks up all IP addresses for +name+ def getaddresses(name) return [name] if ADDRESS_REGEX.match(name) @resolvers.each do |resolver| addresses = [] resolver.each_address(name) { |address| addresses << address } return addresses unless addresses.empty? end [] end # Iterates over all IP addresses for +name+ def each_address(name) getaddresses(name).each { |address| yield(address)} end # Looks up the first hostname of +address+ def getname(address) names = getnames(address) if names.empty? raise ResolvError.new("no name for #{address}") else names.first end end # Looks up all hostnames of +address+ def getnames(address) @resolvers.each do |resolver| names = [] resolver.each_name(address) { |name| names << name } return names unless names.empty? end [] end # Iterates over all hostnames of +address+ def each_name(address) getnames(address).each { |address| yield(address) } end require 'dnsruby/cache' require 'dnsruby/DNS' require 'dnsruby/hosts' require 'dnsruby/message/message' require 'dnsruby/update' require 'dnsruby/zone_transfer' require 'dnsruby/dnssec' require 'dnsruby/zone_reader' end end dnsruby-1.61.3/lib/dnsruby/key_cache.rb0000644000004100000410000000566513607400362020031 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class KeyCache #:nodoc: all # Cache includes expiration time for keys # Cache removes expired records def initialize(keys = nil) # Store key tag against [expiry, key] @keys = {} add(keys) end def add_key_with_expiration(k, expiration) priv_add_key(k, expiration) end def add(k) if (k == nil) return false elsif (k.instance_of?RRSet) add_rrset(k) elsif (k.kind_of?KeyCache) kaes = k.keys_and_expirations kaes.keys.each { |keykey| # priv_add_key(keykey, kaes[keykey]) priv_add_key(keykey[1], keykey[0]) } else raise ArgumentError.new("Expected an RRSet or KeyCache! Got #{k.class}") end return true end def add_rrset(k) # Get expiration from the RRSIG # There can be several RRSIGs here, one for each key which has signed the RRSet # We want to choose the one with the most secure signing algorithm, key length, # and the longest expiration time - not easy! # for now, we simply accept all signed keys k.sigs.each { |sig| if (sig.type_covered = Types.DNSKEY) if (sig.inception <= Time.now.to_i) # Check sig.expiration, sig.algorithm if (sig.expiration > Time.now.to_i) # add the keys to the store k.rrs.each {|rr| priv_add_key(rr, sig.expiration)} end end end } end def priv_add_key(k, exp) # Check that the key does not already exist with a longer expiration! if (@keys[k] == nil) @keys[k.key_tag] = [exp,k] elsif ((@keys[k])[0] < exp) @keys[k.key_tag] = [exp,k] end end def each # Only offer currently-valid keys here remove_expired_keys @keys.values.each {|v| yield v[1]} end def keys # Only offer currently-valid keys here remove_expired_keys ks = [] @keys.values.each {|a| ks.push(a[1])} return ks # return @keys.keys end def keys_and_expirations remove_expired_keys return keys.values end def remove_expired_keys @keys.delete_if {|k,v| v[0] < Time.now.to_i } end def find_key_for(name) each {|key| return key if key.name == name} return false end end enddnsruby-1.61.3/lib/dnsruby/single_resolver.rb0000644000004100000410000001266713607400362021320 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # == Dnsruby::SingleResolver # # This class has been deprecated. # This implementation exists for legacy clients. New code should use the Dnsruby::Resolver class. # The SingleResolver class targets a single resolver, and controls the sending of a single # packet with a packet timeout. It performs no retries. Only two threads are used - the client # thread and a select thread (which is reused across all queries). # # == Methods # # === Synchronous # These methods raise an exception or return a response message with rcode==NOERROR # # * Dnsruby::SingleResolver#send_message(msg [, use_tcp])) # * Dnsruby::SingleResolver#query(name [, type [, klass]]) # # === Asynchronous # These methods use a response queue to return the response and the error to the client. # Support for EventMachine has been deprecated # # * Dnsruby::SingleResolver#send_async(...) # class SingleResolver < Resolver # Can take a hash with the following optional keys : # # * :server # * :port # * :use_tcp # * :no_tcp # * :ignore_truncation # * :src_address # * :src_address6 # * :src_port # * :udp_size # * :persistent_tcp # * :persistent_udp # * :tsig # * :packet_timeout # * :recurse def initialize(*args) arg=args[0] @single_res_mutex = Mutex.new @packet_timeout = Resolver::DefaultPacketTimeout @query_timeout = @packet_timeout @port = Resolver::DefaultPort @udp_size = Resolver::DefaultUDPSize @dnssec = Resolver::DefaultDnssec @use_tcp = false @no_tcp = false @tsig = nil @ignore_truncation = false @src_address = nil @src_address6 = nil @src_port = [0] @recurse = true @persistent_udp = false @persistent_tcp = false @retry_times = 1 @retry_delay = 0 @single_resolvers = [] @configured = false @do_caching = true @config = Config.new if (arg==nil) # Get default config @config = Config.new @config.get_ready @server = @config.nameserver[0] elsif (arg.kind_of?String) @config.get_ready @configured= true @config.nameserver=[arg] @server = @config.nameserver[0] # @server=arg elsif (arg.kind_of?Name) @config.get_ready @configured= true @config.nameserver=arg @server = @config.nameserver[0] # @server=arg elsif (arg.kind_of?Hash) arg.keys.each do |attr| if (attr == :server) @config.get_ready @configured= true @config.nameserver=[arg[attr]] @server = @config.nameserver[0] else begin send(attr.to_s+"=", arg[attr]) rescue Exception Dnsruby.log.error{"Argument #{attr} not valid\n"} end end end end isr = PacketSender.new({:server=>@server, :port=>@port, :dnssec=>@dnssec, :use_tcp=>@use_tcp, :no_tcp=>@no_tcp, :packet_timeout=>@packet_timeout, :tsig => @tsig, :ignore_truncation=>@ignore_truncation, :src_address=>@src_address, :src_address6=>@src_address6, :src_port=>@src_port, :recurse=>@recurse, :udp_size=>@udp_size}) @single_resolvers = [isr] # ResolverRegister::register_single_resolver(self) end def server=(s) if (!@configured) @config.get_ready end @server = Config.resolve_server(s).to_s isr = PacketSender.new({:server=>@server, :dnssec=>@dnssec, :use_tcp=>@use_tcp, :no_tcp=>@no_tcp, :packet_timeout=>@packet_timeout, :tsig => @tsig, :ignore_truncation=>@ignore_truncation, :src_address=>@src_address, :src_address6=>@src_address6, :src_port=>@src_port, :recurse=>@recurse, :udp_size=>@udp_size}) @single_res_mutex.synchronize { @single_resolvers = [isr] } end def server # @single_res_mutex.synchronize { if (!@configured) @config.get_ready add_config_nameservers end return @single_resolvers[0].server # } end def retry_times=(n) # :nodoc: raise NoMethodError.new("SingleResolver does not have retry_times") end def retry_delay=(n) # :nodoc: raise NoMethodError.new("SingleResolver does not have retry_delay") end def packet_timeout=(t) @packet_timeout = t @query_timeout = t end # Add the appropriate EDNS OPT RR for the specified packet. This is done # automatically, unless you are using Resolver#send_plain_message def add_opt_rr(m) @single_res_mutex.synchronize { @single_resolvers[0].add_opt_rr(m) } end alias :query_timeout :packet_timeout alias :query_timeout= :packet_timeout= end end dnsruby-1.61.3/lib/dnsruby/single_verifier.rb0000644000004100000410000015511413607400362021265 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # This class does verification/validation from a single point - signed root, # DLV, trust anchors. Dnssec controls a set of these to perform validation for # the client. # This class should only be used by Dnsruby module Dnsruby class SingleVerifier # :nodoc: all class VerifierType ROOT = 0 ANCHOR = 1 DLV = 2 end def initialize(vtype) @verifier_type = vtype @added_dlv_key = false # The DNSKEY RRs for the signed root (when it exists) @root_anchors = KeyCache.new # The set of trust anchors. # If the root is unsigned, then these must be initialised with at least # one trusted key by the client application, if verification is to be performed. @trust_anchors = KeyCache.new @dlv_registries = [] # The set of keys which are trusted. @trusted_keys = KeyCache.new # The set of keys which have been indicated by a DS RRSet which has been # signed by a trusted key. Although we have not yet located these keys, we # have the details (tag and digest) which can identify the keys when we # see them. At that point, they will be added to our trusted keys. @discovered_ds_store = [] # The configured_ds_store is the set of DS records which have been configured # by the client as trust anchors. Use Dnssec#add_trust_anchor to add these @configured_ds_store = [] end def set_hints(hints) @@hints = hints end def get_recursor if (!defined?@@recursor) if (defined?@@hints) Recursor.set_hints(@@hints, Resolver.new) @@recursor = Recursor.new() else @@recursor = Recursor.new end end @@recursor.dnssec = true return @@recursor end def get_dlv_resolver # :nodoc: # if (Dnssec.do_validation_with_recursor?) # return Recursor.new # else resolver = nil if (Dnssec.default_resolver) resolver = Dnssec.default_resolver else resolver = Resolver.new end # end resolver.dnssec = true return resolver end def add_dlv_key(key) # Is this a ZSK or a KSK? # If it is a KSK, then get the ZSK from the zone if (key.sep_key?) get_dlv_key(key) end end def get_dlv_key(ksk) # :nodoc: # Using the KSK, get the ZSK for the DLV registry if (!@res && (@verifier_type == VerifierType::DLV)) @res = get_dlv_resolver end # print "Sending query : res.dnssec = #{@res.dnssec}" ret = nil begin ret = @res.query_no_validation_or_recursion("dlv.isc.org.", Types.DNSKEY) if (!ret) raise ResolvError.new("Couldn't get response from Recursor") end rescue ResolvError => e # print "ERROR - Couldn't find the DLV key\n" TheLog.error("Couldn't find the DLV key\n") return end key_rrset = ret.answer.rrset("dlv.isc.org", Types.DNSKEY) begin verify(key_rrset, ksk) add_trusted_key(key_rrset) # print "Successfully added DLV key\n" TheLog.info("Successfully added DLV key") @added_dlv_key = true rescue VerifyError => e # print "Error verifying DLV key : #{e}\n" TheLog.error("Error verifying DLV key : #{e}") end end def add_trust_anchor(t) add_trust_anchor_with_expiration(t, Time.utc(2035,"jan",1,20,15,1).to_i) end # Add the def add_trust_anchor_with_expiration(k, expiration) if (k.type == Types.DNSKEY) # k.flags = k.flags | RR::IN::DNSKEY::SEP_KEY @trust_anchors.add_key_with_expiration(k, expiration) # print "Adding trust anchor for #{k.name}\n" TheLog.info("Adding trust anchor for #{k.name}") elsif ((k.type == Types.DS) || ((k.type == Types.DLV) && (@verifier_type == VerifierType::DLV))) @configured_ds_store.push(k) end end def remove_trust_anchor(t) @trust_anchors.delete(t) end # Wipes the cache of trusted keys def clear_trust_anchors @trust_anchors = KeyCache.new end def trust_anchors return @trust_anchors.keys + @configured_ds_store end # Check that the RRSet and RRSIG record are compatible def check_rr_data(rrset, sigrec)#:nodoc: all # Each RR MUST have the same owner name as the RRSIG RR; if (rrset.name.canonical != sigrec.name.canonical) raise VerifyError.new("RRSET should have same owner name as RRSIG for verification (rrsert=#{rrset.name}, sigrec=#{sigrec.name}") end # Each RR MUST have the same class as the RRSIG RR; if (rrset.klass != sigrec.klass) raise VerifyError.new("RRSET should have same DNS class as RRSIG for verification") end # Each RR in the RRset MUST have the RR type listed in the # RRSIG RR's Type Covered field; if (rrset.type != sigrec.type_covered) raise VerifyError.new("RRSET should have same type as RRSIG for verification") end # #Each RR in the RRset MUST have the TTL listed in the # #RRSIG Original TTL Field; # if (rrset.ttl != sigrec.original_ttl) # raise VerifyError.new("RRSET should have same ttl as RRSIG original_ttl for verification (should be #{sigrec.original_ttl} but was #{rrset.ttl}") # end # Now check that we are in the validity period for the RRSIG now = Time.now.to_i if ((sigrec.expiration < now) || (sigrec.inception > now)) raise VerifyError.new("Signature record not in validity period") end end # Add the specified keys to the trusted key cache. # k can be a KeyCache, or an RRSet of DNSKEYs. def add_trusted_key(k) @trusted_keys.add(k) end def add_root_ds(ds) @configured_ds_store.push(ds) end # Wipes the cache of trusted keys def clear_trusted_keys @trusted_keys = KeyCache.new @res = nil @discovered_ds_store = [] @configured_ds_store = [] end def trusted_keys discovered_ds = [] @discovered_ds_store.each {|rrset| rrset.rrs.each {|rr| discovered_ds.push(rr) } } return @trusted_keys.keys + @configured_ds_store + discovered_ds end # Check that the key fits a signed DS record key details # If so, then add the key to the trusted keys def check_ds(key, ds_rrset)#:nodoc: all expiration = 0 found = false ds_rrset.sigs.each { |sig| if ((sig.type_covered == Types.DS) || ((sig.type_covered == Types.DLV)&& (@verifier_type==VerifierType::DLV))) if (sig.inception <= Time.now.to_i) # Check sig.expiration, sig.algorithm if (sig.expiration > expiration) expiration = sig.expiration end end end } if (expiration > 0) ds_rrset.rrs.each { |ds| if ((ds.type === Types.DS) || ((ds.type == Types.DLV) && (@verifier_type == VerifierType::DLV))) if (ds.check_key(key)) @trusted_keys.add_key_with_expiration(key, expiration) found = true end end } end return found end # Verify the specified message (or RRSet) using the set of trusted keys. # If keys is a DNSKEY, or an Array or RRSet of DNSKEYs, then keys # is added to the set of trusted keys before the message (or RRSet) is # verified. # # If msg is a Dnsruby::Message, then any signed DNSKEY or DS RRSets are # processed first, and any new keys are added to the trusted key set # before the other RRSets are checked. # # msg can be a Dnsruby::Message or Dnsruby::RRSet. # keys may be nil, or a KeyCache or an RRSet of Dnsruby::RR::DNSKEY # # Returns true if the message verifies OK, and false otherwise. def verify(msg, keys = nil) if (msg.kind_of?RRSet) if (msg.type == Types.DNSKEY) return verify_key_rrset(msg, keys) end if ((msg.type == Types.DS) || (msg.type == Types.DLV)) return verify_ds_rrset(msg, keys) end return verify_rrset(msg, keys) end # Use the set of trusted keys to check any RRSets we can, ideally # those of other DNSKEY RRSets first. Then, see if we can use any of the # new total set of keys to check the rest of the rrsets. # Return true if we can verify the whole message. msg.each_section do |section| # print "Checking section : #{section}\n" ds_rrsets = section.rrsets(Types.DS) if ((!ds_rrsets || ds_rrsets.length == 0) && (@verifier_type == VerifierType::DLV)) ds_rrsets = section.rrsets(Types.DLV) end ds_rrsets.each {|ds_rrset| if ((ds_rrset && ds_rrset.rrs.length > 0) && !verify_ds_rrset(ds_rrset, keys, msg)) raise VerifyError.new("Failed to verify DS RRSet") # return false end } key_rrsets = section.rrsets(Types.DNSKEY) key_rrsets.each {|key_rrset| if ((key_rrset && key_rrset.rrs.length > 0) && !verify_key_rrset(key_rrset, keys)) raise VerifyError.new("Failed to verify DNSKEY RRSet") # return false end } end verify_nsecs(msg) # Then, look through all the remaining RRSets, and verify them all (unless not necessary). msg.section_rrsets.each do |section, rrsets| rrsets.each do |rrset| # If delegation NS or glue AAAA/A, then don't expect RRSIG. # Otherwise, expect RRSIG and fail verification if RRSIG is not present if ((section == "authority") && (rrset.type == Types.NS)) # Check for delegation dsrrset = msg.authority.rrsets('DS')[0] if ((msg.answer.size == 0) && (!dsrrset) && (rrset.type == Types.NS)) # (isDelegation) # Now check NSEC(3) records for absence of DS and SOA nsec = msg.authority.rrsets('NSEC')[0] if (!nsec || (nsec.length == 0)) nsec = msg.authority.rrsets('NSEC3')[0] end if (nsec && (nsec.rrs.length > 0)) if (!(nsec.rrs()[0].types.include?'DS') || !(nsec.rrs()[0].types.include?'SOA')) next # delegation which we expect to be unsigned - so don't verify it! end end end # If NS records delegate the name to the child's nameservers, then they MUST NOT be signed if (rrset.type == Types.NS) # all_delegate = true # rrset.rrs.each {|rr| # name = Name.create(rr.nsdname) # name.absolute = true # if (!(name.subdomain_of?(rr.name))) # all_delegate = false # end # } # if (all_delegate && rrset.sigs.length == 0) # next # end if ((rrset.name.canonical == msg.question()[0].qname.canonical) && (rrset.sigs.length == 0)) next end end end if (section == "additional") # check for glue # if the ownername (in the addtional section) of the glue address is the same or longer as the ownername of the NS record, it is glue if (msg.additional.size > 0) arec = msg.additional.rrsets('A')[0] if (!arec || arec.rrs.length == 0) arec = msg.additional.rrsets('AAAA')[0] end ns_rrsets = msg.additional.rrsets('NS') ns_rrsets.each {|ns_rrset| if (ns_rrset.length > 0) nsname = ns_rrset.rrs()[0].name if (arec && arec.rrs().length > 0) aname = arec.rrs()[0].name if (nsname.subdomain_of?aname) next end end end } end end # If records are in additional, and no RRSIG, that's Ok - just don't use them! if ((section == "additional") && (rrset.sigs.length == 0)) # @TODO@ Make sure that we don't cache these records! next end # else verify RRSet # print "About to verify #{rrset.name}, #{rrset.type}\n" if (!verify_rrset(rrset, keys)) # print "FAILED TO VERIFY RRSET #{rrset.name}, #{rrset.type}\n" TheLog.debug("Failed to verify rrset") return false end end end return true end def verify_nsecs(msg) # :nodoc: # NSEC(3) handling. Get NSEC(3)s in four cases : (RFC 4035, section 3.1.3) # a) No data - matches, but no either exactly or through wildcard expansion (§3.1.3.2) # - NSEC wil prove i) no exact match for , and ii) no RRSets that could match through wildcard expansion # - this may be proved in one or more NSECs (and associated RRSIGs) # - NXDOMAIN returned - should ensure we verify! # c) Wildcard answer - No direct matches, but matches through wildcard expansion (§3.1.3.3) # - Answer section must include wildcard-expanded answer (and associated RRSIGs) # - label count in answer RRSIG indicates wildcard RRSet was expanded (less labels than in owner name) # - Authority section must include NSEC (and RRSIGs) proving that zone does not contain a closer match # - NOERROR returned # d) Wildcard no data - No direct. yes but no through wildcard expansion (§3.1.3.4) # - Authority section contains NSECs (and RRSIGs) for : # i) NSEC proving no RRSets matching STYPE at wildcard owner name that matched via wildcard expansion # ii) NSEC proving no RRSets in zone that would have been closer match for # - this may be proved by one or more NSECs (and associated RRSIGs) # - NOERROR returned # # Otherwise no NSECs should be returned. # So, check for NSEC records in response, and work out what type of answer we have. # Then, if NSECs are present, make sure that we prove what they said they would. # What if the message *should* have no NSEC records? That can only be known by the validator. # We will assume that the validator has checked the (non)-existence of NSEC records - we should not # get upset if there aren't any. However, if there are, then we should verify that they say the right thing qtype = msg.question()[0].qtype return if (msg.rcode == RCode.NOERROR && ((qtype == Types.ANY) || (qtype == Types.NSEC) || (qtype == Types.NSEC3))) if ((msg.rrsets('NSEC').length > 0) || (msg.rrsets('NSEC3').length > 0)) if (msg.rcode == RCode.NXDOMAIN) # print "Checking NSECs for Name Error\n" # Name error - NSEC wil prove i) no exact match for , and ii) no RRSets that could match through wildcard expansion # - this may be proved in one or more NSECs (and associated RRSIGs) check_name_in_nsecs(msg) return check_no_wildcard_expansion(msg) elsif (msg.rcode == RCode.NOERROR) if (msg.answer.length > 0) # print "Checking NSECs for wildcard expansion\n" # wildcard expansion answer - check NSECs! # We want to make sure that the NSEC tells us that there is no closer match for this name # @TODO@ We need to make replace the RRSIG name with the wildcard name before we can verify it correctly. check_num_rrsig_labels(msg) return check_name_in_nsecs(msg, msg.question()[0].qtype, true) else # Either no data or wildcard no data - check to see which # Should be able to tell this by checking the number of labels in the NSEC records. # Sort these two last cases out! isWildcardNoData = false [msg.authority.rrsets('NSEC'), msg.authority.rrsets('NSEC3')].each {|nsec_rrsets| nsec_rrsets.each {|nsec_rrset| nsec_rrset.rrs.each {|nsec| # print "Checking nsec to see if wildcard : #{nsec}\n" if (nsec.name.wild? ||(nsec.name.labels.length < msg.question()[0].qname.labels.length)) isWildcardNoData = true end } } } if (isWildcardNoData) # print "Checking NSECs for wildcard no data\n" # Check NSECs - # i) NSEC proving no RRSets matching STYPE at wildcard owner name that matched via wildcard expansion check_name_not_in_wildcard_nsecs(msg) # ii) NSEC proving no RRSets in zone that would have been closer match for return check_name_in_and_type_not_in_nsecs(msg) else # (isNoData) # print "Checking NSECs for No data\n" # Check NSEC types covered to make sure this type not present. return check_name_in_and_type_not_in_nsecs(msg) end end else # Anything we should do here? end end end def check_num_rrsig_labels(msg) # :nodoc: # Check that the number of labels in the RRSIG is less than the number # of labels in the answer name answer_rrset = msg.answer.rrset(msg.question()[0].qname, msg.question()[0].qtype) if (answer_rrset.length == 0) raise VerifyError.new("Expected wildcard expanded answer for #{msg.question()[0].qname}") end rrsig = answer_rrset.sigs()[0] if (rrsig.labels >= msg.question()[0].qname.labels.length) raise VerifyError.new("RRSIG does not prove wildcard expansion for #{msg.question()[0].qname}") end end def check_no_wildcard_expansion(msg) # :nodoc: # @TODO@ Do this for NSEC3 records!!! proven_no_wildcards = false name = msg.question()[0].qname [msg.authority.rrsets('NSEC'), msg.authority.rrsets('NSEC3')].each {|nsec_rrsets| nsec_rrsets.each {|nsecs| nsecs.rrs.each {|nsec| # print "Checking NSEC : #{nsec}\n" next if (nsec.name.wild?) if (check_record_proves_no_wildcard(msg, nsec)) proven_no_wildcards = true end } } } if (!proven_no_wildcards) # print "No proof that no RRSets could match through wildcard expansion\n" raise VerifyError.new("No proof that no RRSets could match through wildcard expansion") end end def check_record_proves_no_wildcard(msg, nsec) # :nodoc: # Check that the NSEC goes from the SOA to a zone canonically after a wildcard # print "Checking wildcard proof for #{nsec.name}\n" soa_rrset = msg.authority.rrset(nsec.name, 'SOA') if (soa_rrset.length > 0) # print "Found SOA for #{nsec.name}\n" wildcard_name = Name.create("*." + nsec.name.to_s) # print "Checking #{wildcard_name}\n" if (wildcard_name.canonically_before(nsec.next_domain)) return true end end return false end def check_name_in_nsecs(msg, qtype=nil, expected_qtype = false) # :nodoc: # Check these NSECs to make sure that this name cannot be in the zone # and that no RRSets could match through wildcard expansion # @TODO@ Get this right for NSEC3 too! name = msg.question()[0].qname proven_name_in_nsecs = false type_covered_checked = false [msg.authority.rrsets('NSEC'), msg.authority.rrsets('NSEC3')].each {|nsec_rrsets| nsec_rrsets.each {|nsecs| nsecs.rrs.each {|nsec| # print "Checking NSEC : #{nsec}\n" next if (nsec.name.wild?) if nsec.check_name_in_range(name) proven_name_in_nsecs = true qtype_present = false if (qtype) if (nsec.types.include?qtype) qtype_present = true end if (qtype_present != expected_qtype) # print "#{nsec.type} record #{nsec} does #{expected_qtype ? 'not ' : ''} include #{qtype} type\n" raise VerifyError.new("#{nsec.type} record #{nsec} does #{expected_qtype ? 'not ' : ''}include #{qtype} type") # return false end type_covered_checked = true end end } } } if (!proven_name_in_nsecs) # print "No proof for non-existence for #{name}\n" raise VerifyError.new("No proof for non-existence for #{name}") end if (qtype && !type_covered_checked) # print "Tyes covered wrong for #{name}\n" raise VerifyError.new("Types covered wrong for #{name}") end end def check_name_in_and_type_not_in_nsecs(msg) # :nodoc: check_name_in_nsecs(msg, msg.question()[0].qtype, false) end def check_name_not_in_wildcard_nsecs(msg) # :nodoc: # @TODO@ Do this for NSEC3 records too! name = msg.question()[0].qname qtype = msg.question()[0].qtype done= false [msg.authority.rrsets('NSEC'), msg.authority.rrsets('NSEC3')].each {|nsec_rrsets| nsec_rrsets.each {|nsecs| nsecs.rrs.each {|nsec| # print "Checking NSEC : #{nsec}\n" next if !nsec.name.wild? # Check the wildcard expansion # We want to see that the name is in the wildcard range, and that the type # is not in the types for the NSEC if nsec.check_name_in_wildcard_range(name) # print "Wildcard expansion in #{nsec} includes #{name}\n" raise VerifyError.new("Wildcard expansion in #{nsec} includes #{name}") # return false end if (nsec.types.include?qtype) # print "#{qtype} present in wildcard #{nsec}\n" raise VerifyError.new("#{qtype} present in wildcard #{nsec}") # return false end done = true } } } return if done # print("Expected wildcard expansion in #{msg}\n") raise VerifyError.new("Expected wildcard expansion in #{msg}") # return false end def verify_ds_rrset(ds_rrset, keys = nil, msg = nil) # :nodoc: # print "verify_ds_rrset #{ds_rrset}\n" if (ds_rrset && ds_rrset.num_sigs > 0) if (verify_rrset(ds_rrset, keys)) # Need to handle DS RRSets (with RRSIGs) not just DS records. # ds_rrset.rrs.each do |ds| # Work out which key this refers to, and add it to the trusted key store found = false if (msg) msg.each_section do |section| section.rrsets('DNSKEY').each {|rrset| rrset.rrs.each do |rr| if (check_ds(rr, ds_rrset)) found = true end end } end end get_keys_to_check().each {|key| if (check_ds(key, ds_rrset)) found = true end } # If we couldn't find the trusted key, then we should store the # key tag and digest in a @@discovered_ds_store. # Each time we see a new key (which has been signed) then we should # check if it is sitting on the discovered_ds_store. # If it is, then we should add it to the trusted_keys and remove the # DS from the discovered_ds_store if (!found) @discovered_ds_store.push(ds_rrset) end # end return true else return false end end return false # no DS rrset to verify end def verify_key_rrset(key_rrset, keys = nil) # :nodoc: # print "verify_key_rrset\n" verified = false if (key_rrset && key_rrset.num_sigs > 0) if (verify_rrset(key_rrset, keys)) # key_rrset.rrs.each do |rr| # print "Adding keys : " # key_rrset.rrs.each {|rr| print "#{rr.key_tag}, "} # print "\n" @trusted_keys.add(key_rrset) # rr) verified = true end check_ds_stores(key_rrset) end return verified end def check_ds_stores(key_rrset) # :nodoc: # See if the keys match any of the to_be_trusted_keys key_rrset.rrs.each do |key| @configured_ds_store.each do |ds| if (ds.check_key(key)) @trusted_keys.add_key_with_expiration(key, key_rrset.sigs()[0].expiration) end end @discovered_ds_store.each do |tbtk| # Check that the RRSet is still valid!! # Should we get it out of the main cache? if ((tbtk.sigs()[0].expiration < Time.now.to_i)) @discovered_ds_store.delete(tbtk) else tbtk.rrs.each {|ds| if (ds.check_key(key)) @trusted_keys.add_key_with_expiration(key, tbtk.sigs()[0].expiration) @discovered_ds_store.delete(tbtk) end } end end # end end end def get_keys_to_check # :nodoc: keys_to_check = @trust_anchors.keys + @trusted_keys.keys return keys_to_check end # Find the first matching DNSKEY and RRSIG record in the two sets. def get_matching_key(keys, sigrecs)#:nodoc: all # There can be multiple signatures in the RRSet - which one should we choose? if ((keys == nil) || (sigrecs == nil)) return nil, nil end if ((RR::DNSKEY === keys) || (RR::DS === keys) || ((RR::DLV === keys) && (@verifier_type == VerifierType::DLV))) keys = [keys] end enumerator = keys if (enumerator.class == RRSet) enumerator = enumerator.rrs end enumerator.each {|key| if ((key.revoked?)) # || (key.bad_flags?)) next end sigrecs.each {|sig| # print "Looking at #{sig.key_tag} on sig, #{key.key_tag} on key\n" if ((key.key_tag == sig.key_tag) && (key.algorithm == sig.algorithm)) # print "Found key #{key.key_tag}\n" return key, sig end } } return nil, nil end # Verify the signature of an rrset encoded with the specified KeyCache # or RRSet. If no signature is included, false is returned. # # Returns true if the RRSet verified, false otherwise. def verify_rrset(rrset, keys = nil) # print "Verify_rrset #{rrset.name}, #{rrset.type}\n" # print "ABOUT TO VERIFY WITH #{keys == nil ? '0' : keys.length} keys\n" # if (keys != nil) # if (keys.length > 0) # print "KEY TAG : #{keys[0].key_tag}\n" # end # end sigrecs = rrset.sigs if (rrset.rrs.length == 0) raise VerifyError.new("No RRSet to verify") end if (rrset.num_sigs == 0) raise VerifyError.new("No signatures in the RRSet : #{rrset.name}, #{rrset.type}") end sigrecs.each do |sigrec| check_rr_data(rrset, sigrec) end raise ArgumentError.new("Expecting DNSKEY, DLV, DS, RRSet, Array or nil for keys : got #{keys.class} instead") if (keys && (![Array, RR::IN::DNSKEY, RR::IN::DLV, RR::IN::DS].include?keys.class) && (keys.class != RRSet)) keyrec = nil sigrec = nil if (rrset.type == Types.DNSKEY) if (keys && !(Array === keys) && ((keys.type == Types.DS) || ((keys.type == Types.DLV) && (@verifier_type == VerifierType::DLV)))) rrset.rrs.each do |key| keys.rrs.each do |ds| if (ds.check_key(key)) @trusted_keys.add_key_with_expiration(key, rrset.sigs()[0].expiration) end end end else check_ds_stores(rrset) end end if ((keys.nil?) || ((keys.class != Array) && ((keys.type == Types.DS) || ((keys.type == Types.DLV) && (@verifier_type == VerifierType::DLV))))) keyrec, sigrec = get_matching_key(get_keys_to_check, sigrecs) else keyrec, sigrec = get_matching_key(keys, sigrecs) end # return false if !keyrec if (!keyrec) # print "Couldn't find signing key! #{rrset.name}, #{rrset.type},\n " raise VerifyError.new("Signing key not found") end # RFC 4034 # 3.1.8.1. Signature Calculation if (keyrec.sep_key? && !keyrec.zone_key?) Dnsruby.log.error("DNSKEY with SEP flag set and Zone Key flag not set was used to verify RRSIG over RRSET - this is not allowed by RFC4034 section 2.1.1") # return false raise VerifyError.new("DNSKEY with SEP flag set and Zone Key flag not set") end # print "VERIFY KEY FOUND - doing verification\n" # Any DNS names in the RDATA field of each RR MUST be in # canonical form; and # The RRset MUST be sorted in canonical order. rrset = rrset.sort_canonical sig_data = sigrec.sig_data # RR(i) = owner | type | class | TTL | RDATA length | RDATA rrset.each do |rec| old_ttl = rec.ttl rec.ttl = sigrec.original_ttl data = MessageEncoder.new { |msg| msg.put_rr(rec, true) }.to_s # @TODO@ worry about wildcards here? rec.ttl = old_ttl if (RUBY_VERSION >= "1.9") data.force_encoding("ASCII-8BIT") end sig_data += data end # Now calculate the signature verified = false if [Algorithms.RSASHA1, Algorithms.RSASHA1_NSEC3_SHA1].include?(sigrec.algorithm) verified = keyrec.public_key.verify(OpenSSL::Digest::SHA1.new, sigrec.signature, sig_data) elsif (sigrec.algorithm == Algorithms.RSASHA256) verified = keyrec.public_key.verify(OpenSSL::Digest::SHA256.new, sigrec.signature, sig_data) elsif (sigrec.algorithm == Algorithms.RSASHA512) verified = keyrec.public_key.verify(OpenSSL::Digest::SHA512.new, sigrec.signature, sig_data) elsif [Algorithms.DSA, Algorithms.DSA_NSEC3_SHA1].include?(sigrec.algorithm) # we are ignoring T for now # t = sigrec.signature[0] # t = t.getbyte(0) if t.class == String r = RR::get_num(sigrec.signature[1, 20]) s = RR::get_num(sigrec.signature[21, 20]) r_asn1 = OpenSSL::ASN1::Integer.new(r) s_asn1 = OpenSSL::ASN1::Integer.new(s) asn1 = OpenSSL::ASN1::Sequence.new([r_asn1, s_asn1]).to_der verified = keyrec.public_key.verify(OpenSSL::Digest::DSS1.new, asn1, sig_data) else raise RuntimeError.new("Algorithm #{sigrec.algorithm.code} unsupported by Dnsruby") end if (!verified) raise VerifyError.new("Signature failed to cryptographically verify") end # Sort out the TTLs - set it to the minimum valid ttl expiration_diff = (sigrec.expiration.to_i - Time.now.to_i).abs rrset.ttl = ([rrset.ttl, sigrec.ttl, sigrec.original_ttl, expiration_diff].sort)[0] # print "VERIFIED OK\n" return true end def find_closest_dlv_anchor_for(name) # :nodoc: # To find the closest anchor, query DLV.isc.org for [a.b.c.d], then [a.b.c], [a.b], etc. # once closest anchor found, simply run follow_chain from that anchor # @TODO@ REALLY NEED AGGRESSIVE NEGATIVE CACHING HERE!! # i.e. don't look up zones which we *know* we don't have a DLV anchor for n = Name.create(name) root = Name.create(".") while (n != root) # Try to find name in DLV, and return it if possible dlv_rrset = query_dlv_for(n) if (dlv_rrset) key_rrset = get_zone_key_from_dlv_rrset(dlv_rrset, n) return key_rrset end # strip the name n = n.strip_label end return false end def get_zone_key_from_dlv_rrset(dlv_rrset, name) # :nodoc: # We want to return the key for the zone i.e. DS/DNSKEY for .se, NOT DLV for se.dlv.isc.org # So, we have the DLv record. Now use it to add the zone's DNSKEYs to the trusted key set. res = get_nameservers_for(name) if (!res) if (Dnssec.do_validation_with_recursor?) res = get_recursor else if(Dnssec.default_resolver) res = Dnssec.default_resolver else res = Resolver.new end end end res.dnssec = true # query = Message.new(name, Types.DNSKEY) # query.do_validation = false ret = nil begin # ret = res.send_message(query) ret = res.query_no_validation_or_recursion(name, Types.DNSKEY) if (!ret) raise ResolvError.new("Couldn't get DNSKEY from Recursor") end rescue ResolvError => e # print "Error getting zone key from DLV RR for #{name} : #{e}\n" TheLog.error("Error getting zone key from DLV RR for #{name} : #{e}") return false end key_rrset = ret.answer.rrset(name, Types.DNSKEY) begin verify(key_rrset, dlv_rrset) # Cache.add(ret) return key_rrset rescue VerifyError => e # print "Can't move from DLV RR to zone DNSKEY for #{name}, error : #{e}\n" TheLog.debug("Can't move from DLV RR to zone DNSKEY for #{name}, error : #{e}") end return false end def query_dlv_for(name) # :nodoc: # See if there is a record for name in dlv.isc.org if (!@res && (@verifier_type == VerifierType::DLV)) @res = get_dlv_resolver end begin name_to_query = name.to_s+".dlv.isc.org" # query = Message.new(name_to_query, Types.DLV) # @res.single_resolvers()[0].prepare_for_dnssec(query) # query.do_validation = false ret = nil begin # ret = @res.send_message(query) ret = @res.query_no_validation_or_recursion(name_to_query, Types.DLV) if (!ret) raise ResolvError.new("Couldn't get DLV record from Recursor") end rescue ResolvError => e # print "Error getting DLV record for #{name} : #{e}\n" TheLog.info("Error getting DLV record for #{name} : #{e}") return nil end dlv_rrset = ret.answer.rrset(name_to_query,Types.DLV) if (dlv_rrset.rrs.length > 0) begin verify(dlv_rrset) # Cache.add(ret) return dlv_rrset rescue VerifyError => e # print "Error verifying DLV records for #{name}, #{e}\n" TheLog.info("Error verifying DLV records for #{name}, #{e}") end end rescue NXDomain # print "NXDomain for DLV lookup for #{name}\n" return nil end return nil end def find_closest_anchor_for(name) # :nodoc: # Check if we have an anchor for name. # If not, strip off first label and try again # If we get to root, then return false name = "." if name == "" n = Name.create(name) root = Name.create(".") while (true) # n != root) # Try the trusted keys first, then the DS set (@trust_anchors.keys + @trusted_keys.keys + @configured_ds_store + @discovered_ds_store).each {|key| return key if key.name.canonical == n.canonical } break if (n.to_s == root.to_s) # strip the name n = n.strip_label end return false end # @TODO@ Handle REVOKED keys! (RFC 5011) # Remember that revoked keys will have a different key_tag than pre-revoked. # So, if we see a revoked key, we should go through our key store for # that authority and remove any keys with the pre-revoked key_tag. def follow_chain(anchor, name) # :nodoc: # Follow the chain from the anchor to name, returning the appropriate # key at the end, or false. # # i.e. anchor = se, name = foo.example.se # get anchor for example.se with se anchor # get anchor for foo.example.se with example.se anchor next_key = anchor next_step = anchor.name parent = next_step # print "Follow chain from #{anchor.name} to #{name}\n" TheLog.debug("Follow chain from #{anchor.name} to #{name}") # res = nil res = Dnssec.default_resolver # while ((next_step != name) || (next_key.type != Types.DNSKEY)) while (true) # print "In loop for parent=#{parent}, next step = #{next_step}\n" dont_move_on = false if (next_key.type != Types.DNSKEY) dont_move_on = true end next_key, res = get_anchor_for(next_step, parent, next_key, res) if (next_step.canonical.to_s == name.canonical.to_s) # print "Returning #{next_key.type} for #{next_step}, #{(next_key.type != Types.DNSKEY)}\n" return next_key end return false if (!next_key) # Add the next label on if (!dont_move_on) parent = next_step next_step = Name.new(name.labels[name.labels.length-1-next_step.labels.length,1] + next_step.labels , name.absolute?) # print "Next parent = #{parent}, next_step = #{next_step}, next_key.type = #{next_key.type.string}\n" end end # print "Returning #{next_key.type} for #{next_step}, #{(next_key.type != Types.DNSKEY)}\n" return next_key end def get_anchor_for(child, parent, current_anchor, parent_res = nil) # :nodoc: # print "Trying to discover anchor for #{child} from #{parent}\n" TheLog.debug("Trying to discover anchor for #{child} from #{parent} using #{current_anchor}, #{parent_res}") # We wish to return a DNSKEY which the caller can use to verify name # We are either given a key or a ds record from the parent zone # If given a DNSKEY, then find a DS record signed by that key for the child zone # Use the DS record to find a valid key in the child zone # Return it # Find NS RRSet for parent child_res = nil if (Dnssec.do_validation_with_recursor?) parent_res = get_recursor child_res = get_recursor end begin if (child!=parent) if (!parent_res) # print "No res passed - try to get nameservers for #{parent}\n" parent_res = get_nameservers_for(parent) if (!parent_res) if (Dnssec.do_validation_with_recursor?) parent_res = get_recursor else if (Dnssec.default_resolver) parent_res = Dnssec.default_resolver else parent_res = Resolver.new end end end parent_res.dnssec = true end # Use that Resolver to query for DS record and NS for children ds_rrset = current_anchor if (current_anchor.type == Types.DNSKEY) # print "Trying to find DS records for #{child} from servers for #{parent}\n" TheLog.debug("Trying to find DS records for #{child} from servers for #{parent}") ds_ret = nil begin ds_ret = parent_res.query_no_validation_or_recursion(child, Types.DS) if (!ds_ret) raise ResolvError.new("Couldn't get DS records from Recursor") end rescue ResolvError => e # print "Error getting DS record for #{child} : #{e}\n" TheLog.error("Error getting DS record for #{child} : #{e}") return false, nil end ds_rrset = ds_ret.answer.rrset(child, Types.DS) if (ds_rrset.rrs.length == 0) # @TODO@ Check NSEC(3) records - still need to verify there are REALLY no ds records! # print "NO DS RECORDS RETURNED FOR #{parent}\n" # child_res = parent_res else begin if (verify(ds_rrset, current_anchor) || verify(ds_rrset)) # Try to make the resolver from the authority/additional NS RRSets in DS response if (!Dnssec.do_validation_with_recursor?) child_res = get_nameservers_from_message(child, ds_ret) end end rescue VerifyError => e # print "FAILED TO VERIFY DS RRSET FOR #{child}\n" TheLog.info("FAILED TO VERIFY DS RRSET FOR #{child}") # return false, nil # raise ResolvError.new("FAILED TO VERIFY DS RRSET FOR #{child}") raise VerifyError.new("FAILED TO VERIFY DS RRSET FOR #{child}") end end end end # Make Resolver using all child NSs if (!child_res) child_res = get_nameservers_for(child, parent_res) end if (!child_res) if (Dnssec.do_validation_with_recursor?) child_res = get_recursor else if (Dnssec.default_resolver) child_res = Dnssec.default_resolver else if (Dnssec.default_resolver) child_res = Dnssec.default_resolver else child_res = Resolver.new end end child_res.dnssec = true end end # Query for DNSKEY record, and verify against DS in parent. # Need to get resolver NOT to verify this message - we verify it afterwards # print "Trying to find DNSKEY records for #{child} from servers for #{child}\n" TheLog.info("Trying to find DNSKEY records for #{child} from servers for #{child}") # query = Message.new(child, Types.DNSKEY) # query.do_validation = false key_ret = nil begin # key_ret = child_res.send_message(query) key_ret = child_res.query_no_validation_or_recursion(child, Types.DNSKEY) if (!key_ret) raise ResolvError.new("Couldn't get info from Recursor") end rescue ResolvError => e # print "Error getting DNSKEY for #{child} : #{e}\n" TheLog.error("Error getting DNSKEY for #{child} : #{e}") # return false, nil raise VerifyError.new("Error getting DNSKEY for #{child} : #{e}") end verified = true key_rrset = key_ret.answer.rrset(child, Types.DNSKEY) if (key_rrset.rrs.length == 0) # @TODO@ Still need to check NSEC records to make *sure* no key rrs returned! # print "NO DNSKEY RECORDS RETURNED FOR #{child}\n" TheLog.debug("NO DNSKEY RECORDS RETURNED FOR #{child}") # end verified = false else # Should check that the matching key's zone flag is set (RFC 4035 section 5.2) key_rrset.rrs.each {|k| if (!k.zone_key?) # print "Discovered DNSKEY is not a zone key - ignoring\n" TheLog.debug("Discovered DNSKEY is not a zone key - ignoring") return false, child_res end } begin verify(key_rrset, ds_rrset) rescue VerifyError => e begin verify(key_rrset) rescue VerifyError =>e verified = false raise VerifyError.new("Couldn't verify DNSKEY and DS records") end end end # Try to make the resolver from the authority/additional NS RRSets in DNSKEY response new_res = get_nameservers_from_message(child, key_ret) # @TODO@ ? if (!new_res) new_res = child_res end if (!verified) TheLog.info("Failed to verify DNSKEY for #{child}") return false, nil # new_res # raise VerifyError.new("Failed to verify DNSKEY for #{child}") end # Cache.add(key_ret) return key_rrset, new_res rescue VerifyError => e # print "Verification error : #{e}\n" TheLog.info("Verification error : #{e}\n") # return false, nil # new_res raise VerifyError.new("Verification error : #{e}\n") end end def get_nameservers_for(name, res = nil) # :nodoc: # @TODO@ !!! if (Dnssec.do_validation_with_recursor?) return get_recursor else resolver = nil if (Dnssec.default_resolver) resolver = Dnssec.default_resolver else resolver = Resolver.new end resolver.dnssec = true return resolver end end def get_nameservers_from_message(name, ns_ret) # :nodoc: if (Dnssec.default_resolver) return Dnssec.default_resolver end ns_rrset = ns_ret.answer.rrset(name, Types.NS) if (!ns_rrset || ns_rrset.length == 0) ns_rrset = ns_ret.authority.rrset(name, Types.NS) # @TODO@ Is ths OK? end if (!ns_rrset || ns_rrset.length == 0 || ns_rrset.name.canonical != name.canonical) return nil end if (ns_rrset.sigs.length > 0) # verify_rrset(ns_rrset) # @TODO@ ?? end # Cache.add(ns_ret) ns_additional = [] ns_ret.additional.each {|rr| ns_additional.push(rr) if (rr.type == Types.A) } nameservers = [] add_nameservers(ns_rrset, ns_additional, nameservers) # if (ns_additional.length > 0) ns_additional = [] ns_ret.additional.each {|rr| ns_additional.push(rr) if (rr.type == Types.AAAA) } add_nameservers(ns_rrset, ns_additional, nameservers) if (ns_additional.length > 0) # Make Resolver using all NSs if (nameservers.length == 0) # print "Can't find nameservers for #{ns_ret.question()[0].qname} from #{ns_rrset.rrs}\n" TheLog.info("Can't find nameservers for #{ns_ret.question()[0].qname} from #{ns_rrset.rrs}") return nil # @TODO@ Could return a recursor here? # return Recursor.new end res = Resolver.new() res.nameserver=(nameservers) # Set the retry_delay to be (at least) the number of nameservers # Otherwise, the queries will be sent at a rate of more than one a second! res.retry_delay = nameservers.length * 2 res.dnssec = true return res end def add_nameservers(ns_rrset, ns_additional, nameservers) # :nodoc: # Want to go through all of the ns_rrset NS records, # print "Checking #{ns_rrset.rrs.length} NS records against #{ns_additional.length} address records\n" ns_rrset.rrs.sort_by {rand}.each {|ns_rr| # and see if we can find any of the names in the A/AAAA records in ns_additional found_addr = false ns_additional.each {|addr_rr| if (ns_rr.nsdname.canonical == addr_rr.name.canonical) # print "Found address #{addr_rr.address} for #{ns_rr.nsdname}\n" nameservers.push(addr_rr.address.to_s) found_addr = true break # If we can, then we add the server A/AAAA address to nameservers end # If we can't, then we add the server NS name to nameservers } if (!found_addr) # print "Couldn't find address - adding #{ns_rr.nsdname}\n" nameservers.push(ns_rr.nsdname) end } end def validate_no_rrsigs(msg) # :nodoc: # print "Validating unsigned response\n" # WHAT IF THERE ARE NO RRSIGS IN MSG? # Then we need to check that we do not expect any RRSIGs if (!msg.question()[0] && msg.answer.length == 0) # print "Returning Message insecure OK\n" msg.security_level = Message::SecurityLevel.INSECURE return true end qname = msg.question()[0].qname closest_anchor = find_closest_anchor_for(qname) # print "Found closest anchor :#{closest_anchor}\n" if (closest_anchor) actual_anchor = follow_chain(closest_anchor, qname) # print "Actual anchor : #{actual_anchor}\n" if (actual_anchor) # print("Anchor exists for #{qname}, but no signatures in #{msg}\n") TheLog.error("Anchor exists for #{qname}, but no signatures in #{msg}") msg.security_level = Message::SecurityLevel.BOGUS return false end end if ((@verifier_type == VerifierType::DLV) && @added_dlv_key) # Remember to check DLV registry as well (if appropriate!) # print "Checking DLV for closest anchor\n" dlv_anchor = find_closest_dlv_anchor_for(qname) # print "Found DLV closest anchor :#{dlv_anchor}\n" if (dlv_anchor) actual_anchor = follow_chain(dlv_anchor, qname) # print "Actual anchor : #{actual_anchor}\n" if (actual_anchor) # print("DLV Anchor exists for #{qname}, but no signatures in #{msg}\n") TheLog.error("DLV Anchor exists for #{qname}, but no signatures in #{msg}") msg.security_level = Message::SecurityLevel.BOGUS return false end end end # print "Returning Message insecure OK\n" msg.security_level = Message::SecurityLevel.INSECURE return true end def validate(msg, query) if (msg.rrsets('RRSIG').length == 0) return validate_no_rrsigs(msg) end # See if it is a child of any of our trust anchors. # If it is, then see if we have a trusted key for it # If we don't, then see if we can get to it from the closest # trust anchor # Otherwise, try DLV (if configured) # # # So - find closest existing trust anchor error = nil msg.security_level = Message::SecurityLevel.INDETERMINATE qname = msg.question()[0].qname closest_anchor = find_closest_anchor_for(qname) if (!closest_anchor) end TheLog.debug("Closest anchor for #{qname} is #{closest_anchor} - trying to follow down") error = try_to_follow_from_anchor(closest_anchor, msg, qname) if ((msg.security_level.code < Message::SecurityLevel::SECURE) && (@verifier_type == VerifierType::DLV) && @added_dlv_key) # If we can't find anything, and we're set to check DLV, then # check the DLV registry and work down from there. dlv_anchor = find_closest_dlv_anchor_for(qname) if (dlv_anchor) # print "Trying to follow DLV anchor from #{dlv_anchor.name} to #{qname}\n" TheLog.debug("Trying to follow DLV anchor from #{dlv_anchor.name} to #{qname}") error = try_to_follow_from_anchor(dlv_anchor, msg, qname) else # print "Couldn't find DLV anchor for #{qname}\n" TheLog.debug("Couldn't find DLV anchor for #{qname}") end end if (msg.security_level.code != Message::SecurityLevel::SECURE) begin # print "Trying to verify one last time\n" if verify(msg) # Just make sure we haven't picked the keys up anywhere msg.security_level = Message::SecurityLevel.SECURE return true end rescue VerifyError => e # print "Verify failed : #{e}\n" end end if (error) raise error end if (msg.security_level == Message::SecurityLevel.BOGUS) raise VerifyError.new("Bogus record") end if (msg.security_level.code > Message::SecurityLevel::UNCHECKED) return true else return false end end def try_to_follow_from_anchor(closest_anchor, msg, qname) # :nodoc: error = nil if (closest_anchor) # Then try to descend to the level we're interested in actual_anchor = false begin actual_anchor = follow_chain(closest_anchor, qname) rescue VerifyError => e TheLog.debug("Broken chain from anchor : #{closest_anchor.name}") msg.security_level = Message::SecurityLevel.BOGUS return e end # @TODO@ We need to de ermine whether there was simply no DS record, or whether there was a failure if (!actual_anchor) TheLog.debug("Unable to follow chain from anchor : #{closest_anchor.name}") msg.security_level = Message::SecurityLevel.INSECURE else actual_anchor_keys = "" actual_anchor.rrs.each {|rr| actual_anchor_keys += ", #{rr.key_tag}"} TheLog.debug("Found anchor #{actual_anchor.name}, #{actual_anchor.type} for #{qname} : #{actual_anchor_keys}") # print "Found anchor #{actual_anchor.name}, #{actual_anchor.type} for #{qname} : #{actual_anchor_keys}\n" begin if (verify(msg, actual_anchor)) TheLog.debug("Validated #{qname}") msg.security_level = Message::SecurityLevel.SECURE end rescue VerifyError => e TheLog.info("BOGUS #{qname}! Error : #{e}") # print "BOGUS #{qname}! Error : #{e}\n" msg.security_level = Message::SecurityLevel.BOGUS error = e end end else # print "Unable to find an anchor for #{qname}\n" msg.security_level = Message::SecurityLevel.INSECURE end return error end end enddnsruby-1.61.3/lib/dnsruby/code_mappers.rb0000644000004100000410000002216713607400362020553 0ustar www-datawww-datamodule Dnsruby require 'dnsruby/code_mapper' class OpCode < CodeMapper Query = 0 # RFC 1035 IQuery = 1 # RFC 1035 Status = 2 # RFC 1035 Notify = 4 # RFC 1996 Update = 5 # RFC 2136 update() end class RCode < CodeMapper NOERROR = 0 # RFC 1035 FORMERR = 1 # RFC 1035 SERVFAIL = 2 # RFC 1035 NXDOMAIN = 3 # RFC 1035 NOTIMP = 4 # RFC 1035 REFUSED = 5 # RFC 1035 YXDOMAIN = 6 # RFC 2136 YXRRSET = 7 # RFC 2136 NXRRSET = 8 # RFC 2136 NOTAUTH = 9 # RFC 2136 NOTZONE = 10 # RFC 2136 # BADVERS = 16 # an EDNS ExtendedRCode BADSIG = 16 BADKEY = 17 BADTIME = 18 BADMODE = 19 BADNAME = 20 BADALG = 21 update() end class ExtendedRCode < CodeMapper BADVERS = 16 update() end class Classes < CodeMapper IN = 1 # RFC 1035 CH = 3 # RFC 1035 # CHAOS = 3 # RFC 1035 HS = 4 # RFC 1035 # HESIOD = 4 # RFC 1035 NONE = 254 # RFC 2136 ANY = 255 # RFC 1035 update() def unknown_string(arg) if (arg=~/^CLASS/i) Classes.add_pair(arg, arg.gsub('CLASS', '').to_i) set_string(arg) else raise ArgumentError.new("String #{arg} not a member of #{self.class}") end end def unknown_code(arg) Classes.add_pair('CLASS' + arg.to_s, arg) set_code(arg) end # classesbyval and classesbyname functions are wrappers around the # similarly named hashes. They are used for 'unknown' DNS RR classess # (RFC3597) # See typesbyval and typesbyname, these beasts have the same functionality def Classes.classesbyname(name) #:nodoc: all name.upcase!; if to_code(name) return to_code(name) end if ((name =~/^\s*CLASS(\d+)\s*$/o) == nil) raise ArgumentError, "classesbyval() argument is not CLASS### (#{name})" end val = $1.to_i if val > 0xffff raise ArgumentError, 'classesbyval() argument larger than ' + 0xffff end return val; end def Classes.classesbyval(val) #:nodoc: all if (val.class == String) if ((val =~ /^\s*0*([0-9]+)\s*$/) == nil) raise ArgumentError, "classesbybal() argument is not numeric (#{val})" # unless val.gsub!("^\s*0*([0-9]+)\s*$", "$1") # val =~ s/^\s*0*([0-9]+)\s*$/$1/o;# end val = $1.to_i end return to_string(val) if to_string(val) raise ArgumentError, 'classesbyval() argument larger than ' + 0xffff if val > 0xffff; return "CLASS#{val}"; end end # The RR types explicitly supported by Dnsruby. # # New RR types should be added to this set class Types < CodeMapper SIGZERO = 0 # RFC2931 consider this a pseudo type A = 1 # RFC 1035, Section 3.4.1 NS = 2 # RFC 1035, Section 3.3.11 MD = 3 # RFC 1035, Section 3.3.4 (obsolete) MF = 4 # RFC 1035, Section 3.3.5 (obsolete) CNAME = 5 # RFC 1035, Section 3.3.1 SOA = 6 # RFC 1035, Section 3.3.13 MB = 7 # RFC 1035, Section 3.3.3 MG = 8 # RFC 1035, Section 3.3.6 MR = 9 # RFC 1035, Section 3.3.8 NULL = 10 # RFC 1035, Section 3.3.10 WKS = 11 # RFC 1035, Section 3.4.2 (deprecated) PTR = 12 # RFC 1035, Section 3.3.12 HINFO = 13 # RFC 1035, Section 3.3.2 MINFO = 14 # RFC 1035, Section 3.3.7 MX = 15 # RFC 1035, Section 3.3.9 TXT = 16 # RFC 1035, Section 3.3.14 RP = 17 # RFC 1183, Section 2.2 AFSDB = 18 # RFC 1183, Section 1 X25 = 19 # RFC 1183, Section 3.1 ISDN = 20 # RFC 1183, Section 3.2 RT = 21 # RFC 1183, Section 3.3 NSAP = 22 # RFC 1706, Section 5 NSAP_PTR = 23 # RFC 1348 (obsolete) SIG = 24 # RFC 2535, Section 4.1 KEY = 25 # RFC 2535, Section 3.1 PX = 26 # RFC 2163, GPOS = 27 # RFC 1712 (obsolete) AAAA = 28 # RFC 1886, Section 2.1 LOC = 29 # RFC 1876 NXT = 30 # RFC 2535, Section 5.2 obsoleted by RFC3755 EID = 31 # draft-ietf-nimrod-dns-xx.txt NIMLOC = 32 # draft-ietf-nimrod-dns-xx.txt SRV = 33 # RFC 2052 ATMA = 34 # ??? NAPTR = 35 # RFC 2168 KX = 36 # RFC 2230 CERT = 37 # RFC 2538 DNAME = 39 # RFC 2672 OPT = 41 # RFC 2671 APL = 42 # RFC 3123 DS = 43 # RFC 4034 SSHFP = 44 # RFC 4255 IPSECKEY = 45 # RFC 4025 RRSIG = 46 # RFC 4034 NSEC = 47 # RFC 4034 DNSKEY = 48 # RFC 4034 DHCID = 49 # RFC 4701 NSEC3 = 50 # RFC still pending at time of writing NSEC3PARAM= 51 # RFC still pending at time of writing TLSA = 52 # RFC 6698 HIP = 55 # RFC 5205 CDS = 59 # RFC 7344 CDNSKEY = 60 # RFC 7344 SPF = 99 # RFC 4408 UINFO = 100 # non-standard UID = 101 # non-standard GID = 102 # non-standard UNSPEC = 103 # non-standard TKEY = 249 # RFC 2930 TSIG = 250 # RFC 2931 IXFR = 251 # RFC 1995 AXFR = 252 # RFC 1035 MAILB = 253 # RFC 1035 (MB, MG, MR) MAILA = 254 # RFC 1035 (obsolete - see MX) ANY = 255 # RFC 1035 URI = 256 # RFC 7553 CAA = 257 # RFC 6844 DLV = 32769 # RFC 4431 (informational) update() def unknown_string(arg) #:nodoc: all if (arg=~/^TYPE/i) Types.add_pair(arg, arg.gsub('TYPE', '').to_i) set_string(arg) else raise ArgumentError.new("String #{arg} not a member of #{self.class}") end end def unknown_code(arg) #:nodoc: all Types.add_pair('TYPE' + arg.to_s, arg) set_code(arg) end # -- # typesbyval and typesbyname functions are wrappers around the similarly named # hashes. They are used for 'unknown' DNS RR types (RFC3597) # typesbyname returns they TYPEcode as a function of the TYPE # mnemonic. If the TYPE mapping is not specified the generic mnemonic # TYPE### is returned. def Types.typesbyname(name) #:nodoc: all name.upcase! if to_code(name) return to_code(name) end if ((name =~/^\s*TYPE(\d+)\s*$/o)==nil) raise ArgumentError, "Net::DNS::typesbyname() argument (#{name}) is not TYPE###" end val = $1.to_i if val > 0xffff raise ArgumentError, 'Net::DNS::typesbyname() argument larger than ' + 0xffff end return val; end # typesbyval returns they TYPE mnemonic as a function of the TYPE # code. If the TYPE mapping is not specified the generic mnemonic # TYPE### is returned. def Types.typesbyval(val) #:nodoc: all if (!defined?val) raise ArgumentError, "Net::DNS::typesbyval() argument is not defined" end if val.class == String # if val.gsub!("^\s*0*(\d+)\s*$", "$1") if ((val =~ /^\s*0*(\d+)\s*$", "$1/o) == nil) raise ArgumentError, "Net::DNS::typesbyval() argument (#{val}) is not numeric" # val =~s/^\s*0*(\d+)\s*$/$1/o; end val = $1.to_i end if to_string(val) return to_string(val) end raise ArgumentError, 'Net::DNS::typesbyval() argument larger than ' + 0xffff if val > 0xffff; return "TYPE#{val}"; end end class QTypes < CodeMapper IXFR = 251 # incremental transfer [RFC1995] AXFR = 252 # transfer of an entire zone [RFC1035] MAILB = 253 # mailbox-related RRs (MB, MG or MR) [RFC1035] MAILA = 254 # mail agent RRs (Obsolete - see MX) [RFC1035] ANY = 255 # all records [RFC1035] update() end class MetaTypes < CodeMapper TKEY = 249 # Transaction Key [RFC2930] TSIG = 250 # Transaction Signature [RFC2845] OPT = 41 # RFC 2671 end # http://www.iana.org/assignments/dns-sec-alg-numbers/ class Algorithms < CodeMapper RESERVED = 0 RSAMD5 = 1 DH = 2 DSA = 3 RSASHA1 = 5 RSASHA256 = 8 RSASHA512 = 10 ECDSAP256SHA256 = 13 ECDSAP384SHA384 = 14 INDIRECT = 252 PRIVATEDNS = 253 PRIVATEOID = 254 update() # Referred to as Algorithms.DSA_NSEC3_SHA1 add_pair("DSA-NSEC3-SHA1", 6) # Referred to as Algorithms.RSASHA1_NSEC3_SHA1 add_pair("RSASHA1-NSEC3-SHA1", 7) # Referred to as Algorithms.ECC_GOST add_pair("ECC-GOST",12) end # http://www.iana.org/assignments/dnssec-nsec3-parameters/dnssec-nsec3-parameters.xhtml class Nsec3HashAlgorithms < CodeMapper RESERVED = 0 update() add_pair("SHA-1", 1) end end dnsruby-1.61.3/lib/dnsruby/dnssec.rb0000644000004100000410000003124213607400362017363 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License f181or the specific language governing permissions and # limitations under the License. # ++ require 'digest/sha2' require 'net/ftp' require 'dnsruby/key_cache' require 'dnsruby/single_verifier' module Dnsruby # RFC4033, section 7 # "There is one more step that a security-aware stub resolver can take # if, for whatever reason, it is not able to establish a useful trust # relationship with the recursive name servers that it uses: it can # perform its own signature validation by setting the Checking Disabled # (CD) bit in its query messages. A validating stub resolver is thus # able to treat the DNSSEC signatures as trust relationships between # the zone administrators and the stub resolver itself. " # # Dnsruby is configured to validate responses by default. However, it is not # configured with any trusted keys by default. Applications may use the # verify() method to perform verification with of RRSets of Messages with # given keys. Alternatively, trusted keys may be added to this class (either # directly, or by loading the IANA TAR or the DLV ISC ZSK). Validation will then # be performed from these keys (or the DLV registry, if configured). Negative # and positive responses are validation. # # Messages are tagged with the current security_level (Message::SecurityLevel). # UNCHECKED means Dnsruby has not attempted to validate the response. # BOGUS means the response has been checked, and is bogus. # INSECURE means the response has been validated to be insecure (e.g. in an unsigned zone) # SECURE means that the response has been verfied to be correct. # # Several validators are provided, with each maintaining its own cache of trusted keys. # If validators are added or removed, the caches of the other validators are not affected. class Dnssec # A class to cache trusted keys class ValidationPolicy # @TODO@ Could do this by getting client to add verifiers in the order they # want them to be used. Could then dispense with all this logic # Note that any DLV registries which have been configured will only be tried # after both the root and any local trust anchors (RFC 5074 section 5) # * Always use the root and ignore local trust anchors. ALWAYS_ROOT_ONLY = 1 # * Use the root if successful, otherwise try local anchors. ROOT_THEN_LOCAL_ANCHORS = 2 # * Use local trust anchors if available, otherwise use root. LOCAL_ANCHORS_THEN_ROOT = 3 # * Always use local trust anchors and ignore the root. ALWAYS_LOCAL_ANCHORS_ONLY = 4 end @@validation_policy = ValidationPolicy::LOCAL_ANCHORS_THEN_ROOT def Dnssec.validation_policy=(p) if ((p >= ValidationPolicy::ALWAYS_ROOT_ONLY) && (p <= ValidationPolicy::ALWAYS_LOCAL_ANCHORS_ONLY)) @@validation_policy = p # @TODO@ Should we be clearing the trusted keys now? end end def Dnssec.validation_policy @@validation_policy end @@root_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ROOT) # #NOTE# You may wish to import these via a secure channel yourself, if # using Dnsruby for validation. @@root_key = RR.create(". IN DS 19036 8 2 49AAC11D7B6F6446702E54A1607371607A1A41855200FD2CE1CDDE32F24E8FB5") @@root_verifier.add_root_ds(@@root_key) @@root_key_new = RR.create(". IN DS 20326 8 2 E06D44B80B8F1D39A95C0B0D7C65D08458E880409BBC683457104237C7F8EC8D") @@root_verifier.add_root_ds(@@root_key_new) @@dlv_verifier = SingleVerifier.new(SingleVerifier::VerifierType::DLV) # @TODO@ Could add a new one of these for each anchor. @@anchor_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ANCHOR) # Add a trusted Key Signing Key for the ISC DLV registry. def Dnssec.add_dlv_key(dlv_key) @@dlv_verifier.add_dlv_key(dlv_key) end # Add a new trust anchor def Dnssec.add_trust_anchor(t) # @TODO@ Create a new verifier? @@anchor_verifier.add_trust_anchor(t) end # Add the trusted key with the given expiration time def self.add_trust_anchor_with_expiration(k, expiration) # Create a new verifier? @@anchor_verifier.add_trust_anchor_with_expiration(k, expiration) end # Remove the trusted key def Dnssec.remove_trust_anchor(t) @@anchor_verifier.remove_trust_anchor(t) end # Wipes the cache of trusted keys def self.clear_trust_anchors @@anchor_verifier.clear_trust_anchors end def self.trust_anchors return @@anchor_verifier.trust_anchors end def self.clear_trusted_keys [@@anchor_verifier, @@root_verifier, @@dlv_verifier].each {|v| v.clear_trusted_keys } end def self.reset @@validation_policy = ValidationPolicy::LOCAL_ANCHORS_THEN_ROOT @@root_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ROOT) @@root_verifier.add_root_ds(@@root_key) @@dlv_verifier = SingleVerifier.new(SingleVerifier::VerifierType::DLV) # @TODO@ Could add a new one of these for each anchor. @@anchor_verifier = SingleVerifier.new(SingleVerifier::VerifierType::ANCHOR) @@do_validation_with_recursor = true # Many nameservers don't handle DNSSEC correctly yet @@default_resolver = Resolver.new end def self.set_hints(hints) @@root_verifier.set_hints(hints) @@anchor_verifier.set_hints(hints) end def self.no_keys? no_keys = true [@@anchor_verifier, @@root_verifier, @@dlv_verifier].each {|v| if (v.trusted_keys.length() > 0 || v.trust_anchors.length() > 0) no_keys = false end } return no_keys end @@do_validation_with_recursor = true # Many nameservers don't handle DNSSEC correctly yet @@default_resolver = Resolver.new # This method defines the choice of Resolver or Recursor, when the validator # is checking responses. # If set to true, then a Recursor will be used to query for the DNSSEC records. # Otherwise, the default system resolver will be used. def self.do_validation_with_recursor(on) @@do_validation_with_recursor = on end def self.do_validation_with_recursor? return @@do_validation_with_recursor end # This method overrides the system default resolver configuration for validation # If default_resolver is set, then it will be used to follow the chain of trust. # If it is not, then the default system resolver will be used (unless do_validation_with_recursor # is set. def self.default_resolver=(res) @@default_resolver = res end def self.default_resolver return @@default_resolver end # Returns true for secure/insecure, false otherwise # This method will set the security_level on msg to the appropriate value. # Could be : secure, insecure, bogus or indeterminate # If an error is encountered during verification, then the thrown exception # will define the error. def self.validate(msg) query = Message.new() query.header.cd=true return self.validate_with_query(query, msg) end def self.validate_with_query(query, msg) if (!msg) return false end # First, just check there is something to validate! found_sigs = false msg.each_resource {|rr| if (rr.type == Types::RRSIG) found_sigs = true end } if (found_sigs) begin if (verify(msg)) msg.security_level = Message::SecurityLevel.SECURE return true end rescue VerifyError => e msg.security_error = e msg.security_level = Message::SecurityLevel.BOGUS end end # SHOULD ALWAYS VERIFY DNSSEC-SIGNED RESPONSES? # Yes - if a trust anchor is configured. Otherwise, act on CD bit (in query) TheLog.debug("Checking whether to validate, query.cd = #{query.header.cd}") if (((@@validation_policy > ValidationPolicy::ALWAYS_ROOT_ONLY) && (self.trust_anchors().length > 0)) || # Check query here, and validate if CD is true ((query.header.cd == true))) # && (query.do_validation))) TheLog.debug("Starting validation") # Validate! # Need to think about trapping/storing exceptions and security_levels here last_error = "" last_level = Message::SecurityLevel.BOGUS last_error_level = Message::SecurityLevel.BOGUS if (@@validation_policy == ValidationPolicy::ALWAYS_LOCAL_ANCHORS_ONLY) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_anchors(m, q)}, msg, query) elsif (@@validation_policy == ValidationPolicy::ALWAYS_ROOT_ONLY) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_root(m, q)}, msg, query) elsif (@@validation_policy == ValidationPolicy::LOCAL_ANCHORS_THEN_ROOT) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_anchors(m, q)}, msg, query) if (last_level != Message::SecurityLevel.SECURE) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_root(m, q)}, msg, query) end elsif (@@validation_policy == ValidationPolicy::ROOT_THEN_LOCAL_ANCHORS) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_root(m, q)}, msg, query) if (last_level != Message::SecurityLevel.SECURE) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_anchors(m, q)}, msg, query) end end if (last_level != Message::SecurityLevel.SECURE && last_level != Message::SecurityLevel.BOGUS) last_level, last_error, last_error_level = try_validation(last_level, last_error, last_error_level, Proc.new{|m, q| validate_with_dlv(m, q)}, msg, query) end # Set the message security level! msg.security_level = last_level msg.security_error = last_error if (last_error && last_error.index("ification error")) msg.security_level = Message::SecurityLevel.BOGUS end raise VerifyError.new(last_error) if (last_level < 0) return (msg.security_level.code > Message::SecurityLevel::UNCHECKED) end msg.security_level = Message::SecurityLevel.UNCHECKED return true end def self.try_validation(last_level, last_error, last_error_level, proc, msg, query) # :nodoc: begin proc.call(msg, query) last_level = Message::SecurityLevel.new([msg.security_level.code, last_level.code].max) rescue VerifyError => e if (last_error_level < last_level) last_error = e.to_s last_error_level = last_level end end return last_level, last_error, last_error_level end def self.validate_with_anchors(msg, query) return @@anchor_verifier.validate(msg, query) end def self.validate_with_root(msg, query) return @@root_verifier.validate(msg, query) end def self.validate_with_dlv(msg, query) return @@dlv_verifier.validate(msg, query) end def self.verify(msg, keys=nil) begin return true if @@anchor_verifier.verify(msg, keys) rescue VerifyError begin return true if @@root_verifier.verify(msg, keys) rescue VerifyError return true if @@dlv_verifier.verify(msg, keys) # Will carry error to client end end end def self.anchor_verifier return @@anchor_verifier end def self.dlv_verifier return @@dlv_verifier end def self.root_verifier return @@root_verifier end def self.verify_rrset(rrset, keys = nil) return ((@@anchor_verifier.verify_rrset(rrset, keys) || @@root_verifier.verify_rrset(rrset, keys) || @@dlv_verifier.verify_rrset(rrset, keys))) end end end dnsruby-1.61.3/lib/dnsruby/hosts.rb0000644000004100000410000000746213607400362017253 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # == Dnsruby::Hosts class # Dnsruby::Hosts is a hostname resolver that uses the system hosts file # # === class methods # * Dnsruby::Hosts.new(hosts='/etc/hosts') # # === methods # * Dnsruby::Hosts#getaddress(name) # * Dnsruby::Hosts#getaddresses(name) # * Dnsruby::Hosts#each_address(name) {|address| ...} # address lookup methods. # # * Dnsruby::Hosts#getname(address) # * Dnsruby::Hosts#getnames(address) # * Dnsruby::Hosts#each_name(address) {|name| ...} # hostnames lookup methods. # class Hosts if /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM require 'win32/resolv' DefaultFileName = Win32::Resolv.get_hosts_path else DefaultFileName = '/etc/hosts' end # Creates a new Dnsruby::Hosts using +filename+ for its data source def initialize(filename = DefaultFileName) @filename = filename @mutex = Mutex.new @initialized = nil end def lazy_initialize# :nodoc: @mutex.synchronize { unless @initialized @name2addr = {} @addr2name = {} begin open(@filename) {|f| f.each {|line| line.sub!(/#.*/, '') addr, hostname, *aliases = line.split(/\s+/) next unless addr addr.untaint hostname.untaint @addr2name[addr] = [] unless @addr2name.include? addr @addr2name[addr] << hostname @addr2name[addr] += aliases @name2addr[hostname] = [] unless @name2addr.include? hostname @name2addr[hostname] << addr aliases.each {|n| n.untaint @name2addr[n] = [] unless @name2addr.include? n @name2addr[n] << addr } } } rescue Exception # Java won't find this file if running on Windows end @name2addr.each {|name, arr| arr.reverse!} @initialized = true end } self end # Gets the first IP address for +name+ from the hosts file def getaddress(name) each_address(name) {|address| return address} raise ResolvError.new("#{@filename} has no name: #{name}") end # Gets all IP addresses for +name+ from the hosts file def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end # Iterates over all IP addresses for +name+ retrieved from the hosts file def each_address(name, &proc) lazy_initialize if @name2addr.include?(name) @name2addr[name].each(&proc) end end # Gets the first hostname of +address+ from the hosts file def getname(address) each_name(address) {|name| return name} raise ResolvError.new("#{@filename} has no address: #{address}") end # Gets all hostnames for +address+ from the hosts file def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end # Iterates over all hostnames for +address+ retrieved from the hosts file def each_name(address, &proc) lazy_initialize if @addr2name.include?(address) @addr2name[address].each(&proc) end end end enddnsruby-1.61.3/lib/dnsruby/message/0000755000004100000410000000000013607400362017201 5ustar www-datawww-datadnsruby-1.61.3/lib/dnsruby/message/encoder.rb0000644000004100000410000000357413607400362021156 0ustar www-datawww-datamodule Dnsruby class MessageEncoder #:nodoc: all def initialize @data = '' @names = {} yield self if block_given? end def to_s @data end def put_bytes(d) @data << d end def put_pack(template, *d) begin @data << d.pack(template) rescue Encoding::CompatibilityError => e raise Dnsruby::EncodeError.new("IDN support currently requires punycode string") end end def put_length16 length_index = @data.length @data << "\0\0" data_start = @data.length yield data_end = @data.length @data[length_index, 2] = [data_end - data_start].pack("n") end def put_string(d) begin self.put_pack("C", d.length) @data << d rescue Encoding::CompatibilityError => e raise Dnsruby::EncodeError.new("IDN support currently requires punycode string") end end def put_string_list(ds) ds.each { |d| self.put_string(d) } end def put_rr(rr, canonical=false) # RFC4034 Section 6.2 put_name(rr.name, canonical) put_pack('nnN', rr.type.code, rr.klass.code, rr.ttl) put_length16 { rr.encode_rdata(self, canonical) } end def put_name(d, canonical = false, downcase = canonical) # DNSSEC requires some records (e.g. NSEC, RRSIG) to be canonicalised, but # not downcased. YUK! d = d.downcase if downcase put_labels(d.to_a, canonical) end def put_labels(d, do_canonical) d.each_index do |i| domain = d[i..-1].join('.') if !do_canonical && (idx = @names[domain]) self.put_pack('n', 0xc000 | idx) return else @names[domain] = @data.length self.put_label(d[i]) end end @data << "\0" end def put_label(d) # s, = Name.encode(d) s = d raise RuntimeError, "length of #{s} is #{s.string.length} (larger than 63 octets)" if s.string.length > 63 self.put_string(s.string) end end enddnsruby-1.61.3/lib/dnsruby/message/message.rb0000644000004100000410000004736013607400362021164 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby/name' require 'dnsruby/resource/resource' module Dnsruby # ===Defines a DNS packet. # # RFC 1035 Section 4.1, RFC 2136 Section 2, RFC 2845 # # ===Sections # Message objects have five sections: # # * The header section, a Dnsruby::Header object. # # msg.header=Header.new(...) # header = msg.header # # * The question section, an array of Dnsruby::Question objects. # # msg.add_question(Question.new(domain, type, klass)) # msg.each_question do |question| .... end # # * The answer section, an array of Dnsruby::RR objects. # # msg.add_answer(RR.create({:name => 'a2.example.com', # :type => 'A', :address => '10.0.0.2'})) # msg.each_answer {|answer| ... } # # * The authority section, an array of Dnsruby::RR objects. # # msg.add_authority(rr) # msg.each_authority {|rr| ... } # # * The additional section, an array of Dnsruby::RR objects. # # msg.add_additional(rr) # msg.each_additional {|rr| ... } # # In addition, each_resource iterates the answer, additional # and authority sections : # # msg.each_resource {|rr| ... } # # ===Packet format encoding # # Dnsruby::Message#encode # Dnsruby::Message::decode(data) # # ===Additional information # security_level records the current DNSSEC status of this Message. # answerfrom records the server which this Message was received from. # cached records whether this response came from the cache. # class Message # The security level (see RFC 4035 section 4.3) class SecurityLevel < CodeMapper INDETERMINATE = -2 BOGUS = -1 UNCHECKED = 0 INSECURE = 1 SECURE = 2 update end # If dnssec is set on, then each message will have the security level set # To find the precise error (if any), call Dnsruby::Dnssec::validate(msg) - # the resultant exception will define the error. attr_accessor :security_level # If there was a problem verifying this message with DNSSEC, then securiy_error # will hold a description of the problem. It defaults to '' attr_accessor :security_error # If the Message was returned from the cache, the cached flag will be set # true. It will be false otherwise. attr_accessor :cached # Create a new Message. Takes optional name, type and class # # type defaults to A, and klass defaults to IN # # * Dnsruby::Message.new('example.com') # defaults to A, IN # * Dnsruby::Message.new('example.com', 'AAAA') # * Dnsruby::Message.new('example.com', Dnsruby::Types.PTR, 'HS') # def initialize(*args) @header = Header.new() # @question = Section.new(self) @question = [] @answer = Section.new(self) @authority = Section.new(self) @additional = Section.new(self) @tsigstate = :Unsigned @signing = false @tsigkey = nil @answerfrom = nil @answerip = nil @send_raw = false @do_validation = true @do_caching = true @security_level = SecurityLevel.UNCHECKED @security_error = nil @cached = false type = Types::A klass = Classes::IN if (args.length > 0) name = args[0] if (args.length > 1) type = Types.new(args[1]) if (args.length > 2) klass = Classes.new(args[2]) end end add_question(name, type, klass) end end # The question section, an array of Dnsruby::Question objects. attr_reader :question # The answer section, an array of Dnsruby::RR objects. attr_reader :answer # The authority section, an array of Dnsruby::RR objects. attr_reader :authority # The additional section, an array of Dnsruby::RR objects. attr_reader :additional # The header section, a Dnsruby::Header object. attr_accessor :header # If this Message is a response from a server, then answerfrom contains the address of the server attr_accessor :answerfrom # If this Message is a response from a server, then answerfrom contains the IP address of the server attr_accessor :answerip # If this Message is a response from a server, then answersize contains the size of the response attr_accessor :answersize # If this message has been verified using a TSIG RR then tsigerror contains # the error code returned by the TSIG verification. The error will be an RCode attr_accessor :tsigerror # Can be # * :Unsigned - the default state # * :Signed - the outgoing message has been signed # * :Verified - the incoming message has been verified by TSIG # * :Intermediate - the incoming message is an intermediate envelope in a TCP session # in which only every 100th envelope must be signed # * :Failed - the incoming response failed verification attr_accessor :tsigstate # -- attr_accessor :tsigstart # ++ # Set send_raw if you wish to send and receive the response to this Message # with no additional processing. In other words, if set, then Dnsruby will # not touch the Header of the outgoing Message. This option does not affect # caching or dnssec validation # # This option should not normally be set. attr_accessor :send_raw # do_validation is set by default. If you do not wish dnsruby to validate # this message (on a Resolver with @dnssec==true), then set do_validation # to false. This option does not affect caching, or the header options attr_accessor :do_validation # do_caching is set by default. If you do not wish dnsruby to inspect the # cache before sending the query, nor cache the result of the query, then # set do_caching to false. attr_accessor :do_caching def get_exception exception = nil if rcode == RCode.NXDOMAIN exception = NXDomain.new elsif rcode == RCode.SERVFAIL exception = ServFail.new elsif rcode == RCode.FORMERR exception = FormErr.new elsif rcode == RCode.NOTIMP exception = NotImp.new elsif rcode == RCode.REFUSED exception = Refused.new elsif rcode == RCode.NOTZONE exception = NotZone.new elsif rcode == RCode.NOTAUTH exception = NotAuth.new elsif rcode == RCode.NXRRSET exception = NXRRSet.new elsif rcode == RCode.YXRRSET exception = YXRRSet.new elsif rcode == RCode.YXDOMAIN exception = YXDomain.new elsif rcode >= RCode.BADSIG && rcode <= RCode.BADALG return VerifyError.new # @TODO@ end exception end def ==(other) other.kind_of?(Message) && @header == other.header && @question[0] == other.question[0] && @answer == other.answer && @authority == other.authority && @additional == other.additional end def remove_additional @additional = Section.new(self) @header.arcount = 0 end # Return the first rrset of the specified attributes in the message def rrset(name, type, klass = Classes::IN) [@answer, @authority, @additional].each do |section| if (rrset = section.rrset(name, type, klass)).length > 0 return rrset end end RRSet.new end # Return the rrsets of the specified type in the message def rrsets(type, klass=Classes::IN) rrsetss = [] [@answer, @authority, @additional].each do |section| if (rrsets = section.rrsets(type, klass)).length > 0 rrsets.each { |rrset| rrsetss.push(rrset) } end end rrsetss end # Return a hash, with the section as key, and the RRSets in that # section as the data : {section => section_rrs} def section_rrsets(type = nil, include_opt = false) ret = {} %w(answer authority additional).each do |section| ret[section] = self.send(section).rrsets(type, include_opt) end ret end # Add a new Question to the Message. Takes either a Question, # or a name, and an optional type and class. # # * msg.add_question(Question.new('example.com', 'MX')) # * msg.add_question('example.com') # defaults to Types.A, Classes.IN # * msg.add_question('example.com', Types.LOC) def add_question(question, type=Types.A, klass=Classes.IN) unless question.kind_of?(Question) question = Question.new(question, type, klass) end @question << question update_counts end def each_question @question.each {|rec| yield rec } end def update_counts # :nodoc:all @header.ancount = @answer.length @header.arcount = @additional.length @header.qdcount = @question.length @header.nscount = @authority.length end def _add_answer(rr, force = false) if force || (! @answer.include?(rr)) @answer << rr update_counts end end; private :_add_answer # Adds an RR to the answer section unless it already occurs. def add_answer(rr) #:nodoc: all _add_answer(rr) end # When adding an RR to a Dnsruby::Message, add_answer checks to see if it already occurs, # and, if so, does not add it again. This method adds the record whether or not # it already occurs. This is needed in order to add # a SOA record twice for an AXFR response. def add_answer!(rr) _add_answer(rr, true) end def each_answer @answer.each {|rec| yield rec } end def add_authority(rr) #:nodoc: all unless @authority.include?(rr) @authority << rr update_counts end end def each_authority @authority.each {|rec| yield rec } end def add_additional(rr) #:nodoc: all unless @additional.include?(rr) @additional << rr update_counts end end def each_additional @additional.each { |rec| yield rec } end # Yields each section (question, answer, authority, additional) def each_section [@answer, @authority, @additional].each { |section| yield section} end # Calls each_answer, each_authority, each_additional def each_resource each_answer {|rec| yield rec} each_authority {|rec| yield rec} each_additional {|rec| yield rec} end # Returns the TSIG record from the ADDITIONAL section, if one is present. def tsig if @additional.last if @additional.last.rr_type == Types.TSIG return @additional.last end end nil end # Sets the TSIG to sign this message with. Can either be a Dnsruby::RR::TSIG # object, or it can be a (name, key) tuple, or it can be a hash which takes # Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.) def set_tsig(*args) if args.length == 1 if args[0].instance_of?(RR::TSIG) @tsigkey = args[0] elsif args[0].instance_of?(Hash) @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY'}.merge(args[0])) else raise ArgumentError.new('Wrong type of argument to Dnsruby::Message#set_tsig - should be TSIG or Hash') end elsif args.length == 2 @tsigkey = RR.create({:type=>'TSIG', :klass=>'ANY', :name=>args[0], :key=>args[1]}) else raise ArgumentError.new('Wrong number of arguments to Dnsruby::Message#set_tsig') end end # Was this message signed by a TSIG? def signed? @tsigstate == :Signed || @tsigstate == :Verified || @tsigstate == :Failed end # If this message was signed by a TSIG, was the TSIG verified? def verified? @tsigstate == :Verified end def get_opt @additional.detect { |r| r.type == Types::OPT } end def rcode rcode = @header.get_header_rcode opt = get_opt if opt rcode = rcode.code + (opt.xrcode.code << 4) rcode = RCode.new(rcode) end rcode end def to_s s = '' # the output string to return if @answerfrom && (! @answerfrom.empty?) s << ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n" end s << ";; Security Level : #{@security_level.string}\n" # OPT pseudosection? EDNS flags, udpsize opt = get_opt if opt s << @header.to_s_with_rcode(rcode) << "\n#{opt}\n" else s << "#{@header}\n" end section = (@header.opcode == OpCode.UPDATE) ? 'ZONE' : 'QUESTION' s << ";; #{section} SECTION (#{@header.qdcount} record#{@header.qdcount == 1 ? '' : 's'})\n" each_question { |qr| s << ";; #{qr}\n" } if @answer.size > 0 s << "\n" section = (@header.opcode == OpCode.UPDATE) ? 'PREREQUISITE' : 'ANSWER' s << ";; #{section} SECTION (#{@header.ancount} record#{@header.ancount == 1 ? '' : 's'})\n" each_answer { |rr| s << "#{rr}\n" } end if @authority.size > 0 s << "\n" section = (@header.opcode == OpCode.UPDATE) ? 'UPDATE' : 'AUTHORITY' s << ";; #{section} SECTION (#{@header.nscount} record#{@header.nscount == 1 ? '' : 's'})\n" each_authority { |rr| s << rr.to_s + "\n" } end if (@additional.size > 0 && !opt) || (@additional.size > 1) s << "\n;; ADDITIONAL SECTION (#{@header.arcount} record#{@header.arcount == 1 ? '' : 's'})\n" each_additional { |rr| if rr.type != Types::OPT s << rr.to_s+ "\n" end } end s end def old_to_s retval = '' if (@answerfrom != nil && @answerfrom != '') retval = retval + ";; Answer received from #{@answerfrom} (#{@answersize} bytes)\n;;\n" end retval = retval + ";; Security Level : #{@security_level.string}\n" retval = retval + ";; HEADER SECTION\n" # OPT pseudosection? EDNS flags, udpsize opt = get_opt if (!opt) retval = retval + @header.old_to_s else retval = retval + @header.old_to_s_with_rcode(rcode()) end retval = retval + "\n" if (opt) retval = retval + opt.to_s retval = retval + "\n" end section = (@header.opcode == OpCode.UPDATE) ? "ZONE" : "QUESTION" retval = retval + ";; #{section} SECTION (#{@header.qdcount} record#{@header.qdcount == 1 ? '' : 's'})\n" each_question { |qr| retval = retval + ";; #{qr.to_s}\n" } if (@answer.size > 0) retval = retval + "\n" section = (@header.opcode == OpCode.UPDATE) ? "PREREQUISITE" : "ANSWER" retval = retval + ";; #{section} SECTION (#{@header.ancount} record#{@header.ancount == 1 ? '' : 's'})\n" each_answer { |rr| retval = retval + rr.to_s + "\n" } end if (@authority.size > 0) retval = retval + "\n" section = (@header.opcode == OpCode.UPDATE) ? "UPDATE" : "AUTHORITY" retval = retval + ";; #{section} SECTION (#{@header.nscount} record#{@header.nscount == 1 ? '' : 's'})\n" each_authority { |rr| retval = retval + rr.to_s + "\n" } end if ((@additional.size > 0 && !opt) || (@additional.size > 1)) retval = retval + "\n" retval = retval + ";; ADDITIONAL SECTION (#{@header.arcount} record#{@header.arcount == 1 ? '' : 's'})\n" each_additional { |rr| if (rr.type != Types::OPT) retval = retval + rr.to_s+ "\n" end } end retval end # Signs the message. If used with no arguments, then the message must have already # been set (set_tsig). Otherwise, the arguments can either be a Dnsruby::RR::TSIG # object, or a (name, key) tuple, or a hash which takes # Dnsruby::RR::TSIG attributes (e.g. name, key, fudge, etc.) # # NOTE that this method should only be called by the resolver, rather than the # client code. To use signing from the client, call Dnsruby::Resolver#tsig= def sign!(*args) #:nodoc: all if args.length > 0 set_tsig(*args) sign! else if @tsigkey && (@tsigstate == :Unsigned) @tsigkey.apply(self) end end end # Return the encoded form of the message # If there is a TSIG record present and the record has not been signed # then sign it def encode(canonical=false) if @tsigkey && (@tsigstate == :Unsigned) && !@signing @signing = true sign! @signing = false end return MessageEncoder.new { |msg| header = @header header.encode(msg) @question.each { |q| msg.put_name(q.qname) msg.put_pack('nn', q.qtype.code, q.qclass.code) } [@answer, @authority, @additional].each { |rr| rr.each { |r| msg.put_rr(r, canonical) } } }.to_s end # Decode the encoded message def Message.decode(m) o = Message.new() begin MessageDecoder.new(m) {|msg| o.header = Header.new(msg) o.header.qdcount.times { question = msg.get_question o.question << question } o.header.ancount.times { rr = msg.get_rr o.answer << rr } o.header.nscount.times { rr = msg.get_rr o.authority << rr } o.header.arcount.times { |count| start = msg.index rr = msg.get_rr if rr.type == Types::TSIG if count != o.header.arcount-1 Dnsruby.log.Error('Incoming message has TSIG record before last record') raise DecodeError.new('TSIG record present before last record') end o.tsigstart = start # needed for TSIG verification end o.additional << rr } } rescue DecodeError => e # So we got a decode error # However, we might have been able to fill in many parts of the message # So let's raise the DecodeError, but add the partially completed message e.partial_message = o raise e end o end def clone Message.decode(self.encode) end # In dynamic update packets, the question section is known as zone and # specifies the zone to be updated. alias :zone :question alias :add_zone :add_question alias :each_zone :each_question # In dynamic update packets, the answer section is known as pre or # prerequisite and specifies the RRs or RRsets which must or # must not preexist. alias :pre :answer alias :add_pre :add_answer alias :each_pre :each_answer # In dynamic update packets, the answer section is known as pre or # prerequisite and specifies the RRs or RRsets which must or # must not preexist. alias :prerequisite :pre alias :add_prerequisite :add_pre alias :each_prerequisite :each_pre # In dynamic update packets, the authority section is known as update and # specifies the RRs or RRsets to be added or delted. alias :update :authority alias :add_update :add_authority alias :each_update :each_authority end end require 'dnsruby/message/section' require 'dnsruby/message/header' require 'dnsruby/message/decoder' require 'dnsruby/message/encoder' require 'dnsruby/message/question' dnsruby-1.61.3/lib/dnsruby/message/header.rb0000644000004100000410000001367513607400362020772 0ustar www-datawww-datamodule Dnsruby # The header portion of a DNS packet # # RFC 1035 Section 4.1.1 class Header MAX_ID = 65535 # The header ID attr_accessor :id # The query response flag attr_accessor :qr # Authoritative answer flag attr_accessor :aa # Truncated flag attr_accessor :tc # Recursion Desired flag attr_accessor :rd # The Checking Disabled flag attr_accessor :cd # The Authenticated Data flag # Relevant in DNSSEC context. # (The AD bit is only set on answers where signatures have been # cryptographically verified or the server is authoritative for the data # and is allowed to set the bit by policy.) attr_accessor :ad # The query response flag attr_accessor :qr # Recursion available flag attr_accessor :ra # Query response code # deprecated - use Message#rcode # attr_reader :rcode # This new get_header_rcode method is intended for use only by the Message class. # This is because the Message OPT section may contain an extended rcode (see # RFC 2671 section 4.6). Using the header rcode only ignores this extension, and # is not recommended. def get_header_rcode @rcode end # The header opcode attr_reader :opcode # The number of records in the question section of the message attr_accessor :qdcount # The number of records in the authoriy section of the message attr_accessor :nscount # The number of records in the answer section of the message attr_accessor :ancount # The number of records in the additional record section og the message attr_accessor :arcount def initialize(*args) if (args.length == 0) @id = rand(MAX_ID) @qr = false @opcode = OpCode.Query @aa = false @ad = false @tc = false @rd = false # recursion desired @ra = false # recursion available @cd = false @rcode = RCode.NoError @qdcount = 0 @nscount = 0 @ancount = 0 @arcount = 0 elsif args.length == 1 decode(args[0]) end end def opcode=(op) @opcode = OpCode.new(op) end def rcode=(rcode) @rcode = RCode.new(rcode) end def Header.new_from_data(data) header = Header.new MessageDecoder.new(data) { |msg| header.decode(msg) } header end def data MessageEncoder.new { |msg| self.encode(msg) }.to_s end def encode(msg) msg.put_pack('nnnnnn', @id, (@qr ? 1:0) << 15 | (@opcode.code & 15) << 11 | (@aa ? 1:0) << 10 | (@tc ? 1:0) << 9 | (@rd ? 1:0) << 8 | (@ra ? 1:0) << 7 | (@ad ? 1:0) << 5 | (@cd ? 1:0) << 4 | (@rcode.code & 15), @qdcount, @ancount, @nscount, @arcount) end def Header.decrement_arcount_encoded(bytes) header = Header.new header_end = 0 MessageDecoder.new(bytes) do |msg| header.decode(msg) header_end = msg.index end header.arcount -= 1 bytes[0, header_end] = MessageEncoder.new { |msg| header.encode(msg) }.to_s bytes end def ==(other) @qr == other.qr && @opcode == other.opcode && @aa == other.aa && @tc == other.tc && @rd == other.rd && @ra == other.ra && @cd == other.cd && @ad == other.ad && @rcode == other.get_header_rcode end def to_s to_s_with_rcode(@rcode) end def old_to_s old_to_s_with_rcode(@rcode) end def to_s_with_rcode(rcode) if @opcode == OpCode::Update s = ";; id = #{@id}\n" s << ";; qr = #{@qr} opcode = #{@opcode.string} rcode = #{@rcode.string}\n" s << ";; zocount = #{@qdcount} " s << "prcount = #{@ancount} " s << "upcount = #{@nscount} " s << "adcount = #{@arcount}\n" s else flags_str = begin flags = [] flags << 'qr' if @qr flags << 'aa' if @aa flags << 'tc' if @tc flags << 'rd' if @rd flags << 'ra' if @ra flags << 'ad' if @ad flags << 'cd' if @cd ";; flags: #{flags.join(' ')}; " end head_line_str = ";; ->>HEADER<<- opcode: #{opcode.string.upcase}, status: #{@rcode.string}, id: #{@id}\n" section_counts_str = "QUERY: #{@qdcount}, ANSWER: #{@ancount}, AUTHORITY: #{@nscount}, ADDITIONAL: #{@arcount}\n" head_line_str + flags_str + section_counts_str end end def old_to_s_with_rcode(rcode) retval = ";; id = #{@id}\n" if (@opcode == OpCode::Update) retval += ";; qr = #{@qr} " \ "opcode = #{@opcode.string} "\ "rcode = #{@rcode.string}\n" retval += ";; zocount = #{@qdcount} "\ "prcount = #{@ancount} " \ "upcount = #{@nscount} " \ "adcount = #{@arcount}\n" else retval += ";; qr = #{@qr} " \ "opcode = #{@opcode.string} " \ "aa = #{@aa} " \ "tc = #{@tc} " \ "rd = #{@rd}\n" retval += ";; ra = #{@ra} " \ "ad = #{@ad} " \ "cd = #{@cd} " \ "rcode = #{rcode.string}\n" retval += ";; qdcount = #{@qdcount} " \ "ancount = #{@ancount} " \ "nscount = #{@nscount} " \ "arcount = #{@arcount}\n" end retval end def decode(msg) @id, flag, @qdcount, @ancount, @nscount, @arcount = msg.get_unpack('nnnnnn') @qr = ((flag >> 15) & 1) == 1 @opcode = OpCode.new((flag >> 11) & 15) @aa = ((flag >> 10) & 1) == 1 @tc = ((flag >> 9) & 1) == 1 @rd = ((flag >> 8) & 1) == 1 @ra = ((flag >> 7) & 1) == 1 @ad = ((flag >> 5) & 1) == 1 @cd = ((flag >> 4) & 1) == 1 @rcode = RCode.new(flag & 15) end alias zocount qdcount alias zocount= qdcount= alias prcount ancount alias prcount= ancount= alias upcount nscount alias upcount= nscount= alias adcount arcount alias adcount= arcount= end enddnsruby-1.61.3/lib/dnsruby/message/section.rb0000644000004100000410000000521113607400362021171 0ustar www-datawww-datamodule Dnsruby class Section < Array def initialize(msg = nil) @msg = msg super(0) end # Return the rrset of the specified type in this section def rrset(name, type=Types.A, klass=Classes::IN) rrs = select{|rr| type_ok = (rr.type==type) if rr.type == Types::RRSIG type_ok = (rr.type_covered == type) end unless /\.\z/ =~ name.to_s name = name.to_s + '.' end type_ok && (rr.klass == klass) && (rr.name.to_s(true).downcase == name.to_s().downcase) } rrset = RRSet.new() rrs.each do |rr| rrset.add(rr) end rrset end # Return an array of all the rrsets in the section def rrsets(type = nil, include_opt = false) if type && !(Types === type) type = Types.new(type) end ret = [] each do |rr| next if (!include_opt && (rr.type == Types::OPT)) # if (type) # next if ((rr.type == Types.RRSIG) && (type != Types.RRSIG) && (rr.type_covered != type)) # next if (rr.type != type) # end if (type) # if this is an rrsig type, then : # only include it if the type_covered is the type requested, # OR if the type requested is an RRSIG if rr.type == Types::RRSIG if (rr.type_covered == type) || (type == Types::RRSIG) else next end # next if ((rr.type_covered != type) || (type != Types.RRSIG)) elsif rr.type != type next end end found_rrset = false ret.each do |rrset| found_rrset = rrset.add(rr) break if found_rrset end unless found_rrset ret.push(RRSet.new(rr)) end end ret end def ==(other) return false unless self.class == other.class return false if other.rrsets(nil, true).length != self.rrsets(nil, true).length otherrrsets = other.rrsets(nil) self.rrsets(nil).each {|rrset| return false unless otherrrsets.include?(rrset) } true end def remove_rrset(name, type) # Remove all RRs with the name and type from the section. # Need to worry about header counts here - can we get Message to # update the counts itself, rather than the section worrying about it? rrs_to_delete = [] each do |rr| next if rr.rr_type == Types::OPT if (rr.name.to_s.downcase == name.to_s.downcase) && ((rr.type == type) || ((rr.type == Types::RRSIG) && (rr.type_covered == type))) rrs_to_delete.push(rr) end end rrs_to_delete.each { |rr| delete(rr) } @msg.update_counts if @msg end end end dnsruby-1.61.3/lib/dnsruby/message/decoder.rb0000644000004100000410000001154313607400362021137 0ustar www-datawww-datamodule Dnsruby # This class decodes a binary string containing the raw bytes of the message # as in coming over the wire from a nameserver, and parses it into a # Dnsruby::Message. class MessageDecoder #:nodoc: all # Keeps a running @index containing the current position (like a cursor) # into the binary string. In general 'get_' methods will position @index # to follow the data they have read. attr_reader :data, :index # Creates an instance of the decoder, optionally with code block # to be executed with the instance as its parameter. def initialize(data) @data = data @index = 0 @limit = data.length yield self if block_given? end # Has bytes remaining in the binary string to be parsed? def has_remaining? @limit > @index end # Asserts that the specified position is a valid position in the buffer. # If not, raises a DecodeError. If so, does nothing. def assert_buffer_position_valid(end_position) unless (0..@limit).include?(end_position) raise DecodeError.new("requested position of #{end_position} must be between 0 and buffer size (#{@limit}).") end end # Gets the byte value at the specified position def get_byte_at(position) assert_buffer_position_valid(position) return nil if @data[position].nil? @data[position].getbyte(0) end # Gets a 16-bit length field from the binary string and yields to the block. # This will be the length of the next item to parse in the binary string. # Returns the object returned from that block. # # When this method returns, @index will point to the byte after the # 16-bit length field. def get_length16 len, = self.get_unpack('n') save_limit = @limit @limit = @index + len parsed_data = yield(len) if @index < @limit message = "Junk exists; limit = #{@limit}, index = #{@index}" raise DecodeError.new(message) end assert_buffer_position_valid(@index) @limit = save_limit parsed_data end # Returns the specified number of bytes from the binary string. # Length defaults to the remaining (not yet processed) size of the string. def get_bytes(len = @limit - @index) bytes = @data[@index, len] @index += len bytes end # Calls String.unpack to get numbers as specified in the template string. def get_unpack(template) len = 0 template.bytes.each do |byte| case byte.chr when 'c', 'C', 'h', 'H' len += 1 when 'n' len += 2 when 'N' len += 4 when '*' len = @limit - @index else raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'") end end assert_buffer_position_valid(@index + len) number_array = @data.unpack("@#{@index}#{template}") @index += len number_array end # Gets a string whose 1-byte length is at @index, and the string starting at @index + 1. def get_string len = get_byte_at(@index) || 0 assert_buffer_position_valid(@index + 1 + len) data_item = @data[@index + 1, len] @index += 1 + len data_item end # Gets all strings from @index to the end of the binary string. def get_string_list strings = [] strings << get_string while has_remaining? strings end # Gets a Name from the current @index position. def get_name Name.new(get_labels) end # Returns labels starting at @index. def get_labels(limit = nil) limit = @index if limit.nil? || (@index < limit) labels = [] while true temp = get_byte_at(@index) case temp when 0 @index += 1 return labels when 192..255 idx = get_unpack('n')[0] & 0x3fff if limit <= idx raise DecodeError.new('non-backward name pointer') end save_index = @index @index = idx labels += self.get_labels(limit) @index = save_index return labels when nil return labels else labels << self.get_label end end labels end # Gets a single label. def get_label begin Name::Label.new(get_string) rescue ResolvError => e raise DecodeError.new(e) # Turn it into something more suitable end end # Gets a question record. def get_question name = self.get_name type, klass = self.get_unpack('nn') klass = Classes.new(klass) Question.new(name, type, klass) end # Gets a resource record. def get_rr name = get_name type, klass, ttl = get_unpack('nnN') klass = Classes.new(klass) typeclass = RR.get_class(type, klass) # @TODO@ Trap decode errors here, and somehow mark the record as bad. # Need some way to represent raw data only record = get_length16 { typeclass.decode_rdata(self) } record.name = name record.ttl = ttl record.type = type record.klass = klass record end end end dnsruby-1.61.3/lib/dnsruby/message/question.rb0000644000004100000410000000464513607400362021406 0ustar www-datawww-data# A Dnsruby::Question object represents a record in the # question section of a DNS packet. # # RFC 1035 Section 4.1.2 module Dnsruby class Question # The Question name attr_reader :qname # The Question type attr_reader :qtype # The Question class attr_reader :qclass # Creates a question object from the domain, type, and class passed # as arguments. # # If a String is passed in, a Name, IPv4 or IPv6 object is created. # # If an IPv4 or IPv6 object is used then the type is set to PTR. def initialize(qname, qtype = :not_provided, qclass = :not_provided) raise ArgumentError.new('qname must not be nil') if qname.nil? @qtype = (qtype == :not_provided) ? Types::A : Types.new(qtype) @qclass = (qclass == :not_provided) ? Classes::IN : Classes.new(qclass) set_qname(qname, qtype == :not_provided) end def qtype=(qtype) @qtype = Types.new(qtype) end def qclass=(qclass) @qclass = Classes.new(qclass) end def set_qname(qname, write_PTR_to_qtype_if_ip = true) is_ipv4_addr_string = qname.is_a?(String) && IPv4::Regex.match(qname) is_ipv6_addr_string = qname.is_a?(String) && IPv6::Regex.match(qname) is_ip_addr_string = is_ipv4_addr_string || is_ipv6_addr_string is_ip_addr = [IPv4, IPv6].any? { |klass| qname.is_a?(klass) } if is_ipv4_addr_string @qname = IPv4.create(qname).to_name elsif is_ipv6_addr_string @qname = IPv6.create(qname).to_name else @qname = Name.create(qname) end # If the name looks like an IP address then do an appropriate # PTR query, unless the user specified the qtype if write_PTR_to_qtype_if_ip && (is_ip_addr || is_ip_addr_string) @qtype = Types.PTR end @qname.absolute = true end def qname=(qname) set_qname(qname, true) end def ==(other) other.is_a?(Question) && self.qname == other.qname && self.qtype == other.qtype && self.qclass == Classes.new(other.qclass) end # Returns a string representation of the question record. def to_s "#{@qname}.\t#{@qclass.string}\t#{@qtype.string}" end # For Updates, the qname field is redefined to zname (RFC2136, section 2.3) alias zname qname # For Updates, the qtype field is redefined to ztype (RFC2136, section 2.3) alias ztype qtype # For Updates, the qclass field is redefined to zclass (RFC2136, section 2.3) alias zclass qclass alias type qtype end enddnsruby-1.61.3/lib/dnsruby/code_mapper.rb0000644000004100000410000001142513607400362020363 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # CodeMapper superclass looks after String to code mappings (e.g. OpCode, RCode, etc.) # # Subclasses simply define a mapping of codes to variable names, and CodeMapper provides utility methods. # # All strings will come out as upper case # # Example : # Types::AAAA or Types.AAAA # rcode.string or rcode.code class CodeMapper # :nodoc: all include Comparable @@arrays = {} attr_accessor :string, :code alias to_code code alias to_i code alias to_string string alias to_s string class Arrays attr_accessor :strings, :stringsdown, :values, :maxcode def initialize @strings = {} @stringsdown = {} @values = {} @maxcode = 0 end end def CodeMapper.strings strings = [] @@arrays[self].strings.keys.each {|s| strings.push(s)} return strings end # Creates the CodeMapper from the defined constants def CodeMapper.update @@arrays[self] = Arrays.new constants = self.constants - CodeMapper.constants constants.each do |i| @@arrays[self].strings.store(i.to_s, const_get(i)) end @@arrays[self].maxcode = constants.length @@arrays[self].values = @@arrays[self].strings.invert @@arrays[self].stringsdown = Hash.new @@arrays[self].strings.keys.each do |s| @@arrays[self].stringsdown.store(s.downcase, @@arrays[self].strings[s]) end end # Add new a code to the CodeMapper def CodeMapper.add_pair(string, code) array = @@arrays[self] array.strings.store(string, code) array.values=array.strings.invert array.stringsdown.store(string.downcase, code) array.maxcode+=1 end def unknown_string(arg) #:nodoc: all raise ArgumentError.new("String #{arg} not a member of #{self.class}") end def unknown_code(arg) #:nodoc: all # Be liberal in what you accept... # raise ArgumentError.new("Code #{arg} not a member of #{self.class}") Classes.add_pair(arg.to_s, arg) set_code(arg) end def self.method_missing(methId) #:nodoc: all str = methId.id2name return self.new(str) end def initialize(arg) #:nodoc: all array = @@arrays[self.class] if (arg.kind_of?String) arg = arg.gsub("_", "-") code = array.stringsdown[arg.downcase] if (code != nil) @code = code @string = array.values[@code] else unknown_string(arg) end elsif arg.kind_of?(Integer) if (array.values[arg] != nil) @code = arg @string = array.values[@code] else unknown_code(arg) end elsif (arg.kind_of?self.class) @code = arg.code @string = array.values[@code] else raise ArgumentError.new("Unknown argument of type #{arg.class}: #{arg} for #{self.class}") end end def set_string(arg) array = @@arrays[self.class] @code = array.stringsdown[arg.downcase] @string = array.values[@code] end def set_code(arg) @code = arg @string = @@arrays[self.class].values[@code] end def hash @code end def inspect return @string end def CodeMapper.to_string(arg) if (arg.kind_of?String) return arg else return @@arrays[self].values[arg] end end def CodeMapper.to_code(arg) if arg.kind_of?(Integer) return arg else return @@arrays[self].stringsdown[arg.downcase] end end def <=>(other) if other.is_a?(Integer) self.code <=> other else self.code <=> other.code end end def ==(other) return true if [@code, @string].include?other if (CodeMapper === other) return true if ((other.code == @code) || (other.string == @string)) end return false end alias eql? == # :nodoc: # Return a regular expression which matches any codes or strings from the CodeMapper. def self.regexp # Longest ones go first, so the regex engine will match AAAA before A, etc. return @@arrays[self].strings.keys.sort { |a, b| b.length <=> a.length }.join('|') end end enddnsruby-1.61.3/lib/dnsruby/bitmap.rb0000644000004100000410000000641513607400362017364 0ustar www-datawww-data# This code is copied from the trick_bag gem (see https://github.com/keithrbennett/trick_bag). # It is copied a) to avoid adding a new dependency and b) because that gem is in # version 0 and is unstable. require_relative = ->(*args) do this_file_dir = File.expand_path(File.dirname(__FILE__)) args.each { |arg| require(File.join(this_file_dir, arg)) } end require 'forwardable' require_relative.('bit_mapping') module Dnsruby # Instances of this class can be created that will hold on to bitmap data and be used # to test bits and convert to other formats. # # Where an array is used to represent bits, the first element (#0) will be the # high bit and the last element will be the low (1's column) bit. class Bitmap extend Forwardable # This is the internal representation of the bitmap value: attr_reader :number # Some instance methods can be delegated to this number: [:&, :|, :^, :hash].each do |method_name| def_delegator :@number, method_name end # Set a new value to number, validating first that it is nonnegative. def number=(new_number) self.assert_non_negative(new_number) @number = new_number end # The constructor is made private because: # # 1) each type of initialization requires its own validation, and it # would be wasteful to do the validation unnecessarily # 2) to enforce that the more descriptively # named class methods should be used to create instances. private_class_method :new # Class methods to create instances from the various representation types # handled in the BitMapping module's methods. # Creates an instance from a nonnegative number. def self.from_number(number) new(number) end # Creates an instance from a binary string (e.g. "\x0C"). def self.from_binary_string(string) new(BitMapping.binary_string_to_number(string)) end # Creates an instance from a value array (e.g. [8, 0, 0, 1]) def self.from_place_value_array(array) new(BitMapping.place_value_array_to_number(array)) end # Creates an instance from a bit array (e.g. [1, 0, 0, 1]) def self.from_bit_array(array) new(BitMapping.bit_array_to_number(array)) end # Creates an instance from an array of positions for the bits that are set (e.g. [0, 3]) def self.from_set_bit_position_array(array) new(BitMapping.set_bit_position_array_to_number(array)) end # Instance methods to convert the data to the various representation types: # Returns the instance's value as a binary string (e.g. "\x0C") def to_binary_string(min_length = 0) BitMapping.number_to_binary_string(number, min_length) end # Returns the instance's value as an array of bit column values (e.g. [8, 0, 0, 1]) def to_place_value_array BitMapping.number_to_place_value_array(number) end # Returns the instance's value as an array of bit column place values (e.g. [8, 0, 0, 1]) def to_bit_array BitMapping.number_to_bit_array(number) end # Returns the instance's value as an array of positions for the bits that are set (e.g. [0, 3]) def to_set_bit_position_array BitMapping.number_to_set_bit_positions_array(number) end def initialize(number) BitMapping.assert_non_negative(number) @number = number end def ==(other) other.is_a?(self.class) && other.number == self.number end end end dnsruby-1.61.3/lib/dnsruby/ipv6.rb0000644000004100000410000001104613607400362016770 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # Dnsruby::IPv6 class class IPv6 # IPv6 address format a:b:c:d:e:f:g:h Regex_8Hex = /\A (?:[0-9A-Fa-f]{1,4}:){7} [0-9A-Fa-f]{1,4} \z/x # Compresses IPv6 format a::b Regex_CompressedHex = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) \z/x # IPv4 mapped IPv6 address format a:b:c:d:e:f:w.x.y.z Regex_6Hex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}:){6,6}) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x # Compressed IPv4 mapped IPv6 address format a::b:w.x.y.z Regex_CompressedHex4Dec = /\A ((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?) :: ((?:[0-9A-Fa-f]{1,4}:)*) (\d+)\.(\d+)\.(\d+)\.(\d+) \z/x # A composite IPv6 address RegExp Regex = / (?:#{Regex_8Hex}) | (?:#{Regex_CompressedHex}) | (?:#{Regex_6Hex4Dec}) | (?:#{Regex_CompressedHex4Dec})/x # Created a new IPv6 address from +arg+ which may be: # # * IPv6:: returns +arg+ # * String:: +arg+ must match one of the IPv6::Regex* constants def self.create(arg) case arg when IPv6 return arg when String address = '' if Regex_8Hex =~ arg arg.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} elsif Regex_CompressedHex =~ arg prefix = $1 suffix = $2 a1 = '' a2 = '' prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 16 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 elsif Regex_6Hex4Dec =~ arg prefix, a, b, c, d = $1, $2.to_i, $3.to_i, $4.to_i, $5.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d prefix.scan(/[0-9A-Fa-f]+/) {|hex| address << [hex.hex].pack('n')} address << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end elsif Regex_CompressedHex4Dec =~ arg prefix, suffix, a, b, c, d = $1, $2, $3.to_i, $4.to_i, $5.to_i, $6.to_i if (0..255) === a && (0..255) === b && (0..255) === c && (0..255) === d a1 = '' a2 = '' prefix.scan(/[0-9A-Fa-f]+/) {|hex| a1 << [hex.hex].pack('n')} suffix.scan(/[0-9A-Fa-f]+/) {|hex| a2 << [hex.hex].pack('n')} omitlen = 12 - a1.length - a2.length address << a1 << "\0" * omitlen << a2 << [a, b, c, d].pack('CCCC') else raise ArgumentError.new("not numeric IPv6 address: " + arg) end else raise ArgumentError.new("not numeric IPv6 address: " + arg) end return IPv6.new(address) else raise ArgumentError.new("cannot interpret as IPv6 address: #{arg.inspect}") end end def initialize(address) #:nodoc: unless address.kind_of?(String) && address.length == 16 raise ArgumentError.new('IPv6 address must be 16 bytes') end @address = address end # The raw IPv6 address as a String attr_reader :address def to_s address = sprintf("%X:%X:%X:%X:%X:%X:%X:%X", *@address.unpack("nnnnnnnn")) unless address.sub!(/(^|:)0(:0)+(:|$)/, '::') address.sub!(/(^|:)0(:|$)/, '::') end return address end def inspect #:nodoc: return "#<#{self.class} #{self.to_s}>" end # Turns this IPv6 address into a Dnsruby::Name # -- # ip6.arpa should be searched too. [RFC3152] def to_name return Name.create( # @address.unpack("H32")[0].split(//).reverse + ['ip6', 'arpa']) @address.unpack("H32")[0].split(//).reverse.join(".") + ".ip6.arpa") end def ==(other) #:nodoc: return @address == other.address end def eql?(other) #:nodoc: return self == other end def hash return @address.hash end end enddnsruby-1.61.3/lib/dnsruby/resource/0000755000004100000410000000000013607400362017404 5ustar www-datawww-datadnsruby-1.61.3/lib/dnsruby/resource/GPOS.rb0000644000004100000410000001357613607400362020515 0ustar www-datawww-data# encoding: ASCII-8BIT module Dnsruby class RR # Class for Geographic Position (GPOS) resource records. # # RFC 1712 (https://www.ietf.org/rfc/rfc1712.txt) class GPOS < RR TypeValue = Types::GPOS ClassValue = Classes::IN ClassHash[[TypeValue, ClassValue]] = self #:nodoc: all attr_accessor :longitude, :latitude, :altitude # NOTE: these are strings, not numbers REQUIRED_KEYS = [:longitude, :latitude, :altitude] # As with all resource record subclasses of RR, this class cannot be # directly instantiated, but instead must be instantiated via use of # one of the RR class methods. These GPOS class methods are wrappers # around those RR methods, so that there is an interface on the GPOS # class for creating GPOS instances. # Create an instance from a hash of parameters, e.g.: # { # name: 'techhumans.com', # type: Types::GPOS, # ttl: 1234, # longitude: '10.0', # latitude: '20.0', # altitude: '30.0', # } # # Since the type is assumed to be GPOS, it will be assigned # automatially, and any other value will be overwritten. # Therefore, having it present in the hash is not necessary. def self.new_from_hash(gpos_params_hash) gpos_params_hash[:type] = Types::GPOS RR.new_from_hash(gpos_params_hash) end # Create an instance from a string containing parameters, e.g.: # 'a.dnsruby.com. 10800 IN GPOS 10.0 20.0 30.0' def self.new_from_string(gpos_params_string) RR.new_from_string(gpos_params_string) end # Create an instance from an ordered parameter list, e.g.: # EXAMPLE_GPOS_DATA = begin # rdata = RR::GPOS.build_rdata(EXAMPLE_LONGITUDE, EXAMPLE_LATITUDE, EXAMPLE_ALTITUDE) # [EXAMPLE_HOSTNAME, Types::GPOS, Classes::IN, EXAMPLE_TTL, rdata.length, rdata, 0] # end # self.from_data(*EXAMPLE_GPOS_DATA) def self.new_from_data(*gpos_params_data) RR.new_from_data(*gpos_params_data) end def from_data(array) unless array.size == 3 raise "Array size for creating GPOS record must be 3 (long, lat, alt). Array was:\n#{array.inspect}" end from_hash({ longitude: array[0], latitude: array[1], altitude: array[2] }) end def from_hash(init_data) self.class.validate_floats(init_data) @longitude = init_data[:longitude].to_s @latitude = init_data[:latitude].to_s @altitude = init_data[:altitude].to_s self.rdata = build_rdata self end def from_string(string) # Convert commas to spaces, then split by spaces: from_data(string.gsub(',', ' ').split(' ')) end # From the RFC: # GPOS has the following format: # GPOS # # We handle the rdata, the RR superclass does the rest. def rdata_to_string [longitude, latitude, altitude].join(' ') end def encode_rdata(msg, _canonical) msg.put_bytes(build_rdata) end def build_rdata self.class.build_rdata(longitude, latitude, altitude) end def self.build_rdata(longitude, latitude, altitude) binary_string = ''.force_encoding('ASCII-8BIT') binary_string << longitude.length.chr binary_string << longitude binary_string << latitude.length.chr binary_string << latitude binary_string << altitude.length.chr binary_string << altitude binary_string end def self.decode_rdata(message) rdata_s = message.get_bytes.clone index = 0 long_len = rdata_s[index].ord; index += 1 longitude = rdata_s[index, long_len]; index += long_len lat_len = rdata_s[index].ord; index += 1 latitude = rdata_s[index, lat_len]; index += lat_len alt_len = rdata_s[index].ord; index += 1 altitude = rdata_s[index, alt_len]; index += alt_len validate_latitude(latitude) validate_longitude(longitude) new([longitude, latitude, altitude].join(' ')) # e.g. "10.0 20.0 30.0" end # 'name' is used in the RR superclass, but 'owner' is the term referred to # in the RFC, so we'll make owner an alias for name. def owner name end # 'name' is used in the RR superclass, but 'owner' is the term referred to # in the RFC, so we'll make owner an alias for name. def owner=(owner_string) self.name = owner_string end def self.valid_float?(object) begin Float(object) true rescue false end end def self.validate_float_in_range(label, object, bound) number = Float(object) valid_range = (-Float(bound)..Float(bound)) unless valid_range.include?(number) raise "Value of #{label} (#{number}) was not in the range #{valid_range}." end end def self.validate_longitude(value) validate_float_in_range('longitude', value, 180) end def self.validate_latitude(value) validate_float_in_range('latitude', value, 90) end def self.validate_floats(init_data) bad_float_keys = REQUIRED_KEYS.reject { |key| valid_float?(init_data[key]) } unless bad_float_keys.empty? message = "The following key value pair(s) do not have valid floats or float strings:\n" bad_float_keys.each do |key| message << "%:-12.12s => %s\n" % [init_data[key]] end raise message end validate_longitude(init_data[:longitude]) validate_latitude(init_data[:latitude]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/domain_name.rb0000644000004100000410000000316413607400362022204 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Abstract superclass for RR's which have a domain name in the data section. class DomainName < RR # The domain name in the RR data section. attr_reader :domainname def set_domain_name(newname) @domainname=Name.create(newname) end alias domainname= set_domain_name def from_hash(hash) #:nodoc: all set_domain_name(hash[:domainname]) end def from_data(data) #:nodoc: all @domainname = data end def from_string(input) #:nodoc: all set_domain_name(input) end def rdata_to_string #:nodoc: all return @domainname.to_s(true) end def encode_rdata(msg, canonical=false) #:nodoc: all if !([Classes::NONE, Classes::ANY].include? klass) || @rdata.length > 0 msg.put_name(@domainname, canonical) end end def self.decode_rdata(msg) #:nodoc: all n = msg.get_name if n.length == 0 # n = nil end self.new(n) end end end end dnsruby-1.61.3/lib/dnsruby/resource/RRSet.rb0000644000004100000410000001062513607400362020734 0ustar www-datawww-datamodule Dnsruby # RFC2181, section 5 # "It is however possible for most record types to exist # with the same label, class and type, but with different data. Such a # group of records is hereby defined to be a Resource Record Set # (RRSet)." # This class also stores the RRSIG records which cover the RRSet class RRSet include Comparable # The number of RRSIGs stored in this RRSet attr_reader :num_sigs def initialize(rrs = []) if (!rrs.instance_of?Array) rrs = [rrs] end @rrs = [] @num_sigs = 0 rrs.each {|rr| add(rr)} end def self.new_from_string(string) rr_strings = string.split("\n") rrs = rr_strings.map { |s| Dnsruby::RR.new_from_string(s) } Dnsruby::RRSet.new(rrs) end # The RRSIGs stored with this RRSet def sigs return @rrs[@rrs.length-@num_sigs, @num_sigs] end # The RRs (not RRSIGs) stored in this RRSet def rrs return @rrs[0, @rrs.length-@num_sigs] end def privateAdd(r) #:nodoc: if @rrs.include?r return true end new_pos = @rrs.length - @num_sigs if ((@num_sigs == @rrs.length) && @num_sigs > 0) # if we added RRSIG first if (((r.type != @rrs.last.type_covered) && (r.type != Types.RRSIG))|| ((r.type == Types.RRSIG) && (r.type_covered != @rrs.last.type_covered))) return false end end if (r.type == Types::RRSIG) new_pos = @rrs.length @num_sigs += 1 end @rrs.insert(new_pos, r) return true end # Add the RR to this RRSet # Takes a copy of the RR by default. To suppress this, pass false # as the second parameter. def add(rin, do_clone = true) if (rin.instance_of?RRSet) ret = false [rin.rrs, rin.sigs].each {|rr| ret = add(rr)} return ret end # r = RR.create(r.to_s) # clone the record r = nil if do_clone r = rin.clone else r = rin end if (@rrs.size() == 0) # && !(r.type == Types.RRSIG)) return privateAdd(r) end # Check the type, klass and ttl are correct first = @rrs[0] if (!r.sameRRset(first)) return false # raise ArgumentError.new("record does not match rrset") end if (!(r.type == Types::RRSIG) && (!(first.type == Types::RRSIG))) if (r.ttl != first.ttl) # RFC2181, section 5.2 if (r.ttl > first.ttl) r.ttl=(first.ttl) else @rrs.each do |rr| rr.ttl = r.ttl end end end end return privateAdd(r) # return true end def <=>(other) # return 1 if ((!other) || !(other.name) || !(other.type)) # return -1 if (!@name) if (name.canonical == other.name.canonical) return type.code <=> other.type.code else return name <=> other.name end end def sort_canonical # Make a list, for all the RRs, where each RR contributes # the canonical RDATA encoding canonical_rrs = {} self.rrs.each do |rr| data = MessageEncoder.new {|msg| rr.encode_rdata(msg, true) }.to_s canonical_rrs[data] = rr end return_rrs = RRSet.new canonical_rrs.keys.sort.each { |rdata| return_rrs.add(canonical_rrs[rdata], false) } return return_rrs end def ==(other) return false unless other.instance_of?RRSet return false if (other.sigs.length != self.sigs.length) return false if (other.rrs.length != self.rrs.length) return false if (other.ttl != self.ttl) otherrrs = other.rrs self.rrs.each {|rr| return false if (!otherrrs.include?rr) } othersigs= other.sigs self.sigs.each {|sig| return false if (!othersigs.include?sig) } return true end # Delete the RR from this RRSet def delete(rr) @rrs.delete(rr) end def each @rrs.each do |rr| yield rr end end def [](index) return @rrs[index] end # Return the type of this RRSet def type if (@rrs[0]) return @rrs[0].type end return nil end # Return the klass of this RRSet def klass return @rrs[0].klass end # Return the ttl of this RRSet def ttl return @rrs[0].ttl end def ttl=(ttl) [rrs, sigs].each {|rrs| rrs.each {|rr| rr.ttl = ttl } } end def name if (@rrs[0]) return @rrs[0].name else return nil end end def to_s ret = "" each {|rec| ret += rec.to_s + "\n" } return ret end def length return @rrs.length end end end dnsruby-1.61.3/lib/dnsruby/resource/SOA.rb0000644000004100000410000000623613607400362020362 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR class SOA < RR ClassValue = nil #:nodoc: all TypeValue = Types::SOA #:nodoc: all # The domain name of the original or primary nameserver for # this zone. attr_accessor :mname # A domain name that specifies the mailbox for the person # responsible for this zone. attr_accessor :rname # The zone's serial number. attr_accessor :serial # The zone's refresh interval. # How often, in seconds, a secondary nameserver is to check for # updates from the primary nameserver. attr_accessor :refresh # The zone's retry interval. # How often, in seconds, a secondary nameserver is to retry, after a # failure to check for a refresh attr_accessor :retry # The zone's expire interval. # How often, in seconds, a secondary nameserver is to use the data # before refreshing from the primary nameserver attr_accessor :expire # The minimum (default) TTL for records in this zone. attr_accessor :minimum def from_data(data) #:nodoc: all @mname, @rname, @serial, @refresh, @retry, @expire, @minimum = data end def from_hash(hash) @mname = Name.create(hash[:mname]) @rname = Name.create(hash[:rname]) @serial = hash[:serial].to_i @refresh = hash[:refresh].to_i @retry = hash[:retry].to_i @expire = hash[:expire].to_i @minimum = hash[:minimum].to_i end def from_string(input) if (input.length > 0) names = input.split(" ") @mname = Name.create(names[0]) @rname = Name.create(names[1]) @serial = names[2].to_i @refresh = names[3].to_i @retry = names[4].to_i @expire = names[5].to_i @minimum = names[6].to_i end end def rdata_to_string #:nodoc: all if (@mname!=nil) return "#{@mname.to_s(true)} #{@rname.to_s(true)} #{@serial} #{@refresh} #{@retry} #{@expire} #{@minimum}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_name(@mname, canonical) msg.put_name(@rname, canonical) msg.put_pack('NNNNN', @serial, @refresh, @retry, @expire, @minimum) end def self.decode_rdata(msg) #:nodoc: all mname = msg.get_name rname = msg.get_name serial, refresh, retry_, expire, minimum = msg.get_unpack('NNNNN') return self.new( [mname, rname, serial, refresh, retry_, expire, minimum]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/HIP.rb0000644000004100000410000000760613607400362020362 0ustar www-datawww-data # -- # Copyright 2009 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR class HIP < RR ClassValue = nil #:nodoc: all TypeValue = Types::HIP #:nodoc: all # An 8-bit length for the HIT field attr_accessor :hit_length # The PK algorithm used : # 0 - no key present # 1 - DSA key present # 2 - RSA key present attr_accessor :pk_algorithm # An 8-bit length for the Public Key field attr_accessor :pk_length # An array of Rendezvous Servers attr_accessor :rsvs def from_data(data) #:nodoc: all @rsvs=[] @hit_length = data[0] @pk_algorithm = data[1] @pk_length = data[2] @hit = data[3] @public_key = data[4] @rsvs = data[5] end def from_hash(hash) @rsvs=[] @hit_length = hash[:hit_length] @pk_algorithm = hash[:pk_algorithm] @pk_length = hash[:pk_length] @hit = hash[:hit] @public_key = hash[:public_key] if (hash[:rsvs]) hash[:rsvs].each {|rsv| @rsvs.push(Name.create(rsv)) } end end # HIT field - stored in binary : client methods should handle base16(hex) encoding def hit_string # Return hex value [@hit.to_s].pack("H*").gsub("\n", "") end def hit_from_string(hit_text) # Decode the hex value hit_text.gsub!(/\n/, "") hit_text.gsub!(/ /, "") return hit_text.unpack("H*")[0] end # Public Key field - presentation format is base64 - public_key methods reused from IPSECKEY def public_key_string [@public_key.to_s].pack("m*").gsub("\n", "") end def public_key_from_string(key_text) key_text.gsub!(/\n/, "") key_text.gsub!(/ /, "") return key_text.unpack("m*")[0] end def from_string(input) @rsvs=[] if (input.length > 0) split = input.split(" ") @pk_algorithm = split[0].to_i @hit = hit_from_string(split[1]) @hit_length = @hit.length @public_key = public_key_from_string(split[2]) @pk_length = @public_key.length # Now load in any RSVs there may be count = 3 while (split[count]) @rsvs.push(Name.create(split[count])) count += 1 end end end def rdata_to_string #:nodoc: all ret = "#{@pk_algorithm} #{hit_string} #{public_key_string}" @rsvs.each {|rsv| ret += " #{rsv.to_s(true)}" } return ret end def encode_rdata(msg, canonical=false) #:nodoc: all\ msg.put_pack('ccC', @hit_length, @pk_algorithm, @pk_length) msg.put_bytes(@hit) msg.put_bytes(@public_key) @rsvs.each {|rsv| # RSVs MUST NOT be compressed msg.put_name(rsv, true) } end def self.decode_rdata(msg) #:nodoc: all hit_length, pk_algorithm, pk_length = msg.get_unpack('ccC') hit = msg.get_bytes(hit_length) public_key = msg.get_bytes(pk_length) rsvs = [] # Load in the RSV names, if there are any while (msg.has_remaining?) name = msg.get_name rsvs.push(name) end return self.new( [hit_length, pk_algorithm, pk_length, hit, public_key, rsvs]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/KX.rb0000644000004100000410000000354413607400362020261 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Key Exchange (KX) resource records. # RFC 2230 class KX < RR ClassValue = nil #:nodoc: all TypeValue= Types::KX #:nodoc: all # The preference for this mail exchange. attr_accessor :preference # The name of this mail exchange. attr_accessor :exchange def from_hash(hash) #:nodoc: all @preference = hash[:preference] @exchange = Name.create(hash[:exchange]) end def from_data(data) #:nodoc: all @preference, @exchange = data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") @preference = names[0].to_i @exchange = Name.create(names[1]) end end def rdata_to_string #:nodoc: all if (@preference!=nil) return "#{@preference} #{@exchange.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('n', @preference) msg.put_name(@exchange, true) end def self.decode_rdata(msg) #:nodoc: all preference, = msg.get_unpack('n') exchange = msg.get_name return self.new([preference, exchange]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/PX.rb0000644000004100000410000000427213607400362020265 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR module IN class PX < RR ClassHash[[TypeValue = Types::PX, ClassValue = Classes::IN]] = self #:nodoc: all # The preference given to this RR. attr_accessor :preference # The RFC822 part of the RFC1327 mapping information. attr_accessor :map822 # The X.400 part of the RFC1327 mapping information. attr_accessor :mapx400 def from_hash(hash) #:nodoc: all @preference = hash[:preference] @map822 = Name.create(hash[:map822]) @mapx400 = Name.create(hash[:mapx400]) end def from_data(data) #:nodoc: all @preference, @map822, @mapx400 = data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") @preference = names[0].to_i @map822 = Name.create(names[1]) @mapx400 = Name.create(names[2]) end end def rdata_to_string #:nodoc: all if (@preference!=nil) return "#{@preference} #{@map822.to_s(true)} #{@mapx400.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('n', @preference) msg.put_name(@map822, canonical) msg.put_name(@mapx400, canonical) end def self.decode_rdata(msg) #:nodoc: all preference, = msg.get_unpack('n') map822 = msg.get_name mapx400 = msg.get_name return self.new([preference, map822, mapx400]) end end end end enddnsruby-1.61.3/lib/dnsruby/resource/CAA.rb0000644000004100000410000000431213607400362020315 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for CAA resource records. # RFC 6844 class CAA < RR ClassValue = nil #:nodoc: all TypeValue= Types::CAA #:nodoc: all # The property tag for the record (issue|issuewild|iodef) attr_accessor :property_tag # The value for the property_tag attr_accessor :property_value # The value for the flag attr_accessor :flag def from_hash(hash) #:nodoc: all @property_tag = hash[:property_tag] @property_value = hash[:property_value] @flag = hash[:flag] end def from_data(data) #:nodoc: all @flag, @property_tag, @property_value = data end def flag @flag.to_i end def from_string(input) #:nodoc: all matches = (/(\d+) (issuewild|issue|iodef) "(.+)"$/).match(input) @flag = matches[1] @property_tag = matches[2] @property_value = matches[3] end def rdata_to_string #:nodoc: all "#{flag} #{@property_tag} \"#{@property_value}\"" end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('C', flag) msg.put_string(@property_tag) # We don't put a length byte on the final string. msg.put_bytes(@property_value) end def self.decode_rdata(msg) #:nodoc: all flag, = msg.get_unpack('C') property_tag = msg.get_string # The final string has no length byte - its length is implicit as the remainder of the packet length property_value = msg.get_bytes return self.new("#{flag} #{property_tag} \"#{property_value}\"") end end end end dnsruby-1.61.3/lib/dnsruby/resource/URI.rb0000644000004100000410000000266213607400362020376 0ustar www-datawww-datamodule Dnsruby class RR class URI < RR ClassValue = nil #:nodoc: all TypeValue= Types::URI #:nodoc: all # The NAPTR RR order field attr_accessor :priority # The NAPTR RR order field attr_accessor :weight # The NAPTR RR order field attr_accessor :target def from_hash(hash) #:nodoc: all @priority = hash[:priority] @weight = hash[:weight] @target = hash[:target] end def from_data(data) #:nodoc: all @priority, @weight, @target = data end def from_string(input) #:nodoc: all if (input.strip.length > 0) values = input.split(" ") @priority = values [0].to_i @weight = values [1].to_i @target = values [2].gsub!("\"", "") end end def rdata_to_string #:nodoc: all "#{@priority} #{@weight} \"#{@target}\"" end def encode_rdata(msg, canonical=false) #:nodoc: all if (@priority != nil) msg.put_pack('n', @priority) msg.put_pack('n', @weight) msg.put_bytes(@target) end end def self.decode_rdata(msg) #:nodoc: all priority, = msg.get_unpack('n') weight, = msg.get_unpack('n') target = msg.get_bytes return self.new([priority, weight, target]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/RR.rb0000644000004100000410000003105013607400362020253 0ustar www-datawww-data# Superclass for all Dnsruby resource records. # # Represents a DNS RR (resource record) [RFC1035, section 3.2] # # Use Dnsruby::RR::create(...) to create a new RR record. # # mx = Dnsruby::RR.create("example.com. 7200 MX 10 mailhost.example.com.") # # rr = Dnsruby::RR.create({:name => "example.com", :type => "MX", :ttl => 7200, # :preference => 10, :exchange => "mailhost.example.com"}) # # s = rr.to_s # Get a String representation of the RR (in zone file format) # rr_again = Dnsruby::RR.create(s) # require 'dnsruby/code_mappers' module Dnsruby class RR include Comparable def <=>(other) # return 1 if ((!other) || !(other.name) || !(other.type)) # return -1 if (!@name) if @name.canonical == other.name.canonical @type.code != other.type.code ? (@type.code <=> other.type.code) : (@rdata <=> other.rdata) else @name <=> other.name end end # A regular expression which catches any valid resource record. @@RR_REGEX = Regexp.new("^\\s*(\\S+)\\s*(\\d+)?\\s*(#{Classes.regexp + "|CLASS\\d+"})?\\s*(#{Types.regexp + '|TYPE\\d+'})?\\s*([\\s\\S]*)\$") #:nodoc: all @@implemented_rr_map = nil # The Resource's domain name attr_reader :name # The Resource type attr_reader :type # The Resource class attr_reader :klass # The Resource Time-To-Live attr_accessor :ttl # The Resource data section attr_accessor :rdata def rdlength rdata.length end def name=(new_name) @name = new_name.kind_of?(Name) ? new_name : Name.create(new_name) end def type=(type) @type = Types.new(type) end alias :rr_type :type def klass=(klass) if @type != Types::OPT @klass = Classes.new(klass) else @klass = klass.is_a?(Classes) ? klass : Classes.new("CLASS#{klass}") end end def clone encoded = MessageEncoder.new { |encoder| encoder.put_rr(self, true) }.to_s MessageDecoder.new(encoded).get_rr end # Determines if two Records could be part of the same RRset. # This compares the name, type, and class of the Records; the ttl and # rdata are not compared. def sameRRset(rec) if @klass != rec.klass || @name.downcase != rec.name.downcase return false elsif (rec.type == Types.RRSIG) && (@type == Types.RRSIG) return rec.type_covered == self.type_covered end [rec, self].each do |rr| if rr.type == Types::RRSIG return (@type == rr.type_covered) || (rec.type == rr.type_covered) end end @type == rec.type end def init_defaults # Default to do nothing end private def initialize(*args) #:nodoc: all init_defaults if args.length > 0 if args[0].class == Hash from_hash(args[0]) return else @rdata = args[0] # print "Loading RR from #{args[0]}, class : #{args[0].class}\n" if args[0].class == String from_string(args[0]) return else from_data(args[0]) return end end end # raise ArgumentError.new("Don't call new! Use Dnsruby::RR::create() instead") end public def from_hash(hash) #:nodoc: all hash.keys.each do |param| send("#{param}=", hash[param]) end end # Create a new RR from the hash. The name is required; all other fields are optional. # Type defaults to ANY and the Class defaults to IN. The TTL defaults to 0. # # If the type is specified, then it is necessary to provide ALL of the resource record fields which # are specific to that record; i.e. for # an MX record, you would need to specify the exchange and the preference # # require 'Dnsruby' # rr = Dnsruby::RR.new_from_hash({:name => "example.com"}) # rr = Dnsruby::RR.new_from_hash({:name => "example.com", :type => Types.MX, :ttl => 10, :preference => 5, :exchange => "mx1.example.com"}) def RR.new_from_hash(inhash) hash = inhash.clone type = hash[:type] || Types::ANY klass = Classes.new(hash[:klass] || Classes::IN) ttl = hash[:ttl] || 0 record_class = get_class(type, klass) record = record_class.new record.name = hash[:name] unless record.name.kind_of?(Name) record.name = Name.create(record.name) end record.ttl = ttl record.type = type record.klass = Classes.new(klass) hash.delete(:name) hash.delete(:type) hash.delete(:ttl) hash.delete(:klass) record.from_hash(hash) record end # Returns a Dnsruby::RR object of the appropriate type and # initialized from the string passed by the user. The format of the # string is that used in zone files, and is compatible with the string # returned by Net::DNS::RR.inspect # # The name and RR type are required; all other information is optional. # If omitted, the TTL defaults to 0 and the RR class defaults to IN. # # All names must be fully qualified. The trailing dot (.) is optional. # # # a = Dnsruby::RR.new_from_string("foo.example.com. 86400 A 10.1.2.3") # mx = Dnsruby::RR.new_from_string("example.com. 7200 MX 10 mailhost.example.com.") # cname = Dnsruby::RR.new_from_string("www.example.com 300 IN CNAME www1.example.com") # txt = Dnsruby::RR.new_from_string('baz.example.com 3600 HS TXT "text record"') # # def RR.new_from_string(rrstring) # strip out comments # Test for non escaped ";" by means of the look-behind assertion # (the backslash is escaped) rrstring = rrstring.gsub(/(\?(hexdump, rdlength) do if hexdump.length != rdlength * 2 raise "#{rdata} is inconsistent; length should be #{rdlength * 2} but is #{hexdump.length}." end end pack_rdata = ->(regex) do rdata =~ regex matches = regex.match(rdata) rdlength = matches[1].to_i hexdump = matches[2].gsub(/\s*/, '') test_length.(hexdump, rdlength) packed_rdata = [hexdump].pack('H*') [packed_rdata, rdlength] end if implemented_rrs.include?(rrtype) && rdata !~/^\s*\\#/o return _get_subclass(name, rrtype, rrclass, ttl, rdata) elsif implemented_rrs.include?(rrtype) # A known RR type starting with \# packed_rdata, rdlength = pack_rdata.(/\\#\s+(\d+)\s+(.*)$/o) return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength); elsif rdata =~ /\s*\\#\s+\d+\s+/o regex = /\\#\s+(\d+)\s+(.*)$/o # We are now dealing with the truly unknown. raise 'Expected RFC3597 representation of RDATA' unless rdata =~ regex packed_rdata, rdlength = pack_rdata.(regex) return new_from_data(name, rrtype, rrclass, ttl, rdlength, packed_rdata, 0) # rdata.length() - rdlength); else # God knows how to handle these... return _get_subclass(name, rrtype, rrclass, ttl, '') end end def RR.new_from_data(*args) #:nodoc: all name, rrtype, rrclass, ttl, rdlength, data, offset = args rdata = data ? data[offset, rdlength] : [] decoder = MessageDecoder.new(rdata) record = get_class(rrtype, rrclass).decode_rdata(decoder) record.name = Name.create(name) record.ttl = ttl record.type = rrtype record.klass = Classes.new(rrclass) record end # Return an array of all the currently implemented RR types def RR.implemented_rrs @@implemented_rr_map ||= ClassHash.keys.map { |key| Dnsruby::Types.to_string(key[0]) } end class << self private def _get_subclass(name, rrtype, rrclass, ttl, rdata) #:nodoc: all return unless (rrtype!=nil) record = get_class(rrtype, rrclass).new(rdata) record.name = Name.create(name) record.ttl = ttl record.type = rrtype record.klass = Classes.new(rrclass) return record end end # Returns a string representation of the RR in zone file format def to_s s = name ? (name.to_s(true) + "\t") : '' s << [ttl, klass, type, rdata_to_string].map(&:to_s).join("\t") end # Get a string representation of the data section of the RR (in zone file format) def rdata_to_string (@rdata && @rdata.length > 0) ? @rdata : 'no rdata' end def from_data(data) #:nodoc: all # to be implemented by subclasses raise NotImplementedError.new end def from_string(input) #:nodoc: all # to be implemented by subclasses # raise NotImplementedError.new end def encode_rdata(msg, canonical=false) #:nodoc: all # to be implemented by subclasses raise EncodeError.new("#{self.class} is RR.") end def self.decode_rdata(msg) #:nodoc: all # to be implemented by subclasses raise DecodeError.new("#{self.class} is RR.") end def ==(other) return false unless self.class == other.class ivars_to_compare = ->(object) do ivars = object.instance_variables.map { |var| var.to_s } ivars.delete '@ttl' # RFC 2136 section 1.1 ivars.delete '@rdata' if self.type == Types.DS ivars.delete '@digest' end ivars.sort end get_instance_var_values = ->(object, ivar_names) do ivar_names.map { |ivar_name| object.instance_variable_get(ivar_name) } end self_ivars = ivars_to_compare.(self) other_ivars = ivars_to_compare.(other) return false unless self_ivars == other_ivars self_values = get_instance_var_values.(self, self_ivars) other_values = get_instance_var_values.(other, other_ivars) self_values == other_values end def eql?(other) #:nodoc: self == other end def hash # :nodoc: vars = (self.instance_variables - [:@ttl]).sort vars.inject(0) do |hash_value, var_name| hash_value ^ self.instance_variable_get(var_name).hash end end def self.find_class(type_value, class_value) # :nodoc: all if !! (ret = ClassHash[[type_value, class_value]]) return ret elsif !! (val = ClassInsensitiveTypes[type_value]) klass = Class.new(val) klass.const_set(:TypeValue, type_value) klass.const_set(:ClassValue, class_value) return klass else return Generic.create(type_value, class_value) end end # Get an RR of the specified type and class def self.get_class(type_value, class_value) #:nodoc: all if type_value == Types::OPT return Class.new(OPT) elsif type_value.class == Class type_value = type_value.const_get(:TypeValue) return find_class(type_value, Classes.to_code(class_value)) else type_value = (type_value.class == Types) ? type_value.code : Types.new(type_value).code class_value = (class_value.class == Classes) ? class_value.code : Classes.new(class_value).code return find_class(type_value, class_value) end end # Create a new RR from the arguments, which can be either a String or a Hash. # See new_from_string and new_from_hash for details # # a = Dnsruby::RR.create('foo.example.com. 86400 A 10.1.2.3') # mx = Dnsruby::RR.create('example.com. 7200 MX 10 mailhost.example.com.') # cname = Dnsruby::RR.create('www.example.com 300 IN CNAME www1.example.com') # txt = Dnsruby::RR.create('baz.example.com 3600 HS TXT 'text record'') # # rr = Dnsruby::RR.create({:name => 'example.com'}) # rr = Dnsruby::RR.create({:name => 'example.com', :type => 'MX', :ttl => 10, # :preference => 5, :exchange => 'mx1.example.com'}) # def RR.create(*args) case args[0] when String new_from_string(args[0]) when Hash new_from_hash(args[0]) else new_from_data(args) end end def self.get_num(bytes) ret = 0 shift = (bytes.length - 1) * 8 bytes.each_byte do |byte| ret += byte.to_i << shift shift -= 8 end ret end end end dnsruby-1.61.3/lib/dnsruby/resource/RRSIG.rb0000644000004100000410000002514413607400362020625 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # (RFC4034, section 3) # DNSSEC uses public key cryptography to sign and authenticate DNS # resource record sets (RRsets). Digital signatures are stored in # RRSIG resource records and are used in the DNSSEC authentication # process described in [RFC4035]. A validator can use these RRSIG RRs # to authenticate RRsets from the zone. The RRSIG RR MUST only be used # to carry verification material (digital signatures) used to secure # DNS operations. # # An RRSIG record contains the signature for an RRset with a particular # name, class, and type. The RRSIG RR specifies a validity interval # for the signature and uses the Algorithm, the Signer's Name, and the # Key Tag to identify the DNSKEY RR containing the public key that a # validator can use to verify the signature. class RRSIG < RR ClassValue = nil #:nodoc: all TypeValue = Types::RRSIG #:nodoc: all # 3.1. RRSIG RDATA Wire Format # # The RDATA for an RRSIG RR consists of a 2 octet Type Covered field, a # 1 octet Algorithm field, a 1 octet Labels field, a 4 octet Original # TTL field, a 4 octet Signature Expiration field, a 4 octet Signature # Inception field, a 2 octet Key tag, the Signer's Name field, and the # Signature field. # # 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Type Covered | Algorithm | Labels | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Original TTL | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Signature Expiration | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Signature Inception | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Key Tag | / # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ Signer's Name / # / / # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # / / # / Signature / # / / # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # The type covered by this RRSIG attr_reader :type_covered # The algorithm used for this RRSIG # See Dnsruby::Algorithms for permitted values attr_reader :algorithm # The number of labels in the original RRSIG RR owner name # Can be used to determine if name was synthesised from a wildcard. attr_accessor :labels # The TTL of the covered RRSet as it appears in the authoritative zone attr_accessor :original_ttl # The signature expiration attr_accessor :expiration # The signature inception attr_accessor :inception # The key tag value of the DNSKEY RR that validates this signature attr_accessor :key_tag # identifies the owner name of the DNSKEY RR that a validator is # supposed to use to validate this signature attr_reader :signers_name # contains the cryptographic signature that covers # the RRSIG RDATA (excluding the Signature field) and the RRset # specified by the RRSIG owner name, RRSIG class, and RRSIG Type # Covered field attr_accessor :signature def init_defaults @algorithm=Algorithms.RSASHA1 @type_covered = Types::A @original_ttl = 3600 @inception = Time.now.to_i @expiration = Time.now.to_i @key_tag = 0 @labels = 0 self.signers_name="." @signature = "\0" end def algorithm=(a) if (a.instance_of?String) if (a.to_i > 0) a = a.to_i end end begin alg = Algorithms.new(a) @algorithm = alg rescue ArgumentError => e raise DecodeError.new(e) end end def type_covered=(t) begin type = Types.new(t) @type_covered = type rescue ArgumentError => e raise DecodeError.new(e) end end def signers_name=(s) begin name = Name.create(s) @signers_name = name rescue ArgumentError => e raise DecodeError.new(e) end end def from_data(data) #:nodoc: all type_covered, algorithm, @labels, @original_ttl, expiration, inception, @key_tag, signers_name, @signature = data @expiration = expiration @inception = inception self.type_covered=(type_covered) self.signers_name=(signers_name) self.algorithm=(algorithm) end def from_string(input) if (input.length > 0) data = input.split(" ") self.type_covered=(data[0]) self.algorithm=(data[1]) self.labels=data[2].to_i self.original_ttl=data[3].to_i self.expiration=get_time(data[4]) # Brackets may also be present index = 5 end_index = data.length - 1 if (data[index]=="(") index = 6 end_index = data.length - 2 end self.inception=get_time(data[index]) self.key_tag=data[index+1].to_i self.signers_name=(data[index+2]) # signature can include whitespace - include all text # until we come to " )" at the end, and then gsub # the white space out buf="" (index+3..end_index).each {|i| if (comment_index = data[i].index(";")) buf += data[i].slice(0, comment_index) # @TODO@ We lose the comments here - we should really keep them for when we write back to string format? break else buf += data[i] end } buf.gsub!(/\n/, "") buf.gsub!(/ /, "") # self.signature=Base64.decode64(buf) self.signature=buf.unpack("m*")[0] end end def RRSIG.get_time(input) if input.kind_of?(Integer) return input end # RFC 4034, section 3.2 # The Signature Expiration Time and Inception Time field values MUST be # represented either as an unsigned decimal integer indicating seconds # since 1 January 1970 00:00:00 UTC, or in the form YYYYMMDDHHmmSS in # UTC, where: # # YYYY is the year (0001-9999, but see Section 3.1.5); # MM is the month number (01-12); # DD is the day of the month (01-31); # HH is the hour, in 24 hour notation (00-23); # mm is the minute (00-59); and # SS is the second (00-59). # # Note that it is always possible to distinguish between these two # formats because the YYYYMMDDHHmmSS format will always be exactly 14 # digits, while the decimal representation of a 32-bit unsigned integer # can never be longer than 10 digits. if (input.length == 10) return input.to_i elsif (input.length == 14) year = input[0,4] mon=input[4,2] day=input[6,2] hour=input[8,2] min=input[10,2] sec=input[12,2] # @TODO@ REPLACE THIS BY LOCAL CODE - Time.gm DOG SLOW! return Time.gm(year, mon, day, hour, min, sec).to_i else raise DecodeError.new("RRSIG : Illegal time value #{input} - see RFC 4034 section 3.2") end end def get_time(input) return RRSIG.get_time(input) end def format_time(time) return Time.at(time).gmtime.strftime("%Y%m%d%H%M%S") end def rdata_to_string #:nodoc: all if (@type_covered!=nil) # signature = Base64.encode64(@signature) # .gsub(/\n/, "") signature = [@signature].pack("m*").gsub(/\n/, "") # @TODO@ Display the expiration and inception as return "#{@type_covered.string} #{@algorithm.string} #{@labels} #{@original_ttl} " + "#{format_time(@expiration)} ( #{format_time(@inception)} " + "#{@key_tag} #{@signers_name.to_s(true)} #{signature} )" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all # 2 octets, then 2 sets of 1 octet msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels) msg.put_pack("NNN", @original_ttl, @expiration, @inception) msg.put_pack("n", @key_tag) msg.put_name(@signers_name, canonical, false) msg.put_bytes(@signature) end def self.decode_rdata(msg) #:nodoc: all type_covered, algorithm, labels = msg.get_unpack('ncc') original_ttl, expiration, inception = msg.get_unpack('NNN') key_tag, = msg.get_unpack('n') signers_name = msg.get_name signature = msg.get_bytes return self.new( [type_covered, algorithm, labels, original_ttl, expiration, inception, key_tag, signers_name, signature]) end def sig_data # RRSIG_RDATA is the wire format of the RRSIG RDATA fields # with the Signer's Name field in canonical form and # the Signature field excluded; data = MessageEncoder.new { |msg| msg.put_pack('ncc', @type_covered.to_i, @algorithm.to_i, @labels) msg.put_pack("NNN", @original_ttl, @expiration, @inception) msg.put_pack("n", @key_tag) msg.put_name(@signers_name, true) }.to_s return data end end end enddnsruby-1.61.3/lib/dnsruby/resource/NXT.rb0000644000004100000410000002376713607400362020421 0ustar www-datawww-datarequire_relative = ->(*args) do this_file_dir = File.expand_path(File.dirname(__FILE__)) args.each { |arg| require(File.join(this_file_dir, arg)) } end require_relative.('../bitmap', '../bit_mapping', 'RR') module Dnsruby class RR # Class for NXT resource records. # # NXT-specific data types, present in RDATA, are: # next_domain: the next domain name, as a Name instance # types: array of record types as numbers # # RFC 2535 (https://www.ietf.org/rfc/rfc2535.txt) # # The RFC mentions that a low bit of zero in the type RDATA # indicates that the highest type code does not exceed 127, # and that a low bit of 1 indicates that some mechanism # other than a bitmap is being used. This class does not # support such non-bitmap mechanisms, and assumes there # will always be a bitmap. class NXT < RR ClassHash[[TypeValue = Types::NXT, Classes::IN]] = self #:nodoc: all attr_accessor :next_domain, :types REQUIRED_KEYS = [:next_domain, :types] def from_hash(params_hash) unless REQUIRED_KEYS.all? { |key| params_hash[key] } raise ArgumentError.new("NXT hash must contain all of: #{REQUIRED_KEYS.join(', ')}.") end @next_domain = Name.create(params_hash[:next_domain]) unless @next_domain.is_a?(Name) @types = params_hash[:types] end def from_data(data) next_domain, types = data from_hash(next_domain: next_domain, types: types) end def from_string(string) next_domain, *type_names = string.split # type names are all but first types = NxtTypes::names_to_codes(type_names) from_hash(next_domain: next_domain, types: types) end # As with all resource record subclasses of RR, this class cannot be # directly instantiated, but instead must be instantiated via use of # one of the RR class methods. These NXT class methods are wrappers # around those RR methods, so that there is an interface on the NXT # class for creating NXT instances. # Create an instance from a hash of parameters, e.g.: # # rr = RR::NXT.new_from_hash( # name: 'b.dnsruby.com.', # ttl: 10800, # klass: Classes::IN, # next_domain: 'a.dnsruby.com.', # types: [Types::SOA, Types::NXT]) # # Since the type is assumed to be NXT, it will be assigned # automatically, and any other value will be overwritten. # Therefore, having it present in the hash is not necessary. def self.new_from_hash(params_hash) params_hash[:type] = Types::NXT RR.new_from_hash(params_hash) end # Create an instance from a string containing parameters, e.g.: # b.dnsruby.com. 10800 IN NXT A.dnsruby.com. SOA NXT def self.new_from_string(params_string) RR.new_from_string(params_string) end # Create an instance from an ordered parameter list, e.g.: # rdata = RR::NXT.build_rdata('a.dnsruby.com.', [Types::SOA, Types::NXT]) # # rr = RR::NXT.new_from_data('b.dnsruby.com.', Types::NXT, # Classes::IN, 10800, rdata.size, rdata, 0) def self.new_from_data(*params_data) RR.new_from_data(*params_data) end # Builds rdata from the provided information. # @param next_domain either a string or a Name # @param types an array of types (where each type is the numeric type code) # or a TypeBitmap def self.build_rdata(next_domain, types) next_domain = Name.create(next_domain) if next_domain.is_a?(String) types = TypeBitmap.from_type_codes(types) if types.is_a?(Array) binary_string = ''.force_encoding('ASCII-8BIT') binary_string << next_domain.canonical binary_string << BitMapping.reverse_binary_string_bits(types.to_binary_string) binary_string end # From the RFC: # NXT has the following format: # foo.nil. NXT big.foo.nil NS KEY SOA NXT # NXT # # We handle the rdata, the RR superclass does the rest. def rdata_to_string "#{next_domain} #{NxtTypes.codes_to_names(types).join(' ')}" end def encode_rdata(message_encoder, _canonical) message_encoder.put_bytes(build_rdata) end def build_rdata self.class.build_rdata(next_domain, types) end def self.decode_rdata(message_decoder) start_index = message_decoder.index rdata_len = -> do rdata_length_str = message_decoder.data[start_index - 2, 2] rdata_length_str.unpack('n').first end next_domain_and_bitmap = -> do next_domain = message_decoder.get_name bitmap_start_index = message_decoder.index # If we're being called from new_from_data, the MessageDecoder # contains only the rdata, not the entire message, and there will # be no encoded length for us to read. called_from_new_from_data = (start_index == 0) bitmap_length = called_from_new_from_data \ ? message_decoder.data.size \ : rdata_len.() - (bitmap_start_index - start_index) bitmap = message_decoder.get_bytes(bitmap_length) bitmap = BitMapping.reverse_binary_string_bits(bitmap) [next_domain, bitmap] end next_domain, type_bitmap = next_domain_and_bitmap.() types = TypeBitmap.from_binary_string(type_bitmap).to_type_array new(next_domain: next_domain, types: types) end # 'name' is used in the RR superclass, but 'owner' is the term referred to # in the RFC, so we'll make owner an alias for name. alias_method(:owner, :name) alias_method(:owner=, :name=) # Methods used to manipulate the storage and representation of # record types as stored in NXT record bitmaps. module NxtTypes module_function # Maximum bitmap size is 128 bytes; since it's zero offset # values are 0..(2 ** 128 - 1). However, the least # significant bit must not be set, so the maximum is 1 less than that. MAX_BITMAP_NUMBER_VALUE = (2 ** 128) - 1 - 1 # Convert a numeric type code to its corresponding name (e.g. "A" => 1). # Unknown types are named "TYPE#{number}". def code_to_name(number) Types.to_string(number) || "TYPE#{number}" end # Convert a type name to its corresponding numeric type code. # Names matching /^TYPE(\d+)$/ are assumed to have a code # corresponding to the numeric value of the substring following 'TYPE'. def name_to_code(name) code = Types.to_code(name) if code.nil? matches = /^TYPE(\d+)$/.match(name) code = matches[1].to_i if matches end code end # For a given array of type names, return an array of codes. def names_to_codes(names) names.map { |s| name_to_code(s) } end # For the specified string containing names (e.g. 'A NS'), # return an array containing the corresponding codes. def names_string_to_codes(name_string) names_to_codes(name_string.split(' ')) end # For the given array of type codes, return an array of their # corresponding names. def codes_to_names(codes) codes.map { |code| code_to_name(code) } end # Generate a string containing the names corresponding to the # numeric type codes. Sort it by the numeric type code, ascending. def codes_to_string(codes) codes.sort.map { |code| code_to_name(code) }.join(' ') end # From a binary string of type code bits, return an array # of type codes. def binary_string_to_codes(binary_string) bitmap_number = BitMapping.binary_string_to_number(binary_string) assert_legal_bitmap_value(bitmap_number) BitMapping.number_to_set_bit_positions_array(bitmap_number) end # From a binary string of type code bits, return an array # of type names. def binary_string_to_names(binary_string) codes = binary_string_to_codes(binary_string) codes_to_names(codes) end # From an array of type codes, return a binary string. def codes_to_binary_string(codes) codes = codes.sort unless legal_code_value?(codes.first) && legal_code_value?(codes.last) raise ArgumentError.new("All codes must be between 1 and 127: #{codes.inspect}.") end bitmap_number = BitMapping.set_bit_position_array_to_number(codes) BitMapping.number_to_binary_string(bitmap_number) end # Assert that the specified number is a legal value with which to # instantiate a NXT type bitmap. Raise on error, do nothing on success. def assert_legal_bitmap_value(number) max_value = NxtTypes::MAX_BITMAP_NUMBER_VALUE if number > max_value raise ArgumentError.new("Bitmap maximum value is #{max_value} (0x#{max_value.to_s(16)}).") end if number & 1 == 1 raise ArgumentError.new("Bitmap number must not have low bit set.") end end def legal_code_value?(code) (1..127).include?(code) end end class TypeBitmap attr_accessor :bitmap # Create an instance from a string containing type names separated by spaces # e.g. "A TXT NXT" def self.from_names_string(names_string) type_codes = BitMapping.names_string_to_codes(names_string) from_type_codes(type_codes) end # Create an instance from type numeric codes (e.g. 30 for NXT). def self.from_type_codes(type_codes) new(BitMapping.set_bit_position_array_to_number(type_codes)) end # Create an instance from a binary string, e.g. from a NXT record RDATA: def self.from_binary_string(binary_string) new(BitMapping.binary_string_to_number(binary_string)) end # The constructor is made private so that the name of the method called # to create the instance reveals to the reader the type of the initial data. private_class_method :new def initialize(bitmap_number) NxtTypes.assert_legal_bitmap_value(bitmap_number) @bitmap = Bitmap.from_number(bitmap_number) end # Returns a binary string representing this data, in as few bytes as possible # (i.e. no leading zero bytes). def to_binary_string bitmap.to_binary_string end # Returns the instance's data as an array of type codes. def to_type_array bitmap.to_set_bit_position_array end # Output types in dig format, e.g. "A AAAA NXT" def to_s type_codes = bitmap.to_set_bit_position_array NxtTypes.codes_to_string(type_codes) end end end end end dnsruby-1.61.3/lib/dnsruby/resource/TLSA.rb0000644000004100000410000001056713607400362020505 0ustar www-datawww-datarequire 'openssl' module Dnsruby class RR module IN # Class for DNS TLSA server certificate or public key (TLSA) resource records. # # RFC 6698 class TLSA < RR ClassHash[[TypeValue = Types::TLSA, ClassValue = ClassValue]] = self #:nodoc: all # sec 2.1.1 ,7,2 # # 0 CA constraint # 1 Service certificate constraint # 2 Trust anchor assertion # 3 Domain-issued certificate # 4-254 Unassigned # 255 Private use attr_accessor :usage # sec 2.1.2, 7.3 # # 0 Full certificate # 1 SubjectPublicKeyInfo # 2-254 Unassigned # 255 Private use attr_accessor :selector # sec 2.3.1 # # 0 Exact match on selected content # 1 SHA-256 hash of selected content # 2 SHA-512 hash of selected content # 3-254 Unassigned # 255 Private use attr_accessor :matching_type # sec 2.1.4 attr_accessor :data attr_accessor :databin def verify raise ArgumentError, "usage with invalid value: #{@usage}" if @usage < 0 || @usage > 255 raise ArgumentError, "selector with invalid value: #{@selector}" if @selector < 0 || @selector > 255 raise ArgumentError, "matching_type with invalid value: #{@matching_type}" if @matching_type < 0 || @matching_type > 255 raise ArgumentError, "data with invalid value: #{@data}" if (@matching_type == 1 && @databin.bytesize != 32) || (@matching_type == 2 && @databin.bytesize != 64) pkey if @matching_type == 0 end def from_data(data) #:nodoc: all self.usage = data[0] self.selector = data[1] self.matching_type = data[2] self.databin = data[3] verify end # Create the RR from a hash def from_hash(hash) super(hash) verify end def data=(data) self.databin = parse_string(data) end def databin=(databin) @databin = databin @data = @databin.unpack('H*')[0].each_char.each_slice(57).map(&:join).join(' ') end def cert if @matching_type == 0 && @selector == 0 && @databin begin cert = OpenSSL::X509::Certificate.new(@databin) rescue => e raise ArgumentError, 'data is invalid cert ' end end cert end def pkey pubkey = nil if @matching_type == 0 && @databin if @selector == 0 cert = self.cert pubkey = cert.public_key elsif @selector == 1 begin pubkey = OpenSSL::PKey.read(@databin) rescue raise ArgumentError, 'data is invalid pkey' end end end pubkey end def parse_string(data) buf = '' comment = false multiline = false data.each_char do |ch| case ch when ';' then comment = true when '\n' raise ArgumentError, 'string format error' unless multiline comment = false when '\r' then next when ' ' then next when comment then next when '(' then multiline = true when ')' then multiline = false else buf += ch end end raise ArgumentError, 'string format error' if multiline [buf].pack('H*') end # Create the RR from a standard string def from_string(input) values = input.split(' ', 4) self.usage = values[0].to_i self.selector = values[1].to_i self.matching_type = values[2].to_i self.data = values[3] verify end def rdata_to_string "#{@usage} #{@selector} #{@matching_type} #{@data}" end def encode_rdata(msg, _canonical = false) #:nodoc: all msg.put_pack('CCC', @usage, @selector, @matching_type) msg.put_bytes(@databin) end def self.decode_rdata(msg) #:nodoc: all usage, selector, matching_type = msg.get_unpack('CCC') databin = msg.get_bytes new([usage, selector, matching_type, databin]) end end end end end dnsruby-1.61.3/lib/dnsruby/resource/DS.rb0000644000004100000410000002015513607400362020242 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'base64' begin require 'Digest/sha2' rescue LoadError require 'digest/sha2' end module Dnsruby class RR # RFC4034, section 4 # The DS Resource Record refers to a DNSKEY RR and is used in the DNS # DNSKEY authentication process. A DS RR refers to a DNSKEY RR by # storing the key tag, algorithm number, and a digest of the DNSKEY RR. # Note that while the digest should be sufficient to identify the # public key, storing the key tag and key algorithm helps make the # identification process more efficient. By authenticating the DS # record, a resolver can authenticate the DNSKEY RR to which the DS # record points. The key authentication process is described in # [RFC4035]. class DS < RR class DigestTypes < CodeMapper update() add_pair("SHA-1", 1) add_pair("SHA-256", 2 ) add_pair("SHA-384", 4) end ClassValue = nil #:nodoc: all TypeValue = Types::DS #:nodoc: all # The RDATA for a DS RR consists of a 2 octet Key Tag field, a 1 octet # Algorithm field, a 1 octet Digest Type field, and a Digest field. # # 1 1 1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 3 3 # 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # | Key Tag | Algorithm | Digest Type | # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # / / # / Digest / # / / # +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ # The Key Tag field lists the key tag of the DNSKEY RR referred to by # the DS record, in network byte order. attr_accessor :key_tag # The algorithm used for this key # See Dnsruby::Algorithms for permitted values attr_reader :algorithm # The DS RR refers to a DNSKEY RR by including a digest of that DNSKEY # RR. The Digest Type field identifies the algorithm used to construct # the digest. attr_reader :digest_type # The DS record refers to a DNSKEY RR by including a digest of that # DNSKEY RR. attr_accessor :digest attr_accessor :digestbin def digest_type=(d) dig = DS.get_digest_type(d) @digest_type = dig end def DS.get_digest_type(d) if (d.instance_of?String) if (d.length == 1) d = d.to_i end end begin digest = DigestTypes.new(d) return digest rescue ArgumentError => e raise DecodeError.new(e) end end def algorithm=(a) if (a.instance_of?String) if (a.length < 3) a = a.to_i end end begin alg = Algorithms.new(a) @algorithm = alg rescue ArgumentError => e raise DecodeError.new(e) end end # Return the digest of the specified DNSKEY RR def digest_key(*args) # key, digest_type) digest_type = @digest_type key = args[0] if (args.length == 2) digest_type = args[1] end data = MessageEncoder.new {|msg| msg.put_name(key.name, true) key.encode_rdata(msg, true) }.to_s if (digest_type.code == 1) digestbin = OpenSSL::Digest::SHA1.digest(data) return digestbin elsif (digest_type.code == 2) digestbin = OpenSSL::Digest::SHA256.digest(data) return digestbin elsif (digest_type.code == 4) digestbin = OpenSSL::Digest::SHA384.digest(data) return digestbin end end # Check if the key's digest is the same as that stored in the DS record def check_key(key) if ((key.key_tag == @key_tag) && (key.algorithm == @algorithm)) digestbin = digest_key(key) if (@digestbin == digestbin) if (!key.zone_key?) else return true end else end end return false end def DS.from_key(key, digest_type) # # The key must not be a NULL key. # if ((key.flags & 0xc000 ) == 0xc000 ) # puts "\nCreating a DS record for a NULL key is illegal" # return # end # # # Bit 0 must not be set. # if (key.flags & 0x8000) # puts "\nCreating a DS record for a key with flag bit 0 set " + # "to 0 is illegal" # return # end # # Bit 6 must be set to 0 bit 7 must be set to 1 if (( key.flags & 0x300) != 0x100) puts "\nCreating a DS record for a key with flags 6 and 7 not set "+ "0 and 1 respectively is illegal" return end # # # if (key.protocol != 3 ) # puts "\nCreating a DS record for a non DNSSEC (protocol=3) " + # "key is illegal" # return # end # digest_type = get_digest_type(digest_type) # Create a new DS record from the specified key ds = RR.create(:name => key.name, :type => "DS", :ttl => key.ttl, :key_tag => key.key_tag, :digest_type => digest_type, :algorithm => key.algorithm) ds.digestbin = ds.digest_key(key, digest_type) ds.digest = ds.digestbin.unpack("H*")[0] return ds end def from_data(data) #:nodoc: all key_tag, algorithm, digest_type, digest = data self.key_tag=(key_tag) self.algorithm=(algorithm) self.digest_type=(digest_type) self.digestbin=(digest) self.digest=@digestbin.unpack("H*")[0] end def from_string(input) if (input.length > 0) data = input.split(" ") self.key_tag=(data[0].to_i) self.algorithm=(data[1]) self.digest_type=(data[2]) buf = "" index = 3 end_index = data.length - 1 if (data[index]=="(") end_index = data.length - 2 index = 4 end (index..end_index).each {|i| if (comment_index = data[i].index(";")) buf += data[i].slice(0, comment_index) # @TODO@ We lose the comments here - we should really keep them for when we write back to string format? break else buf += data[i] end } # self.digest=Base64.decode64(buf) buf.gsub!(/\n/, "") buf.gsub!(/ /, "") # self.digest=buf.unpack("m*")[0] self.digest=buf self.digestbin = [buf].pack("H*") end end def rdata_to_string #:nodoc: all if (@key_tag != nil) # return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type} ( #{Base64.encode64(@digest)} )" # return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type.code} ( #{[@digest].pack("m*").gsub("\n", "")} )" return "#{@key_tag.to_i} #{@algorithm.string} #{@digest_type.code} ( #{@digest.upcase} )" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack("ncc", @key_tag, @algorithm.code, @digest_type.code) msg.put_bytes(@digestbin) end def self.decode_rdata(msg) #:nodoc: all key_tag, algorithm, digest_type = msg.get_unpack("ncc") digest = msg.get_bytes return self.new( [key_tag, algorithm, digest_type, digest]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/type_bitmap.rb0000644000004100000410000000000013607400362022234 0ustar www-datawww-datadnsruby-1.61.3/lib/dnsruby/resource/IN.rb0000644000004100000410000000626113607400362020244 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR ClassInsensitiveTypes = { Types::NS => NS, Types::CNAME => CNAME, Types::DNAME => DNAME, Types::URI => URI, Types::DS => DS, Types::CDS => CDS, Types::DNSKEY => DNSKEY, Types::CDNSKEY => CDNSKEY, Types::SOA => SOA, Types::PTR => PTR, Types::HINFO => HINFO, Types::MINFO => MINFO, Types::MX => MX, Types::TXT => TXT, Types::ISDN => ISDN, Types::MB => MB, Types::MG => MG, Types::MR => MR, Types::NAPTR => NAPTR, Types::NSAP => NSAP, Types::OPT => OPT, Types::RP => RP, Types::RT => RT, Types::X25 => X25, Types::KX => KX, Types::SPF => SPF, Types::CERT => CERT, Types::LOC => LOC, Types::TSIG => TSIG, Types::TKEY => TKEY, Types::ANY => ANY, Types::RRSIG => RRSIG, Types::NSEC => NSEC, Types::NSEC3 => NSEC3, Types::NSEC3PARAM => NSEC3PARAM, Types::DLV => DLV, Types::SSHFP => SSHFP, Types::IPSECKEY => IPSECKEY, Types::HIP => HIP, Types::DHCID => DHCID, Types::GPOS => GPOS, Types::NXT => NXT, Types::CAA => CAA, } #:nodoc: all # module IN contains ARPA Internet specific RRs module IN ClassValue = Classes::IN ClassInsensitiveTypes::values::each {|s| c = Class.new(s) # c < Record c.const_set(:TypeValue, s::TypeValue) c.const_set(:ClassValue, ClassValue) ClassHash[[s::TypeValue, ClassValue]] = c self.const_set(s.name.sub(/.*::/, ''), c) } # RFC 1035, Section 3.4.2 (deprecated) class WKS < RR ClassHash[[TypeValue = Types::WKS, ClassValue = ClassValue]] = self #:nodoc: all def initialize(address, protocol, bitmap) @address = IPv4.create(address) @protocol = protocol @bitmap = bitmap end attr_reader :address, :protocol, :bitmap def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_bytes(@address.address) msg.put_pack("n", @protocol) msg.put_bytes(@bitmap) end def self.decode_rdata(msg) #:nodoc: all address = IPv4.new(msg.get_bytes(4)) protocol, = msg.get_unpack("n") bitmap = msg.get_bytes return self.new(address, protocol, bitmap) end end end end end require 'dnsruby/resource/A' require 'dnsruby/resource/AAAA' require 'dnsruby/resource/AFSDB' require 'dnsruby/resource/PX' require 'dnsruby/resource/SRV' require 'dnsruby/resource/APL' require 'dnsruby/resource/TLSA' dnsruby-1.61.3/lib/dnsruby/resource/DNSKEY.rb0000644000004100000410000002546613607400362020743 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License f181or the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # RFC4034, section 2 # DNSSEC uses public key cryptography to sign and authenticate DNS # resource record sets (RRsets). The public keys are stored in DNSKEY # resource records and are used in the DNSSEC authentication process # described in [RFC4035]: A zone signs its authoritative RRsets by # using a private key and stores the corresponding public key in a # DNSKEY RR. A resolver can then use the public key to validate # signatures covering the RRsets in the zone, and thus to authenticate # them. class DNSKEY < RR ClassValue = nil #:nodoc: all TypeValue = Types::DNSKEY #:nodoc: all # Key is revoked REVOKED_KEY = 0x80 # Key is a zone key ZONE_KEY = 0x100 # Key is a secure entry point key SEP_KEY = 0x1 # The flags for the DNSKEY RR attr_reader :flags # The protocol for this DNSKEY RR. # MUST be 3. attr_reader :protocol # The algorithm used for this key # See Dnsruby::Algorithms for permitted values attr_reader :algorithm # The public key attr_reader :key # The length (in bits) of the key - NOT key.length attr_reader :key_length def init_defaults @make_new_key_tag = false self.protocol=3 self.flags=ZONE_KEY @algorithm=Algorithms.RSASHA1 @public_key = nil @key_tag = nil @make_new_key_tag = true end def protocol=(p) if (p!=3) raise DecodeError.new("DNSKEY protocol field set to #{p}, contrary to RFC4034 section 2.1.2") else @protocol = p end get_new_key_tag end def algorithm=(a) if (a.instance_of?String) if (a.to_i > 0) a = a.to_i end end begin alg = Algorithms.new(a) @algorithm = alg rescue ArgumentError => e raise DecodeError.new(e) end get_new_key_tag end def revoked=(on) if (on) @flags |= REVOKED_KEY else @flags &= (~REVOKED_KEY) end get_new_key_tag end def revoked? return ((@flags & REVOKED_KEY) > 0) end def zone_key=(on) if (on) @flags |= ZONE_KEY else @flags &= (~ZONE_KEY) end get_new_key_tag end def zone_key? return ((@flags & ZONE_KEY) > 0) end def sep_key=(on) if (on) @flags |= SEP_KEY else @flags &= (~SEP_KEY) end get_new_key_tag end def sep_key? return ((@flags & SEP_KEY) > 0) end def flags=(f) # Only three values allowed - # Zone Key flag (bit 7) # Secure Entry Point flag (bit 15) # Revoked bit (bit 8) - RFC 5011 if ((f & ~ZONE_KEY & ~SEP_KEY & ~REVOKED_KEY) > 0) TheLog.info("DNSKEY: Only zone key, secure entry point and revoked flags allowed for DNSKEY" + " (RFC4034 section 2.1.1) : #{f} entered as input") end @flags = f get_new_key_tag end # def bad_flags? # if ((@flags & ~ZONE_KEY & ~SEP_KEY) > 0) # return true # end # return false # end # def from_data(data) #:nodoc: all flags, protocol, algorithm, @key = data @make_new_key_tag = false self.flags=(flags) self.protocol=(protocol) self.algorithm=(algorithm) @make_new_key_tag = true get_new_key_tag end def from_hash(hash) #:nodoc: all @make_new_key_tag = false hash.keys.each do |param| send(param.to_s+"=", hash[param]) end @make_new_key_tag = true get_new_key_tag end def from_string(input) if (input.length > 0) @make_new_key_tag = false data = input.split(" ") self.flags=(data[0].to_i) self.protocol=(data[1].to_i) self.algorithm=(data[2]) # key can include whitespace - include all text # until we come to " )" at the end, and then gsub # the white space out # Also, brackets may or may not be present # Not to mention comments! ";" buf = "" index = 3 end_index = data.length - 1 if (data[index]=="(") end_index = data.length - 2 index = 4 end (index..end_index).each {|i| if (comment_index = data[i].index(";")) buf += data[i].slice(0, comment_index) # @TODO@ We lose the comments here - we should really keep them for when we write back to string format? break else buf += data[i] end } self.key=(buf) @make_new_key_tag = true get_new_key_tag end end def rdata_to_string #:nodoc: all if (@flags!=nil) # return "#{@flags} #{@protocol} #{@algorithm.string} ( #{Base64.encode64(@key.to_s)} )" return "#{@flags} #{@protocol} #{@algorithm.string} ( #{[@key.to_s].pack("m*").gsub("\n", "")} ) ; key_tag=#{key_tag}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all # 2 octets, then 2 sets of 1 octet msg.put_pack('ncc', @flags, @protocol, @algorithm.code) msg.put_bytes(@key) end def self.decode_rdata(msg) #:nodoc: all # 2 octets, then 2 sets of 1 octet flags, protocol, algorithm = msg.get_unpack('ncc') key = msg.get_bytes return self.new( [flags, protocol, algorithm, key]) end # Return the the key tag this key would have had before it was revoked # If the key is not revoked, then the current key_tag will be returned def key_tag_pre_revoked if (!revoked?) return key_tag end new_key = clone new_key.revoked = false return new_key.key_tag end def get_new_key_tag if (@make_new_key_tag) rdata = MessageEncoder.new {|msg| encode_rdata(msg) }.to_s tag = generate_key_tag(rdata, @algorithm) @key_tag = tag end end # Return the tag for this key def key_tag if (!@key_tag) @make_new_key_tag = true get_new_key_tag end return @key_tag end def generate_key_tag(rdata, algorithm) tag=0 if (algorithm == Algorithms.RSAMD5) # The key tag for algorithm 1 (RSA/MD5) is defined differently from the # key tag for all other algorithms, for historical reasons. d1 = rdata[rdata.length - 3] & 0xFF d2 = rdata[rdata.length - 2] & 0xFF tag = (d1 << 8) + d2 else tag = 0 last = 0 0.step(rdata.length - 1, 2) {|i| last = i d1 = rdata[i] d2 = rdata[i + 1] || 0 # odd number of bytes possible d1 = d1.getbyte(0) if d1.class == String # Ruby 1.9 d2 = d2.getbyte(0) if d2.class == String # Ruby 1.9 d1 = d1 & 0xFF d2 = d2 & 0xFF tag += ((d1 << 8) + d2) } last+=2 if (last < rdata.length) d1 = rdata[last] if (d1.class == String) # Ruby 1.9 d1 = d1.getbyte(0) end d1 = d1 & 0xFF tag += (d1 << 8) end tag += ((tag >> 16) & 0xFFFF) end tag=tag&0xFFFF return tag end def key=(key_text) begin key_text.gsub!(/\n/, "") key_text.gsub!(/ /, "") # @key=Base64.decode64(key_text) @key=key_text.unpack("m*")[0] public_key get_new_key_tag rescue Exception raise ArgumentError.new("Key #{key_text} invalid") end end def public_key if (!@public_key) if [Algorithms.RSASHA1, Algorithms.RSASHA256, Algorithms.RSASHA512, Algorithms.RSASHA1_NSEC3_SHA1].include?(@algorithm) @public_key = rsa_key elsif [Algorithms.DSA, Algorithms.DSA_NSEC3_SHA1].include?(@algorithm) @public_key = dsa_key end end # @TODO@ Support other key encodings! return @public_key end def rsa_key exponentLength = @key[0] if (exponentLength.class == String) exponentLength = exponentLength.getbyte(0) # Ruby 1.9 end pos = 1 if (exponentLength == 0) key1 = @key[1] if (key1.class == String) # Ruby 1.9 key1 = key1.getbyte(0) end exponentLength = (key1<<8) + key1 pos += 2 end exponent = RR::get_num(@key[pos, exponentLength]) pos += exponentLength modulus = RR::get_num(@key[pos, @key.length]) @key_length = (@key.length - pos) * 8 pkey = OpenSSL::PKey::RSA.new begin pkey.set_key(modulus, exponent, nil) # use set_key, present in later versions of openssl gem rescue NoMethodError pkey.e = exponent # set_key not available in earlier versions, use this approach instead pkey.n = modulus end return pkey end def dsa_key t = @key[0] t = t.getbyte(0) if t.class == String pgy_len = t * 8 + 64 pos = 1 q = RR::get_num(@key[pos, 20]) pos += 20 p = RR::get_num(@key[pos, pgy_len]) pos += pgy_len g = RR::get_num(@key[pos, pgy_len]) pos += pgy_len y = RR::get_num(@key[pos, pgy_len]) pos += pgy_len @key_length = (pgy_len * 8) pkey = OpenSSL::PKey::DSA.new begin pkey.set_pgq(p,g,q) pkey.set_key(y, nil) # use set_pgq and set_key, present in later versions of openssl gem rescue NoMethodError pkey.p = p # set_key not available in earlier versions, use this approach instead pkey.q = q pkey.g = g pkey.pub_key = y end pkey end end end enddnsruby-1.61.3/lib/dnsruby/resource/MINFO.rb0000644000004100000410000000373513607400362020611 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Mailbox Information (MINFO) resource records. # RFC 1035 Section 3.3.7 class MINFO < RR ClassValue = nil #:nodoc: all TypeValue = Types::MINFO #:nodoc: all # The RR's responsible mailbox field. See RFC 1035. attr_accessor :rmailbx # The RR's error mailbox field. attr_accessor :emailbx def from_hash(hash) #:nodoc: all if (hash[:rmailbx]) @rmailbx = Name.create(hash[:rmailbx]) end if (hash[:emailbx]) @emailbx = Name.create(hash[:emailbx]) end end def from_data(data) #:nodoc: all @rmailbx, @emailbx = data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") @rmailbx = Name.create(names[0]) @emailbx = Name.create(names[1]) end end def rdata_to_string #:nodoc: all if (@rmailbx!=nil) return "#{@rmailbx.to_s(true)} #{@emailbx.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_name(@rmailbx, canonical) msg.put_name(@emailbx, canonical) end def self.decode_rdata(msg) #:nodoc: all rmailbx = msg.get_name emailbx = msg.get_name return self.new([rmailbx, emailbx]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/TXT.rb0000644000004100000410000001270413607400362020414 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ begin require 'jcode' rescue LoadError => _e end module Dnsruby class RR # Class for DNS Text (TXT) resource records. # RFC 1035 Section 3.3.14 class TXT < RR ClassValue = nil #:nodoc: all TypeValue = Types::TXT #:nodoc: all # List of the individual elements attr_accessor :strings def data @strings.join end def from_data(data) @strings = data end def from_hash(hash) if (hash.has_key?:strings) from_string(hash[:strings]) end end ESCAPE_CHARS = {"b" => 8, "t" => 9, "n" => 10, "v" => 11, "f" => 12, "r" => 13} ESCAPE_CODES = ESCAPE_CHARS.invert def from_string(input) @strings = TXT.parse(input) end def TXT.parse(input) # Need to look out for special characters. # Need to split the input up into strings (which are defined by non-escaped " characters) # Then need to fix up any \ escape characters (should just be " and ; and binary?) # Sadly, it's going to be easiest just to scan through this character by character... in_escaped = false in_string = false count = -1 strings = [] current_binary = "" current_quote_char = '"' unquoted = false seen_strings = false pos = 0 input.sub!(/^\s*\(\s*/, "") input.sub!(/\s*\)\s*$/, "") input.each_char {|c| if (((c == "'") || (c == '"')) && (!in_escaped) && (!unquoted)) if (!in_string) seen_strings = true current_quote_char = c in_string = true count+=1 strings[count] = "" else if (c == current_quote_char) in_string = false else strings[count]+=c end end else if (seen_strings && !in_string) next end if (pos == 0) unquoted = true count+=1 strings[count] = "" elsif (unquoted) if (c == " ") count+=1 strings[count] = "" pos += 1 next end end if (c == "\\") if (in_escaped) in_escaped = false strings[count]+=(c) else in_escaped = true end else if (in_escaped) # Build up the binary if (c == ";") || (c == '"') strings[count]+=c in_escaped = false elsif (ESCAPE_CHARS[c]) in_escaped=false strings[count]+=ESCAPE_CHARS[c].chr elsif (c<"0" || c>"9") in_escaped = false strings[count]+=c else # Must be building up three digit string to identify binary value? # if (c >= "0" && c <= "9") current_binary += c # end if ((current_binary.length == 3) ) # || (c < "0" || c > "9")) strings[count]+=current_binary.to_i.chr in_escaped = false current_binary = "" end end else strings[count]+=(c) end end end pos += 1 } return strings end def TXT.display(str, do_escapes = true) output = "" # Probably need to scan through each string manually # Make sure to remember to escape binary characters. # Go through copying to output, and adding "\" characters as necessary? str.each_byte {|c| if (c == 34) || (c == 92) # || (c == 59) if (do_escapes) output+='\\' end output+=c.chr elsif (c < 32) # c is binary if (ESCAPE_CODES[c]) output += c.chr else output+= '\\' num = c.to_i.to_s (3-num.length).times {|i| num="0"+num } output+= num # Need a 3 digit number here. end else output += c.chr end } return output end def rdata_to_string if (defined?@strings) temp = [] @strings.each {|str| output = TXT.display(str) temp.push("\"#{output}\"") } return temp.join(' ') end return '' end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_string_list(@strings) end def self.decode_rdata(msg) #:nodoc: all strings = msg.get_string_list return self.new(strings) end end end end dnsruby-1.61.3/lib/dnsruby/resource/TKEY.rb0000644000004100000410000001153013607400362020505 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class Modes < CodeMapper # The key is assigned by the server (unimplemented) SERVERASSIGNED = 1 # The key is computed using a Diffie-Hellman key exchange DIFFIEHELLMAN = 2 # The key is computed using GSS_API (unimplemented) GSSAPI = 3 # The key is assigned by the resolver (unimplemented) RESOLVERASSIGNED = 4 # The key should be deleted DELETE = 5 update() end class RR # RFC2930 class TKEY < RR TypeValue = Types::TKEY #:nodoc: all ClassValue = nil #:nodoc: all ClassHash[[TypeValue, Classes::ANY]] = self #:nodoc: all attr_reader :key_size attr_accessor :key # Gets or sets the domain name that specifies the name of the algorithm. # The default algorithm is gss.microsoft.com # # rr.algorithm=(algorithm_name) # print "algorithm = ", rr.algorithm, "\n" # attr_accessor :algorithm # Gets or sets the inception time as the number of seconds since 1 Jan 1970 # 00:00:00 UTC. # # The default inception time is the current time. # # rr.inception=(time) # print "inception = ", rr.inception, "\n" # attr_accessor :inception # Gets or sets the expiration time as the number of seconds since 1 Jan 1970 # 00:00:00 UTC. # # The default expiration time is the current time plus 1 day. # # rr.expiration=(time) # print "expiration = ", rr.expiration, "\n" # attr_accessor :expiration # Sets the key mode (see rfc2930). The default is 3 which corresponds to GSSAPI # # rr.mode=(3) # print "mode = ", rr.mode, "\n" # attr_accessor :mode # Returns the RCODE covering TKEY processing. See RFC 2930 for details. # # print "error = ", rr.error, "\n" # attr_accessor :error # Returns the length of the Other Data. Should be zero. # # print "other size = ", rr.other_size, "\n" # attr_reader :other_size # Returns the Other Data. This field should be empty. # # print "other data = ", rr.other_data, "\n" # attr_reader :other_data def other_data=(od) @other_data=od @other_size=@other_data.length end def initialize @algorithm = "gss.microsoft.com" @inception = Time.now @expiration = Time.now + 24*60*60 @mode = Modes.GSSAPI @error = 0 @other_size = 0 @other_data = "" # RFC 2845 Section 2.3 @klass = Classes.ANY # RFC 2845 Section 2.3 @ttl = 0 end def from_hash(hash) super(hash) if (algorithm) @algorithm = Name.create(hash[:algorithm]) end end def from_data(data) #:nodoc: all @algorithm, @inception, @expiration, @mode, @error, @key_size, @key, @other_size, @other_data = data end # Create the RR from a standard string def from_string(string) #:nodoc: all Dnsruby.log.error("Dnsruby::RR::TKEY#from_string called, but no text format defined for TKEY") end def rdata_to_string rdatastr="" if (@algorithm!=nil) error = @error error = "UNDEFINED" unless error!=nil rdatastr = "#{@algorithm.to_s(true)} #{error}" if (@other_size != nil && @other_size >0 && @other_data!=nil) rdatastr += " #{@other_data}" end end return rdatastr end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_name(@algorithm, canonical) msg.put_pack("NNnn", @inception, @expiration, @mode, @error) msg.put_pack("n", @key.length) msg.put_bytes(@key) msg.put_pack("n", @other_data.length) msg.put_bytes(@other_data) end def self.decode_rdata(msg) #:nodoc: all alg=msg.get_name inc, exp, mode, error = msg.get_unpack("NNnn") key_size, =msg.get_unpack("n") key=msg.get_bytes(key_size) other_size, =msg.get_unpack("n") other=msg.get_bytes(other_size) return self.new([alg, inc, exp, mode, error, key_size, key, other_size, other]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/TSIG.rb0000644000004100000410000005330413607400362020504 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # require 'base64' begin require 'openssl' rescue LoadError print "OpenSSL not found - ignoring\n" end module Dnsruby class RR # TSIG implements RFC2845. # # "This protocol allows for transaction level authentication using # shared secrets and one way hashing. It can be used to authenticate # dynamic updates as coming from an approved client, or to authenticate # responses as coming from an approved recursive name server." # # A Dnsruby::RR::TSIG can represent the data present in a TSIG RR. # However, it can also represent the data (specified in RFC2845) used # to sign or verify a DNS message. # # # Example code : # res = Dnsruby::Resolver.new("ns0.validation-test-servers.nominet.org.uk") # # # Now configure the resolver with the TSIG key for signing/verifying # KEY_NAME="rubytsig" # KEY = "8n6gugn4aJ7MazyNlMccGKH1WxD2B3UvN/O/RA6iBupO2/03u9CTa3Ewz3gBWTSBCH3crY4Kk+tigNdeJBAvrw==" # res.tsig=KEY_NAME, KEY # # update = Dnsruby::Update.new("validation-test-servers.nominet.org.uk") # # Generate update record name, and test it has been made. Then delete it and check it has been deleted # update_name = generate_update_name # update.absent(update_name) # update.add(update_name, 'TXT', 100, "test signed update") # # # Resolver will automatically sign message and verify response # response = res.send_message(update) # assert(response.verified?) # Check that the response has been verified class TSIG < RR HMAC_MD5 = Name.create("HMAC-MD5.SIG-ALG.REG.INT.") HMAC_SHA1 = Name.create("hmac-sha1.") HMAC_SHA256 = Name.create("hmac-sha256.") HMAC_SHA512 = Name.create("hmac-sha512.") DEFAULT_FUDGE = 300 DEFAULT_ALGORITHM = HMAC_MD5 # Generates a TSIG record and adds it to the message. # Takes an optional original_request argument for the case where this is # a response to a query (RFC2845 3.4.1) # # Message#tsigstate will be set to :Signed. def apply(message, original_request=nil) if (!message.signed?) tsig_rr = generate(message, original_request) message.add_additional(tsig_rr) message.tsigstate = :Signed @query = message tsig_rr.query = message end end def query=q#:nodoc: all @query = q end # Generates a TSIG record def generate(msg, original_request = nil, data="", msg_bytes=nil, tsig_rr=self)#:nodoc: all time_signed=@time_signed if (!time_signed) time_signed=Time.now.to_i end if (tsig_rr.time_signed) time_signed = tsig_rr.time_signed end if (original_request) # # Add the request MAC if present (used to validate responses). # hmac.update(pack("H*", request_mac)) mac_bytes = MessageEncoder.new {|m| m.put_pack('n', original_request.tsig.mac_size) m.put_bytes(original_request.tsig.mac) }.to_s data += mac_bytes # Original ID - should we set message ID to original ID? if (tsig_rr != self) msg.header.id = tsig_rr.original_id else msg.header.id = original_request.header.id end end if (!msg_bytes) msg_bytes = msg.encode data += msg_bytes else # If msg_bytes came in, we need somehow to remove the TSIG RR # It is the last record, so we can strip it if we know where it starts # We must also poke the header ARcount to decrement it msg_bytes = Header.decrement_arcount_encoded(msg_bytes) data += msg_bytes[0, msg.tsigstart] end data += sig_data(tsig_rr, time_signed) mac = calculate_mac(tsig_rr.algorithm, data) mac_size = mac.length new_tsig_rr = Dnsruby::RR.create({ :name => tsig_rr.name, :type => Types.TSIG, :ttl => tsig_rr.ttl, :klass => tsig_rr.klass, :algorithm => tsig_rr.algorithm, :fudge => tsig_rr.fudge, :key => @key, :mac => mac, :mac_size => mac_size, :error => tsig_rr.error, :time_signed => time_signed, :original_id => msg.header.id }) return new_tsig_rr end def calculate_mac(algorithm, data) mac=nil # + if (key_size > max_digest_len) { # + EVP_DigestInit(&ectx, digester); # + EVP_DigestUpdate(&ectx, (const void*) key_bytes, key_size); # + EVP_DigestFinal(&ectx, key_bytes, NULL); # + key_size = max_digest_len; # + } key = @key.gsub(" ", "") # key = Base64::decode64(key) key = key.unpack("m*")[0] if (algorithm.to_s.downcase == HMAC_MD5.to_s.downcase) mac = OpenSSL::HMAC.digest(OpenSSL::Digest::MD5.new, key, data) elsif (algorithm == HMAC_SHA1) mac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA1.new, key, data) elsif (algorithm == HMAC_SHA256) mac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, key, data) elsif (algorithm == HMAC_SHA512) mac = OpenSSL::HMAC.digest(OpenSSL::Digest::SHA512.new, key, data) else # Should we allow client to pass in their own signing function? raise VerifyError.new("Algorithm #{algorithm} unsupported by TSIG") end return mac end # Private method to return the TSIG RR data to be signed def sig_data(tsig_rr, time_signed=@time_signed) #:nodoc: all return MessageEncoder.new { |msg| msg.put_name(tsig_rr.name.downcase, true) msg.put_pack('nN', tsig_rr.klass.code, tsig_rr.ttl) msg.put_name(tsig_rr.algorithm.downcase, true) time_high = (time_signed >> 32) time_low = (time_signed & 0xFFFFFFFF) msg.put_pack('nN', time_high, time_low) msg.put_pack('n', tsig_rr.fudge) msg.put_pack('n', tsig_rr.error) msg.put_pack('n', tsig_rr.other_size) msg.put_bytes(tsig_rr.other_data) }.to_s end # Verify a response. This method will be called by Dnsruby::SingleResolver # before passing a response to the client code. # The TSIG record will be removed from packet before passing to client, and # the Message#tsigstate and Message#tsigerror will be set accordingly. # Message#tsigstate will be set to one of : # * :Failed # * :Verified def verify(query, response, response_bytes, buf="") # 4.6. Client processing of answer # # When a client receives a response from a server and expects to see a # TSIG, it first checks if the TSIG RR is present in the response. # Otherwise, the response is treated as having a format error and # discarded. The client then extracts the TSIG, adjusts the ARCOUNT, # and calculates the keyed digest in the same way as the server. If # the TSIG does not validate, that response MUST be discarded, unless # the RCODE is 9 (NOTAUTH), in which case the client SHOULD attempt to # verify the response as if it were a TSIG Error response, as specified # in [4.3]. A message containing an unsigned TSIG record or a TSIG # record which fails verification SHOULD not be considered an # acceptable response; the client SHOULD log an error and continue to # wait for a signed response until the request times out. # So, this verify method should simply remove the TSIG RR and calculate # the MAC (using original request MAC if required). # Should set tsigstate on packet appropriately, and return error. # Side effect is packet is stripped of TSIG. # Resolver (or client) can then decide what to do... msg_tsig_rr = response.tsig if (!verify_common(response)) return false end new_msg_tsig_rr = generate(response, query, buf, response_bytes, msg_tsig_rr) if (msg_tsig_rr.mac == new_msg_tsig_rr.mac) response.tsigstate = :Verified response.tsigerror = RCode.NOERROR return true else response.tsigstate = :Failed response.tsigerror = RCode.BADSIG return false end end def verify_common(response)#:nodoc: all tsig_rr = response.tsig if (!tsig_rr) response.tsigerror = RCode.FORMERR response.tsigstate = :Failed return false end response.additional.delete(tsig_rr) response.header.arcount-=1 # First, check the TSIG error in the RR if (tsig_rr.error != RCode.NOERROR) response.tsigstate = :Failed response.tsigerror = tsig_rr.error return false end if ((tsig_rr.name != @name) || (tsig_rr.algorithm.downcase != @algorithm.downcase)) Dnsruby.log.error("BADKEY failure") response.tsigstate = :Failed response.tsigerror = RCode.BADKEY return false end # Check time_signed (RFC2845, 4.5.2) - only really necessary for server if (Time.now.to_i > tsig_rr.time_signed + tsig_rr.fudge || Time.now.to_i < tsig_rr.time_signed - tsig_rr.fudge) Dnsruby.log.error("TSIG failed with BADTIME") response.tsigstate = :Failed response.tsigerror = RCode.BADTIME return false end return true end # Checks TSIG signatures across sessions of multiple DNS envelopes. # This method is called each time a new envelope comes in. The envelope # is checked - if a TSIG is present, them the stream so far is verified, # and the response#tsigstate set to :Verified. If a TSIG is not present, # and does not need to be present, then the message is added to the digest # stream and the response#tsigstate is set to :Intermediate. # If there is an error with the TSIG verification, then the response#tsigstate # is set to :Failed. # Like verify, this method will only be called by the Dnsruby::SingleResolver # class. Client code need not call this method directly. def verify_envelope(response, response_bytes) # RFC2845 Section 4.4 # ----- # A DNS TCP session can include multiple DNS envelopes. This is, for # example, commonly used by zone transfer. Using TSIG on such a # connection can protect the connection from hijacking and provide data # integrity. The TSIG MUST be included on the first and last DNS # envelopes. It can be optionally placed on any intermediary # envelopes. It is expensive to include it on every envelopes, but it # MUST be placed on at least every 100'th envelope. The first envelope # is processed as a standard answer, and subsequent messages have the # following digest components: # # * Prior Digest (running) # * DNS Messages (any unsigned messages since the last TSIG) # * TSIG Timers (current message) # # This allows the client to rapidly detect when the session has been # altered; at which point it can close the connection and retry. If a # client TSIG verification fails, the client MUST close the connection. # If the client does not receive TSIG records frequently enough (as # specified above) it SHOULD assume the connection has been hijacked # and it SHOULD close the connection. The client SHOULD treat this the # same way as they would any other interrupted transfer (although the # exact behavior is not specified). # ----- # # Each time a new envelope comes in, this method is called on the QUERY TSIG RR. # It will set the response tsigstate to :Verified :Intermediate or :Failed # as appropriate. # Keep digest going of messages as they come in (and mark them intermediate) # When TSIG comes in, work out what key should be and check. If OK, mark # verified. Can reset digest then. if (!@buf) @num_envelopes = 0 @last_signed = 0 end @num_envelopes += 1 if (!response.tsig) if ((@num_envelopes > 1) && (@num_envelopes - @last_signed < 100)) Dnsruby.log.debug("Receiving intermediate envelope in TSIG TCP session") response.tsigstate = :Intermediate response.tsigerror = RCode.NOERROR @buf = @buf + response_bytes return else response.tsigstate = :Failed Dnsruby.log.error("Expecting signed packet") return false end end @last_signed = @num_envelopes # We have a TSIG - process it! tsig = response.tsig if (@num_envelopes == 1) Dnsruby.log.debug("First response in TSIG TCP session - verifying normally") # Process it as a standard answer ok = verify(@query, response, response_bytes) if (ok) mac_bytes = MessageEncoder.new {|m| m.put_pack('n', tsig.mac_size) m.put_bytes(tsig.mac) }.to_s @buf = mac_bytes end return ok end Dnsruby.log.debug("Processing TSIG on TSIG TCP session") if (!verify_common(response)) return false end # Now add the current message data - remember to frig the arcount response_bytes = Header.decrement_arcount_encoded(response_bytes) @buf += response_bytes[0, response.tsigstart] # Let's add the timers timers_data = MessageEncoder.new { |msg| time_high = (tsig.time_signed >> 32) time_low = (tsig.time_signed & 0xFFFFFFFF) msg.put_pack('nN', time_high, time_low) msg.put_pack('n', tsig.fudge) }.to_s @buf += timers_data mac = calculate_mac(tsig.algorithm, @buf) if (mac != tsig.mac) Dnsruby.log.error("TSIG Verify error on TSIG TCP session") response.tsigstate = :Failed return false end mac_bytes = MessageEncoder.new {|m| m.put_pack('n', mac.length) m.put_bytes(mac) }.to_s @buf=mac_bytes response.tsigstate = :Verified response.tsigerror = RCode.NOERROR return true end TypeValue = Types::TSIG #:nodoc: all ClassValue = nil #:nodoc: all ClassHash[[TypeValue, Classes::ANY]] = self #:nodoc: all # Gets or sets the domain name that specifies the name of the algorithm. # The only algorithms currently supported are hmac-md5 and hmac-sha1. # # rr.algorithm=(algorithm_name) # print "algorithm = ", rr.algorithm, "\n" # attr_reader :algorithm # Gets or sets the signing time as the number of seconds since 1 Jan 1970 # 00:00:00 UTC. # # The default signing time is the current time. # # rr.time_signed=(time) # print "time signed = ", rr.time_signed, "\n" # attr_accessor :time_signed # Gets or sets the "fudge", i.e., the seconds of error permitted in the # signing time. # # The default fudge is 300 seconds. # # rr.fudge=(60) # print "fudge = ", rr.fudge, "\n" # attr_reader :fudge # Returns the number of octets in the message authentication code (MAC). # The programmer must call a Net::DNS::Packet object's data method # before this will return anything meaningful. # # print "MAC size = ", rr.mac_size, "\n" # attr_accessor :mac_size # Returns the message authentication code (MAC) as a string of hex # characters. The programmer must call a Net::DNS::Packet object's # data method before this will return anything meaningful. # # print "MAC = ", rr.mac, "\n" # attr_accessor :mac # Gets or sets the original message ID. # # rr.original_id(12345) # print "original ID = ", rr.original_id, "\n" # attr_accessor :original_id # Returns the RCODE covering TSIG processing. Common values are # NOERROR, BADSIG, BADKEY, and BADTIME. See RFC 2845 for details. # # print "error = ", rr.error, "\n" # attr_accessor :error # Returns the length of the Other Data. Should be zero unless the # error is BADTIME. # # print "other len = ", rr.other_size, "\n" # attr_accessor :other_size # Returns the Other Data. This field should be empty unless the # error is BADTIME, in which case it will contain the server's # time as the number of seconds since 1 Jan 1970 00:00:00 UTC. # # print "other data = ", rr.other_data, "\n" # attr_accessor :other_data # Stores the secret key used for signing/verifying messages. attr_accessor :key def init_defaults # @TODO@ Have new() method which takes key_name and key? @algorithm = DEFAULT_ALGORITHM @fudge = DEFAULT_FUDGE @mac_size = 0 @mac = "" @original_id = rand(65536) @error = 0 @other_size = 0 @other_data = "" @time_signed = nil @buf = nil # RFC 2845 Section 2.3 @klass = Classes.ANY @ttl = 0 # RFC 2845 Section 2.3 end def from_data(data) #:nodoc: all @algorithm, @time_signed, @fudge, @mac_size, @mac, @original_id, @error, @other_size, @other_data = data end def name=(n) if (n.instance_of?String) n = Name.create(n) end if (!n.absolute?) @name = Name.create(n.to_s + ".") else @name = n end end # Create the RR from a standard string def from_string(str) #:nodoc: all parts = str.split("[:/]") if (parts.length < 2 || parts.length > 3) raise ArgumentException.new("Invalid TSIG key specification") end if (parts.length == 3) return TSIG.new(parts[0], parts[1], parts[2]); else return TSIG.new(HMAC_MD5, parts[0], parts[1]); end end # Set the algorithm to use to generate the HMAC # Supported values are : # * hmac-md5 # * hmac-sha1 # * hmac-sha256 # * hmac-sha512 def algorithm=(alg) if (alg.class == String) if (alg.downcase=="hmac-md5") @algorithm = HMAC_MD5; elsif (alg.downcase=="hmac-sha1") @algorithm = HMAC_SHA1; elsif (alg.downcase=="hmac-sha256") @algorithm = HMAC_SHA256; elsif (alg.downcase=="hmac-sha512") @algorithm = HMAC_SHA512; else raise ArgumentError.new("Invalid TSIG algorithm") end elsif (alg.class == Name) if (alg!=HMAC_MD5 && alg!=HMAC_SHA1 && alg!=HMAC_SHA256 && alg!=HMAC_SHA512) raise ArgumentException.new("Invalid TSIG algorithm") end @algorithm=alg else raise ArgumentError.new("#{alg.class} not valid type for Dnsruby::RR::TSIG#algorithm= - use String or Name") end Dnsruby.log.debug{"Using #{@algorithm.to_s} algorithm"} end def fudge=(f) if (f < 0 || f > 0x7FFF) @fudge = DEFAULT_FUDGE else @fudge = f end end def rdata_to_string rdatastr="" if (@algorithm!=nil) error = @error error = "UNDEFINED" unless error!=nil rdatastr = "#{@original_id} #{@time_signed} #{@algorithm.to_s(true)} #{error}"; if (@other_size > 0 && @other_data!=nil) rdatastr += " #{@other_data}" end rdatastr += " " + mac.unpack("H*").to_s end return rdatastr end def encode_rdata(msg, canonical=false) #:nodoc: all # Name needs to be added with no compression - done in Dnsruby::Message#encode msg.put_name(@algorithm.downcase, true) time_high = (@time_signed >> 32) time_low = (@time_signed & 0xFFFFFFFF) msg.put_pack('nN', time_high, time_low) msg.put_pack('n', @fudge) msg.put_pack('n', @mac_size) msg.put_bytes(@mac) msg.put_pack('n', @original_id) msg.put_pack('n', @error) msg.put_pack('n', @other_size) msg.put_bytes(@other_data) end def self.decode_rdata(msg) #:nodoc: all alg=msg.get_name time_high, time_low = msg.get_unpack("nN") time_signed = (time_high << 32) + time_low fudge, = msg.get_unpack("n") mac_size, = msg.get_unpack("n") mac = msg.get_bytes(mac_size) original_id, = msg.get_unpack("n") error, = msg.get_unpack("n") other_size, = msg.get_unpack("n") other_data = msg.get_bytes(other_size) return self.new([alg, time_signed, fudge, mac_size, mac, original_id, error, other_size, other_data]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/HINFO.rb0000644000004100000410000000363113607400362020577 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Host Information (HINFO) resource records. class HINFO < RR ClassValue = nil #:nodoc: all TypeValue = Types::HINFO #:nodoc: all # The CPU type for this RR. attr_accessor :cpu # The operating system type for this RR. attr_accessor :os def from_data(data) #:nodoc: all @cpu, @os= data end def from_string(input) #:nodoc: all strings = TXT.parse(input) cpu = "" os = "" if (strings.length == 1) cpu, os = input.split(" ") else cpu = strings[0] os = strings[1] end cpu.sub!(/^\"/, "") @cpu = cpu.sub(/\"$/, "") os.sub!(/^\"/, "") @os = os.sub(/\"$/, "") end def rdata_to_string #:nodoc: all if (defined?@cpu) temp = [] [@cpu, @os].each {|str| output = TXT.display(str) temp.push("\"#{output}\"") } return temp.join(' ') end return '' end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_string(@cpu) msg.put_string(@os) end def self.decode_rdata(msg) #:nodoc: all cpu = msg.get_string os = msg.get_string return self.new([cpu, os]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/X25.rb0000644000004100000410000000260313607400362020310 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS X25 resource records. # RFC 1183 Section 3.1 class X25 < RR ClassValue = nil #:nodoc: all TypeValue = Types::X25 #:nodoc: all # The PSDN address attr_accessor :address def from_data(data) @address = data end def from_string(input) address = input address.sub!(/^\"/, "") @address = address.sub(/\"$/, "") end def rdata_to_string if (@address!=nil) return @address else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_string(@address) end def self.decode_rdata(msg) #:nodoc: all address = msg.get_string return self.new(*address) end end end enddnsruby-1.61.3/lib/dnsruby/resource/NSAP.rb0000644000004100000410000001207313607400362020475 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Network Service Access Point (NSAP) resource records. # RFC 1706. class NSAP < RR ClassValue = nil #:nodoc: all TypeValue= Types::NSAP #:nodoc: all # The RR's authority and format identifier. Dnsruby # currently supports only AFI 47 (GOSIP Version 2). attr_accessor :afi # The RR's initial domain identifier. attr_accessor :idi # The RR's DSP format identifier. attr_accessor :dfi # The RR's administrative authority. attr_accessor :aa # The RR's routing domain identifier. attr_accessor :rd # The RR's area identifier. attr_accessor :area # The RR's system identifier. attr_accessor :id # The RR's NSAP selector. attr_accessor :sel # The RR's reserved field. attr_writer :rsvd # The RR's initial domain part (the AFI and IDI fields). def idp ret = [@afi, @idi].join('') return ret end # The RR's domain specific part (the DFI, AA, Rsvd, RD, Area, # ID, and SEL fields). def dsp ret = [@dfi,@aa,rsvd,@rd,@area,@id,@sel].join('') return ret end def rsvd if (@rsvd==nil) return "0000" else return @rsvd end end # ------------------------------------------------------------------------------ # Usage: str2bcd(STRING, NUM_BYTES) # # Takes a string representing a hex number of arbitrary length and # returns an equivalent BCD string of NUM_BYTES length (with # NUM_BYTES * 2 digits), adding leading zeros if necessary. # ------------------------------------------------------------------------------ def str2bcd(s, bytes) retval = ""; digits = bytes * 2; string = sprintf("%#{digits}s", s); string.tr!(" ","0"); i=0; bytes.times do bcd = string[i*2, 2]; retval += [bcd.to_i(16)].pack("C"); i+=1 end return retval; end def from_data(data) #:nodoc: all @afi, @idi, @dfi, @aa, @rsvd, @rd, @area, @id, @sel = data end def from_string(s) #:nodoc: all if (s) string = s.gsub(/\./, ""); # remove all dots. string.gsub!(/^0x/,""); # remove leading 0x if (string =~ /^[a-zA-Z0-9]{40}$/) (@afi, @idi, @dfi, @aa, @rsvd, @rd, @area, @id, @sel) = string.unpack("A2A4A2A6A4A4A4A12A2") end end end def rdata_to_string #:nodoc: all rdatastr="" if (defined?@afi) if (@afi == "47") rdatastr = [idp, dsp].join('') else rdatastr = "; AFI #{@afi} not supported" end else rdatastr = '' end return rdatastr end def encode_rdata(msg, canonical=false) #:nodoc: all if (defined?@afi) msg.put_pack("C", @afi.to_i(16)) if (@afi == "47") msg.put_bytes(str2bcd(@idi, 2)) msg.put_bytes(str2bcd(@dfi, 1)) msg.put_bytes(str2bcd(@aa, 3)) msg.put_bytes(str2bcd(0, 2)) # rsvd) msg.put_bytes(str2bcd(@rd, 2)) msg.put_bytes(str2bcd(@area, 2)) msg.put_bytes(str2bcd(@id, 6)) msg.put_bytes(str2bcd(@sel, 1)) end # Checks for other versions would go here. end return rdata end def self.decode_rdata(msg) #:nodoc: all afi = msg.get_unpack("C")[0] afi = sprintf("%02x", afi) if (afi == "47") idi = msg.get_unpack("CC") dfi = msg.get_unpack("C")[0] aa = msg.get_unpack("CCC") rsvd = msg.get_unpack("CC") rd = msg.get_unpack("CC") area = msg.get_unpack("CC") id = msg.get_unpack("CCCCCC") sel = msg.get_unpack("C")[0] idi = sprintf("%02x%02x", idi[0], idi[1]) dfi = sprintf("%02x", dfi) aa = sprintf("%02x%02x%02x", aa[0], aa[1], aa[2]) rsvd = sprintf("%02x%02x", rsvd[0],rsvd[1]) rd = sprintf("%02x%02x", rd[0],rd[1]) area = sprintf("%02x%02x", area[0],area[1]) id = sprintf("%02x%02x%02x%02x%02x%02x", id[0],id[1],id[2],id[3],id[4],id[5]) sel = sprintf("%02x", sel) else # What to do for unsupported versions? end return self.new([afi, idi, dfi, aa, rsvd, rd, area, id, sel]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/APL.rb0000644000004100000410000001005013607400362020341 0ustar www-datawww-datamodule Dnsruby class Prefix Regex = %r{\A([!])?([12]):(.*)/(\d+)\z} attr_reader :af, :prefix_length, :negative, :address_lenght, :address class << self def create(prefix) #:nodoc: unless md = Regex.match(prefix) raise ArgumentError.new('APL format error') end negative=md[1] af = md[2].to_i prefix_length = md[4].to_i case af when 1 if prefix_length > 32 || prefix_length < 0 raise ArgumentError.new('APL IPv4 prefix format error') end address = IPv4.create(md[3]) when 2 if prefix_length > 128 || prefix_length < 0 raise ArgumentError.new('APL IPv6 prefix format error') end address = IPv6.create(md[3]) else raise ArgumentError.new('APL address family error') end address_length = (prefix_length / 8.0).ceil Prefix.new(af, prefix_length, negative, address_length, address) end end def initialize(af, prefix_length, negative, address_length, address) @af = af @prefix_length = prefix_length @negative = negative @address_length = address_length @address = address @flag = address_length @flag |= 0x80 if @negative end def to_s "#{@negative}#{@af}:#{@address}/#{@prefix_length}" end def put_msg(msg) #:nodoc: all msg.put_pack('nCC',@af,@prefix_length,@flag) msg.put_bytes(@address.address[0,@address_length]) end end class Prefixes attr_accessor :prefixes class << self def create(arg) case arg when Prefixes return arg when String prefixes = arg.split(/\s/).map { |prefix| Prefix.create(prefix) } when Array prefixes = arg.map { |prefix| Prefix.create(prefix) } else raise ArgumentError.new("APL format erro #{arg}") end Prefixes.new(prefixes) end def create_from_message(msg) prefixes = [] while(msg.has_remaining?) do negative = nil af,prefix_length,flag = msg.get_unpack('nCC') negative = '!' if 0x80 & flag == 0x80 address_length = flag & 0x7f case(af) when 1 addr = msg.get_bytes(address_length) + "\0" * (4 - address_length) address = IPv4.new(addr) when 2 addr = msg.get_bytes(address_length) + "\0" * (16 - address_length) address = IPv6.new(addr) else raise ArgumentError.new("APL format error") end prefixes.push(Prefix.new(af, prefix_length, negative, address_length, address)) end Prefixes.new(prefixes) end end def initialize(prefixes) @prefixes = prefixes end def to_s @prefixes.map(&:to_s).join(' ') end def encode_rdata(msg, _canonical = false) #:nodoc: all @prefixes.each do |prefix| prefix.put_msg(msg) end end end class RR module IN # Class for DNS Address (A) resource records. # # RFC 1035 Section 3.4.1 class APL < RR ClassHash[[TypeValue = Types::APL, ClassValue = ClassValue]] = self #:nodoc: all # The RR's (Resolv::IPv4) address field attr_accessor :prefixes def from_data(data) #:nodoc: all @prefixes = Prefixes.create(data) end # Create the RR from a hash def from_hash(hash) @prefixes = Prefixes.create(hash[:prefixes]) end # Create the RR from a standard string def from_string(input) @prefixes = Prefixes.create(input) end def rdata_to_string @prefixes.to_s end def encode_rdata(msg, canonical = false) #:nodoc: all @prefixes.encode_rdata(msg,canonical) end def self.decode_rdata(msg) #:nodoc: all new(Prefixes.create_from_message(msg)) end end end end end dnsruby-1.61.3/lib/dnsruby/resource/CERT.rb0000644000004100000410000000650013607400362020467 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Certificate (CERT) resource records. (see RFC 2538) # # RFC 2782 class CERT < RR ClassValue = nil #:nodoc: all TypeValue = Types::CERT #:nodoc: all # Returns the format code for the certificate attr_accessor :certtype # Returns the key tag for the public key in the certificate attr_accessor :keytag # Returns the algorithm used by the certificate attr_accessor :alg # Returns the data comprising the certificate itself (in raw binary form) attr_accessor :cert class CertificateTypes < CodeMapper PKIX = 1 # PKIX (X.509v3) SPKI = 2 # Simple Public Key Infrastructure PGP = 3 # Pretty Good Privacy IPKIX = 4 # URL of an X.509 data object ISPKI = 5 # URL of an SPKI certificate IPGP = 6 # Fingerprint and URL of an OpenPGP packet ACPKIX = 7 # Attribute Certificate IACPKIX = 8 # URL of an Attribute Certificate URI = 253 # Certificate format defined by URI OID = 254 # Certificate format defined by OID update() end def from_data(data) #:nodoc: all @certtype = CertificateTypes::new(data[0]) @keytag = data[1] @alg = Dnsruby::Algorithms.new(data[2]) @cert= data[3] end def from_hash(hash) #:nodoc: all @certtype = CertificateTypes::new(hash[:certtype]) @keytag = hash[:keytag] @alg = Dnsruby::Algorithms.new(hash[:alg]) @cert= hash[:cert] end def from_string(input) #:nodoc: all if (input != "") names = input.split(" ") begin @certtype = CertificateTypes::new(names[0]) rescue ArgumentError @certtype = CertificateTypes::new(names[0].to_i) end @keytag = names[1].to_i begin @alg = Dnsruby::Algorithms.new(names[2]) rescue ArgumentError @alg = Dnsruby::Algorithms.new(names[2].to_i) end buf = "" (names.length - 3).times {|index| buf += names[index + 3] } buf.gsub!(/\n/, "") buf.gsub!(/ /, "") @cert = buf.unpack("m*").first end end def rdata_to_string #:nodoc: all return "#{@certtype.string} #{@keytag} #{@alg.string} #{[@cert.to_s].pack("m*").gsub("\n", "")}" end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('nnc', @certtype.code, @keytag, @alg.code) msg.put_bytes(@cert) end def self.decode_rdata(msg) #:nodoc: all certtype, keytag, alg = msg.get_unpack('nnc') cert = msg.get_bytes return self.new([certtype, keytag, alg, cert]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/ISDN.rb0000644000004100000410000000335113607400362020470 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Net::DNS::RR::ISDN - DNS ISDN resource record # RFC 1183 Section 3.2 class ISDN < RR ClassValue = nil #:nodoc: all TypeValue = Types::ISDN #:nodoc: all # The RR's address field. attr_accessor :address # The RR's sub-address field. attr_accessor :subaddress def from_data(data) #:nodoc: all @address, @subaddress= data end def from_string(input) #:nodoc: all address, subaddress = input.split(" ") address.sub!(/^\"/, "") @address = address.sub(/\"$/, "") if (subaddress) subaddress.sub!(/^\"/, "") @subaddress = subaddress.sub(/\"$/, "") else @subaddress = nil end end def rdata_to_string #:nodoc: all return "#{@address} #{@subaddress}" end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_string(@address) msg.put_string(@subaddress) end def self.decode_rdata(msg) #:nodoc: all address = msg.get_string subaddress = msg.get_string return self.new([address, subaddress]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/resource.rb0000644000004100000410000000144513607400362021564 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby ClassHash = {} #:nodoc: all end require 'dnsruby/resource/RRSet' require 'dnsruby/resource/RR' require 'dnsruby/resource/domain_name' require 'dnsruby/resource/generic' require 'dnsruby/resource/IN' dnsruby-1.61.3/lib/dnsruby/resource/NAPTR.rb0000644000004100000410000000576213607400362020627 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Naming Authority Pointer (NAPTR) resource records. # RFC 2168 class NAPTR < RR ClassValue = nil #:nodoc: all TypeValue= Types::NAPTR #:nodoc: all # The NAPTR RR order field attr_accessor :order # The NAPTR RR preference field attr_accessor :preference # The NAPTR RR flags field attr_accessor :flags # The NAPTR RR service field attr_accessor :service # The NAPTR RR regexp field attr_reader :regexp # The NAPTR RR replacement field attr_accessor :replacement def from_hash(hash) #:nodoc: all @order = hash[:order] @preference = hash[:preference] @flags = hash[:flags] @service = hash[:service] @regexp = hash[:regexp] @replacement = Name.create(hash[:replacement]) end def from_data(data) #:nodoc: all @order, @preference, @flags, @service, @regexp, @replacement = data end def regexp=(s) @regexp = TXT.parse(s)[0] end def from_string(input) #:nodoc: all if (input.strip.length > 0) values = input.split(" ") @order = values [0].to_i @preference = values [1].to_i @flags = values [2].gsub!("\"", "") @service = values [3].gsub!("\"", "") @regexp = TXT.parse(values[4])[0] @replacement = Name.create(values[5]) end end def rdata_to_string #:nodoc: all if (@order!=nil) ret = "#{@order} #{@preference} \"#{@flags}\" \"#{@service}\" \"" ret += TXT.display(@regexp) ret += "\" #{@replacement.to_s(true)}" return ret else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all if (@order != nil) msg.put_pack('n', @order) msg.put_pack('n', @preference) msg.put_string(@flags) msg.put_string(@service) msg.put_string(@regexp) msg.put_name(@replacement, true) end end def self.decode_rdata(msg) #:nodoc: all order, = msg.get_unpack('n') preference, = msg.get_unpack('n') flags = msg.get_string service = msg.get_string regexp = msg.get_string replacement = msg.get_name return self.new([order, preference, flags, service, regexp, replacement]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/RT.rb0000644000004100000410000000364113607400362020262 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Route Through (RT) resource records. # RFC 1183 Section 3.3 class RT < RR ClassValue = nil #:nodoc: all TypeValue = Types::RT #:nodoc: all # The preference for this route. attr_accessor :preference # The domain name of the intermediate host. attr_accessor :intermediate def from_hash(hash) #:nodoc: all @preference = hash[:preference] @intermediate = Name.create(hash[:intermediate]) end def from_data(data) #:nodoc: all @preference, @intermediate = data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") @preference = names[0].to_i @intermediate = Name.create(names[1]) end end def rdata_to_string #:nodoc: all if (@preference!=nil) return "#{@preference} #{@intermediate.to_s(true)}" else return "" end end def encode_rdata(msg, canonical = false) #:nodoc: all msg.put_pack('n', @preference) msg.put_name(@intermediate, canonical) end def self.decode_rdata(msg) #:nodoc: all preference, = msg.get_unpack('n') intermediate = msg.get_name return self.new([preference, intermediate]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/AAAA.rb0000644000004100000410000000306013607400362020413 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR module IN # Class for DNS IPv6 Address (AAAA) resource records. # # RFC 1886 Section 2, RFC 1884 Sections 2.2 & 2.4.4 class AAAA < RR ClassHash[[TypeValue = Types::AAAA, ClassValue = ClassValue]] = self #:nodoc: all # The RR's (Resolv::IPv6) address field attr_accessor :address def from_data(data) #:nodoc: all @address = IPv6.create(data) end def from_hash(hash) #:nodoc: all @address = IPv6.create(hash[:address]) end def from_string(input) #:nodoc: all @address = IPv6.create(input) end def rdata_to_string #:nodoc: all return @address.to_s end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_bytes(@address.address) end def self.decode_rdata(msg) #:nodoc: all return self.new(IPv6.new(msg.get_bytes(16))) end end end end enddnsruby-1.61.3/lib/dnsruby/resource/DLV.rb0000644000004100000410000000153613607400362020363 0ustar www-datawww-data# -- # Copyright 2008 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # RFC4431 specifies that the DLV is assigned type 32769, and the # rdata is identical to that of the DS record. class DLV < RR::DS ClassValue = nil #:nodoc: all TypeValue = Types::DLV #:nodoc: all end end enddnsruby-1.61.3/lib/dnsruby/resource/generic.rb0000644000004100000410000001101313607400362021341 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class to store generic RRs (RFC 3597) class Generic < RR # RFC 3597 # data for the generic resource record attr_reader :data def from_data(data) #:nodoc: all @data = data[0] end def rdata_to_string #:nodoc: all if (@data!=nil) return "\\# " + @data.length.to_s + " " + @data.unpack("H*")[0] end return "#NO DATA" end def from_string(data) #:nodoc: all @data = data end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_bytes(data) end def self.decode_rdata(msg) #:nodoc: all return self.new(msg.get_bytes) end def self.create(type_value, class_value) #:nodoc: c = Class.new(Generic) # c.type = type_value # c.klass = class_value c.const_set(:TypeValue, type_value) c.const_set(:ClassValue, class_value) Generic.const_set("Type#{type_value}_Class#{class_value}", c) ClassHash[[type_value, class_value]] = c return c end end # -- # Standard (class generic) RRs # ++ # NS RR # Nameserver resource record class NS < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::NS #:nodoc: all alias nsdname domainname alias nsdname= domainname= end # CNAME RR # The canonical name for an alias class CNAME < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::CNAME #:nodoc: all alias cname domainname alias cname= domainname= end # DNAME RR class DNAME < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::DNAME #:nodoc: all alias dname domainname alias dname= domainname= end # MB RR class MB < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::MB #:nodoc: all alias madname domainname alias madname= domainname= end # MG RR class MG < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::MG #:nodoc: all alias mgmname domainname alias mgmname= domainname= end # MR RR class MR < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::MR #:nodoc: all alias newname domainname alias newname= domainname= end # PTR RR class PTR < DomainName ClassValue = nil #:nodoc: all TypeValue = Types::PTR #:nodoc: all end # ANY RR # A Query type requesting any RR class ANY < RR ClassValue = nil #:nodoc: all TypeValue = Types::ANY #:nodoc: all def encode_rdata(msg, canonical=false) #:nodoc: all return "" end def self.decode_rdata(msg) #:nodoc: all return self.new([]) end def from_data(data) end end end end require 'dnsruby/resource/HINFO' require 'dnsruby/resource/MINFO' require 'dnsruby/resource/ISDN' require 'dnsruby/resource/MX' require 'dnsruby/resource/NAPTR' require 'dnsruby/resource/NSAP' require 'dnsruby/resource/PX' require 'dnsruby/resource/RP' require 'dnsruby/resource/RT' require 'dnsruby/resource/SOA' require 'dnsruby/resource/TXT' require 'dnsruby/resource/X25' require 'dnsruby/resource/SPF' require 'dnsruby/resource/CERT' require 'dnsruby/resource/LOC' require 'dnsruby/resource/OPT' require 'dnsruby/resource/TSIG' require 'dnsruby/resource/TKEY' require 'dnsruby/resource/DNSKEY' require 'dnsruby/resource/CDNSKEY' require 'dnsruby/resource/RRSIG' require 'dnsruby/resource/NSEC' require 'dnsruby/resource/DS' require 'dnsruby/resource/CDS' require 'dnsruby/resource/URI' require 'dnsruby/resource/NSEC3' require 'dnsruby/resource/NSEC3PARAM' require 'dnsruby/resource/DLV' require 'dnsruby/resource/SSHFP' require 'dnsruby/resource/IPSECKEY' require 'dnsruby/resource/HIP' require 'dnsruby/resource/KX' require 'dnsruby/resource/DHCID' require 'dnsruby/resource/GPOS' require 'dnsruby/resource/NXT' require 'dnsruby/resource/CAA' dnsruby-1.61.3/lib/dnsruby/resource/NSEC3.rb0000644000004100000410000002527313607400362020555 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'digest/sha1' module Base32 module_function def encode32hex(str) str.gsub(/\G(.{5})|(.{1,4}\z)/mn) do full = $1; frag = $2 n, c = (full || frag.ljust(5, "\0")).unpack('NC') full = ((n << 8) | c).to_s(32).rjust(8, '0') if frag full[0, (frag.length * 8 + 4).div(5)].ljust(8, '=').upcase else full.upcase end end end HEX = '[0-9a-v]' def decode32hex(str) str.gsub(/\G\s*(#{HEX}{8}|#{HEX}{7}=|#{HEX}{5}={3}|#{HEX}{4}={4}|#{HEX}{2}={6}|(\S))/imno) do raise 'invalid base32' if $2 s = $1 s.tr('=', '0').to_i(32).divmod(256).pack('NC')[0, (s.count('^=') * 5).div(8)] end end end module Dnsruby class RR # The NSEC3 Resource Record (RR) provides authenticated denial of # existence for DNS Resource Record Sets. # # The NSEC3 RR lists RR types present at the original owner name of the # NSEC3 RR. It includes the next hashed owner name in the hash order # of the zone. The complete set of NSEC3 RRs in a zone indicates which # RRSets exist for the original owner name of the RR and form a chain # of hashed owner names in the zone. This information is used to # provide authenticated denial of existence for DNS data. To provide # protection against zone enumeration, the owner names used in the # NSEC3 RR are cryptographic hashes of the original owner name # prepended as a single label to the name of the zone. The NSEC3 RR # indicates which hash function is used to construct the hash, which # salt is used, and how many iterations of the hash function are # performed over the original owner name. class NSEC3 < RR ClassValue = nil #:nodoc: all TypeValue = Types::NSEC3 #:nodoc: all # The Hash Algorithm field identifies the cryptographic hash algorithm # used to construct the hash-value. attr_reader :hash_alg # The Flags field contains 8 one-bit flags that can be used to indicate # different processing. All undefined flags must be zero. The only # flag defined by the NSEC3 specification is the Opt-Out flag. attr_reader :flags # The Iterations field defines the number of additional times the hash # function has been performed. attr_accessor :iterations # The Salt Length field defines the length of the Salt field in octets, # ranging in value from 0 to 255. attr_reader :salt_length # The Hash Length field defines the length of the Next Hashed Owner # Name field, ranging in value from 1 to 255 octets. attr_reader :hash_length # The Next Hashed Owner Name field contains the next hashed owner name # in hash order. attr_accessor :next_hashed # The Type Bit Maps field identifies the RRset types that exist at the # NSEC RR's owner name attr_reader :types def check_name_in_range(_name) # @TODO@ Check if the name is covered by this record false end def check_name_in_wildcard_range(_name) # @TODO@ Check if the name is covered by this record false end def calculate_hash NSEC3.calculate_hash(@name, @iterations, @salt, @hash_alg) end def NSEC3.calculate_hash(name, iterations, salt, hash_alg) # RFC5155 # 5. Calculation of the Hash # Define H(x) to be the hash of x using the Hash Algorithm selected by # the NSEC3 RR, k to be the number of Iterations, and || to indicate # concatenation. Then define: # # IH(salt, x, 0) = H(x || salt), and # # IH(salt, x, k) = H(IH(salt, x, k-1) || salt), if k > 0 # # Then the calculated hash of an owner name is # # IH(salt, owner name, iterations), # # where the owner name is in the canonical form, defined as: # # The wire format of the owner name where: # # 1. The owner name is fully expanded (no DNS name compression) and # fully qualified; # 2. All uppercase US-ASCII letters are replaced by the corresponding # lowercase US-ASCII letters; # 3. If the owner name is a wildcard name, the owner name is in its # original unexpanded form, including the '*' label (no wildcard # substitution); # # This form is as defined in Section 6.2 of [RFC 4034]. # n = Name.create(name) out = n.canonical begin (iterations + 1).times { out = NSEC3.h(out + salt, hash_alg) } return Base32.encode32hex(out).downcase rescue ArgumentError TheLog.error("Unknown hash algorithm #{hash_alg} used for NSEC3 hash") return 'Unknown NSEC3 hash algorithm' end end def h(x) # :nodoc: all NSEC3.h(x, @hash_alg) end def NSEC3.h(x, hash_alg) # :nodoc: all if Nsec3HashAlgorithms.SHA_1 == hash_alg return Digest::SHA1.digest(x) end raise ArgumentError.new('Unknown hash algorithm') end def hash_alg=(a) if a.instance_of?(String) if a.length == 1 a = a.to_i end end begin alg = Nsec3HashAlgorithms.new(a) @hash_alg = alg rescue ArgumentError => e raise DecodeError.new(e) end end def types=(t) @types = (t && t.length > 0) ? NSEC.get_types(t) : [] end def add_type(t) self.types = (@types + [t]) end OPT_OUT = 1 def flags=(f) if f == 0 || f == OPT_OUT @flags = f else raise DecodeError.new("Unknown NSEC3 flags field - #{f}") end end # If the Opt-Out flag is set, the NSEC3 record covers zero or more # unsigned delegations. def opt_out? @flags == OPT_OUT end # def salt_length=(l) # if ((l < 0) || (l > 255)) # raise DecodeError.new('NSEC3 salt length must be between 0 and 255') # end # @salt_length = l # end # def hash_length=(l) if (l < 0) || (l > 255) raise DecodeError.new("NSEC3 hash length must be between 0 and 255 but was #{l}") end @hash_length = l end def from_data(data) #:nodoc: all hash_alg, flags, iterations, _salt_length, salt, hash_length, next_hashed, types = data self.hash_alg = hash_alg self.flags = flags self.iterations = iterations # self.salt_length=(salt_length) # self.salt=(salt) @salt = salt self.hash_length = hash_length self.next_hashed = next_hashed self.types = types end # The Salt field is appended to the original owner name before hashing # in order to defend against pre-calculated dictionary attacks. def salt return NSEC3.encode_salt(@salt) end def salt=(s) @salt = NSEC3.decode_salt(s) @salt_length = @salt.length end def NSEC3.decode_salt(input) input == '-' ? '' : [input].pack('H*') end def NSEC3.encode_salt(s) (!s || s.length == 0) ? '-' : s.unpack('H*')[0] end def decode_next_hashed(input) @next_hashed = NSEC3.decode_next_hashed(input) end def NSEC3.decode_next_hashed(input) return Base32.decode32hex(input) end def encode_next_hashed(n) return NSEC3.encode_next_hashed(n) end def NSEC3.encode_next_hashed(n) return Base32.encode32hex(n).downcase end def from_string(input) if input.length > 0 data = input.split self.hash_alg = (data[0]).to_i self.flags = (data[1]).to_i self.iterations = (data[2]).to_i self.salt = (data[3]) len = data[0].length + data[1].length + data[2].length + data[3].length + 4 # There may or may not be brackets around next_hashed if data[4] == '(' len += data[4].length + 1 end next_hashed_and_types = (input[len, input.length-len]) data2 = next_hashed_and_types.split() self.next_hashed = decode_next_hashed(data2[0]) self.hash_length = @next_hashed.length len2 = data2[0].length + 1 self.types = next_hashed_and_types[len2, next_hashed_and_types.length - len2] # self.types=data2[1] # # len = data[0].length + data[1].length + data[2].length + data[3].length + data[5].length + 7 # # self.types=(input[len, input.length-len]) end end def rdata_to_string #:nodoc: all if @next_hashed type_strings = [] @types.each { |t| type_strings << t.string } # salt = NSEC3.encode_salt(@salt) salt = salt() # TODO: Remove this? next_hashed = encode_next_hashed(@next_hashed) types = type_strings.join(' ') "#{@hash_alg.code} #{@flags} #{@iterations} #{salt} ( #{next_hashed} #{types} )" else '' end end def encode_rdata(msg, canonical=false) #:nodoc: all # s = salt() s = @salt sl = s.length if s == '-' sl = 0 end msg.put_pack('ccnc', @hash_alg.code, @flags, @iterations, sl) if sl > 0 msg.put_bytes(s) end msg.put_pack('c', @hash_length) msg.put_bytes(@next_hashed) types = NSEC.encode_types(self) msg.put_bytes(types) end def self.decode_rdata(msg) #:nodoc: all hash_alg, flags, iterations, salt_length = msg.get_unpack('ccnc') # Salt may be omitted salt = [] if salt_length > 0 salt = msg.get_bytes(salt_length) end hash_length, = msg.get_unpack('c') next_hashed = msg.get_bytes(hash_length) types = NSEC.decode_types(msg.get_bytes) return self.new( [hash_alg, flags, iterations, salt_length, salt, hash_length, next_hashed, types]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/NSEC.rb0000644000004100000410000002307213607400362020465 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # RFC4034, section 4 # The NSEC resource record lists two separate things: the next owner # name (in the canonical ordering of the zone) that contains # authoritative data or a delegation point NS RRset, and the set of RR # types present at the NSEC RR's owner name [RFC3845]. The complete # set of NSEC RRs in a zone indicates which authoritative RRsets exist # in a zone and also form a chain of authoritative owner names in the # zone. This information is used to provide authenticated denial of # existence for DNS data, as described in [RFC4035]. class NSEC < RR ClassValue = nil #:nodoc: all TypeValue = Types::NSEC #:nodoc: all # The next name which exists after this NSEC # The Next Domain field contains the next owner name (in the canonical # ordering of the zone) that has authoritative data or contains a # delegation point NS RRset attr_reader :next_domain # The Type Bit Maps field identifies the RRset types that exist at the # NSEC RR's owner name attr_reader :types def next_domain=(n) nxt = Name.create(n) @next_domain = nxt end def check_name_in_range(n) # Check if the name is covered by this record @name.wild? \ ? check_name_in_wildcard_range(n) \ : name.canonically_before(n) && n.canonically_before(next_domain) end def check_name_in_wildcard_range(n) # Check if the name is covered by this record return false unless @name.wild? return false if @next_domain.canonically_before(n) # Now just check that the wildcard is *before* the name # Strip the first label ("*") and then compare n2 = Name.create(@name) n2.labels.delete_at(0) ! n.canonically_before(n2) end def types=(t) @types = (t && t.length > 0) ? NSEC.get_types(t) : [] end def self.get_types(t) if t.instance_of?(Array) # from the wire, already decoded types = t elsif t.instance_of?(String) if (index = t.index(/[;)]/)) # check for ; or ) t = t[0, index] end # List of mnemonics types = [] mnemonics = t.split(' ') mnemonics.each { |m| types << Types.new(m) } else raise DecodeError.new('Unknown format of types for Dnsruby::RR::NSEC') end types end def add_type(t) self.types = (@types + [t]) end def self.decode_types(bytes) types = [] # RFC4034 section 4.1.2 # The RR type space is split into 256 window blocks, each representing # the low-order 8 bits of the 16-bit RR type space. Each block that # has at least one active RR type is encoded using a single octet # window number (from 0 to 255), a single octet bitmap length (from 1 # to 32) indicating the number of octets used for the window block's # bitmap, and up to 32 octets (256 bits) of bitmap. # Blocks are present in the NSEC RR RDATA in increasing numerical # order. # Type Bit Maps Field = ( Window Block # | Bitmap Length | Bitmap )+ # where "|" denotes concatenation. pos = 0 while pos < bytes.length # So, read the first two octets if bytes.length - pos < 2 raise DecodeError.new("NSEC : Expected window number and bitmap length octets") end window_number = bytes[pos] bitmap_length = bytes[pos+1] if window_number.class == String # Ruby 1.9 window_number = window_number.getbyte(0) bitmap_length = bitmap_length.getbyte(0) end pos += 2 bitmap = bytes[pos,bitmap_length] pos += bitmap_length # Each bitmap encodes the low-order 8 bits of RR types within the # window block, in network bit order. The first bit is bit 0. For # window block 0, bit 1 corresponds to RR type 1 (A), bit 2 corresponds # to RR type 2 (NS), and so forth. For window block 1, bit 1 # corresponds to RR type 257, and bit 2 to RR type 258. If a bit is # set, it indicates that an RRset of that type is present for the NSEC # RR's owner name. If a bit is clear, it indicates that no RRset of # that type is present for the NSEC RR's owner name. index = 0 bitmap.each_byte do |char| if char.to_i != 0 # decode these RR types 8.times do |i| if ((1 << (7-i)) & char) == (1 << (7-i)) type = Types.new((256 * window_number) + (8 * index) + i) # Bits representing pseudo-types MUST be clear, as they do not appear # in zone data. If encountered, they MUST be ignored upon being read. unless [Types::OPT, Types::TSIG].include?(type) types << type end end end end index += 1 end end return types end def encode_types NSEC.encode_types(self) end def self.encode_types(nsec) output = '' # types represents all 65536 possible RR types. # Split up types into sets of 256 different types. type_codes = [] nsec.types.each { |type| type_codes << type.code } type_codes.sort! window = -1 0.step(65536,256) { |step| # Gather up the RR types for this set of 256 types_to_go = [] while (!type_codes.empty? && type_codes[0] < step) types_to_go << type_codes[0] # And delete them from type_codes type_codes = type_codes.last(type_codes.length - 1) break if type_codes.empty? end unless types_to_go.empty? # Then create the bitmap for them bitmap = '' # keep on adding them until there's none left pos = 0 bitmap_pos = 0 while (!types_to_go.empty?) # Check the next eight byte = 0 pos += 8 while types_to_go[0] < (pos + step - 256) byte = byte | (1 << (pos - 1 - (types_to_go[0] - (step - 256)))) # Add it to the list # And remove it from the to_go queue types_to_go = types_to_go.last(types_to_go.length - 1) break if types_to_go.empty? end bitmap << ' ' if bitmap[bitmap_pos].class == String bitmap.setbyte(bitmap_pos, byte) # Ruby 1.9 else bitmap[bitmap_pos] = byte end bitmap_pos += 1 end # Now add data to output bytes start = output.length output << (' ' * (2 + bitmap.length)) if output[start].class == String output.setbyte(start, window) output.setbyte(start + 1, bitmap.length) bitmap.length.times do |i| output.setbyte(start + 2 + i, bitmap[i].getbyte(0)) end else output[start] = window output[start + 1] = bitmap.length bitmap.length.times do |i| output[start + 2 + i] = bitmap[i] end end end window += 1 # Are there any more types after this? if type_codes.empty? # If not, then break (so we don't add more zeros) break end } if output[0].class == String output = output.force_encoding("ascii-8bit") end output end def from_data(data) #:nodoc: all next_domain, types = data self.next_domain = next_domain self.types = types end def from_string(input) if input.length > 0 data = input.split(' ') self.next_domain = data[0] len = data[0].length+ 1 if data[1] == '(' len += data[1].length end self.types = input[len, input.length-len] @types = NSEC.get_types(input[len, input.length-len]) end end def rdata_to_string #:nodoc: all if @next_domain type_strings = [] @types.each { |t| type_strings << t.string } types = type_strings.join(' ') "#{@next_domain.to_s(true)} ( #{types} )" else '' end end def encode_rdata(msg, canonical=false) #:nodoc: all # Canonical msg.put_name(@next_domain, canonical, false) # dnssec-bis-updates says NSEC should not be downcased types = encode_types msg.put_bytes(types) end def self.decode_rdata(msg) #:nodoc: all next_domain = msg.get_name types = decode_types(msg.get_bytes) return self.new([next_domain, types]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/AFSDB.rb0000644000004100000410000000372513607400362020557 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR module IN # Class for DNS AFS Data Base (AFSDB) resource records. # # RFC 1183 Section 1 class AFSDB < RR ClassHash[[TypeValue = Types::AFSDB, ClassValue = ClassValue]] = self #:nodoc: all # The RR's subtype field. See RFC 1183. attr_accessor :subtype # The RR's hostname field. See RFC 1183. attr_accessor :hostname def from_hash(hash) #:nodoc: all @subtype = hash[:subtype] @hostname = Name.create(hash[:hostname]) end def from_data(data) #:nodoc: all @subtype, @hostname = data end def from_string(input) #:nodoc: all if (input!=nil && (input =~ /^(\d+)\s+(\S+)$/o)) @subtype = $1; @hostname = Name.create($2) end end def rdata_to_string #:nodoc: all if defined?@subtype return "#{@subtype} #{@hostname.to_s(true)}" else return ''; end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack("n", @subtype.to_i) msg.put_name(@hostname, canonical) end def self.decode_rdata(msg) #:nodoc: all subtype, = msg.get_unpack("n") hostname = msg.get_name return self.new([subtype, hostname]) end end end end end dnsruby-1.61.3/lib/dnsruby/resource/A.rb0000644000004100000410000000306113607400362020111 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR module IN # Class for DNS Address (A) resource records. # # RFC 1035 Section 3.4.1 class A < RR ClassHash[[TypeValue = Types::A, ClassValue = ClassValue]] = self #:nodoc: all # The RR's (Resolv::IPv4) address field attr_accessor :address def from_data(data) #:nodoc: all @address = IPv4.create(data) end # Create the RR from a hash def from_hash(hash) @address = IPv4.create(hash[:address]) end # Create the RR from a standard string def from_string(input) @address = IPv4.create(input) end def rdata_to_string return @address.to_s end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_bytes(@address.address) end def self.decode_rdata(msg) #:nodoc: all return self.new(IPv4.new(msg.get_bytes(4))) end end end end enddnsruby-1.61.3/lib/dnsruby/resource/DHCID.rb0000644000004100000410000000301413607400362020542 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS DHCP ID (DHCID) resource records. # RFC 4701 class DHCID < RR ClassValue = nil #:nodoc: all TypeValue= Types::DHCID #:nodoc: all # The opaque rdata for DHCID attr_accessor :dhcid_data def from_hash(hash) #:nodoc: all @dhcid_data = hash[:dhcid_data] end def from_data(data) #:nodoc: all @dhcid_data, = data end def from_string(input) #:nodoc: all buf = input.gsub(/\n/, "") buf.gsub!(/ /, "") @dhcid_data = buf.unpack("m*").first end def rdata_to_string #:nodoc: all return "#{[@dhcid_data.to_s].pack("m*").gsub("\n", "")}" end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_bytes(@dhcid_data) end def self.decode_rdata(msg) #:nodoc: all dhcid_data, = msg.get_bytes() return self.new([dhcid_data]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/SPF.rb0000644000004100000410000000175413607400362020370 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # DNS SPF resource record # This is a clone of the TXT record. This class therfore completely inherits # all properties of the Dnsruby::Resource::TXT class. # # Please see the Dnsruby::Resource::TXT documentation for details # RFC 1035 Section 3.3.14, draft-schlitt-ospf-classic-02.txt class SPF < TXT TypeValue = Types::SPF #:nodoc: all end end enddnsruby-1.61.3/lib/dnsruby/resource/RP.rb0000644000004100000410000000416313607400362020256 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Responsible Person (RP) resource records. # RFC 1183 Section 2.2 class RP < RR ClassValue = nil #:nodoc: all TypeValue = Types::RP #:nodoc: all # Returns a domain name that specifies the mailbox for the responsible person. attr_reader :mailbox # A domain name that specifies a TXT record containing further # information about the responsible person. attr_reader :txtdomain def txtdomain=(s) @txtdomain = Name.create(s) end def mailbox=(s) @mailbox = Name.create(s) end def from_hash(hash) @mailbox = Name.create(hash[:mailbox]) @txtdomain = Name.create(hash[:txtdomain]) end def from_data(data) #:nodoc: all @mailbox, @txtdomain= data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") @mailbox = Name.create(names[0]) @txtdomain = Name.create(names[1]) end end def rdata_to_string #:nodoc: all if (@mailbox!=nil) return "#{@mailbox.to_s(true)} #{@txtdomain.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_name(@mailbox, canonical) msg.put_name(@txtdomain, canonical) end def self.decode_rdata(msg) #:nodoc: all mailbox = msg.get_name txtdomain = msg.get_name return self.new([mailbox, txtdomain]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/NSEC3PARAM.rb0000644000004100000410000001035213607400362021326 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # The NSEC3PARAM RR contains the NSEC3 parameters (hash algorithm, # flags, iterations and salt) needed by authoritative servers to # calculate hashed owner names. The presence of an NSEC3PARAM RR at a # zone apex indicates that the specified parameters may be used by # authoritative servers to choose an appropriate set of NSEC3 RRs for # negative responses. The NSEC3PARAM RR is not used by validators or # resolvers. class NSEC3PARAM < RR ClassValue = nil #:nodoc: all TypeValue = Types::NSEC3PARAM #:nodoc: all # The Hash Algorithm field identifies the cryptographic hash algorithm # used to construct the hash-value. attr_reader :hash_alg # The Flags field contains 8 one-bit flags that can be used to indicate # different processing. All undefined flags must be zero. The only # flag defined by the NSEC3 specification is the Opt-Out flag. attr_reader :flags # The Iterations field defines the number of additional times the hash # function has been performed. attr_accessor :iterations # The Salt Length field defines the length of the Salt field in octets, # ranging in value from 0 to 255. attr_reader :salt_length # The Salt field is appended to the original owner name before hashing # in order to defend against pre-calculated dictionary attacks. def salt return NSEC3.encode_salt(@salt) end def salt=(s) @salt = NSEC3.decode_salt(s) @salt_length = @salt.length end def hash_alg=(a) if (a.instance_of?String) if (a.length == 1) a = a.to_i end end begin alg = Nsec3HashAlgorithms.new(a) @hash_alg = alg rescue ArgumentError => e raise DecodeError.new(e) end end def types=(t) @types = NSEC.get_types(t) end def flags=(f) if (f==0 || f==1) @flags=f else raise DecodeError.new("Unknown NSEC3 flags field - #{f}") end end # def salt_length=(l) # :nodoc: all # if ((l < 0) || (l > 255)) # raise DecodeError.new("NSEC3 salt length must be between 0 and 255") # end # @salt_length = l # end # def from_data(data) #:nodoc: all hash_alg, flags, iterations, salt_length, salt = data self.hash_alg=(hash_alg) self.flags=(flags) self.iterations=(iterations) # self.salt_length=(salt_length) # self.salt=(salt) @salt=salt end def from_string(input) if (input.length > 0) data = input.split(" ") self.hash_alg=(data[0]).to_i self.flags=(data[1]).to_i self.iterations=(data[2]).to_i self.salt=(data[3]) # self.salt_length=(data[3].length) end end def rdata_to_string #:nodoc: all s = salt() return "#{@hash_alg.code} #{@flags} #{@iterations} #{s}" end def encode_rdata(msg, canonical=false) #:nodoc: all # s = salt() s = @salt sl = s.length() if (s == "-") sl == 0 end msg.put_pack("ccnc", @hash_alg.code, @flags, @iterations, sl) if (sl > 0) msg.put_bytes(s) end end def self.decode_rdata(msg) #:nodoc: all hash_alg, flags, iterations, salt_length = msg.get_unpack("ccnc") salt = msg.get_bytes(salt_length) return self.new( [hash_alg, flags, iterations, salt_length, salt]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/SRV.rb0000644000004100000410000000742213607400362020410 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR module IN # SRV resource record defined in RFC 2782 # # These records identify the hostname and port that a service is # available at. # # The format is: # _Service._Proto.Name TTL Class SRV Priority Weight Port Target # # The fields specific to SRV are defined in RFC 2782 class SRV < RR ClassHash[[TypeValue = Types::SRV, ClassValue = ClassValue]] = self #:nodoc: all # The priority of this target host. # A client MUST attempt # to contact the target host with the lowest-numbered priority it can # reach; target hosts with the same priority SHOULD be tried in an # order defined by the weight field. The range is 0-65535. Note that # it is not widely implemented and should be set to zero. attr_accessor :priority # A server selection mechanism. # The weight field specifies # a relative weight for entries with the same priority. Larger weights # SHOULD be given a proportionately higher probability of being # selected. The range of this number is 0-65535. Domain administrators # SHOULD use Weight 0 when there isn't any server selection to do, to # make the RR easier to read for humans (less noisy). Note that it is # not widely implemented and should be set to zero. attr_accessor :weight # The port on this target host of this service. The range is 0-65535. attr_accessor :port # The domain name of the target host. A target of "." means # that the service is decidedly not available at this domain. attr_accessor :target def from_data(data) #:nodoc: all @priority, @weight, @port, @target = data end def from_hash(hash) if hash[:priority] @priority = hash[:priority].to_i end if hash[:weight] @weight = hash[:weight].to_i end if hash[:port] @port = hash[:port].to_i end if hash[:target] @target= Name.create(hash[:target]) end end def from_string(input) if (input.length > 0) names = input.split(" ") @priority = names[0].to_i @weight = names[1].to_i @port = names[2].to_i if (names[3]) @target = Name.create(names[3]) end end end def rdata_to_string if (@target!=nil) return "#{@priority} #{@weight} #{@port} #{@target.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack("n", @priority) msg.put_pack("n", @weight) msg.put_pack("n", @port) msg.put_name(@target,canonical) end def self.decode_rdata(msg) #:nodoc: all priority, = msg.get_unpack("n") weight, = msg.get_unpack("n") port, = msg.get_unpack("n") target = msg.get_name return self.new([priority, weight, port, target]) end end end end enddnsruby-1.61.3/lib/dnsruby/resource/LOC.rb0000644000004100000410000002041513607400362020350 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Location (LOC) resource records. See RFC 1876 for # details. class LOC < RR ClassValue = nil #:nodoc: all TypeValue = Types::LOC #:nodoc: all # The version number of the representation; programs should # always check this. Dnsruby currently supports only version 0. attr_accessor :version @version = 0 # The diameter of a sphere enclosing the described entity, # in centimeters. attr_accessor :size # The horizontal precision of the data, in centimeters. attr_accessor :horiz_pre # The vertical precision of the data, in centimeters. attr_accessor :vert_pre # The latitude of the center of the sphere described by # the size method, in thousandths of a second of arc. 2**31 # represents the equator; numbers above that are north latitude. attr_accessor :latitude # The longitude of the center of the sphere described by # the size method, in thousandths of a second of arc. 2**31 # represents the prime meridian; numbers above that are east # longitude. attr_accessor :longitude # The altitude of the center of the sphere described by # the size method, in centimeters, from a base of 100,000m # below the WGS 84 reference spheroid used by GPS. attr_accessor :altitude # Powers of 10 from 0 to 9 (used to speed up calculations). POWEROFTEN = [1, 10, 100, 1_000, 10_000, 100_000, 1_000_000, 10_000_000, 100_000_000, 1_000_000_000] # Reference altitude in centimeters (see RFC 1876). REFERENCE_ALT = 100_000 * 100; # Reference lat/lon (see RFC 1876). REFERENCE_LATLON = 2**31; # Conversions to/from thousandths of a degree. CONV_SEC = 1000; CONV_MIN = 60 * CONV_SEC; CONV_DEG = 60 * CONV_MIN; # Defaults (from RFC 1876, Section 3). DEFAULT_MIN = 0; DEFAULT_SEC = 0; DEFAULT_SIZE = 1; DEFAULT_HORIZ_PRE = 10_000; DEFAULT_VERT_PRE = 10; def latlon2dms(rawmsec, hems) # Tried to use modulus here, but Perl dumped core if # the value was >= 2**31. abs = (rawmsec - REFERENCE_LATLON).abs; deg = (abs / CONV_DEG).round; abs -= deg * CONV_DEG; min = (abs / CONV_MIN).round; abs -= min * CONV_MIN; sec = (abs / CONV_SEC).round; # $conv_sec abs -= sec * CONV_SEC; msec = abs; hem = hems[(rawmsec >= REFERENCE_LATLON ? 0 : 1), 1] return sprintf("%d %02d %02d.%03d %s", deg, min, sec, msec, hem); end def dms2latlon(deg, min, sec, hem) retval=0 retval = (deg * CONV_DEG) + (min * CONV_MIN) + (sec * CONV_SEC).round; retval = -retval if ((hem != nil) && ((hem == "S") || (hem == "W"))); retval += REFERENCE_LATLON; return retval; end # Returns the latitude and longitude as floating-point degrees. # Positive numbers represent north latitude or east longitude; # negative numbers represent south latitude or west longitude. # # lat, lon = rr.latlon # system("xearth", "-pos", "fixed #{lat} #{lon}") # def latlon retlat, retlon = nil if (@version == 0) retlat = latlon2deg(@latitude); retlon = latlon2deg(@longitude); end return retlat, retlon end def latlon2deg(rawmsec) deg=0; deg = (rawmsec - reference_latlon) / CONV_DEG; return deg; end def from_data(data) #:nodoc: all @version, @size, @horiz_pre, @vert_pre, @latitude, @longitude, @altitude = data end def from_string(string) #:nodoc: all if (string && string =~ /^ (\d+) \s+ # deg lat ((\d+) \s+)? # min lat (([\d.]+) \s+)? # sec lat (N|S) \s+ # hem lat (\d+) \s+ # deg lon ((\d+) \s+)? # min lon (([\d.]+) \s+)? # sec lon (E|W) \s+ # hem lon (-?[\d.]+) m? # altitude (\s+ ([\d.]+) m?)? # size (\s+ ([\d.]+) m?)? # horiz precision (\s+ ([\d.]+) m?)? # vert precision /ix) # size = DEFAULT_SIZE # What to do for other versions? version = 0; horiz_pre = DEFAULT_HORIZ_PRE vert_pre = DEFAULT_VERT_PRE latdeg, latmin, latsec, lathem = $1.to_i, $3.to_i, $5.to_f, $6; londeg, lonmin, lonsec, lonhem = $7.to_i, $9.to_i, $11.to_f, $12 alt = $13.to_i if ($15) size = $15.to_f end if ($17) horiz_pre = $17.to_f end if ($19) vert_pre = $19.to_f end latmin = DEFAULT_MIN unless latmin; latsec = DEFAULT_SEC unless latsec; lathem = lathem.upcase; lonmin = DEFAULT_MIN unless lonmin; lonsec = DEFAULT_SEC unless lonsec; lonhem = lonhem.upcase @version = version; @size = size * 100; @horiz_pre = horiz_pre * 100; @vert_pre = vert_pre * 100; @latitude = dms2latlon(latdeg, latmin, latsec, lathem); @longitude = dms2latlon(londeg, lonmin, lonsec, lonhem); @altitude = alt * 100 + REFERENCE_ALT; end end def from_hash(hash) #:nodoc: all super(hash) if (@size == nil) @size = DEFAULT_SIZE * 100 end if @horiz_pre == nil @horiz_pre = DEFAULT_HORIZ_PRE * 100 end if @vert_pre == nil @vert_pre = DEFAULT_VERT_PRE * 100 end end def rdata_to_string #:nodoc: all rdatastr="" if (defined?@version) if (@version == 0) lat = @latitude; lon = @longitude; altitude = @altitude; size = @size; horiz_pre = @horiz_pre; vert_pre = @vert_pre; altitude = (altitude - REFERENCE_ALT) / 100; size /= 100; horiz_pre /= 100; vert_pre /= 100; rdatastr = latlon2dms(lat, "NS") + " " + latlon2dms(lon, "EW") + " " + sprintf("%.2fm", altitude) + " " + sprintf("%.2fm", size) + " " + sprintf("%.2fm", horiz_pre) + " " + sprintf("%.2fm", vert_pre); else rdatastr = "; version " + @version + " not supported"; end else rdatastr = ''; end return rdatastr; end def self.decode_rdata(msg) #:nodoc: all version, = msg.get_unpack("C") if (version == 0) size, horiz_pre, vert_pre, latitude, longitude, altitude = msg.get_unpack('CCCNNN') size = precsize_ntoval(size) horiz_pre = precsize_ntoval(horiz_pre) vert_pre = precsize_ntoval(vert_pre) return self.new([version, size, horiz_pre, vert_pre, latitude, longitude, altitude]) end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('C', @version) if (@version == 0) msg.put_pack('CCCNNN', precsize_valton(@size), precsize_valton(@horiz_pre), precsize_valton(@vert_pre), @latitude, @longitude, @altitude) end end def self.precsize_ntoval(prec) mantissa = ((prec >> 4) & 0x0f) % 10; exponent = (prec & 0x0f) % 10; return mantissa * POWEROFTEN[exponent]; end def precsize_valton(val) exponent = 0; while (val >= 10) val /= 10; exponent+=1 end return (val.round << 4) | (exponent & 0x0f); end end end enddnsruby-1.61.3/lib/dnsruby/resource/CDNSKEY.rb0000644000004100000410000000126613607400362021036 0ustar www-datawww-datamodule Dnsruby class RR # RFC4034, section 2 # DNSSEC uses public key cryptography to sign and authenticate DNS # resource record sets (RRsets). The public keys are stored in DNSKEY # resource records and are used in the DNSSEC authentication process # described in [RFC4035]: A zone signs its authoritative RRsets by # using a private key and stores the corresponding public key in a # DNSKEY RR. A resolver can then use the public key to validate # signatures covering the RRsets in the zone, and thus to authenticate # them. class CDNSKEY < DNSKEY ClassValue = nil #:nodoc: all TypeValue = Types::CDNSKEY #:nodoc: all end end enddnsruby-1.61.3/lib/dnsruby/resource/CDS.rb0000644000004100000410000000251013607400362020340 0ustar www-datawww-data# -- # Copyright 2018 Caerketton Tech Ltd # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # RFC4034, section 4 # The DS Resource Record refers to a DNSKEY RR and is used in the DNS # DNSKEY authentication process. A DS RR refers to a DNSKEY RR by # storing the key tag, algorithm number, and a digest of the DNSKEY RR. # Note that while the digest should be sufficient to identify the # public key, storing the key tag and key algorithm helps make the # identification process more efficient. By authenticating the DS # record, a resolver can authenticate the DNSKEY RR to which the DS # record points. The key authentication process is described in # [RFC4035]. class CDS < DS ClassValue = nil #:nodoc: all TypeValue = Types::CDS #:nodoc: all end end end dnsruby-1.61.3/lib/dnsruby/resource/OPT.rb0000644000004100000410000002047613607400362020404 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for EDNS pseudo resource record OPT. # This class is effectively internal to Dnsruby # See RFC 2671, RFC 2435 Section 3 # @TODO@ Extended labels RFC2671 section 3 class OPT < RR #:nodoc: all ClassValue = nil #:nodoc: all TypeValue = Types::OPT #:nodoc: all DO_BIT = 0x8000 ADDRESS_FAMILIES = [1, 2] IPV4_ADDRESS_FAMILY, IPV6_ADDRESS_FAMILY = ADDRESS_FAMILIES EDNS_SUBNET_OPTION = 8 # @TODO@ Add BADVERS to an XRCode CodeMapper object # Can be called with up to 3 arguments, none of which must be present # * OPT.new() # * OPT.new(size) # * OPT.new(size,flags) # * OPT.new(size,flags,options) def initialize(*args) @type = Types.new('OPT') @ttl = nil @options=nil if (args.length > 0) self.payloadsize=(args[0]) if (args.length > 1) self.flags=(args[1]) if (args.length > 2) self.options=(args[2]) else self.options=nil end else self.flags=0 end else self.payloadsize=0 end end # From RFC 2671 : # 4.3. The fixed part of an OPT RR is structured as follows: # # Field Name Field Type Description # ------------------------------------------------------ # NAME domain name empty (root domain) # TYPE u_int16_t OPT # CLASS u_int16_t sender's UDP payload size # TTL u_int32_t extended RCODE and flags # RDLEN u_int16_t describes RDATA # RDATA octet stream {attribute,value} pairs # 4.6. The extended RCODE and flags (which OPT stores in the RR TTL field) # are structured as follows: # # +0 (MSB) +1 (LSB) # +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ # 0: | EXTENDED-RCODE | VERSION | # +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ # 2: | Z | # +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ # # EXTENDED-RCODE Forms upper 8 bits of extended 12-bit RCODE. Note # that EXTENDED-RCODE value "0" indicates that an # unextended RCODE is in use (values "0" through "15"). # # VERSION Indicates the implementation level of whoever sets # it. Full conformance with this specification is # indicated by version "0." def flags_from_ttl if (@ttl) return [@ttl].pack("N") else return [0].pack("N") end end def xrcode return ExtendedRCode.new(flags_from_ttl[0, 1].unpack("C")[0]) end def xrcode=(c) code = ExtendedRCode.new(c) @ttl = (code.code << 24) + (version() << 16) + flags() end def version return flags_from_ttl[1, 1].unpack("C")[0] end def version=(code) @ttl = (xrcode().code << 24) + (code << 16) + flags() end def flags return flags_from_ttl[2, 2].unpack("n")[0] end def flags=(code) set_flags(code) end def set_flags(code) # Should always be zero @ttl = (xrcode().code << 24) + (version() << 16) + code end def dnssec_ok return ((flags() & DO_BIT) == DO_BIT) end def dnssec_ok=(on) if (on) set_flags(flags() | DO_BIT) else set_flags(flags() & (~DO_BIT)) end end def payloadsize return @klass.code end def payloadsize=(size) self.klass=Classes.new(size) end def options(args) if (args==nil) return @options elsif args.kind_of?(Integer) # return list of options with that code ret = [] @options.each do |option| if (option.code == args) ret.push(option) end end return ret end end def options=(options) @options = options end def from_data(data) @options = data end def from_string(input) raise NotImplementedError end def get_ip_addr(opt, family, source_netmask) pad_format_string = family == IPV4_ADDRESS_FAMILY ? 'x3C' : 'x15C' ip_addr = [0].pack(pad_format_string) num_to_copy = (source_netmask + 7) / 8 num_to_copy.times { |index| ip_addr[index] = opt.data[index+4] } ip_addr end def get_client_subnet(opt) family = opt.data[1].unpack('C')[0] return "Unsupported(family=#{family})" unless ADDRESS_FAMILIES.include?(family) source_netmask = opt.data[2].unpack('C')[0] scope_netmask = opt.data[3].unpack('C')[0] case family when IPV4_ADDRESS_FAMILY return "#{IPAddr::ntop(get_ip_addr(opt,family,source_netmask))}/#{source_netmask}/#{scope_netmask}" when IPV6_ADDRESS_FAMILY new_ipv6 = IPAddr.new(IPAddr::ntop(get_ip_addr(opt,family,source_netmask)), Socket::AF_INET6) return "#{new_ipv6}/#{source_netmask}/#{scope_netmask}" end end def set_client_subnet(subnet) family = IPV4_ADDRESS_FAMILY scope_netmask = 0 ip, source_netmask = subnet.split('/') source_netmask = source_netmask.to_i if subnet == "0.0.0.0/0" edns_client_subnet = RR::OPT::Option.new( EDNS_SUBNET_OPTION, [family, source_netmask, scope_netmask].pack("xcc*")) else ip_address = IPAddr.new(ip) family = IPV6_ADDRESS_FAMILY if ip_address.ipv6? num_addr_bytes = source_netmask / 8 num_addr_bytes = num_addr_bytes + 1 if source_netmask % 8 > 0 edns_client_subnet = RR::OPT::Option.new(EDNS_SUBNET_OPTION, [family, source_netmask, scope_netmask].pack("xcc*") + ip_address.hton.slice(0, num_addr_bytes)) end self.options = [edns_client_subnet] end def edns_client_subnet return nil if @options.nil? subnet_option = @options.detect { |option| option.code == EDNS_SUBNET_OPTION } subnet_option ? get_client_subnet(subnet_option) : nil end def to_s ret = "OPT pseudo-record : payloadsize #{payloadsize}, xrcode #{xrcode.code}, version #{version}, flags #{flags}\n" if @options @options.each do |opt| if opt.code == EDNS_SUBNET_OPTION ret = ret + "CLIENT-SUBNET: #{get_client_subnet(opt)}" else ret = ret + " " + opt.to_s end end end ret = ret + "\n" return ret end def encode_rdata(msg, canonical=false) if (@options) @options.each do |opt| msg.put_pack('n', opt.code) msg.put_pack('n', opt.data.length) msg.put_pack('a*', opt.data) end end end def self.decode_rdata(msg)#:nodoc: all if (msg.has_remaining?) options = [] while (msg.has_remaining?) do code = msg.get_unpack('n')[0] len = msg.get_unpack('n')[0] data = msg.get_bytes(len) options.push(Option.new(code, data)) end end return self.new(0, 0, options) end class Option attr_accessor :code, :data def initialize(code, data) @code = code @data = data end end end end end dnsruby-1.61.3/lib/dnsruby/resource/MX.rb0000644000004100000410000000377513607400362020271 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR # Class for DNS Mail Exchanger (MX) resource records. # RFC 1035 Section 3.3.9 class MX < RR ClassValue = nil #:nodoc: all TypeValue= Types::MX #:nodoc: all # The preference for this mail exchange. attr_accessor :preference # The name of this mail exchange. attr_accessor :exchange def from_hash(hash) #:nodoc: all @preference = hash[:preference] @exchange = Name.create(hash[:exchange]) end def from_data(data) #:nodoc: all @preference, @exchange = data end def from_string(input) #:nodoc: all if (input.length > 0) names = input.split(" ") if(names.size != 2) raise DecodeError.new("MX record expects preference and domain") end @preference = names[0].to_i @exchange = Name.create(names[1]) end end def rdata_to_string #:nodoc: all if (@preference!=nil) return "#{@preference} #{@exchange.to_s(true)}" else return "" end end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('n', @preference, canonical) msg.put_name(@exchange, canonical) end def self.decode_rdata(msg) #:nodoc: all preference, = msg.get_unpack('n') exchange = msg.get_name return self.new([preference, exchange]) end end end enddnsruby-1.61.3/lib/dnsruby/resource/SSHFP.rb0000644000004100000410000000463413607400362020623 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR class SSHFP < RR ClassValue = nil #:nodoc: all TypeValue = Types::SSHFP #:nodoc: all attr_accessor :alg attr_accessor :fptype attr_accessor :fp class Algorithms < CodeMapper RSA = 1 DSS = 2 update() end class FpTypes < CodeMapper SHA1 = 1 update() end def from_data(data) #:nodoc: all alg, fptype, @fp = data @alg = Algorithms.new(alg) @fptype = FpTypes.new(fptype) end def from_hash(hash) if hash[:alg] @alg = Algorithms.new(hash[:alg]) end if hash[:fptype] @fptype = FpTypes.new(hash[:fptype]) end if hash[:fp] @fp = hash[:fp] end end def from_string(input) if (input.length > 0) names = input.split(" ") begin @alg = Algorithms.new(names[0].to_i) rescue ArgumentError @alg = Algorithms.new(names[0]) end begin @fptype = FpTypes.new(names[1].to_i) rescue ArgumentError @fptype = FpTypes.new(names[1]) end remaining = "" for i in 2..(names.length + 1) remaining += names[i].to_s end @fp = [remaining].pack("H*") end end def rdata_to_string ret = "#{@alg.code} #{@fptype.code} " ret += @fp.unpack("H*")[0] return ret end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack("c", @alg.code) msg.put_pack("c", @fptype.code) msg.put_bytes(@fp) end def self.decode_rdata(msg) #:nodoc: all alg, fptype = msg.get_unpack("cc") fp = msg.get_bytes return self.new([alg, fptype, fp]) end end end end dnsruby-1.61.3/lib/dnsruby/resource/IPSECKEY.rb0000644000004100000410000001046413607400362021152 0ustar www-datawww-data# -- # Copyright 2009 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class RR class IPSECKEY < RR ClassValue = nil #:nodoc: all TypeValue = Types::IPSECKEY #:nodoc: all # An 8-bit precedence for this field. Lower values are preferred. attr_accessor :precedence # Specifies the type of gateway : # 0 - no gateway present # 1 - 4 byte IPv4 address present # 2 - 16 byte IPv6 address present # 3 - wire-encoded domain name present attr_accessor :gateway_type # The algorithm used by this key : # 0 - no key present # 1 - DSA key present # 2 - RSA key present attr_accessor :algorithm # The gateway. May either be a 32-bit network order IPv4 address, or a # 128-bit IPv6 address, or a domain name, or may not be present. attr_accessor :gateway def from_data(data) #:nodoc: all @precedence = data[0] @gateway_type = data[1] @algorithm = data[2] @public_key = nil @gateway = load_gateway_from_string(@gateway_type, data[3]) if (@gateway) @public_key = data[4] else @public_key = data[3] end end def from_hash(hash) @precedence = hash[:precedence] @gateway_type = hash[:gateway_type] @algorithm = hash[:algorithm] @gateway = load_gateway_from_string(@gateway_type, hash[:gateway]) @public_key = hash[:public_key] end def load_gateway_from_string(gateway_type, s) gateway = nil if (gateway_type == 0) gateway = nil elsif (gateway_type == 1) # Load IPv4 gateway gateway = IPv4.create(s) elsif (gateway_type == 2) # Load IPv6 gateway gateway = IPv6.create(s) else # Load gateway domain name gateway = Name.create(s) end return gateway end def public_key_string [@public_key.to_s].pack("m*").gsub("\n", "") end def public_key_from_string(key_text) key_text.gsub!(/\n/, "") key_text.gsub!(/ /, "") return key_text.unpack("m*")[0] end def from_string(input) if (input.length > 0) split = input.split(" ") @precedence = split[0].to_i @gateway_type = split[1].to_i @algorithm = split[2].to_i @gateway = load_gateway_from_string(@gateway_type, split[3]) @public_key = public_key_from_string(split[4]) end end def rdata_to_string #:nodoc: all ret = "#{@precedence} #{@gateway_type} #{@algorithm} " if (@gateway_type > 0) ret += "#{@gateway} " end ret += "#{public_key_string()}" return ret end def encode_rdata(msg, canonical=false) #:nodoc: all msg.put_pack('ccc', @precedence, @gateway_type, @algorithm) if ([1,2].include?@gateway_type) msg.put_bytes(@gateway.address) end if (@gateway_type == 3) msg.put_name(@gateway, true) # gateway MUST NOT be compressed end msg.put_bytes(@public_key) end def self.decode_rdata(msg) #:nodoc: all precedence, gateway_type, algorithm = msg.get_unpack('ccc') gateway = nil if (gateway_type == 1) gateway = IPv4.new(msg.get_bytes(4)) elsif (gateway_type == 2) gateway = IPv6.new(msg.get_bytes(16)) elsif (gateway_type == 3) gateway = msg.get_name end public_key = msg.get_bytes if (gateway_type == 0) return self.new( [precedence, gateway_type, algorithm, public_key]) else return self.new( [precedence, gateway_type, algorithm, gateway, public_key]) end end end end enddnsruby-1.61.3/lib/dnsruby/packet_sender.rb0000644000004100000410000006765113607400362020730 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby/select_thread' require 'ipaddr' # require 'dnsruby/iana_ports' module Dnsruby class PacketSender # :nodoc: all include Socket::Constants @@authoritative_cache = Cache.new @@recursive_cache = Cache.new def PacketSender.cache(query, response) return if response.cached # ONLY cache the response if it is not an update response question = query.question()[0] if (query.do_caching && (query.class != Update) && (question.qtype != Types::AXFR) && (question.qtype != Types::IXFR) && (response.rcode == RCode::NOERROR) &&(!response.tsig) && (query.class != Update) && (response.header.ancount > 0)) # # @TODO@ What about TSIG-signed responses? # Don't cache any packets with "*" in the query name! (RFC1034 sec 4.3.3) if (!question.qname.to_s.include? "*") # Now cache response RRSets if (query.header.rd) PacketSender.cache_recursive(response); else PacketSender.cache_authoritative(response); end end end end def PacketSender.cache_authoritative(answer) return if !answer.header.aa @@authoritative_cache.add(answer) end def PacketSender.cache_recursive(answer) @@recursive_cache.add(answer) end def PacketSender.clear_caches @@recursive_cache.clear @@authoritative_cache.clear end def PacketSender.recursive_cache_length @@recursive_cache.length end attr_accessor :packet_timeout # The port on the resolver to send queries to. # # Defaults to 53 attr_accessor :port # Use TCP rather than UDP as the transport. # # Defaults to false attr_accessor :use_tcp # Reuse tcp connection # # Defaults to false attr_accessor :tcp_pipelining # Limit the number of queries per pipeline attr_accessor :tcp_pipelining_max_queries # Use UDP only - don't use TCP # For test/debug purposes only # Defaults to false attr_accessor :no_tcp # The TSIG record to sign/verify messages with attr_reader :tsig # Don't worry if the response is truncated - return it anyway. # # Defaults to false attr_accessor :ignore_truncation # The source address to send queries from # # Defaults to localhost attr_reader :src_address # should the Recursion Desired bit be set on queries? # # Defaults to true attr_accessor :recurse # The max UDP packet size # # Defaults to 512 attr_reader :udp_size # The address of the resolver to send queries to attr_reader :server # Use DNSSEC for this PacketSender # dnssec defaults to ON attr_reader :dnssec # Set the source address. If the arg is nil, do nothing def src_address6=(arg) if (not arg.nil?) @src_address6 = arg end end # Set the source address. If the arg is nil, do nothing def src_address=(arg) if (not arg.nil?) @src_address = arg end end # Sets the TSIG to sign outgoing messages with. # Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key) # Pass in nil to stop tsig signing. # It is possible for client code to sign packets prior to sending - see # Dnsruby::RR::TSIG#apply and Dnsruby::Message#sign # Note that pre-signed packets will not be signed by PacketSender. # * res.tsig=(tsig_rr) # * res.tsig=(key_name, key) # * res.tsig=nil # Stop the resolver from signing def tsig=(*args) @tsig = Resolver.get_tsig(args) end def dnssec=(on) @dnssec=on if (on) # Set the UDP size (RFC 4035 section 4.1) if (udp_packet_size < Resolver::MinDnssecUdpSize) self.udp_size = Resolver::MinDnssecUdpSize end end end def udp_size=(size) @udp_size = size end def server=(server) Dnsruby.log.debug { "InternalResolver setting server to #{server}" } @server=Config.resolve_server(server) check_ipv6 end # Can take a hash with the following optional keys : # # * :server # * :port # * :use_tcp # * :tcp_pipelining # * :tcp_pipelining_max_queries # * :no_tcp # * :ignore_truncation # * :src_address # * :src_address6 # * :src_port # * :udp_size # * :tsig # * :packet_timeout # * :recurse def initialize(*args) arg=args[0] @ipv6 = false @packet_timeout = Resolver::DefaultPacketTimeout @port = Resolver::DefaultPort @udp_size = Resolver::DefaultUDPSize @dnssec = Resolver::DefaultDnssec @use_tcp = false @no_tcp = false @tsig = nil @ignore_truncation = false @src_address = '0.0.0.0' @src_address6 = '::' @src_port = [0] @recurse = true @tcp_pipelining = false @tcp_pipelining_max_queries = :infinite @use_counts = {} if (arg==nil) # Get default config config = Config.new # @server = config.nameserver[0] elsif (arg.kind_of? String) @server=arg elsif (arg.kind_of? Name) @server=arg elsif (arg.kind_of? Hash) arg.keys.each do |attr| begin if (((attr.to_s == "src_address")||(attr.to_s == "src_address6")) && ((arg[attr] == nil) || (arg[attr] == ""))) else send(attr.to_s+"=", arg[attr]) end rescue Exception => e Dnsruby.log.error { "PacketSender : Argument #{attr}, #{arg[attr]} not valid : #{e}\n" } end # end end end # Check server is IP @server=Config.resolve_server(@server) check_ipv6 # ResolverRegister::register_single_resolver(self) end def check_ipv6 begin i = IPv4.create(@server) # @src_address = '0.0.0.0' @ipv6=false rescue Exception begin i = IPv6.create(@server) # @src_address6 = '::' @ipv6=true rescue Exception Dnsruby.log.error { "Server is neither IPv4 or IPv6!\n" } end end end def close # @TODO@ What about closing? # Any queries to complete? Sockets to close? end # Asynchronously send a Message to the server. The send can be done using just # Dnsruby. Support for EventMachine has been deprecated. # # == Dnsruby pure Ruby event loop : # # A client_queue is supplied by the client, # along with an optional client_query_id to identify the response. The client_query_id # is generated, if not supplied, and returned to the client. # When the response is known, the tuple # (query_id, response_message, response_exception) is put in the queue for the client to process. # # The query is sent synchronously in the caller's thread. The select thread is then used to # listen for and process the response (up to pushing it to the client_queue). The client thread # is then used to retrieve the response and deal with it. # # Takes : # # * msg - the message to send # * client_queue - a Queue to push the response to, when it arrives # * client_query_id - an optional ID to identify the query to the client # * use_tcp - whether to use TCP (defaults to PacketSender.use_tcp) # # Returns : # # * client_query_id - to identify the query response to the client. This ID is # generated if it is not passed in by the client # # If the native Dsnruby networking layer is being used, then this method returns the client_query_id # # id = res.send_async(msg, queue) # NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp) # id = res.send_async(msg, queue, id) # id = res.send_async(msg, queue, id, use_tcp) # # Use Message#send_raw to send the packet with an untouched header. # Use Message#do_caching to tell dnsruby whether to check the cache before # sending, and update the cache upon receiving a response. # Use Message#do_validation to tell dnsruby whether or not to do DNSSEC # validation for this particular packet (assuming SingleResolver#dnssec == true) # Note that these options should not normally be used! def send_async(*args) # msg, client_queue, client_query_id, use_tcp=@use_tcp) # @TODO@ Need to select a good Header ID here - see forgery-resilience RFC draft for details msg = args[0] client_query_id = nil client_queue = nil use_tcp = @use_tcp if (msg.kind_of? String) msg = Message.new(msg) if (@dnssec) msg.header.cd = @dnssec # we'll do our own validation by default if (Dnssec.no_keys?) msg.header.cd = false end end end if (args.length > 1) if (args[1].class==Queue) client_queue = args[1] elsif (args.length == 2) use_tcp = args[1] end if (args.length > 2) client_query_id = args[2] if (args.length > 3) use_tcp = args[3] end end end # Need to keep track of the request mac (if using tsig) so we can validate the response (RFC2845 4.1) # #Are we using EventMachine or native Dnsruby? # if (Resolver.eventmachine?) # return send_eventmachine(query_packet, msg, client_query_id, client_queue, use_tcp) # else if (!client_query_id) client_query_id = Time.now + rand(10000) # is this safe?! end begin query_packet = make_query_packet(msg, use_tcp) rescue EncodeError => err Dnsruby.log.error { "#{err}" } st = SelectThread.instance st.push_exception_to_select(client_query_id, client_queue, err, nil) return end if (msg.do_caching && (msg.class != Update)) # Check the cache!! cachedanswer = nil if (msg.header.rd) cachedanswer = @@recursive_cache.find(msg.question()[0].qname, msg.question()[0].type) else cachedanswer = @@authoritative_cache.find(msg.question()[0].qname, msg.question()[0].type) end if (cachedanswer) TheLog.debug("Sending cached answer to client\n") # @TODO@ Fix up the header - ID and flags cachedanswer.header.id = msg.header.id # If we can find the answer, send it to the client straight away # Post the result to the client using SelectThread st = SelectThread.instance st.push_response_to_select(client_query_id, client_queue, cachedanswer, msg, self) return client_query_id end end # Otherwise, run the query if (udp_packet_size < query_packet.length) if (@no_tcp) # Can't send the message - abort! err=IOError.new("Can't send message - too big for UDP and no_tcp=true") Dnsruby.log.error { "#{err}" } st.push_exception_to_select(client_query_id, client_queue, err, nil) return end Dnsruby.log.debug { "Query packet length exceeds max UDP packet size - using TCP" } use_tcp = true end send_dnsruby(query_packet, msg, client_query_id, client_queue, use_tcp) return client_query_id # end end # This method returns the current tcp socket for pipelining # If this is the first time the method is called then the socket is bound to # @src_address:@src_port and connected to the remote dns server @server:@port. # If the connection has been closed because of an EOF on recv_nonblock (closed by server) # the function will recreate of the socket (since @pipeline_socket.connect will result in a IOError # exception) # In general, every subsequent call the function will either return the current tcp # pipeline socket or a new connected socket if the current one was closed by the server def tcp_pipeline_socket(src_port) Dnsruby.log.debug("Using tcp_pipeline_socket") sockaddr = Socket.sockaddr_in(@port, @server) reuse_pipeline_socket = -> do begin max = @tcp_pipelining_max_queries use_count = @use_counts[@pipeline_socket] if use_count && max != :infinite && use_count >= max #we can't reuse the socket since max is reached @use_counts.delete(@pipeline_socket) @pipeline_socket = nil Dnsruby.log.debug("Max queries per connection attained - creating new socket") else @pipeline_socket.connect(sockaddr) end rescue Errno::EISCONN #already connected, do nothing and reuse! rescue IOError, Errno::ECONNRESET #close by remote host, reconnect @pipeline_socket = nil Dnsruby.log.debug("Connection closed - recreating socket") end end create_pipeline_socket = -> do @tcp_pipeline_local_port = src_port src_address = @ipv6 ? @src_address6 : @src_address begin @pipeline_socket = Socket.new(Socket::AF_INET, Socket::SOCK_STREAM, 0) @pipeline_socket.bind(Addrinfo.tcp(src_address, src_port)) @pipeline_socket.connect(sockaddr) Dnsruby.log.debug("Creating socket #{src_address}:#{src_port}") @use_counts[@pipeline_socket] = 0 rescue Exception => e @pipeline_socket = nil raise e end end # Don't combine the following 2 statements; the reuse lambda can set the # socket to nil and if so we'd want to call the create lambda to recreate it. reuse_pipeline_socket.() if @pipeline_socket new_socket = @pipeline_socket.nil? create_pipeline_socket.() unless @pipeline_socket @use_counts[@pipeline_socket] += 1 [@pipeline_socket, new_socket] end # This method sends the packet using the built-in pure Ruby event loop, with no dependencies. def send_dnsruby(query_bytes, query, client_query_id, client_queue, use_tcp) #:nodoc: all endtime = Time.now + @packet_timeout # First send the query (synchronously) st = SelectThread.instance socket = nil runnextportloop = true numtries = 0 src_address = @src_address if (@ipv6) src_address = @src_address6 end while (runnextportloop) do begin numtries += 1 src_port = get_next_src_port if (use_tcp) begin if (@tcp_pipelining) socket, new_socket = tcp_pipeline_socket(src_port) src_port = @tcp_pipeline_local_port else socket = Socket.tcp(@server, @port, src_address, src_port, connect_timeout: @packet_timeout) new_socket = true end rescue Errno::EBADF, Errno::ENETUNREACH => e # Can't create a connection err=IOError.new("TCP connection error to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e}") Dnsruby.log.error { "#{err}" } st.push_exception_to_select(client_query_id, client_queue, err, nil) return end else socket = nil # JRuby UDPSocket only takes 0 parameters - no IPv6 support in JRuby... if (/java/ =~ RUBY_PLATFORM) socket = UDPSocket.new() else # ipv6 = @src_address =~ /:/ socket = UDPSocket.new(@ipv6 ? Socket::AF_INET6 : Socket::AF_INET) end new_socket = true socket.bind(src_address, src_port) socket.connect(@server, @port) end runnextportloop = false rescue Exception => e if (socket!=nil) begin #let the select thread close the socket if tcp_pipeli socket.close unless @tcp_pipelining && !new_socket rescue Exception end end # Try again if the error was EADDRINUSE and a random source port is used # Maybe try a max number of times? if ((e.class != Errno::EADDRINUSE) || (numtries > 50) || ((e.class == Errno::EADDRINUSE) && (src_port == @src_port[0]))) err_msg = "dnsruby can't connect to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception = #{e.class}, #{e} #{e.backtrace}" err=IOError.new(err_msg) Dnsruby.log.error( "#{err}") Dnsruby.log.error(e.backtrace) if @tcp_pipelining st.push_exception_to_select(client_query_id, client_queue, SocketEofResolvError.new(err_msg), nil) else st.push_exception_to_select(client_query_id, client_queue, err, nil) end return end end end if (socket==nil) err=IOError.new("dnsruby can't connect to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}") Dnsruby.log.error { "#{err}" } st.push_exception_to_select(client_query_id, client_queue, err, nil) return end Dnsruby.log.debug { "Sending packet to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}" } # print "#{Time.now} : Sending packet to #{@server} : #{query.question()[0].qname}, #{query.question()[0].qtype}\n" # Listen for the response before we send the packet (to avoid any race conditions) query_settings = SelectThread::QuerySettings.new(query_bytes, query, @ignore_truncation, client_queue, client_query_id, socket, @server, @port, endtime, udp_packet_size, self) query_settings.is_persistent_socket = @tcp_pipelining if use_tcp query_settings.tcp_pipelining_max_queries = @tcp_pipelining_max_queries if @tcp_pipelining begin if (use_tcp) lenmsg = [query_bytes.length].pack('n') socket.send(lenmsg, 0) end socket.send(query_bytes, 0) # The select thread will now wait for the response and send that or a # timeout back to the client_queue. st.add_to_select(query_settings) rescue Exception => e err_msg = "Send failed to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception : #{e}" err=IOError.new(err_msg) Dnsruby.log.error { "#{err}" } Dnsruby.log.error(e.backtrace) if @tcp_pipelining st.push_exception_to_select(client_query_id, client_queue, SocketEofResolvError.new(err_msg), nil) if new_socket else st.push_exception_to_select(client_query_id, client_queue, err, nil) end begin #we let the select_thread close the socket when doing tcp #pipelining socket.close unless @tcp_pipelining && !new_socket rescue Exception end return end Dnsruby.log.debug { "Packet sent to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}" } # print "Packet sent to #{@server}:#{@port} from #{@src_address}:#{src_port}, use_tcp=#{use_tcp} : #{query.question()[0].qname}, #{query.question()[0].qtype}\n" end # The source port to send queries from # Returns either a single Integer or an Array # e.g. "0", or "[60001, 60002, 60007]" # # Defaults to 0 - random port def src_port if (@src_port.length == 1) return @src_port[0] end return @src_port end # Can be a single Integer or a Range or an Array # If an invalid port is selected (one reserved by # IANA), then an ArgumentError will be raised. # # res.src_port=0 # res.src_port=[60001,60005,60010] # res.src_port=60015..60115 # def src_port=(p) @src_port=[] add_src_port(p) end # Can be a single Integer or a Range or an Array # If an invalid port is selected (one reserved by # IANA), then an ArgumentError will be raised. # "0" means "any valid port" - this is only a viable # option if it is the only port in the list. # An ArgumentError will be raised if "0" is added to # an existing set of source ports. # # res.add_src_port(60000) # res.add_src_port([60001,60005,60010]) # res.add_src_port(60015..60115) # def add_src_port(p) if (Resolver.check_port(p, @src_port)) a = Resolver.get_ports_from(p) a.each do |x| if ((@src_port.length > 0) && (x == 0)) raise ArgumentError.new("src_port of 0 only allowed as only src_port value (currently #{@src_port.length} values") end @src_port.push(x) end end end def get_next_src_port # Different OSes have different interpretations of "random port" here. # Apparently, Linux will just give you the same port as last time, unless it is still # open, in which case you get n+1. # We need to determine an actual (random) number here, then ask the OS for it, and # continue until we get one. if (@src_port[0] == 0) candidate = -1 # # better to construct an array of all the ports we *can* use, and then just pick one at random! # candidate = Iana::UNRESERVED_PORTS[rand(Iana::UNRESERVED_PORTS.length())] # # while (!(Resolver.port_in_range(candidate))) # # candidate = (rand(65535-1024) + 1024) # # end # @TODO@ Should probably construct a bitmap of the IANA ports... candidate = 50000 + (rand(15535)) # pick one over 50000 return candidate end pos = rand(@src_port.length) return @src_port[pos] end def check_response(response, response_bytes, query, client_queue, client_query_id, tcp) # @TODO@ Should send_raw avoid this? if (!query.send_raw) sig_value = check_tsig(query, response, response_bytes) if (sig_value != :okay) # Should send error back up to Resolver here, and then NOT QUERY AGAIN!!! return sig_value end if ((response.header.get_header_rcode == RCode.FORMERR) && (query.header.arcount == 0)) # Raise an error return true end # Should check that question section is same as question that was sent! RFC 5452 # If it's not an update... if (query.class == Update) # @TODO@!! else if ((response.question.size == 0) || (response.question[0].qname.labels != query.question[0].qname.labels) || (response.question[0].qtype != query.question[0].qtype) || (response.question[0].qclass != query.question[0].qclass) || (response.question.length != query.question.length) || (response.header.id != query.header.id)) TheLog.info("Incorrect packet returned : #{response.to_s}") return false end end # end # IF WE GET FORMERR BACK HERE (and we have EDNS0 on) THEN # TRY AGAIN WITH NO OPT RECORDS! (rfc2671 section 5.3) if ((response.header.get_header_rcode == RCode.FORMERR) && (query.header.arcount > 0)) # try resending the message with no OPT record query.remove_additional query.send_raw = true send_async(query, client_queue, client_query_id, false) return false end if (response.header.tc && !tcp && !@ignore_truncation) if (@no_tcp) Dnsruby.log.debug { "Truncated response - not resending over TCP as no_tcp==true" } else # Try to resend over tcp Dnsruby.log.debug { "Truncated - resending over TCP" } # @TODO@ Are the query options used correctly here? DNSSEC in particular... # query.send_raw = true # Make sure that the packet is not messed with. send_async(query, client_queue, client_query_id, true) return false end end end return true end def check_tsig(query, response, response_bytes) if (query.tsig) if (response.tsig) if !query.tsig.verify(query, response, response_bytes) # Discard packet and wait for correctly signed response Dnsruby.log.error { "TSIG authentication failed!" } return TsigError.new end else # Treated as having format error and discarded (RFC2845, 4.6) # but return a different error code, because some servers fail at # this Dnsruby.log.error { "Expecting TSIG signed response, but got unsigned response - discarding" } return TsigNotSignedResponseError.new end elsif (response.tsig) # Error - signed response to unsigned query Dnsruby.log.error { "Signed response to unsigned query" } return TsigError.new end return :okay end def make_query(name, type = Types::A, klass = Classes::IN, set_cd=@dnssec) msg = Message.new msg.header.rd = 1 msg.add_question(name, type, klass) if (@dnssec) msg.header.cd = set_cd # We do our own validation by default end return msg end # Prepare the packet for sending def make_query_packet(packet, use_tcp = @use_tcp) #:nodoc: all if (!packet.send_raw) # Don't mess with this packet! if (packet.header.opcode == OpCode.QUERY || @recurse) packet.header.rd=@recurse end # Only do this if the packet has not been prepared already! if (@dnssec) prepare_for_dnssec(packet) elsif ((udp_packet_size > Resolver::DefaultUDPSize) && !use_tcp) # if ((udp_packet_size > Resolver::DefaultUDPSize) && !use_tcp) # @TODO@ What if an existing OPT RR is not big enough? Should we replace it? add_opt_rr(packet) end end if (@tsig && !packet.signed?) @tsig.apply(packet) end return packet.encode end def add_opt_rr(packet) Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size}.\n" } # RFC 3225 optrr = RR::OPT.new(udp_packet_size) # Only one OPT RR allowed per packet - do we already have one? if (packet.additional.rrset(packet.question()[0].qname, Types::OPT).rrs.length == 0) packet.add_additional(optrr) end end def prepare_for_dnssec(packet) # RFC 4035 Dnsruby.log.debug { ";; Adding EDNS extension with UDP packetsize #{udp_packet_size} and DNS OK bit set\n" } optrr = RR::OPT.new(udp_packet_size) # Decimal UDPpayload optrr.dnssec_ok=true if (packet.additional.rrset(packet.question()[0].qname, Types::OPT).rrs.length == 0) packet.add_additional(optrr) end packet.header.ad = false # RFC 4035 section 4.6 # SHOULD SET CD HERE!!! if (packet.do_validation) packet.header.cd = true end if (Dnssec.no_keys?) packet.header.cd = false end end # Return the packet size to use for UDP def udp_packet_size # if @udp_size > DefaultUDPSize then we use EDNS and # @udp_size should be taken as the maximum packet_data length ret = (@udp_size > Resolver::DefaultUDPSize ? @udp_size : Resolver::DefaultUDPSize) return ret end end end dnsruby-1.61.3/lib/dnsruby/select_thread.rb0000644000004100000410000007045413607400362020722 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'socket' # require 'thread' begin require 'fastthread' rescue LoadError require 'thread' end require 'set' require 'singleton' require 'dnsruby/validator_thread.rb' module Dnsruby class SelectThread #:nodoc: all class SelectWakeup < RuntimeError; end include Singleton # This singleton class runs a continuous select loop which # listens for responses on all of the in-use sockets. # When a new query is sent, the thread is woken up, and # the socket is added to the select loop (and the new timeout # calculated). # Note that a combination of the socket and the packet ID is # sufficient to uniquely identify the query to the select thread. # # But how do we find the response queue for a particular query? # Hash of client_id->[query, client_queue, socket] # and socket->[client_id] # # @todo@ should we implement some of cancel function? def initialize @@mutex = Mutex.new @@mutex.synchronize { @@in_select=false # @@notifier,@@notified=IO.pipe @@sockets = Set.new @@timeouts = Hash.new # @@mutex.synchronize do @@query_hash = Hash.new @@socket_hash = Hash.new @@socket_is_persistent = Hash.new @@observers = Hash.new @@tcp_buffers=Hash.new @@socket_remaining_queries = Hash.new @@tick_observers = [] @@queued_exceptions=[] @@queued_responses=[] @@queued_validation_responses=[] @@wakeup_sockets = get_socket_pair @@sockets << @@wakeup_sockets[1] # Suppress reverse lookups BasicSocket.do_not_reverse_lookup = true # end # Now start the select thread @@select_thread = Thread.new { do_select } # # Start the validator thread # @@validator = ValidatorThread.instance } end def get_socket_pair # Emulate socketpair on platforms which don't support it srv = nil begin srv = TCPServer.new('localhost', 0) rescue Errno::EADDRNOTAVAIL, SocketError # OSX Snow Leopard issue - need to use explicit IP begin srv = TCPServer.new('127.0.0.1', 0) rescue Error # Try IPv6 srv = TCPServer.new('::1', 0) end end rsock = TCPSocket.new(srv.addr[3], srv.addr[1]) lsock = srv.accept srv.close return [lsock, rsock] end class QuerySettings attr_accessor :query_bytes, :query, :ignore_truncation, :client_queue, :client_query_id, :socket, :dest_server, :dest_port, :endtime, :udp_packet_size, :single_resolver, :is_persistent_socket, :tcp_pipelining_max_queries # new(query_bytes, query, ignore_truncation, client_queue, client_query_id, # socket, dest_server, dest_port, endtime, , udp_packet_size, single_resolver) def initialize(*args) @query_bytes = args[0] @query = args[1] @ignore_truncation=args[2] @client_queue = args[3] @client_query_id = args[4] @socket = args[5] @dest_server = args[6] @dest_port=args[7] @endtime = args[8] @udp_packet_size = args[9] @single_resolver = args[10] @is_persistent_socket = false @tcp_pipelining_max_queries = nil end end def tcp?(socket) type = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) [Socket::SOCK_STREAM].pack("i") == type.data end def udp?(socket) type = socket.getsockopt(Socket::SOL_SOCKET, Socket::SO_TYPE) [Socket::SOCK_DGRAM].pack("i") == type.data end def add_to_select(query_settings) # Add the query to sockets, and then wake the select thread up @@mutex.synchronize { check_select_thread_synchronized # @TODO@ This assumes that all client_query_ids are unique! # Would be a good idea at least to check this... @@query_hash[query_settings.client_query_id]=query_settings @@socket_hash[query_settings.socket] ||= [] @@socket_hash[query_settings.socket] << query_settings.client_query_id @@socket_remaining_queries[query_settings.socket] ||= query_settings.tcp_pipelining_max_queries if query_settings.tcp_pipelining_max_queries != :infinite @@timeouts[query_settings.client_query_id]=query_settings.endtime @@sockets << query_settings.socket @@socket_is_persistent[query_settings.socket] = query_settings.is_persistent_socket } begin @@wakeup_sockets[0].send("wakeup!", 0) rescue Exception => e # do nothing end end def check_select_thread_synchronized if (!@@select_thread.alive?) Dnsruby.log.debug{"Restarting select thread"} @@select_thread = Thread.new { do_select } end end def select_thread_alive? ret=true @@mutex.synchronize{ ret = @@select_thread.alive? } return ret end def do_select unused_loop_count = 0 last_tick_time = Time.now - 10 while true do if (last_tick_time < (Time.now - 0.5)) send_tick_to_observers # ONLY NEED TO SEND THIS TWICE A SECOND - NOT EVERY SELECT!!! last_tick_time = Time.now end send_queued_exceptions send_queued_responses send_queued_validation_responses timeout = tick_time = 0.1 # We provide a timer service to various Dnsruby classes sockets, timeouts, has_observer = @@mutex.synchronize { [@@sockets.to_a, @@timeouts.values, !@@observers.empty?] } if (timeouts.length > 0) timeouts.sort! timeout = timeouts[0] - Time.now if (timeout <= 0) process_timeouts timeout = 0 next end end ready=nil if (has_observer && (timeout > tick_time)) timeout = tick_time end # next if (timeout < 0) begin ready, write, errors = IO.select(sockets, nil, nil, timeout) rescue SelectWakeup # If SelectWakeup, then just restart this loop - the select call will be made with the new data next rescue IOError, EncodeError => e # print "IO Error =: #{e}\n" exceptions = clean_up_closed_sockets exceptions.each { |exception| send_exception_to_client(*exception) } next end if ready && ready.include?(@@wakeup_sockets[1]) ready.delete(@@wakeup_sockets[1]) wakeup_msg = "loop" begin while wakeup_msg && wakeup_msg.length > 0 wakeup_msg = @@wakeup_sockets[1].recv_nonblock(20) end rescue # do nothing end end if (ready == nil) # process the timeouts process_timeouts unused_loop_count+=1 else process_ready(ready) unused_loop_count=0 # process_error(errors) end @@mutex.synchronize do if (unused_loop_count > 10 && @@query_hash.empty? && @@observers.empty?) Dnsruby.log.debug("Try stop select loop") non_persistent_sockets = @@sockets.select { |s| ! @@socket_is_persistent[s] } non_persistent_sockets.each do |socket| socket.close rescue nil @@sockets.delete(socket) end Dnsruby.log.debug("Deleted #{non_persistent_sockets.size} non-persistent sockets," + " #{@@sockets.count} persistent sockets remain.") @@socket_hash.clear if @@sockets.empty? Dnsruby.log.debug("Stopping select loop") return end end end # } end end # Removes closed sockets from @@sockets, and returns an array containing 1 # exception for each closed socket contained in @@socket_hash. def clean_up_closed_sockets exceptions = @@mutex.synchronize do closed_sockets_in_hash = @@sockets.select(&:closed?).select { |s| @@socket_hash[s] } @@sockets.delete_if { | socket | socket.closed? } closed_sockets_in_hash.each_with_object([]) do |socket, exceptions| @@socket_hash[socket].each do | client_id | exceptions << [SocketEofResolvError.new("TCP socket closed before all answers received"), socket, client_id] end end end end def process_error(errors) Dnsruby.log.debug{"Error! #{errors.inspect}"} # @todo@ Process errors [can we do this in single socket environment?] end def get_active_ids(queries, id) queries.keys.select { |client_query_id| client_query_id[1].header.id == id } end # @@query_hash[query_settings.client_query_id]=query_settings def process_ready(ready) persistent_sockets, nonpersistent_sockets = @@mutex.synchronize { ready.partition { |socket| persistent?(socket) } } nonpersistent_sockets.each do |socket| query_settings = @@mutex.synchronize { @@query_hash[@@socket_hash[socket][0]] } next if !query_settings udp_packet_size = query_settings.udp_packet_size msg, bytes = get_incoming_data(socket, udp_packet_size) process_message(msg, bytes, socket) if msg ready.delete(socket) end persistent_sockets.each do |socket| msg, bytes = get_incoming_data(socket, 0) process_message(msg, bytes, socket) if msg ready.delete(socket) end end def process_message(msg, bytes, socket) @@mutex.synchronize do ids = get_active_ids(@@query_hash, msg.header.id) return if ids.empty? # should be only one query_settings = @@query_hash[ids[0]].clone end answerip = msg.answerip.downcase answerfrom = msg.answerfrom.downcase answeripaddr = IPAddr.new(answerip) dest_server = IPAddr.new("0.0.0.0") begin destserveripaddr = IPAddr.new(dest_server) rescue ArgumentError # Host name not IP address end if (dest_server && (dest_server != '0.0.0.0') && (answeripaddr != destserveripaddr) && (answerfrom != dest_server)) Dnsruby.log.warn("Unsolicited response received from #{answerip} instead of #{query_settings.dest_server}") else send_response_to_client(msg, bytes, socket) end end def send_response_to_client(msg, bytes, socket) # Figure out which client_ids we were expecting on this socket, then see if any header ids match up # @TODO@ Can get rid of this, as we only have one query per socket. client_ids=[] @@mutex.synchronize{ client_ids = @@socket_hash[socket].clone } # get the queries associated with them client_ids.each do |id| query_header_id=nil @@mutex.synchronize{ query_header_id = @@query_hash[id].query.header.id } if (query_header_id == msg.header.id) # process the response client_queue = nil res = nil query=nil @@mutex.synchronize{ client_queue = @@query_hash[id].client_queue res = @@query_hash[id].single_resolver query = @@query_hash[id].query } tcp = tcp?(socket) # At this point, we should check if the response is OK if (ret = res.check_response(msg, bytes, query, client_queue, id, tcp)) remove_id(id) exception = msg.get_exception if (ret.kind_of?TsigError) exception = ret end Dnsruby.log.debug{"Pushing response to client queue"} push_to_client(id, client_queue, msg, exception, query, res) # client_queue.push([id, msg, exception]) # notify_queue_observers(client_queue, id) else # Sending query again - don't return response end return end end # If not, then we have an error Dnsruby.log.error{"Stray packet - " + msg.inspect + "\n from " + socket.inspect} print("Stray packet - " + msg.question()[0].qname.to_s + " from " + msg.answerip.to_s + ", #{client_ids.length} client_ids\n") end def persistent?(socket) @@socket_is_persistent[socket] end def remove_id(id) @@mutex.synchronize do socket = @@query_hash[id].socket @@timeouts.delete(id) @@query_hash.delete(id) @@socket_hash[socket].delete(id) decrement_remaining_queries(socket) if persistent?(socket) if !persistent?(socket) || max_attained?(socket) @@sockets.delete(socket) @@socket_hash.delete(socket) Dnsruby.log.debug("Closing socket #{socket}") socket.close rescue nil end end end def decrement_remaining_queries(socket) if @@socket_remaining_queries[socket] @@socket_remaining_queries[socket] -= 1 end end def max_attained?(socket) remaining = @@socket_remaining_queries[socket] attained = persistent?(socket) && remaining && remaining <= 0 Dnsruby.log.debug("Max queries per conn attained") if attained attained end def process_timeouts # NOTE: It's @@timeouts we need to protect; after the clone we're ok timeouts = @@mutex.synchronize { @@timeouts.clone } time_now = Time.now timeouts.each do |client_id, timeout| if timeout < time_now send_exception_to_client(ResolvTimeout.new("Query timed out"), nil, client_id) end end end def tcp_read(socket) # Keep buffer for all TCP sockets, and return # to select after reading available data. Once all data has been received, # then process message. buf="" expected_length = 0 @@mutex.synchronize { buf, expected_length = @@tcp_buffers[socket] if (!buf) buf = "" expected_length = 2 @@tcp_buffers[socket]=[buf, expected_length] end } if (buf.length() < expected_length) begin input, = socket.recv_nonblock(expected_length-buf.length) if (input=="") Dnsruby.log.debug("EOF from server - no bytes read - closing socket") socket.close #EOF closed by server, if we were interrupted we need to resend exceptions = @@mutex.synchronize do @@sockets.delete(socket) #remove ourselves from select, app will have to retry #maybe fire an event @@socket_hash[socket].map do | client_id | [SocketEofResolvError.new("TCP socket closed before all answers received"), socket, client_id] end end exceptions.each { |exception| send_exception_to_client(*exception) } return false end buf << input rescue # Oh well - better luck next time! return false end end # If data is complete, then return it. if (buf.length == expected_length) if (expected_length == 2) # We just read the data_length field. Now we need to start reading that many bytes. @@mutex.synchronize { answersize = buf.unpack('n')[0] @@tcp_buffers[socket] = ["", answersize] } return tcp_read(socket) else # We just read the data - now return it @@mutex.synchronize { @@tcp_buffers.delete(socket) } return buf end else @@mutex.synchronize { @@tcp_buffers[socket]=[buf, expected_length] } return false end end def get_incoming_data(socket, packet_size) answerfrom,answerip,answerport,answersize=nil ans,buf = nil is_tcp = tcp?(socket) begin if is_tcp # Call TCP read here - that will take care of reading the 2 byte length, # and then the full packet - without blocking select. buf = tcp_read(socket) if (!buf) # Wait for the buffer to comletely fill # handle_recvfrom_failure(socket, "") return end else # @TODO@ Can we get recvfrom to stop issuing PTR queries when we already # know both the FQDN and the IP address? if (ret = socket.recvfrom(packet_size)) buf = ret[0] answerport=ret[1][1] answerfrom=ret[1][2] answerip=ret[1][3] answersize=(buf.length) else # recvfrom failed - why? Dnsruby.log.error{"Error - recvfrom failed from #{socket}"} handle_recvfrom_failure(socket, "") return end end rescue Exception => e Dnsruby.log.error{"Error - recvfrom failed from #{socket}, exception : #{e}"} handle_recvfrom_failure(socket, e) return end Dnsruby.log.debug{";; answer from #{answerfrom} : #{answersize} bytes\n"} begin ans = Message.decode(buf) if is_tcp @@mutex.synchronize do ids = get_active_ids(@@query_hash, ans.header.id) if ids.empty? Dnsruby.log.error("Decode error from #{answerip} but can't determine packet id") #todo add error event? The problem is we don't have a valid id so we don't #know which client queue to send the exception to end answerfrom = @@query_hash[ids[0]].dest_server answerip = answerfrom answerport = @@query_hash[ids[0]].dest_port end end rescue Exception => e Dnsruby.log.error("Decode error! #{e.class}, #{e}\nfor msg (length=#{buf.length}) : #{buf}") client_id=get_client_id_from_answerfrom(socket, answerip, answerport) if (client_id == nil) Dnsruby.log.error{"Decode error from #{answerip} but can't determine packet id"} end # We should check if the TC bit is set (if we can get that far) if ((DecodeError === e) && (e.partial_message.header.tc)) Dnsruby.log.error{"Decode error (from {answerip})! Header shows truncation, so trying again over TCP"} # If it is, then we should retry over TCP sent = false @@mutex.synchronize{ client_ids = @@socket_hash[socket] # get the queries associated with them client_ids.each do |id| query_header_id=nil query_header_id = @@query_hash[id].query.header.id if (query_header_id == e.partial_message.header.id) # process the response client_queue = nil res = nil query=nil client_queue = @@query_hash[id].client_queue res = @@query_hash[id].single_resolver query = @@query_hash[id].query # NOW RESEND OVER TCP! Thread.new { res.send_async(query, client_queue, id, true) } sent = true end end } if !sent send_exception_to_client(e, socket, client_id) end else send_exception_to_client(e, socket, client_id) end return end if (ans!= nil) Dnsruby.log.debug{"#{ans}"} ans.answerfrom=(answerfrom) ans.answersize=(answersize) ans.answerip =(answerip) end return ans, buf end def handle_recvfrom_failure(socket, exception) # No way to notify the client about this error, unless there was only one connection on the socket # Not a problem, as there only will ever be one connection on the socket (Kaminsky attack mitigation) ids_for_socket = [] @@mutex.synchronize{ ids_for_socket = @@socket_hash[socket] } if (ids_for_socket.length == 1) answerfrom=nil @@mutex.synchronize{ query_settings = @@query_hash[ids_for_socket[0]] answerfrom=query_settings.dest_server } send_exception_to_client(OtherResolvError.new("recvfrom failed from #{answerfrom}; #{exception}"), socket, ids_for_socket[0]) else Dnsruby.log.fatal{"Recvfrom failed from #{socket}, no way to tell query id"} end end def get_client_id_from_answerfrom(socket, answerip, answerport) # @TODO@ Can get rid of this, as there is only one query per socket client_id=nil # Figure out client id from answerfrom @@mutex.synchronize{ ids = @@socket_hash[socket] ids.each do |id| # Does this id speak to this dest_server? query_settings = @@query_hash[id] if (answerip == query_settings.dest_server && answerport == query_settings.dest_port) # We have a match client_id = id break end end } return client_id end def send_exception_to_client(err, socket, client_id, msg=nil) # find the client response queue client_queue = nil @@mutex.synchronize { client_queue = @@query_hash[client_id].client_queue } remove_id(client_id) # push_to_client(client_id, client_queue, msg, err) client_queue.push([client_id, Resolver::EventType::ERROR, msg, err]) notify_queue_observers(client_queue, client_id) end def push_exception_to_select(client_id, client_queue, err, msg) @@mutex.synchronize{ @@queued_exceptions.push([client_id, client_queue, err, msg]) } # Make sure select loop is running! if (@@select_thread && @@select_thread.alive?) else @@select_thread = Thread.new { do_select } end end def push_response_to_select(client_id, client_queue, msg, query, res) # This needs to queue the response TO THE SELECT THREAD, which then needs # to send it out from its normal loop. Dnsruby.log.debug{"Pushing response to client queue direct from resolver or validator"} @@mutex.synchronize{ err = nil if (msg.rcode == RCode.NXDOMAIN) err = NXDomain.new end @@queued_responses.push([client_id, client_queue, msg, err, query, res]) } # Make sure select loop is running! if (@@select_thread && @@select_thread.alive?) else @@select_thread = Thread.new { do_select } end end def push_validation_response_to_select(client_id, client_queue, msg, err, query, res) # This needs to queue the response TO THE SELECT THREAD, which then needs # to send it out from its normal loop. Dnsruby.log.debug{"Pushing response to client queue direct from resolver or validator"} @@mutex.synchronize{ @@queued_validation_responses.push([client_id, client_queue, msg, err, query, res]) } # Make sure select loop is running! if (@@select_thread && @@select_thread.alive?) else @@select_thread = Thread.new { do_select } end end def send_queued_exceptions exceptions = [] @@mutex.synchronize{ exceptions = @@queued_exceptions @@queued_exceptions = [] } exceptions.each do |item| client_id, client_queue, err, msg = item # push_to_client(client_id, client_queue, msg, err) client_queue.push([client_id, Resolver::EventType::ERROR, msg, err]) notify_queue_observers(client_queue, client_id) end end def send_queued_responses responses = [] @@mutex.synchronize{ responses = @@queued_responses @@queued_responses = [] } responses.each do |item| client_id, client_queue, msg, err, query, res = item # push_to_client(client_id, client_queue, msg, err) client_queue.push([client_id, Resolver::EventType::RECEIVED, msg, err]) notify_queue_observers(client_queue, client_id) # Do we need to validate this? The response has come from the cache - # validate it only if it has not been validated already # So, if we need to validate it, send it to the validation thread # Otherwise, send VALIDATED to the requester. if (((msg.security_level == Message::SecurityLevel.UNCHECKED) || (msg.security_level == Message::SecurityLevel.INDETERMINATE)) && (ValidatorThread.requires_validation?(query, msg, err, res))) validator = ValidatorThread.new(client_id, client_queue, msg, err, query ,self, res) validator.run else PacketSender.cache(query, msg) # The validator won't cache it, so we'd better do it now client_queue.push([client_id, Resolver::EventType::VALIDATED, msg, err]) notify_queue_observers(client_queue, client_id) end end end def send_queued_validation_responses responses = [] @@mutex.synchronize{ responses = @@queued_validation_responses @@queued_validation_responses = [] } responses.each do |item| client_id, client_queue, msg, err, query, res = item # push_to_client(client_id, client_queue, msg, err) client_queue.push([client_id, Resolver::EventType::VALIDATED, msg, err]) notify_queue_observers(client_queue, client_id) end end def push_to_client(client_id, client_queue, msg, err, query, res) # @TODO@ Really need to let the client know that we have received a valid response! # Can do that by calling notify_observers here, but with an identifier which # defines the response to be a "Response received - validating. Please stop sending" # type of response. client_queue.push([client_id, Resolver::EventType::RECEIVED, msg, err]) notify_queue_observers(client_queue, client_id) if (!err || (err.instance_of?(NXDomain))) # # This method now needs to push the response to the validator, # which will then take responsibility for delivering it to the client. # The validator will need access to the queue observers - validator = ValidatorThread.new(client_id, client_queue, msg, err, query ,self, res) validator.run # @@validator.add_to_queue([client_id, client_queue, msg, err, query, self, res]) end end def add_observer(client_queue, observer) @@mutex.synchronize { @@observers[client_queue]=observer check_select_thread_synchronized # Is this really necessary? The client should start the thread by sending a query, really... if (!@@tick_observers.include?observer) @@tick_observers.push(observer) end } end def remove_observer(client_queue, observer) @@mutex.synchronize { if (@@observers[client_queue]==observer) # @@observers.delete(observer) @@observers.delete(client_queue) else if (@@observers[client_queue] == nil) end Dnsruby.log.error{"remove_observer called with wrong observer for queue"} raise ArgumentError.new("remove_observer called with wrong observer for queue") end if (!@@observers.values.include?observer) @@tick_observers.delete(observer) end } end def notify_queue_observers(client_queue, client_query_id) # If any observers are known for this query queue then notify them observer=nil @@mutex.synchronize { observer = @@observers[client_queue] } if (observer) observer.handle_queue_event(client_queue, client_query_id) end end def send_tick_to_observers # If any observers are known then send them a tick tick_observers=nil @@mutex.synchronize { tick_observers = @@tick_observers } tick_observers.each do |observer| observer.tick end end end end dnsruby-1.61.3/lib/dnsruby/validator_thread.rb0000644000004100000410000001042513607400362021420 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # Takes care of the validation for the SelectThread. If queries need to be # made in order to validate the response, then a separate thread is fired up # to do this. class ValidatorThread # :nodoc: all # include Singleton def initialize(*args) @client_id, @client_queue, @response, @error, @query, @st, @res = args # Create the validation thread, and a queue to receive validation requests # Actually, need to have a thread per validator, as they make recursive calls. # @@mutex = Mutex.new # @@validation_queue = Queue.new # @@validator_thread = Thread.new{ # do_validate # } end def run # ONLY START THE NEW THREAD IF VALIDATION NEED OCCUR!! if (should_validate) Thread.new{ do_validate } else do_validate end end # def add_to_queue(item) # print "ADding to validator queue\n" # # @@mutex.synchronize{ # @@validation_queue.push(item) # # } # end def do_validate # while (true) # item = nil # print "Waiting to pop validation item\n" # # @@mutex.synchronize{ # item = @@validation_queue.pop # # } # print "Popped validation request\n" # client_id, client_queue, response, err, query, st, res = item validated_ok = validate(@query, @response, @res) validated_ok = false if (@error && !(NXDomain === @error)) cache_if_valid(@query, @response) # Now send the response back to the client... # print "#{Time.now} : Got result for #{@query.question()[0].qname}, #{@query.question()[0].qtype}\n" if (validated_ok) @st.push_validation_response_to_select(@client_id, @client_queue, @response, nil, @query, @res) else @st.push_validation_response_to_select(@client_id, @client_queue, @response, @response.security_error, @query, @res) end # end end def should_validate return ValidatorThread.requires_validation?(@query, @response, @error, @res) end def ValidatorThread.requires_validation?(query, response, error, res) # @error will be nil for DNS RCODE errors - it will be true for TsigError. really?! if ((!error || (error.instance_of?NXDomain)) && query.do_validation) if (res.dnssec) if (response.security_level != Message::SecurityLevel.SECURE) return true end end end return false end def validate(query, response, res) if (should_validate) begin # So, we really need to be able to take the response out of the select thread, along # with the responsibility for sending the answer to the client. # Should we have a validator thread? Or a thread per validation? # Then, select thread gets response. It performs basic checks here. # After basic checks, the select-thread punts the response (along with queues, etc.) # to the validator thread. # The validator validates it (or just releases it with no validation), and then # sends the request to the client via the client queue. Dnssec.validate_with_query(query,response) return true rescue VerifyError => e response.security_error = e response.security_level = BOGUS # Response security_level should already be set return false end end return true end def cache_if_valid(query, response) return if @error PacketSender.cache(query, response) end end end dnsruby-1.61.3/lib/dnsruby/ipv4.rb0000644000004100000410000000372713607400362016775 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby class IPv4 # Regular expression IPv4 addresses must match Regex = /\A(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\.(\d\d?\d?)\z/ def self.create(arg) case arg when IPv4 return arg when Regex if (0..255) === (a = $1.to_i) && (0..255) === (b = $2.to_i) && (0..255) === (c = $3.to_i) && (0..255) === (d = $4.to_i) return self.new([a, b, c, d].pack("CCCC")) else raise ArgumentError.new("IPv4 address with invalid value: " + arg) end else raise ArgumentError.new("cannot interpret as IPv4 address: #{arg.inspect}") end end def initialize(address) #:nodoc: unless address.kind_of?(String) && address.length == 4 raise ArgumentError.new('IPv4 address must be 4 bytes') end @address = address end # A String representation of the IPv4 address. attr_reader :address def to_s #:nodoc: return sprintf("%d.%d.%d.%d", *@address.unpack("CCCC")) end def inspect #:nodoc: return "#<#{self.class} #{self.to_s}>" end def to_name return Name.create( '%d.%d.%d.%d.in-addr.arpa.' % @address.unpack('CCCC').reverse) end def ==(other) return @address == other.address end def eql?(other) return self == other end def hash return @address.hash end end end dnsruby-1.61.3/lib/dnsruby/update.rb0000644000004100000410000002321013607400362017362 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # Dnsruby::Update is a subclass of Dnsruby::Packet, # to be used for making DNS dynamic updates. Programmers # should refer to RFC 2136 for the semantics of dynamic updates. # The first example below shows a complete program; subsequent examples # show only the creation of the update packet. # # == Add a new host # # require 'Dnsruby' # # # Create the update packet. # update = Dnsruby::Update.new('example.com') # # # Prerequisite is that no A records exist for the name. # update.absent('foo.example.com.', 'A') # # # Add two A records for the name. # update.add('foo.example.com.', 'A', 86400, '192.168.1.2') # update.add('foo.example.com.', 'A', 86400, '172.16.3.4') # # # Send the update to the zone's primary master. # res = Dnsruby::Resolver.new({:nameserver => 'primary-master.example.com'}) # # begin # reply = res.send_message(update) # print "Update succeeded\n" # rescue Exception => e # print 'Update failed: #{e}\n' # end # # == Add an MX record for a name that already exists # # update = Dnsruby::Update.new('example.com') # update.present('example.com') # update.add('example.com', Dnsruby::Types.MX, 86400, 10, 'mailhost.example.com') # # == Add a TXT record for a name that doesn't exist # # update = Dnsruby::Update.new('example.com') # update.absent('info.example.com') # update.add('info.example.com', Types.TXT, 86400, "yabba dabba doo"') # # == Delete all A records for a name # # update = Dnsruby::Update.new('example.com') # update.present('foo.example.com', 'A') # update.delete('foo.example.com', 'A') # # == Delete all RRs for a name # # update = Dnsruby::Update.new('example.com') # update.present('byebye.example.com') # update.delete('byebye.example.com') # # == Perform a signed update # # key_name = 'tsig-key' # key = 'awwLOtRfpGE+rRKF2+DEiw==' # # update = Dnsruby::Update.new('example.com') # update.add('foo.example.com', 'A', 86400, '10.1.2.3')) # update.add('bar.example.com', 'A', 86400, '10.4.5.6')) # res.tsig=(key_name,key) # class Update < Message # Returns a Dnsruby::Update object suitable for performing a DNS # dynamic update. Specifically, it creates a message with the header # opcode set to UPDATE and the zone record type to SOA (per RFC 2136, # Section 2.3). # # Programs must use the push method to add RRs to the prerequisite, # update, and additional sections before performing the update. # # Arguments are the zone name and the class. If the zone is omitted, # the default domain will be taken from the resolver configuration. # If the class is omitted, it defaults to IN. # packet = Dnsruby::Update.new # packet = Dnsruby::Update.new('example.com') # packet = Dnsruby::Update.new('example.com', 'HS') # def initialize(zone=nil, klass=nil) # sort out the zone section (RFC2136, section 2.3) if (zone==nil) config = Config.new zone = (config.search)[0] return unless zone end type = 'SOA' klass ||= 'IN' super(zone, type, klass) || return @header.opcode=('UPDATE') @header.rd=(0) @do_validation = false end # Ways to create the prerequisite records (exists, notexists, inuse, etc. - RFC2136, section 2.4) # # (1) RRset exists (value independent). At least one RR with a # specified NAME and TYPE (in the zone and class specified by # the Zone Section) must exist. # # update.present(name, type) # # (2) RRset exists (value dependent). A set of RRs with a # specified NAME and TYPE exists and has the same members # with the same RDATAs as the RRset specified here in this # Section. # # update.present(name, type, rdata) # # (4) Name is in use. At least one RR with a specified NAME (in # the zone and class specified by the Zone Section) must exist. # Note that this prerequisite is NOT satisfied by empty # nonterminals. # # update.present(name) def present(*args) ttl = 0 rdata = "" klass = Classes.ANY if (args.length>=1) # domain (RFC2136, Section 2.4.4) name = args[0] type = Types.ANY if (args.length>=2) # RRSET (RFC2136, Section 2.4.1) type = args[1] end if (args.length > 2) # RRSET (RFC2136, Section 2.4.2) klass = Classes.new(zone()[0].zclass) rdata=args[2] end rec = RR.create("#{name} #{ttl} #{klass} #{type} #{rdata}") add_pre(rec) return rec else raise ArgumentError.new("Wrong number of arguments (#{args.length} for 1 or 2) for Update#present") end end # Ways to create the prerequisite records (exists, notexists, inuse, etc. - RFC2136, section 2.4) # Can be called with one arg : # # update.absent(name) # (5) Name is not in use. No RR of any type is owned by a # specified NAME. Note that this prerequisite IS satisfied by # empty nonterminals. # # Or with two : # # update.absent(name, type) # (3) RRset does not exist. No RRs with a specified NAME and TYPE # (in the zone and class denoted by the Zone Section) can exist. # def absent(*args) ttl = 0 rdata = "" klass = Classes.NONE if (args.length>=1) # domain (RFC2136, Section 2.4.5) name = args[0] type = Types.ANY if (args.length==2) # RRSET (RFC2136, Section 2.4.3) type = args[1] end rec = RR.create("#{name} #{ttl} #{klass} #{type} #{rdata}") add_pre(rec) return rec else raise ArgumentError.new("Wrong number of arguments (#{args.length} for 1 or 2) for Update#absent") end end # Ways to create the update records (add, delete, RFC2136, section 2.5) # " 2.5.1 - Add To An RRset # # RRs are added to the Update Section whose NAME, TYPE, TTL, RDLENGTH # and RDATA are those being added, and CLASS is the same as the zone # class. Any duplicate RRs will be silently ignored by the primary # master." # # update.add(rr) # update.add([rr1, rr2]) # update.add(name, type, ttl, rdata) # def add(*args) zoneclass=zone()[0].zclass case args[0] when Array args[0].each do |resource| add(resource) end when RR # Make sure that the Class is the same as the zone resource = args[0] if (resource.klass != zoneclass) raise ArgumentError.new("Wrong class #{resource.klass} for update (should be #{zoneclass})!") end add_update(resource) return resource else name=args[0] type=args[1] ttl=args[2] rdata=args[3] resource = nil if (Types.new(type) == Types.TXT) instring = "#{name} #{ttl} #{zoneclass} #{type} "; if (String === rdata) instring += " '#{rdata}'" elsif (Array === rdata) rdata.length.times {|rcounter| instring += " '#{rdata[rcounter]}' " } else instring += rdata end resource = RR.create(instring) else resource = RR.create("#{name} #{ttl} #{zoneclass} #{type} #{rdata}") end add_update(resource) return resource end # @TODO@ Should be able to take RRSet! end # Ways to create the update records (add, delete, RFC2136, section 2.5) # # 2.5.2 - Delete An RRset # update.delete(name, type) # # # 2.5.3 - Delete All RRsets From A Name # update.delete(name) # # 2.5.4 - Delete An RR From An RRset # update.delete(name, type, rdata) # def delete(*args) ttl = 0 klass = Classes.ANY rdata="" resource = nil case args.length when 1 # name resource = RR.create("#{args[0]} #{ttl} #{klass} #{Types.ANY} #{rdata}") add_update(resource) when 2 # name, type resource = RR.create("#{args[0]} #{ttl} #{klass} #{args[1]} #{rdata}") add_update(resource) when 3 # name, type, rdata name = args[0] type = args[1] rdata = args[2] if (Types.new(type) == Types.TXT) instring = "#{name} #{ttl} IN #{type} "; if (String === rdata) instring += " '#{rdata}'" elsif (Array === rdata) rdata.length.times {|rcounter| instring += " '#{rdata[rcounter]}' " } else instring += rdata end resource = RR.create(instring) else resource = RR.create("#{name} #{ttl} IN #{type} #{rdata}") end resource.klass = Classes.NONE add_update(resource) end return resource end end end dnsruby-1.61.3/lib/dnsruby/config.rb0000644000004100000410000003362713607400362017362 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # == Description # The Config class determines the system configuration for DNS. # In particular, it determines the nameserver to target queries to. # # # It also specifies whether and how the search list and default # domain should be applied to queries, according to the following # algorithm : # # * If the name is absolute, then it is used as is. # # * If the name is not absolute, then : # # If apply_domain is true, and ndots is greater than the number # of labels in the name, then the default domain is added to the name. # # If apply_search_list is true, then each member of the search list # is appended to the name. # # The Config class has now been modified for lazy loading. Previously, the config # was loaded when a Resolver was instantiated. Now, the config is only loaded if # a query is performed (or a config parameter requested on) a Resolver which has # not yet been configured. class Config DEFAULT_PORT = 53 # -- # @TODO@ Switches for : # # -- single socket for all packets # -- single new socket for individual client queries (including retries and multiple nameservers) # ++ # The list of nameservers to query def nameserver if (!@configured) parse_config end return @nameserver end # Should the search list be applied? attr_accessor :apply_search_list # Should the default domain be applied? attr_accessor :apply_domain # The minimum number of labels in the query name (if it is not absolute) before it is considered complete def ndots if (!@configured) parse_config end return @ndots end # Set the config. Parameter can be : # # * A String containing the name of the config file to load # e.g. /etc/resolv.conf # # * A hash with the following elements : # nameserver (String) # domain (String) # search (String) # ndots (Integer) # # This method should not normally be called by client code. def set_config_info(config_info) parse_config(config_info) end # Create a new Config with system default values def initialize() @mutex = Mutex.new @configured = false # parse_config end # Reset the config to default values def Config.reset c = Config.new @configured = false # c.parse_config end def parse_config(config_info=nil) #:nodoc: all @mutex.synchronize { ns = [] @nameserver = [] @domain, s, @search = nil dom="" nd = 1 @ndots = 1 @port = DEFAULT_PORT @apply_search_list = true @apply_domain = true config_hash = Config.default_config_hash case config_info when nil when String config_hash.merge!(Config.parse_resolv_conf(config_info)) when Hash config_hash.merge!(config_info.dup) if String === config_hash[:nameserver] config_hash[:nameserver] = [config_hash[:nameserver]] end if String === config_hash[:search] config_hash[:search] = [config_hash[:search]] end else raise ArgumentError.new("invalid resolv configuration: #{@config_info.inspect}") end ns = config_hash[:nameserver] if config_hash.include? :nameserver s = config_hash[:search] if config_hash.include? :search nd = config_hash[:ndots] if config_hash.include? :ndots p = config_hash[:port] if config_hash.include? :port @apply_search_list = config_hash[:apply_search_list] if config_hash.include? :apply_search_list @apply_domain= config_hash[:apply_domain] if config_hash.include? :apply_domain dom = config_hash[:domain] if config_hash.include? :domain if (!@configured) send("nameserver=",ns) end @configured = true send("search=",s) send("ndots=",nd) send("port=",p) send("domain=",dom) } Dnsruby.log.info{to_s} end # Set the default domain def domain=(dom) # @configured = true if (dom) if !dom.kind_of?(String) raise ArgumentError.new("invalid domain config: #{@domain.inspect}") end @domain = Name::split(dom) else @domain=nil end end # Set ndots def ndots=(nd) @configured = true @ndots=nd if !@ndots.kind_of?(Integer) raise ArgumentError.new("invalid ndots config: #{@ndots.inspect}") end end # Set port def port=(p) @configured = true @port=p if p if !@port.kind_of?(Integer) raise ArgumentError.new("invalid port config: #{@port.inspect}") end end # Set the default search path def search=(s) @configured = true @search=s if @search if @search.class == Array @search = @search.map {|arg| Name::split(arg) } else raise ArgumentError.new("invalid search config: search must be an array!") end else hostname = Socket.gethostname if /\./ =~ hostname @search = [Name.split($')] else @search = [[]] end end if !@search.kind_of?(Array) || # !@search.all? {|ls| ls.all? {|l| Label::Str === l } } !@search.all? {|ls| ls.all? {|l| Name::Label === l } } raise ArgumentError.new("invalid search config: #{@search.inspect}") end end def check_ns(ns) #:nodoc: all if !ns.kind_of?(Array) || !ns.all? {|n| (Name === n || String === n || IPv4 === n || IPv6 === n)} raise ArgumentError.new("invalid nameserver config: #{ns.inspect}") end ns.each {|n| if (String ===n) # Make sure we can make a Name or an address from it begin a = IPv4.create(n) rescue ArgumentError begin a = IPv6.create(n) rescue ArgumentError begin a = Name.create(n) rescue ArgumentError raise ArgumentError.new("Can't interpret #{n} as IPv4, IPv6 or Name") end end end end } end # Add a nameserver to the list of nameservers. # # Can take either a single String or an array of Strings. # The new nameservers are added at a higher priority. def add_nameserver(ns) @configured = true if (ns.kind_of?String) ns=[ns] end check_ns(ns) ns.reverse_each do |n| if (!@nameserver.include?(n)) self.nameserver=[n]+@nameserver end end end # Set the config to point to a single nameserver def nameserver=(ns) @configured = true check_ns(ns) # @nameserver = ['0.0.0.0'] if (@nameserver.class != Array || @nameserver.empty?) # Now go through and ensure that all ns point to IP addresses, not domain names @nameserver=ns Dnsruby.log.debug{"Nameservers = #{@nameserver.join(", ")}"} end def Config.resolve_server(ns) #:nodoc: all # Sanity check server # If it's an IP address, then use that for server # If it's a name, then we'll need to resolve it first server=ns if (Name === ns) ns = ns.to_s end begin addr = IPv4.create(ns) server = ns rescue Exception begin addr=IPv6.create(ns) server = ns rescue Exception begin # try to resolve server to address if ns == "localhost" server = "127.0.0.1" else # Use Dnsruby to resolve the servers # First, try the default resolvers resolver = Resolver.new found = false begin ret = resolver.query(ns) ret.answer.each {|rr| if ([Types::A, Types::AAAA].include?rr.type) addr = rr.address.to_s server = addr found = true end } rescue Exception end if (!found) # That didn't work - try recursing from the root recursor = Recursor.new ret = recursor.query(ns) ret.answer.each {|rr| if ([Types::A, Types::AAAA].include?rr.type) addr = rr.address.to_s server = addr end } if (!found) raise ArgumentError.new("Recursor can't locate #{server}") end end end rescue Exception => e Dnsruby.log.error{"Can't make sense of nameserver : #{server}, exception : #{e}"} raise ArgumentError.new("Can't make sense of nameserver : #{server}, exception : #{e}") return nil end end end return server end def Config.parse_resolv_conf(filename) #:nodoc: all nameserver = [] search = nil domain = nil ndots = 1 port = DEFAULT_PORT open(filename) {|f| f.each {|line| line.sub!(/[#;].*/, '') keyword, *args = line.split(/\s+/) args.each { |arg| arg.untaint } next unless keyword case keyword when 'port' port = args[0].to_i when 'nameserver' nameserver += args when 'domain' next if args.empty? domain = args[0] # if search == nil # search = [] # end # search.push(args[0]) when 'search' next if args.empty? if search == nil search = [] end args.each {|a| search.push(a)} when 'options' args.each {|arg| case arg when /\Andots:(\d+)\z/ ndots = $1.to_i end } end } } return { :nameserver => nameserver, :domain => domain, :search => search, :ndots => ndots, :port => port } end def inspect #:nodoc: all to_s end def to_s if (!@configured) parse_config end ret = "Config - nameservers : " @nameserver.each {|n| ret += n.to_s + ", "} domain_string="empty" if (@domain!=nil) domain_string=@domain.to_s end ret += " domain : #{domain_string}, search : " search.each {|s| ret += s + ", " } ret += " ndots : #{@ndots}" ret += " port : #{@port}" return ret end def Config.default_config_hash(filename="/etc/resolv.conf") #:nodoc: all config_hash={} if File.exist? filename config_hash = Config.parse_resolv_conf(filename) else if (/java/ =~ RUBY_PLATFORM && !(filename=~/:/)) # Problem with paths and Windows on JRuby - see if we can munge the drive... wd = Dir.getwd drive = wd.split(':')[0] if (drive.length==1) file = drive << ":" << filename if File.exist? file config_hash = Config.parse_resolv_conf(file) end end elsif /mswin32|cygwin|mingw|bccwin/ =~ RUBY_PLATFORM # @TODO@ Need to get windows domain sorted search, nameserver = Win32::Resolv.get_resolv_info # config_hash[:domain] = domain if domain config_hash[:nameserver] = nameserver if nameserver config_hash[:search] = [search].flatten if search end end config_hash end # Return the search path def search if (!@configured) parse_config end search = [] @search.each do |s| search.push(Name.new(s).to_s) end return search end # Return the default domain def domain if (!@configured) parse_config end if (@domain==nil) return nil end return Name.create(@domain).to_s end def single? #:nodoc: all if @nameserver.length == 1 return @nameserver[0] else return nil end end def get_ready if (!@configured) parse_config end end def generate_candidates(name_in) #:nodoc: all if !@configured parse_config end candidates = [] name = Name.create(name_in) if name.absolute? candidates = [name] else candidates.push(Name.create(name_in.to_s + ".")) if (@apply_domain) if @ndots > name.length - 1 if (@domain != nil) candidates.push(Name.create(name.to_a+@domain)) end end end if (!@apply_search_list) candidates.push(Name.create(name.to_a)) else if @ndots <= name.length - 1 candidates.push(Name.create(name.to_a)) end candidates.concat(@search.map {|domain| Name.create(name.to_a + domain)}) if (name.length == 1) candidates.concat([Name.create(name.to_a)]) end end end return candidates end end enddnsruby-1.61.3/lib/dnsruby/resolver.rb0000644000004100000410000013170313607400362017750 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # require 'Dnsruby/resolver_register.rb' require 'dnsruby/packet_sender' require 'dnsruby/recursor' module Dnsruby # == Description # Dnsruby::Resolver is a DNS stub resolver. # This class performs queries with retries across multiple nameservers. # The system configured resolvers are used by default. # # The retry policy is a combination of the Net::DNS and dnsjava approach, and has the option of : # * A total timeout for the query (defaults to 0, meaning "no total timeout") # * A retransmission system that targets the namervers concurrently once the first query round is # complete, but in which the total time per query round is split between the number of nameservers # targetted for the first round. and total time for query round is doubled for each query round # # Note that, if a total timeout is specified, then that will apply regardless of the retry policy # (i.e. it may cut retries short). # # Note also that these timeouts are distinct from the SingleResolver's packet_timeout # # Timeouts apply to the initial query and response. If DNSSEC validation is to # be performed, then additional queries may be required (these are performed automatically # by Dnsruby). Each additional query will be performed with its own timeouts. # So, even with a query_timeout of 5 seconds, a response which required extensive # validation may take several times that long. # (Future versions of Dnsruby may expose finer-grained events for client tracking of # responses and validation) # # == Methods # # === Synchronous # These methods raise an exception or return a response message with rcode==NOERROR # # * Dnsruby::Resolver#send_message(msg) # * Dnsruby::Resolver#query(name [, type [, klass]]) # # There are "!" versions of these two methods that return an array [response, error] # instead of raising an error on failure. They can be called as follows: # # response, error = resolver.send_message!(...) # response, error = resolver.query!(...) # # If the request succeeds, response will contain the Dnsruby::Message response # and error will be nil. # # If the request fails, response will be nil and error will contain the error raised. # # === Asynchronous # These methods use a response queue to return the response and the error # # * Dnsruby::Resolver#send_async(msg, response_queue, query_id) # # == Event Loop # Dnsruby runs a pure Ruby event loop to handle I/O in a single thread. # Support for EventMachine has been deprecated. class Resolver DefaultQueryTimeout = 0 DefaultPacketTimeout = 5 DefaultRetryTimes = 1 DefaultRetryDelay = 5 DefaultPipeLiningMaxQueries = 5 DefaultPort = 53 DefaultDnssec = false AbsoluteMinDnssecUdpSize = 1220 MinDnssecUdpSize = 4096 DefaultUDPSize = MinDnssecUdpSize class EventType RECEIVED = 0 VALIDATED = 1 # @TODO@ Should be COMPLETE? ERROR = 2 end # The port to send queries to on the resolver attr_reader :port # Should TCP be used as a transport rather than UDP? # If use_tcp==true, then ONLY TCP will be used as a transport. attr_reader :use_tcp # If tcp_pipelining==true, then we reuse the TCP connection attr_reader :tcp_pipelining # How many times (number of messages) to reuse the pipelining connection # before closing, :infinite for infinite number of requests per connection attr_reader :tcp_pipelining_max_queries # If no_tcp==true, then ONLY UDP will be used as a transport. # This should not generally be used, but is provided as a debugging aid. attr_reader :no_tcp attr_reader :tsig # Should truncation be ignored? # i.e. the TC bit is ignored and thus the resolver will not requery over TCP if TC is set attr_reader :ignore_truncation # The source address to send queries from for IPv4 attr_reader :src_address # The source address to send queries from for IPv6 attr_reader :src_address6 # Should the Recursion Desired bit be set? attr_reader :recurse # The maximum UDP size to be used attr_reader :udp_size # The current Config attr_reader :config # Does this Resolver cache answers, and attempt to retrieve answer from the cache? attr_reader :do_caching # The array of SingleResolvers used for sending query messages # attr_accessor :single_resolvers # :nodoc: def single_resolvers=(s) # :nodoc: @configured = true # @single_res_mutex.synchronize { @single_resolvers = s # } end def single_resolvers # :nodoc: unless @configured add_config_nameservers end @single_resolvers end # The timeout for any individual packet. This is the timeout used by SingleResolver attr_reader :packet_timeout # Note that this timeout represents the total time a query may run for - multiple packets # can be sent to multiple nameservers in this time. # This is distinct from the SingleResolver per-packet timeout # The query_timeout is not required - it will default to 0, which means "do not use query_timeout". # If this is the case then the timeout will be dictated by the retry_times and retry_delay attributes attr_accessor :query_timeout # The query will be tried across nameservers retry_times times, with a delay of retry_delay seconds # between each retry. The first time round, retry_delay will be divided by the number of nameservers # being targetted, and a new nameserver will be queried with the resultant delay. attr_accessor :retry_times, :retry_delay # Use DNSSEC for this Resolver attr_reader :dnssec # Defines whether validation is performed by default on this Resolver when the # query method is called. # Note that send_message and send_async expect a # Message object to be passed in, which is already configured to the callers # requirements. attr_accessor :do_validation # Defines whether we will cache responses, or pass every request to the # upstream resolver. This is only really useful when querying authoritative # servers (as the upstream recursive resolver is likely to cache) attr_accessor :do_caching # -- # @TODO@ add load_balance? i.e. Target nameservers in a random, rather than pre-determined, order? # This is best done when configuring the Resolver, as it will re-order servers based on their response times. # # ++ # Query for a name. If a valid Message is received, then it is returned # to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised. # # require 'dnsruby' # res = Dnsruby::Resolver.new # response = res.query('example.com') # defaults to Types.A, Classes.IN # response = res.query('example.com', Types.MX) # response = res.query('208.77.188.166') # IPv4 address so PTR query will be made # response = res.query('208.77.188.166', Types.PTR) def query(name, type=Types.A, klass=Classes.IN, set_cd=@dnssec) msg = Message.new msg.do_caching = @do_caching msg.header.rd = 1 msg.add_question(name, type, klass) msg.do_validation = @do_validation if @dnssec msg.header.cd = set_cd # We do our own validation by default end send_message(msg) end # Like query, but does not raise an error when an error occurs. # Instead, it returns it. # @return a 2 element array: [response, error] def query!(name, type=Types.A, klass=Classes.IN, set_cd=@dnssec) response = nil; error = nil begin response = query(name, type, klass, set_cd) rescue => e error = e end [response, error] end def query_no_validation_or_recursion(name, type=Types.A, klass=Classes.IN) # :nodoc: all msg = Message.new msg.do_caching = @do_caching msg.header.rd = false msg.do_validation = false msg.add_question(name, type, klass) if @dnssec msg.header.cd = true # We do our own validation by default end send_message(msg) end # Send a message, and wait for the response. If a valid Message is received, then it is returned # to the caller. Otherwise an exception (a Dnsruby::ResolvError or Dnsruby::ResolvTimeout) is raised. # # send_async is called internally. # # example : # # require 'dnsruby' # include Dnsruby # res = Dnsruby::Resolver.new # begin # response = res.send_message(Message.new('example.com', Types.MX)) # rescue ResolvError # # ... # rescue ResolvTimeout # # ... # end def send_message(message) Dnsruby.log.debug{'Resolver : sending message'} q = Queue.new send_async(message, q) _id, result, error = q.pop if error error.response = result if error.is_a?(ResolvError) raise error else result end end # Like send_message, but does not raise an error when an error occurs. # Instead, it returns it. # @return a 2 element array: [response, error] def send_message!(message) response = nil; error = nil begin response = send_message(message) rescue => e error = e end [response, error] end # Sends a message with send_plain_message. # Effectively a wrapper around send_plain_message, but adds # the ability to configure whether an error will be raised # or returned if it occurs. # # @param message the message to send to the DNS server # @param error_strategy :return to return [response, error] (default), # :raise to return response only, or raise an error if one occurs def query_raw(message, error_strategy = :return) unless [:return, :raise].include?(error_strategy) raise ArgumentError.new('error_strategy should be one of [:return, :raise].') end response, error = send_plain_message(message) if error_strategy == :return [response, error] else raise error if error response end end # This method takes a Message (supplied by the client), and sends it to # the configured nameservers. No changes are made to the Message before it # is sent (TSIG signatures will be applied if configured on the Resolver). # Retries are handled as the Resolver is configured to do. # Incoming responses to the query are not cached or validated (although TCP # fallback will be performed if the TC bit is set and the (Single)Resolver has # ignore_truncation set to false). # Note that the Message is left untouched - this means that no OPT records are # added, even if the UDP transport for the server is specified at more than 512 # bytes. If it is desired to use EDNS for this packet, then you should call # the Dnsruby::PacketSender#prepare_for_dnssec(msg), or # Dnsruby::PacketSender#add_opt_rr(msg) # The return value from this method is the [response, error] tuple. Either of # these values may be nil - it is up to the client to check. # # example : # # require 'dnsruby' # include Dnsruby # res = Dnsruby::Resolver.new # response, error = res.send_plain_message(Message.new('example.com', Types.MX)) # if error # print "Error returned : #{error}\n" # else # process_response(response) # end def send_plain_message(message) Dnsruby::TheLog.debug('Resolver : send_plain_message') message.do_caching = false message.do_validation = false message.send_raw = true q = Queue.new send_async(message, q) _id, result, error = q.pop error.response = result if !error.nil? && error.is_a?(ResolvError) [result, error] end # Asynchronously send a Message to the server. The send can be done using just # Dnsruby. Support for EventMachine has been deprecated. # # == Dnsruby pure Ruby event loop : # # A client_queue is supplied by the client, # along with an optional client_query_id to identify the response. The client_query_id # is generated, if not supplied, and returned to the client. # When the response is known, # a tuple of (query_id, response_message, exception) will be added to the client_queue. # # The query is sent synchronously in the caller's thread. The select thread is then used to # listen for and process the response (up to pushing it to the client_queue). The client thread # is then used to retrieve the response and deal with it. # # Takes : # # * msg - the message to send # * client_queue - a Queue to push the response to, when it arrives # * client_query_id - an optional ID to identify the query to the client # * use_tcp - whether to use only TCP (defaults to SingleResolver.use_tcp) # # Returns : # # * client_query_id - to identify the query response to the client. This ID is # generated if it is not passed in by the client # # === Example invocations : # # id = res.send_async(msg, queue) # NOT SUPPORTED : id = res.send_async(msg, queue, use_tcp) # id = res.send_async(msg, queue, id) # id = res.send_async(msg, queue, id, use_tcp) # # === Example code : # # require 'dnsruby' # res = Dnsruby::Resolver.newsend # query_id = 10 # can be any object you like # query_queue = Queue.new # res.send_async(Message.new('example.com', Types.MX), query_queue, query_id) # query_id_2 = res.send_async(Message.new('example.com', Types.A), query_queue) # # ...do a load of other stuff here... # 2.times do # response_id, response, exception = query_queue.pop # # You can check the ID to see which query has been answered # if exception == nil # # deal with good response # else # # deal with problem # end # end # def send_async(msg, client_queue, client_query_id = nil) unless @configured add_config_nameservers end # @single_res_mutex.synchronize { unless @resolver_ruby # @TODO@ Synchronize this? @resolver_ruby = ResolverRuby.new(self) end # } client_query_id = @resolver_ruby.send_async(msg, client_queue, client_query_id) if @single_resolvers.length == 0 Thread.start { sleep(@query_timeout == 0 ? 1 : @query_timeout) client_queue.push([client_query_id, nil, ResolvTimeout.new('Query timed out - no nameservers configured')]) } end client_query_id end # Close the Resolver. Unfinished queries are terminated with OtherResolvError. def close @resolver_ruby.close if @resolver_ruby end # Create a new Resolver object. If no parameters are passed in, then the default # system configuration will be used. Otherwise, a Hash may be passed in with the # following optional elements : # # # * :port # * :use_tcp # * :tsig # * :ignore_truncation # * :src_address # * :src_address6 # * :src_port # * :recurse # * :udp_size # * :config_info - see Config # * :nameserver - can be either a String or an array of Strings # * :packet_timeout # * :query_timeout # * :retry_times # * :retry_delay # * :do_caching # * :tcp_pipelining # * :tcp_pipelining_max_queries - can be a number or :infinite symbol def initialize(*args) # @TODO@ Should we allow :namesver to be an RRSet of NS records? Would then need to randomly order them? @resolver_ruby = nil @src_address = nil @src_address6 = nil @single_res_mutex = Mutex.new @configured = false @do_caching = true @config = Config.new() reset_attributes # Process args if args.length == 1 if args[0].class == Hash args[0].keys.each do |key| begin if key == :config_info @config.set_config_info(args[0][:config_info]) elsif key == :nameserver set_config_nameserver(args[0][:nameserver]) elsif key == :nameservers set_config_nameserver(args[0][:nameservers]) else send(key.to_s + '=', args[0][key]) end rescue Exception => e Dnsruby.log.error{"Argument #{key} not valid : #{e}\n"} end end elsif args[0].class == String set_config_nameserver(args[0]) elsif args[0].class == Config # also accepts a Config object from Dnsruby::Resolv @config = args[0] end else # Anything to do? end update end def add_config_nameservers # :nodoc: all unless @configured @config.get_ready end @configured = true @single_res_mutex.synchronize { # Add the Config nameservers @config.nameserver.each do |ns| res = PacketSender.new({ server: ns, port: @port, dnssec: @dnssec, use_tcp: @use_tcp, no_tcp: @no_tcp, tcp_pipelining: @tcp_pipelining, tcp_pipelining_max_queries: @tcp_pipelining_max_queries, packet_timeout: @packet_timeout, tsig: @tsig, ignore_truncation: @ignore_truncation, src_address: @src_address, src_address6: @src_address6, src_port: @src_port, recurse: @recurse, udp_size: @udp_size}) @single_resolvers.push(res) if res end } end def set_config_nameserver(n) # @TODO@ Should we allow NS RRSet here? If so, then .sort_by {rand} @config.get_ready unless @configured @configured = true @config.nameserver = n.kind_of?(String) ? [n] : n add_config_nameservers end def reset_attributes # :nodoc: all @resolver_ruby.reset_attributes if @resolver_ruby # Attributes # do_validation tells the Resolver whether to try to validate the response # with DNSSEC. This should work for NSEC-signed domains, but NSEC3 # validation is not currently supported. This attribute now defaults to # false. Please let me know if you require NSEC3 validation. @do_validation = false @query_timeout = DefaultQueryTimeout @retry_delay = DefaultRetryDelay @retry_times = DefaultRetryTimes @packet_timeout = DefaultPacketTimeout @port = DefaultPort @udp_size = DefaultUDPSize @dnssec = DefaultDnssec @do_caching= true @use_tcp = false @no_tcp = false @tcp_pipelining = false @tcp_pipelining_max_queries = DefaultPipeLiningMaxQueries @tsig = nil @ignore_truncation = false @config = Config.new() @src_address = nil @src_address6 = nil @src_port = [0] @recurse = true @single_res_mutex.synchronize { @single_resolvers=[] } @configured = false end def update # :nodoc: all # Update any resolvers we have with the latest config @single_res_mutex.synchronize do @single_resolvers.delete(nil) # Just in case... @single_resolvers.each { |res| update_internal_res(res) } end end # # Add a new SingleResolver to the list of resolvers this Resolver object will # # query. # def add_resolver(internal) # :nodoc: # # @TODO@ Make a new PacketSender from this SingleResolver!! # @single_resolvers.push(internal) # end def add_server(server)# :nodoc: @configured = true res = PacketSender.new(server) log_and_raise("Can't create server #{server}", ArgumentError) unless res update_internal_res(res) @single_res_mutex.synchronize { @single_resolvers.push(res) } end def update_internal_res(res) [:port, :use_tcp, :no_tcp, :tcp_pipelining, :tcp_pipelining_max_queries, :tsig, :ignore_truncation, :packet_timeout, :src_address, :src_address6, :src_port, :recurse, :udp_size, :dnssec].each do |param| res.send(param.to_s + '=', instance_variable_get('@' + param.to_s)) end end def nameservers=(ns) self.nameserver=(ns) end def nameserver=(n) @configured = true @single_res_mutex.synchronize { @single_resolvers=[] } set_config_nameserver(n) add_config_nameservers end # -- # @TODO@ Should really auto-generate these methods. # Also, any way to tie them up with SingleResolver RDoc? # ++ def packet_timeout=(t) @packet_timeout = t update end # The source port to send queries from # Returns either a single Integer or an Array # e.g. '0', or '[60001, 60002, 60007]' # # Defaults to 0 - random port def src_port @src_port.length == 1 ? @src_port[0] : @src_port end # Can be a single Integer or a Range or an Array # If an invalid port is selected (one reserved by # IANA), then an ArgumentError will be raised. # # res.src_port=0 # res.src_port=[60001,60005,60010] # res.src_port=60015..60115 # def src_port=(p) if Resolver.check_port(p) @src_port = Resolver.get_ports_from(p) update end end # Can be a single Integer or a Range or an Array # If an invalid port is selected (one reserved by # IANA), then an ArgumentError will be raised. # "0" means "any valid port" - this is only a viable # option if it is the only port in the list. # An ArgumentError will be raised if "0" is added to # an existing set of source ports. # # res.add_src_port(60000) # res.add_src_port([60001,60005,60010]) # res.add_src_port(60015..60115) # def add_src_port(p) if Resolver.check_port(p, @src_port) a = Resolver.get_ports_from(p) a.each do |x| if (@src_port.length > 0) && (x == 0) log_and_raise("src_port of 0 only allowed as only src_port value (currently #{@src_port.length} values", ArgumentError) end @src_port.push(x) end end update end def Resolver.check_port(p, src_port=[]) unless p.is_a?(Integer) tmp_src_ports = Array.new(src_port) p.each do |x| unless Resolver.check_port(x, tmp_src_ports) return false end tmp_src_ports.push(x) end return true end if Resolver.port_in_range(p) return ! ((p == 0) && (src_port.length > 0)) else Dnsruby.log.error("Illegal port (#{p})") log_and_raise("Illegal port #{p}", ArgumentError) end end def Resolver.port_in_range(p) (p == 0) || ((p >= 50000) && (p <= 65535)) end def Resolver.get_ports_from(p) a = [] if p.is_a?(Integer) a = [p] else p.each do |x| a.push(x) end end a end def tcp_pipelining=(on) @tcp_pipelining = on update end def tcp_pipelining_max_queries=(max) @tcp_pipelining_max_queries = max update end def use_tcp=(on) @use_tcp = on update end def no_tcp=(on) @no_tcp=on update end # Sets the TSIG to sign outgoing messages with. # Pass in either a Dnsruby::RR::TSIG, or a key_name and key (or just a key) # Pass in nil to stop tsig signing. # * res.tsig=(tsig_rr) # * res.tsig=(key_name, key) # defaults to hmac-md5 # * res.tsig=(key_name, key, alg) # e.g. alg = 'hmac-sha1' # * res.tsig=nil # Stop the resolver from signing def tsig=(t) @tsig = t update end protected def Resolver.create_tsig_options(name, key, algorithm = nil) options = { type: Types.TSIG, klass: Classes.ANY, name: name, key: key } options[:algorithm] = algorithm if algorithm options end public def Resolver.get_tsig(args) tsig = nil if args.length == 1 if args[0] if args[0].instance_of?(RR::TSIG) tsig = args[0] elsif args[0].instance_of?(Array) tsig = RR.new_from_hash(create_tsig_options(*args[0])) end else # Dnsruby.log.debug{'TSIG signing switched off'} return nil end else tsig = RR.new_from_hash(create_tsig_options(args)) end Dnsruby.log.info{"TSIG signing now using #{tsig.name}, key=#{tsig.key}"} tsig end def ignore_truncation=(on) @ignore_truncation = on update end def src_address=(a) @src_address = a update end def src_address6=(a) @src_address6 = a update end def port=(a) @port = a update end def persistent_tcp=(on) @persistent_tcp = on update end def persistent_udp=(on) @persistent_udp = on update end def do_caching=(on) @do_caching=on update end def recurse=(a) @recurse = a update end def dnssec=(d) @dnssec = d if d # Set the UDP size (RFC 4035 section 4.1) if @udp_size < MinDnssecUdpSize self.udp_size = MinDnssecUdpSize end end update end def udp_size=(s) @udp_size = s update end def single_res_mutex # :nodoc: all @single_res_mutex end def generate_timeouts(base=0) # :nodoc: all # These should be be pegged to the single_resolver they are targetting : # e.g. timeouts[timeout1]=nameserver timeouts = {} retry_delay = @retry_delay # @single_res_mutex.synchronize { @retry_times.times do |retry_count| if retry_count > 0 retry_delay *= 2 end @single_resolvers.delete(nil) # Just in case... @single_resolvers.each_index do |i| res = @single_resolvers[i] offset = (i * @retry_delay.to_f / @single_resolvers.length) if retry_count == 0 timeouts[base + offset]=[res, retry_count] else if timeouts.has_key?(base + retry_delay + offset) log_and_raise('Duplicate timeout key!') end timeouts[base + retry_delay + offset]=[res, retry_count] end end end # } timeouts end end # This class implements the I/O using pure Ruby, with no dependencies. # Support for EventMachine has been deprecated. class ResolverRuby # :nodoc: all def initialize(parent) reset_attributes @parent=parent end def reset_attributes # :nodoc: all # data structures # @mutex=Mutex.new @query_list = {} @timeouts = {} end def send_async(msg, client_queue, client_query_id=nil) # This is the whole point of the Resolver class. # We want to use multiple SingleResolvers to run a query. # So we kick off a system with select_thread where we send # a query with a queue, but log ourselves as observers for that # queue. When a new response is pushed on to the queue, then the # select thread will call this class' handler method IN THAT THREAD. # When the final response is known, this class then sticks it in # to the client queue. q = Queue.new if client_query_id.nil? client_query_id = Time.now + rand(10000) end unless client_queue.kind_of?(Queue) log_and_raise('Wrong type for client_queue in Resolver# send_async') # @TODO@ Handle different queue tuples - push this to generic send_error method client_queue.push([client_query_id, ArgumentError.new('Wrong type of client_queue passed to Dnsruby::Resolver# send_async - should have been Queue, was #{client_queue.class}')]) return end unless msg.kind_of?Message Dnsruby.log.error{'Wrong type for msg in Resolver# send_async'} # @TODO@ Handle different queue tuples - push this to generic send_error method client_queue.push([client_query_id, ArgumentError.new("Wrong type of msg passed to Dnsruby::Resolver# send_async - should have been Message, was #{msg.class}")]) return end begin msg.encode rescue EncodeError => err Dnsruby.log.error { "Can't encode " + msg.to_s + " : #{err}" } client_queue.push([client_query_id, err]) return end tick_needed = false # add to our data structures # @mutex.synchronize{ @parent.single_res_mutex.synchronize { tick_needed = true if @query_list.empty? if @query_list.has_key?(client_query_id) Dnsruby.log.error("Duplicate query id requested (#{client_query_id}") # @TODO@ Handle different queue tuples - push this to generic send_error method client_queue.push([client_query_id, ArgumentError.new('Client query ID already in use')]) return end outstanding = [] @query_list[client_query_id]=[msg, client_queue, q, outstanding] query_timeout = Time.now + @parent.query_timeout if @parent.query_timeout == 0 query_timeout = Time.now + 31536000 # a year from now end @timeouts[client_query_id] = [query_timeout, generate_timeouts] } # Now do querying stuff using SingleResolver # All this will be handled by the tick method (if we have 0 as the first timeout) st = SelectThread.instance st.add_observer(q, self) tick if tick_needed client_query_id end def generate_timeouts # :nodoc: all # Create the timeouts for the query from the retry_times and retry_delay attributes. # These are created at the same time in case the parameters change during the life of the query. # # These should be absolute, rather than relative # The first value should be Time.now[ @parent.generate_timeouts(Time.now) end # Close the Resolver. Unfinished queries are terminated with OtherResolvError. def close # @mutex.synchronize { @parent.single_res_mutex.synchronize { @query_list.each do |client_query_id, values| _msg, client_queue, q, _outstanding = values send_result_and_stop_querying(client_queue, client_query_id, q, nil, OtherResolvError.new('Resolver closing!')) end } end # MUST BE CALLED IN A SYNCHRONIZED BLOCK! # # Send the result back to the client, and close the socket for that query by removing # the query from the select thread. def send_result_and_stop_querying(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all stop_querying(client_query_id) send_result(client_queue, client_query_id, select_queue, msg, error) end # MUST BE CALLED IN A SYNCHRONIZED BLOCK! # # Stops send any more packets for a client-level query def stop_querying(client_query_id) # :nodoc: all @timeouts.delete(client_query_id) end # MUST BE CALLED IN A SYNCHRONIZED BLOCK! # # Sends the result to the client's queue, and removes the queue observer from the select thread def send_result(client_queue, client_query_id, select_queue, msg, error) # :nodoc: all stop_querying(client_query_id) # @TODO@ ! # We might still get some callbacks, which we should ignore st = SelectThread.instance st.remove_observer(select_queue, self) # @mutex.synchronize{ # Remove the query from all of the data structures @query_list.delete(client_query_id) # } # Return the response to the client client_queue.push([client_query_id, msg, error]) end # This method is called twice a second from the select loop, in the select thread. # It should arguably be called from another worker thread... (which also handles the queue) # Each tick, we check if any timeouts have occurred. If so, we take the appropriate action : # Return a timeout to the client, or send a new query def tick # :nodoc: all # Handle the tick # Do we have any retries due to be sent yet? # @mutex.synchronize{ @parent.single_res_mutex.synchronize { time_now = Time.now @timeouts.keys.each do |client_query_id| msg, client_queue, select_queue, outstanding = @query_list[client_query_id] query_timeout, timeouts = @timeouts[client_query_id] if query_timeout < Time.now # Time the query out send_result_and_stop_querying(client_queue, client_query_id, select_queue, nil, ResolvTimeout.new('Query timed out')) next end timeouts_done = [] timeouts.keys.sort.each do |timeout| if timeout < time_now # Send the next query res, retry_count = timeouts[timeout] id = [res, msg, client_query_id, retry_count] Dnsruby.log.debug("Sending msg to #{res.server}") # We should keep a list of the queries which are outstanding outstanding.push(id) timeouts_done.push(timeout) timeouts.delete(timeout) # Pick a new QID here @TODO@ !!! # msg.header.id = rand(65535); # print "New query : #{new_msg}\n" res.send_async(msg, select_queue, id) else break end end timeouts_done.each { |t| timeouts.delete(t) } end } end # This method is called by the SelectThread (in the select thread) when the queue has a new item on it. # The queue interface is used to separate producer/consumer threads, but we're using it here in one thread. # It's probably a good idea to create a new "worker thread" to take items from the select thread queue and # call this method in the worker thread. # def handle_queue_event(queue, id) # :nodoc: all # Time to process a new queue event. # If we get a callback for an ID we don't know about, don't worry - # just ignore it. It may be for a query we've already completed. # # So, get the next response from the queue (presuming there is one!) # # @TODO@ Tick could poll the queue and then call this method if needed - no need for observer interface. # @TODO@ Currently, tick and handle_queue_event called from select_thread - could have thread chuck events in to tick_queue. But then, clients would have to call in on other thread! # # So - two types of response : # 1) we've got a coherent response (or error) - stop sending more packets for that query! # 2) we've validated the response - it's ready to be sent to the client # # so need two more methods : # handleValidationResponse : basically calls send_result_and_stop_querying and # handleValidationError : does the same as handleValidationResponse, but for errors # can leave handleError alone # but need to change handleResponse to stop sending, rather than send_result_and_stop_querying. # # @TODO@ Also, we could really do with a MaxValidationTimeout - if validation not OK within # this time, then raise Timeout (and stop validation)? # # @TODO@ Also, should there be some facility to stop validator following same chain # concurrently? # # @TODO@ Also, should have option to speak only to configured resolvers (not follow authoritative chain) # if queue.empty? log_and_raise('Severe internal error - Queue empty in handle_queue_event') end event_id, event_type, response, error = queue.pop # We should remove this packet from the list of outstanding packets for this query _resolver, _msg, client_query_id, _retry_count = id if id != event_id log_and_raise("Serious internal error!! #{id} expected, #{event_id} received") end # @mutex.synchronize{ @parent.single_res_mutex.synchronize { if @query_list[client_query_id] == nil # print "Dead query response - ignoring\n" Dnsruby.log.debug{'Ignoring response for dead query'} return end _msg, _client_queue, _select_queue, outstanding = @query_list[client_query_id] if event_type == Resolver::EventType::RECEIVED || event_type == Resolver::EventType::ERROR unless outstanding.include?(id) log_and_raise("Query id not on outstanding list! #{outstanding.length} items. #{id} not on #{outstanding}") end outstanding.delete(id) end # } if event_type == Resolver::EventType::RECEIVED # if (event.kind_of?(Exception)) if error handle_error_response(queue, event_id, error, response) else # if event.kind_of?(Message) handle_response(queue, event_id, response) # else # Dnsruby.log.error('Random object #{event.class} returned through queue to Resolver') end elsif event_type == Resolver::EventType::VALIDATED if error handle_validation_error(queue, event_id, error, response) else handle_validation_response(queue, event_id, response) end elsif event_type == Resolver::EventType::ERROR handle_error_response(queue, event_id, error, response) else # print "ERROR - UNKNOWN EVENT TYPE IN RESOLVER : #{event_type}\n" TheLog.error("ERROR - UNKNOWN EVENT TYPE IN RESOLVER : #{event_type}") end } end def handle_error_response(select_queue, query_id, error, response) # :nodoc: all # Handle an error # @mutex.synchronize{ Dnsruby.log.debug{"handling error #{error.class}, #{error}"} # Check what sort of error it was : resolver, _msg, client_query_id, _retry_count = query_id _msg, client_queue, select_queue, outstanding = @query_list[client_query_id] if error.kind_of?(ResolvTimeout) # - if it was a timeout, then check which number it was, and how many retries are expected on that server # - if it was the last retry, on the last server, then return a timeout to the client (and clean up) # - otherwise, continue # Do we have any more packets to send to this resolver? decrement_resolver_priority(resolver) timeouts = @timeouts[client_query_id] if outstanding.empty? && timeouts && timeouts[1].values.empty? Dnsruby.log.debug{'Sending timeout to client'} send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error) end elsif error.kind_of?(NXDomain) # - if it was an NXDomain, then return that to the client, and stop all new queries (and clean up) # send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error) increment_resolver_priority(resolver) unless response.cached stop_querying(client_query_id) # @TODO@ Does the client want notified at this point? elsif error.kind_of?(EncodeError) Dnsruby.log.debug{'Encode error - sending to client'} send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error) else # - if it was any other error, then remove that server from the list for that query # If a Too Many Open Files error, then don't remove, but let retry work. timeouts = @timeouts[client_query_id] unless error.to_s =~ /Errno::EMFILE/ Dnsruby.log.debug{"Removing #{resolver.server} from resolver list for this query"} if timeouts timeouts[1].each do |key, value| res = value[0] if res == resolver timeouts[1].delete(key) end end end # Also stick it to the back of the list for future queries demote_resolver(resolver) else Dnsruby.log.debug("NOT Removing #{resolver.server} due to Errno::EMFILE") end # - if it was the last server, then return an error to the client (and clean up) if outstanding.empty? && ((!timeouts) || (timeouts && timeouts[1].values.empty?)) # if outstanding.empty? Dnsruby.log.debug{'Sending error to client'} send_result_and_stop_querying(client_queue, client_query_id, select_queue, response, error) end end # @TODO@ If we're still sending packets for this query, but none are outstanding, then # jumpstart the next query? # } end # TO BE CALLED IN A SYNCHRONIZED BLOCK def increment_resolver_priority(res) TheLog.debug("Incrementing resolver priority for #{res.server}\n") # @parent.single_res_mutex.synchronize { index = @parent.single_resolvers.index(res) if index > 0 @parent.single_resolvers.delete(res) @parent.single_resolvers.insert(index-1,res) end # } end # TO BE CALLED IN A SYNCHRONIZED BLOCK def decrement_resolver_priority(res) TheLog.debug("Decrementing resolver priority for #{res.server}\n") # @parent.single_res_mutex.synchronize { index = @parent.single_resolvers.index(res) if index < @parent.single_resolvers.length @parent.single_resolvers.delete(res) @parent.single_resolvers.insert(index+1,res) end # } end # TO BE CALLED IN A SYNCHRONIZED BLOCK def demote_resolver(res) TheLog.debug("Demoting resolver priority for #{res.server} to bottom\n") # @parent.single_res_mutex.synchronize { @parent.single_resolvers.delete(res) @parent.single_resolvers.push(res) # } end def handle_response(select_queue, query_id, response) # :nodoc: all # Handle a good response # Should also stick resolver more to the front of the list for future queries Dnsruby.log.debug('Handling good response') resolver, _msg, client_query_id, _retry_count = query_id increment_resolver_priority(resolver) unless response.cached # @mutex.synchronize{ _query, _client_queue, s_queue, _outstanding = @query_list[client_query_id] if s_queue != select_queue log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}") end stop_querying(client_query_id) # @TODO@ Does the client want notified at this point? # client_queue.push([client_query_id, Resolver::EventType::RECEIVED, msg, nil]) # } end def handle_validation_response(select_queue, query_id, response) # :nodoc: all _resolver, _msg, client_query_id, _retry_count = query_id # @mutex.synchronize { _query, client_queue, s_queue, _outstanding = @query_list[client_query_id] if s_queue != select_queue log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}") end if response.rcode == RCode.NXDOMAIN send_result(client_queue, client_query_id, select_queue, response, NXDomain.new) else # @TODO@ Was there an error validating? Should we raise an exception for certain security levels? # This should be configurable by the client. send_result(client_queue, client_query_id, select_queue, response, nil) # } end end def handle_validation_error(select_queue, query_id, error, response) _resolver, _msg, client_query_id, _retry_count = query_id _query, client_queue, s_queue, _outstanding = @query_list[client_query_id] if s_queue != select_queue log_and_raise("Serious internal error : expected select queue #{s_queue}, got #{select_queue}") end # For some errors, we immediately send result. For others, should we retry? # Either : # handle_error_response(queue, event_id, error, response) # Or: send_result(client_queue, client_query_id, select_queue, response, error) # # end end end require 'dnsruby/single_resolver' dnsruby-1.61.3/lib/dnsruby/recursor.rb0000644000004100000410000007261213607400362017756 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # Dnsruby::Recursor - Perform recursive dns lookups # # require 'Dnsruby' # rec = Dnsruby::Recursor.new() # answer = rec.recurse("rob.com.au") # # This module uses a Dnsruby::Resolver to perform recursive queries. # # === AUTHOR # # Rob Brown, bbb@cpan.org # Alex Dalitz, alexd@nominet.org.uk # # === SEE ALSO # # Dnsruby::Resolver, # # === COPYRIGHT # # Copyright (c) 2002, Rob Brown. All rights reserved. # Portions Copyright (c) 2005, Olaf M Kolkman. # Ruby version with caching and validation Copyright (c) 2008, AlexD (Nominet UK) # # Example lookup process: # # [root@box root]# dig +trace www.rob.com.au. # # ; <<>> DiG 9.2.0 <<>> +trace www.rob.com.au. # ;; global options: printcmd # . 507343 IN NS C.ROOT-SERVERS.NET. # . 507343 IN NS D.ROOT-SERVERS.NET. # . 507343 IN NS E.ROOT-SERVERS.NET. # . 507343 IN NS F.ROOT-SERVERS.NET. # . 507343 IN NS G.ROOT-SERVERS.NET. # . 507343 IN NS H.ROOT-SERVERS.NET. # . 507343 IN NS I.ROOT-SERVERS.NET. # . 507343 IN NS J.ROOT-SERVERS.NET. # . 507343 IN NS K.ROOT-SERVERS.NET. # . 507343 IN NS L.ROOT-SERVERS.NET. # . 507343 IN NS M.ROOT-SERVERS.NET. # . 507343 IN NS A.ROOT-SERVERS.NET. # . 507343 IN NS B.ROOT-SERVERS.NET. # ;; Received 436 bytes from 127.0.0.1#53(127.0.0.1) in 9 ms # ;;; But these should be hard coded as the hints # # ;;; Ask H.ROOT-SERVERS.NET gave: # au. 172800 IN NS NS2.BERKELEY.EDU. # au. 172800 IN NS NS1.BERKELEY.EDU. # au. 172800 IN NS NS.UU.NET. # au. 172800 IN NS BOX2.AUNIC.NET. # au. 172800 IN NS SEC1.APNIC.NET. # au. 172800 IN NS SEC3.APNIC.NET. # ;; Received 300 bytes from 128.63.2.53#53(H.ROOT-SERVERS.NET) in 322 ms # ;;; A little closer than before # # ;;; Ask NS2.BERKELEY.EDU gave: # com.au. 259200 IN NS ns4.ausregistry.net. # com.au. 259200 IN NS dns1.telstra.net. # com.au. 259200 IN NS au2ld.CSIRO.au. # com.au. 259200 IN NS audns01.syd.optus.net. # com.au. 259200 IN NS ns.ripe.net. # com.au. 259200 IN NS ns1.ausregistry.net. # com.au. 259200 IN NS ns2.ausregistry.net. # com.au. 259200 IN NS ns3.ausregistry.net. # com.au. 259200 IN NS ns3.melbourneit.com. # ;; Received 387 bytes from 128.32.206.12#53(NS2.BERKELEY.EDU) in 10312 ms # ;;; A little closer than before # # ;;; Ask ns4.ausregistry.net gave: # com.au. 259200 IN NS ns1.ausregistry.net. # com.au. 259200 IN NS ns2.ausregistry.net. # com.au. 259200 IN NS ns3.ausregistry.net. # com.au. 259200 IN NS ns4.ausregistry.net. # com.au. 259200 IN NS ns3.melbourneit.com. # com.au. 259200 IN NS dns1.telstra.net. # com.au. 259200 IN NS au2ld.CSIRO.au. # com.au. 259200 IN NS ns.ripe.net. # com.au. 259200 IN NS audns01.syd.optus.net. # ;; Received 259 bytes from 137.39.1.3#53(ns4.ausregistry.net) in 606 ms # ;;; Uh... yeah... I already knew this # ;;; from what NS2.BERKELEY.EDU told me. # ;;; ns4.ausregistry.net must have brain damage # # ;;; Ask ns1.ausregistry.net gave: # rob.com.au. 86400 IN NS sy-dns02.tmns.net.au. # rob.com.au. 86400 IN NS sy-dns01.tmns.net.au. # ;; Received 87 bytes from 203.18.56.41#53(ns1.ausregistry.net) in 372 ms # ;;; Ah, much better. Something more useful. # # ;;; Ask sy-dns02.tmns.net.au gave: # www.rob.com.au. 7200 IN A 139.134.5.123 # rob.com.au. 7200 IN NS sy-dns01.tmns.net.au. # rob.com.au. 7200 IN NS sy-dns02.tmns.net.au. # ;; Received 135 bytes from 139.134.2.18#53(sy-dns02.tmns.net.au) in 525 ms # ;;; FINALLY, THE ANSWER! # Now,DNSSEC validation is performed (unless disabled). class Recursor class AddressCache # :nodoc: all # Like an array, but stores the expiration of each record. def initialize(*args) @hash = Hash.new # stores addresses against their expiration @mutex = Mutex.new # This class is thread-safe end def push(item) address, ttl = item expiration = Time.now + ttl @mutex.synchronize { @hash[address] = expiration } end def values ret =[] keys_to_delete = [] @mutex.synchronize { @hash.keys.each {|address| if (@hash[address] > Time.now) ret.push(address) else keys_to_delete.push(address) end } keys_to_delete.each {|key| @hash.delete(key) } } return ret end def length @mutex.synchronize { return @hash.length } end def each() values.each {|v| yield v } end end attr_accessor :nameservers, :callback, :recurse, :ipv6_ok attr_reader :hints, :dnssec # The resolver to use for the queries attr_accessor :resolver # For guarding access to shared caches. @@mutex = Mutex.new # :nodoc: all @@hints = nil @@authority_cache = Hash.new @@zones_cache = nil @@nameservers = nil def dnssec=(dnssec_on) @dnssec = dnssec_on @resolver.dnssec = dnssec_on end def initialize(res = nil) if (res) @resolver = res else if (defined?@@nameservers && @@nameservers.length > 0) @resolver = Resolver.new({:nameserver => @@nameservers}) else @resolver = Resolver.new end end @resolver.dnssec = @dnssec @ipv6_ok = false end # Initialize the hint servers. Recursive queries need a starting name # server to work off of. This method takes a list of IP addresses to use # as the starting servers. These name servers should be authoritative for # the root (.) zone. # # res.hints=(ips) # # If no hints are passed, the default nameserver is asked for the hints. # Normally these IPs can be obtained from the following location: # # ftp://ftp.internic.net/domain/named.root # def hints=(hints) Recursor.set_hints(hints, @resolver) end def Recursor.set_hints(hints, resolver) TheLog.debug(";; hints(#{hints.inspect})\n") @resolver = resolver if (resolver.single_resolvers.length == 0) resolver = Resolver.new() resolver.dnssec = @dnssec end if (hints && hints.length > 0) resolver.nameservers=hints if (String === hints) hints = [hints] end hints.each {|hint| @@hints = Hash.new @@hints[hint]=hint } end if (!hints && @@nameservers) @@hints=(@@nameservers) else @@nameservers=(hints) @@hints = hints end TheLog.debug(";; verifying (root) zone...\n") # bind always asks one of the hint servers # for who it thinks is authoritative for # the (root) zone as a sanity check. # Nice idea. # if (!@@hints || @@hints.length == 0) resolver.recurse=(1) packet=resolver.query_no_validation_or_recursion(".", "NS", "IN") hints = Hash.new if (packet) if (ans = packet.answer) ans.each do |rr| if (rr.name.to_s =~ /^\.?$/ and rr.type == Types::NS) # Found root authority server = rr.nsdname.to_s.downcase server.sub!(/\.$/,"") TheLog.debug(";; FOUND HINT: #{server}\n") hints[server] = AddressCache.new end end if ((packet.additional.length == 0) || ((packet.additional.length == 1) && (packet.additional()[0].type == Types.OPT))) # Some resolvers (e.g. 8.8.8.8) do not send an additional section - # need to make explicit queries for these :( # Probably best to limit the number of outstanding queries - extremely bursty behaviour otherwise # What happens if we select only name q = Queue.new hints.keys.each {|server| # Query for the server address and add it to hints. ['A', 'AAAA'].each {|type| msg = Message.new msg.do_caching = @do_caching msg.header.rd = false msg.do_validation = false msg.add_question(server, type, 'IN') if (@dnssec) msg.header.cd = true # We do our own validation by default end resolver.send_async(msg, q) } } (hints.length * 2).times { id, result, error = q.pop if (result) result.answer.each {|rr| TheLog.debug(";; NS address: " + rr.inspect+"\n") add_to_hints(hints, rr) } end } else packet.additional.each do |rr| TheLog.debug(";; ADDITIONAL: "+rr.inspect+"\n") add_to_hints(hints, rr) end end end # foreach my $server (keys %hints) { hints.keys.each do |server| if (!hints[server] || hints[server].length == 0) # Wipe the servers without lookups hints.delete(server) end end @@hints = hints else @@hints = {} end if (@@hints.size > 0) TheLog.info(";; USING THE FOLLOWING HINT IPS:\n") @@hints.values.each do |ips| ips.each do |server| TheLog.info(";; #{server}\n") end end else raise ResolvError.new( "Server ["+(@@nameservers)[0].to_s+".] did not give answers") end # Disable recursion flag. resolver.recurse=(0) # end # return $self->nameservers( map { @{ $_ } } values %{ $self->{'hints'} } ); if (Array === @@hints) temp = [] @@hints.each {|hint| temp.push(hint) } @@hints = Hash.new count = 0 temp.each {|hint| print "Adding hint : #{temp[count]}\n" @@hints[count] = temp[count] count += 1 } end if (String === @@hints) temp = @@hints @@hints = Hash.new @@hints[0] = temp end # @@nameservers = @@hints.values @@nameservers=[] @@hints.each {|key, value| @@nameservers.push(key) } return @@nameservers end def Recursor.add_to_hints(hints, rr) server = rr.name.to_s.downcase server.sub!(/\.$/,"") if (server) if ( rr.type == Types::A) # print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'}; if (hints[server]!=nil) TheLog.debug(";; STORING IP: #{server} IN A "+rr.address.to_s+"\n") hints[server].push([rr.address.to_s, rr.ttl]) end end if ( rr.type == Types::AAAA) # print ";; ADDITIONAL HELP: $server -> [".$rr->rdatastr."]\n" if $self->{'debug'}; if (hints[server]) TheLog.debug(";; STORING IP6: #{server} IN AAAA "+rr.address.to_s+"\n") hints[server].push([rr.address.to_s, rr.ttl]) end end end end # This method takes a code reference, which is then invoked each time a # packet is received during the recursive lookup. For example to emulate # dig's C<+trace> function: # # res.recursion_callback(Proc.new { |packet| # print packet.additional.inspect # # print";; Received %d bytes from %s\n\n", # packetanswersize, # packet.answerfrom); # }) # def recursion_callback=(sub) # if (sub && UNIVERSAL::isa(sub, 'CODE')) @callback = sub # end end def recursion_callback return @callback end def Recursor.clear_caches(resolver = Resolver.new) resolver.dnssec = @dnssec Recursor.set_hints(Hash.new, resolver) @@zones_cache = Hash.new # key zone_name, values Hash of servers and AddressCaches @@zones_cache["."] = @@hints @@authority_cache = Hash.new end def query_no_validation_or_recursion(name, type=Types.A, klass=Classes.IN) # :nodoc: all return query(name, type, klass, true) end # This method is much like the normal query() method except it disables # the recurse flag in the packet and explicitly performs the recursion. # # packet = res.query( "www.netscape.com.", "A") # packet = res.query( "www.netscape.com.", "A", "IN", true) # no validation # # The Recursor maintains a cache of known nameservers. # DNSSEC validation is performed unless true is passed as the fourth parameter. def query(name, type=Types.A, klass=Classes.IN, no_validation = false) # @TODO@ PROVIDE AN ASYNCHRONOUS SEND WHICH RETURNS MESSAGE WITH ERROR!!! # Make sure the hint servers are initialized. @@mutex.synchronize { self.hints=(Hash.new) unless @@hints } @resolver.recurse=(0) # Make sure the authority cache is clean. # It is only used to store A and AAAA records of # the suposedly authoritative name servers. # TTLs are respected @@mutex.synchronize { if (!@@zones_cache) Recursor.clear_caches(@resolver) end } # So we have normal hashes, but the array of addresses at the end is now an AddressCache # which respects the ttls of the A/AAAA records # Now see if we already know the zone in question # Otherwise, see if we know any of its parents (will know at least ".") known_zone, known_authorities = get_closest_known_zone_authorities_for(name) # ".", @hints if nothing else # Seed name servers with the closest known authority # ret = _dorecursion( name, type, klass, ".", @hints, 0) ret = _dorecursion( name, type, klass, known_zone, known_authorities, 0, no_validation) Dnssec.validate(ret) if !no_validation # print "\n\nRESPONSE:\n#{ret}\n" return ret end def get_closest_known_zone_for(n) # :nodoc: # Find the closest parent of name that we know # e.g. for nominet.org.uk, try nominet.org.uk., org.uk., uk., . # does @zones_cache contain the name we're after if (Name === n) n = n.to_s # @TODO@ This is a bit crap! end if (n == nil) TheLog.error("Name is nil") raise ResolvError.new("Nameserver invalid!") end name = n.tr("","") if (name[name.length-1] != ".") name = name + "." end while (true) # print "Checking for known zone : #{name}\n" zone = nil @@mutex.synchronize{ zone = @@zones_cache[name] if (zone != nil) return name end } return false if name=="." # strip the name up to the first dot first_dot = name.index(".") if (first_dot == (name.length-1)) name = "." else name = name[first_dot+1, name.length] end end end def get_closest_known_zone_authorities_for(name) # :nodoc: done = false known_authorities, known_zone = nil while (!done) known_zone = get_closest_known_zone_for(name) # print "GOT KNOWN ZONE : #{known_zone}\n" @@mutex.synchronize { known_authorities = @@zones_cache[known_zone] # ".", @hints if nothing else } # print "Known authorities : #{known_authorities}\n" # Make sure that known_authorities still contains some authorities! # If not, remove the zone from zones_cache, and start again if (known_authorities && known_authorities.values.length > 0) done = true else @@mutex.synchronize{ @@zones_cache.delete(known_zone) } end end return known_zone, known_authorities # @TODO@ Need to synchronize access to these! end def _dorecursion(name, type, klass, known_zone, known_authorities, depth, no_validation) # :nodoc: if ( depth > 255 ) TheLog.debug(";; _dorecursion() Recursion too deep, aborting...\n") @errorstring="Recursion too deep, aborted" return nil end known_zone.sub!(/\.*$/, ".") ns = [] # Array of AddressCaches (was array of array of addresses) @@mutex.synchronize{ # Get IPs from authorities known_authorities.keys.each do |ns_rec| if (known_authorities[ns_rec] != nil && known_authorities[ns_rec] != [] ) @@authority_cache[ns_rec] = known_authorities[ns_rec] ns.push(@@authority_cache[ns_rec]) elsif (@@authority_cache[ns_rec]!=nil && @@authority_cache[ns_rec]!=[]) known_authorities[ns_rec] = @@authority_cache[ns_rec] ns.push(@@authority_cache[ns_rec]) end end if (ns.length == 0) found_auth = 0 TheLog.debug(";; _dorecursion() Failed to extract nameserver IPs:") TheLog.debug(known_authorities.inspect + @@authority_cache.inspect) known_authorities.keys.each do |ns_rec| if (known_authorities[ns_rec]==nil || known_authorities[ns_rec]==[]) TheLog.debug(";; _dorecursion() Manual lookup for authority [#{ns_rec}]") auth_packet=nil ans=[] # Don't query for V6 if its not there. # Do this in parallel ip_mutex = Mutex.new ip6_thread = Thread.start { if ( @ipv6_ok) auth_packet = _dorecursion(ns_rec,"AAAA", klass, # packet ".", # known_zone @@hints, # known_authorities depth+1); # depth ip_mutex.synchronize { ans.push(auth_packet.answer) if auth_packet } end } ip4_thread = Thread.start { auth_packet = _dorecursion(ns_rec,"A",klass, # packet ".", # known_zone @@hints, # known_authorities depth+1); # depth ip_mutex.synchronize { ans.push(auth_packet.answer ) if auth_packet } } ip6_thread.join ip4_thread.join if ( ans.length > 0 ) TheLog.debug(";; _dorecursion() Answers found for [#{ns_rec}]") # foreach my $rr (@ans) { ans.each do |rr_arr| rr_arr.each do |rr| TheLog.debug(";; RR:" + rr.inspect + "") if (rr.type == Types::CNAME) # Follow CNAME server = rr.name.to_s.downcase if (server) server.sub!(/\.*$/, ".") if (server == ns_rec) cname = rr.cname.downcase cname.sub!(/\.*$/, ".") TheLog.debug(";; _dorecursion() Following CNAME ns [#{ns_rec}] -> [#{cname}]") if (!(known_authorities[cname])) known_authorities[cname] = AddressCache.new end known_authorities.delete(ns_rec) next end end elsif (rr.type == Types::A || rr.type == Types::AAAA ) server = rr.name.to_s.downcase if (server) server.sub!(/\.*$/, ".") if (known_authorities[server]!=nil) ip = rr.address.to_s TheLog.debug(";; _dorecursion() Found ns: #{server} IN A #{ip}") @@authority_cache[server] = known_authorities[server] @@authority_cache[ns_rec].push([ip, rr.ttl]) found_auth+=1 next end end end TheLog.debug(";; _dorecursion() Ignoring useless answer: " + rr.inspect + "") end end else TheLog.debug(";; _dorecursion() Could not find A records for [#{ns_rec}]") end end end if (found_auth > 0) TheLog.debug(";; _dorecursion() Found #{found_auth} new NS authorities...") return _dorecursion( name, type, klass, known_zone, known_authorities, depth+1) end TheLog.debug(";; _dorecursion() No authority information could be obtained.") return nil end } # Cut the deck of IPs in a random place. TheLog.debug(";; _dorecursion() cutting deck of (" + ns.length.to_s + ") authorities...") splitpos = rand(ns.length) start = ns[0, splitpos] endarr = ns[splitpos, ns.length - splitpos] ns = endarr + start nameservers = [] ns.each do |nss| nss.each {|n| nameservers.push(n.to_s) } end resolver = Resolver.new({:nameserver=>nameservers}) resolver.dnssec = @dnssec servers = [] resolver.single_resolvers.each {|s| servers.push(s.server) } resolver.retry_delay = nameservers.length begin # Should construct packet ourselves and clear RD bit query = Message.new(name, type, klass) query.header.rd = false query.do_validation = true query.do_caching = false query.do_validation = false if no_validation # print "Sending msg from resolver, dnssec = #{resolver.dnssec}, do_validation = #{query.do_validation}\n" packet = resolver.send_message(query) # @TODO@ Now prune unrelated RRSets (RFC 5452 section 6) prune_rrsets_to_rfc5452(packet, known_zone) rescue ResolvTimeout, IOError => e # TheLog.debug(";; nameserver #{levelns.to_s} didn't respond") # next TheLog.debug("No response!") return nil end if (packet) # @TODO@ Check that the packet *is* actually authoritative!! if (@callback) @callback.call(packet) end of = nil TheLog.debug(";; _dorecursion() Response received from [" + @answerfrom.to_s + "]") status = packet.rcode authority = packet.authority if (status) if (status == "NXDOMAIN") # I guess NXDOMAIN is the best we'll ever get TheLog.debug(";; _dorecursion() returning NXDOMAIN") return packet elsif (packet.answer.length > 0) TheLog.debug(";; _dorecursion() Answers were found.") return packet elsif (packet.header.aa) TheLog.debug(";; _dorecursion() Authoritative answer found") return packet elsif (authority.length > 0) auth = Hash.new # foreach my $rr (@authority) { authority.each do |rr| if (rr.type.to_s =~ /^(NS|SOA)$/) server = (rr.type == Types::NS ? rr.nsdname : rr.mname).to_s.downcase server.sub!(/\.*$/, ".") of = rr.name.to_s.downcase of.sub!(/\.*$/, ".") TheLog.debug(";; _dorecursion() Received authority [#{of}] [" + rr.type().to_s + "] [#{server}]") if (of.length <= known_zone.length) TheLog.debug(";; _dorecursion() Deadbeat name server did not provide new information.") next elsif (of =~ /#{known_zone}/) TheLog.debug(";; _dorecursion() FOUND closer authority for [#{of}] at [#{server}].") auth[server] ||= AddressCache.new #[] @TODO@ If there is no additional record for this, then we want to use the authority! if (rr.type == Types.NS) if ((packet.additional.rrset(rr.nsdname, Types::A).length == 0) && (packet.additional.rrset(rr.nsdname, Types::AAAA).length == 0)) auth[server].push([rr.nsdname, rr.ttl]) end end else TheLog.debug(";; _dorecursion() Confused name server [" + @answerfrom + "] thinks [#{of}] is closer than [#{known_zone}]?") return nil end else TheLog.debug(";; _dorecursion() Ignoring NON NS entry found in authority section: " + rr.inspect) end end # foreach my $rr ($packet->additional) packet.additional.each do |rr| if (rr.type == Types::CNAME) # Store this CNAME into %auth too server = rr.name.to_s.downcase if (server) server.sub!(/\.*$/, ".") if (auth[server]!=nil && auth[server].length > 0) cname = rr.cname.to_s.downcase cname.sub!(/\.*$/, ".") TheLog.debug(";; _dorecursion() FOUND CNAME authority: " + rr.string) auth[cname] ||= AddressCache.new # [] auth[server] = auth[cname] next end end elsif (rr.type == Types::A || rr.type == Types::AAAA) server = rr.name.to_s.downcase if (server) server.sub!(/\.*$/, ".") if (auth[server]!=nil) if (rr.type == Types::A) TheLog.debug(";; _dorecursion() STORING: #{server} IN A " + rr.address.to_s) end if (rr.type == Types::AAAA) TheLog.debug(";; _dorecursion() STORING: #{server} IN AAAA " + rr.address.to_s) end auth[server].push([rr.address.to_s, rr.ttl]) next end end end TheLog.debug(";; _dorecursion() Ignoring useless: " + rr.inspect) end if (of =~ /#{known_zone}/) # print "Adding #{of} with :\n#{auth}\nto zones_cache\n" @@mutex.synchronize{ @@zones_cache[of]=auth } return _dorecursion( name, type, klass, of, auth, depth+1, no_validation) else return _dorecursion( name, type, klass, known_zone, known_authorities, depth+1, no_validation ) end end end end return nil end def prune_rrsets_to_rfc5452(packet, zone) # Now prune the response of any unrelated rrsets (RFC5452 section6) # "One very simple way to achieve this is to only accept data if it is # part of the domain for which the query was intended." if (!packet.header.aa) return end if (!packet.question()[0]) return end section_rrsets = packet.section_rrsets section_rrsets.keys.each {|section| section_rrsets[section].each {|rrset| n = Name.create(rrset.name) n.absolute = true if ((n.to_s == zone) || (n.to_s == Name.create(zone).to_s) || (n.subdomain_of?(Name.create(zone))) || (rrset.type == Types::OPT)) # # @TODO@ Leave in the response if it is an SOA, NSEC or RRSIGfor the parent zone # # elsif ((query_name.subdomain_of?rrset.name) && # elsif ((rrset.type == Types.SOA) || (rrset.type == Types.NSEC) || (rrset.type == Types.NSEC3)) #) else TheLog.debug"Removing #{rrset.name}, #{rrset.type} from response from server for #{zone}" packet.send(section).remove_rrset(rrset.name, rrset.type) end } } end end end dnsruby-1.61.3/lib/dnsruby/name.rb0000644000004100000410000003062413607400362017027 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ module Dnsruby # == Dnsruby::Name class # # A representation of a DNS name # (RFC1035, section 3.1) # # == methods # # * Name::create(namestring) # * Name#absolute? # * Name#wild? # * Name#subdomain_of?(other) # * Name#labels # require 'addressable' class Name include Comparable MaxNameLength=255 # -- # A Name is a collection of Labels. Each label is presentation-formatted # When a Name is wire-encoded, the label array is walked, and each label is wire-encoded. # When a Name is unencoded, each label is unencoded, and added to the Name collection of labels. # When a Name is made from a string, the Name is split into Labels. # ++ # Creates a new Dnsruby::Name from +arg+. +arg+ can be : # # * Name:: returns +arg+ # * String:: returns a new Name def self.create(arg) case arg when Name return Name.new(arg.labels, arg.absolute?) when String # arg.gsub!(/\.$/o, "") if (arg==".") return Name.new([],true) end if (arg=="") return Name.new([],false) end arg = punycode(arg) return Name.new(split_escaped(arg), /\.\z/ =~ arg ? true : false) # return Name.new(Label.split(arg), /\.\z/ =~ arg ? true : false) when Array return Name.new(arg, /\.\z/ =~ (arg.last ? ((arg.last.kind_of?String)?arg.last : arg.last.string) : arg.last) ? true : false) else raise ArgumentError.new("cannot interpret as DNS name: #{arg.inspect}") end end def self.punycode(d) begin c = Addressable::URI.parse("http://" + d.to_s) ret = c.normalized_host.sub("http://", "") if (!d.end_with?".") return ret.chomp(".") end if (!ret.end_with?".") return ret + "." end return ret rescue Exception => e return d end end def self.split_escaped(arg) #:nodoc: all encodedlabels = name2encodedlabels(arg) return encodedlabels end def self.split(name) encodedlabels = name2encodedlabels(name) labels = encodedlabels.each {|el| Name.decode(el.to_s)} return labels end attr_accessor :labels # This method should only be called internally. # Use Name::create to create a new Name def initialize(labels, absolute=true) #:nodoc: all total_length=labels.length-1 labels.each do |l| if (!l.kind_of?Label) raise ArgumentError.new("Name::new called with non-labels. Use Name::create instead?") end total_length+=l.length end if (total_length > MaxNameLength) raise ResolvError.new("Name length is #{total_length}, greater than max of #{MaxNameLength} octets!") end @labels = labels @absolute = absolute end def downcase labels = [] @labels.each do |label| labels << Label.new(label.downcase) end return Name.new(labels) end def inspect # :nodoc: "#<#{self.class}: #{self.to_s}#{@absolute ? '.' : ''}>" end # Returns true if this Name is absolute def absolute? return @absolute end def absolute=(on) # :nodoc: @absolute = on end def strip_label # :nodoc: n = Name.new(self.labels()[1, self.labels.length-1], self.absolute?) return n end # Is this name a wildcard? def wild? if (labels.length == 0) return false end return (labels[0].string == '*') end # Return the canonical form of this name (RFC 4034 section 6.2) def canonical # return MessageEncoder.new {|msg| msg.put_name(self, true) }.to_s end def <=>(other) # return -1 if other less than us, +1 if greater than us return 0 if (canonical == other.canonical) if (canonically_before(other)) return +1 end return -1 end def canonically_before(n) if (!(Name === n)) n = Name.create(n) end # Work out whether this name is canonically before the passed Name # RFC 4034 section 6.1 # For the purposes of DNS security, owner names are ordered by treating # individual labels as unsigned left-justified octet strings. The # absence of a octet sorts before a zero value octet, and uppercase # US-ASCII letters are treated as if they were lowercase US-ASCII # letters. # To compute the canonical ordering of a set of DNS names, start by # sorting the names according to their most significant (rightmost) # labels. For names in which the most significant label is identical, # continue sorting according to their next most significant label, and # so forth. # Get the list of labels for both names, and then swap them my_labels = @labels.reverse other_labels = n.labels.reverse my_labels.each_index {|i| if (!other_labels[i]) return false end next if (other_labels[i].downcase == my_labels[i].downcase) return (my_labels[i].downcase < other_labels[i].downcase) } return true end def ==(other) # :nodoc: return false if other.class != Name return @labels == other.labels && @absolute == other.absolute? end alias eql? == # :nodoc: # Tests subdomain-of relation : returns true if this name # is a subdomain of +other+. # # domain = Resolv::Name.create("y.z") # p Resolv::Name.create("w.x.y.z").subdomain_of?(domain) #=> true # p Resolv::Name.create("x.y.z").subdomain_of?(domain) #=> true # p Resolv::Name.create("y.z").subdomain_of?(domain) #=> false # p Resolv::Name.create("z").subdomain_of?(domain) #=> false # p Resolv::Name.create("x.y.z.").subdomain_of?(domain) #=> false # p Resolv::Name.create("w.z").subdomain_of?(domain) #=> false def subdomain_of?(other) raise ArgumentError, "not a domain name: #{other.inspect}" unless Name === other return false if @absolute != other.absolute? other_len = other.length return false if @labels.length <= other_len return @labels[-other_len, other_len] == other.to_a end def hash # :nodoc: return @labels.hash ^ @absolute.hash end def to_a #:nodoc: all return @labels end def length #:nodoc: all return @labels.length end def [](i) #:nodoc: all return @labels[i] end # returns the domain name as a string. # # The domain name doesn't have a trailing dot even if the name object is # absolute. # # Example : # # p Resolv::Name.create("x.y.z.").to_s #=> "x.y.z" # p Resolv::Name.create("x.y.z").to_s #=> "x.y.z" # def to_s(include_absolute=false) ret = to_str(@labels) if (@absolute && include_absolute) ret += "." end return ret end def to_str(labels) # :nodoc: all ls =[] labels.each {|el| ls.push(Name.decode(el.to_s))} return ls.join('.') # return @labels.collect{|l| (l.kind_of?String) ? l : l.string}.join('.') end # Utility function # # name2labels to translate names from presentation format into an # array of "wire-format" labels. # in: dName a string with a domain name in presentation format (1035 # sect 5.1) # out: an array of labels in wire format. def self.name2encodedlabels (dName) #:nodoc: all # Check for "\" in the name : If there, then decode properly - otherwise, cheat and split on "." if (dName.index("\\")) names=[] j=0; while (dName && dName.length > 0) names[j],dName = encode(dName) j+=1 end return names else labels = [] dName.split(".").each {|l| labels.push(Label.new(l)) } return labels end end def self.decode(wire) #:nodoc: all presentation="" length=wire.length # There must be a nice regexp to do this.. but since I failed to # find one I scan the name string until I find a '\', at that time # I start looking forward and do the magic. i=0; unpacked = wire.unpack("C*") while (i < length ) c = unpacked[i] if ( c < 33 || c > 126 ) presentation=presentation + sprintf("\\%03u" ,c) elsif ( c.chr == "\"" ) presentation=presentation + "\\\"" elsif ( c.chr == "\$") presentation=presentation + "\\\$" elsif ( c.chr == "(" ) presentation=presentation + "\\(" elsif ( c.chr == ")" ) presentation=presentation + "\\)" elsif ( c.chr == ";" ) presentation=presentation + "\\;" elsif ( c.chr == "@" ) presentation=presentation + "\\@" elsif ( c.chr == "\\" ) presentation=presentation + "\\\\" elsif ( c.chr == ".") presentation=presentation + "\\." else presentation=presentation + c.chr() end i=i+1 end return presentation # return Label.new(presentation) end # wire,leftover=presentation2wire(leftover) # Will parse the input presentation format and return everything before # the first non-escaped "." in the first element of the return array and # all that has not been parsed yet in the 2nd argument. def self.encode(presentation) #:nodoc: all presentation=presentation.to_s wire=""; length=presentation.length; i=0; while (i < length ) c=presentation.unpack("x#{i}C1")[0] if (c == 46) # ord('.') endstring = presentation[i+1, presentation.length-(i+1)] return Label.new(wire),endstring end if (c == 92) # ord'\\' # backslash found pos = i+1 # pos sets where next pattern matching should start if (presentation.index(/\G(\d\d\d)/o, pos)) wire=wire+[$1.to_i].pack("C") i=i+3 elsif(presentation.index(/\Gx(\h\h)/o, pos)) wire=wire+[$1].pack("H*") i=i+3 elsif(presentation.index(/\G\./o, pos)) wire=wire+"\." i=i+1 elsif(presentation.index(/\G@/o,pos)) wire=wire+"@" i=i+1 elsif(presentation.index(/\G\(/o, pos)) wire=wire+"(" i=i+1 elsif(presentation.index(/\G\)/o, pos)) wire=wire+")" i=i+1 elsif(presentation.index(/\G\\/o, pos)) wire=wire+"\\" i+=1 end else wire = wire + [c].pack("C") end i=i+1 end return Label.new(wire) end # end # == Dnsruby::Label class # # (RFC1035, section 3.1) # class Label include Comparable MaxLabelLength = 63 @@max_length=MaxLabelLength # Split a Name into its component Labels def self.split(arg) return Name.split(arg) end def self.set_max_length(l) @@max_length=l end def initialize(string) if (string.length > @@max_length) raise ResolvError.new("Label too long (#{string.length}, max length=#{MaxLabelLength}). Label = #{string}") end @downcase = string.downcase @string = string @string_length = string.length end attr_reader :string, :downcase def to_s return @string.to_s # + "." end def length return @string_length end def inspect return "#<#{self.class} #{self.to_s}>" end def <=>(other) return (@downcase <=> other.downcase) end def ==(other) return @downcase == other.downcase end def eql?(other) return self == other end def hash return @downcase.hash end end end end dnsruby-1.61.3/lib/dnsruby/zone_reader.rb0000644000004100000410000003671413607400362020412 0ustar www-datawww-data# -- # Copyright 2009 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # This class provides the facility to load a zone file. # It can either process one line at a time, or return an entire zone as a list of # records. module Dnsruby class ZoneReader class ParseException < Exception end # Create a new ZoneReader. The zone origin is required. If the desired SOA minimum # and TTL are passed in, then they are used as default values. def initialize(origin, soa_minimum = nil, soa_ttl = nil) @origin = origin.to_s if (!Name.create(@origin).absolute?) @origin = @origin.to_s + "." end @soa_ttl = soa_ttl if (soa_minimum && !@last_explicit_ttl) @last_explicit_ttl = soa_minimum else @last_explicit_ttl = 0 end @last_explicit_class = Classes.new("IN") @last_name = nil @continued_line = nil @in_quoted_section = false end # Takes a filename string, or any type of IO object, and attempts to load a zone. # Returns a list of RRs if successful, nil otherwise. def process_file(source) if source.is_a?(String) File.open(source) do |file| process_io(file) end else process_io(source) end end # Iterate over each line in a IO object, and process it. # Returns a list of RRs if successful, nil otherwise. def process_io(io) zone = nil io.each do |line| begin ret = process_line(line) if (ret) rr = RR.create(ret) if (!zone) zone = [] end zone.push(rr) end rescue Exception => e raise ParseException.new("Error reading line #{io.lineno} of #{io.inspect} : [#{line}]") end end return zone end # Process the next line of the file # Returns a string representing the normalised line. def process_line(line, do_prefix_hack = false) return nil if (line[0,1] == ";") line = strip_comments(line) return nil if (line.strip.length == 0) return nil if (!line || (line.length == 0)) @in_quoted_section = false if !@continued_line if (line.index("$ORIGIN") == 0) @origin = line.split()[1].strip # $ORIGIN [] # print "Setting $ORIGIN to #{@origin}\n" return nil end if (line.index("$TTL") == 0) @last_explicit_ttl = get_ttl(line.split()[1].strip) # $TTL # print "Setting $TTL to #{ttl}\n" return nil end if (@continued_line) # Add the next line until we see a ")" # REMEMBER TO STRIP OFF COMMENTS!!! @continued_line = strip_comments(@continued_line) line = @continued_line.rstrip.chomp + " " + line if (line.index(")")) # OK @continued_line = false end end open_bracket = line.index("(") if (open_bracket) # Keep going until we see ")" index = line.index(")") if (index && (index > open_bracket)) # OK @continued_line = false else @continued_line = line end end return nil if @continued_line line = strip_comments(line) + "\n" # If SOA, then replace "3h" etc. with expanded seconds # begin return normalise_line(line, do_prefix_hack) # rescue Exception => e # print "ERROR parsing line #{@line_num} : #{line}\n" # return "\n", Types::ANY # end end def strip_comments(line) last_index = 0 # Are we currently in a quoted section? # Does a quoted section begin or end in this line? # Are there any semi-colons? # Ary any of the semi-colons inside a quoted section? # Handle escape characters if (line.index"\\") return strip_comments_meticulously(line) end while (next_index = line.index(";", last_index + 1)) # Have there been any quotes since we last looked? process_quotes(line[last_index, next_index - last_index]) # Now use @in_quoted_section to work out if the ';' terminates the line if (!@in_quoted_section) return line[0,next_index] end last_index = next_index end # Check out the quote situation to the end of the line process_quotes(line[last_index, line.length-1]) return line end def strip_comments_meticulously(line) # We have escape characters in the text. Go through it character by # character and work out what's escaped and quoted and what's not escaped = false quoted = false pos = 0 line.each_char {|c| if (c == "\\") if (!escaped) escaped = true else escaped = false end else if (escaped) if (c >= "0" && c <= "9") # rfc 1035 5.1 \DDD pos = pos + 2 end escaped = false next else if (c == "\"") if (quoted) quoted = false else quoted = true end else if (c == ";") if (!quoted) return line[0, pos+1] end end end end end pos +=1 } return line end def process_quotes(section) # Look through the section of text and set the @in_quoted_section # as it should be at the end of the given section last_index = 0 while (next_index = section.index("\"", last_index + 1)) @in_quoted_section = !@in_quoted_section last_index = next_index end end # Take a line from the input zone file, and return the normalised form # do_prefix_hack should always be false def normalise_line(line, do_prefix_hack = false) # Note that a freestanding "@" is used to denote the current origin - we can simply replace that straight away # Remove the ( and ) # Note that no domain name may be specified in the RR - in that case, last_name should be used. How do we tell? Tab or space at start of line. # If we have text in the record, then ignore that in the parsing, and stick it on again at the end stored_line = ""; if (line.index('"') != nil) stored_line = line[line.index('"'), line.length]; line = line [0, line.index('"')] end if ((line[0,1] == " ") || (line[0,1] == "\t")) line = @last_name + " " + line end line.chomp! line.sub!(/\s+@$/, " #{@origin}") # IN CNAME @ line.sub!(/^@\s+/, "#{@origin} ") # IN CNAME @ line.sub!(/\s+@\s+/, " #{@origin} ") line.strip! # o We need to identify the domain name in the record, and then split = line.split(' ') # split on whitespace name = split[0].strip if (name.index"\\") ls =[] Name.create(name).labels.each {|el| ls.push(Name.decode(el.to_s))} new_name = ls.join('.') if (!(/\.\z/ =~ name)) new_name += "." + @origin else new_name += "." end line = new_name + " " (split.length - 1).times {|i| line += "#{split[i+1]} "} line += "\n" name = new_name split = line.split # o add $ORIGIN to it if it is not absolute elsif !(/\.\z/ =~ name) new_name = name + "." + @origin line.sub!(name, new_name) name = new_name split = line.split end # If the second field is not a number, then we should add the TTL to the line # Remember we can get "m" "w" "y" here! So need to check for appropriate regexp... found_ttl_regexp = (split[1]=~/^[0-9]+[smhdwSMHDW]/) if (found_ttl_regexp == 0) # Replace the formatted ttl with an actual number ttl = get_ttl(split[1]) line = name + " #{ttl} " @last_explicit_ttl = ttl (split.length - 2).times {|i| line += "#{split[i+2]} "} line += "\n" split = line.split elsif (((split[1]).to_i == 0) && (split[1] != "0")) # Add the TTL if (!@last_explicit_ttl) # If this is the SOA record, and no @last_explicit_ttl is defined, # then we need to try the SOA TTL element from the config. Otherwise, # find the SOA Minimum field, and use that. # We should also generate a warning to that effect # How do we know if it is an SOA record at this stage? It must be, or # else @last_explicit_ttl should be defined # We could put a marker in the RR for now - and replace it once we know # the actual type. If the type is not SOA then, then we can raise an error line = name + " %MISSING_TTL% " else line = name + " #{@last_explicit_ttl} " end (split.length - 1).times {|i| line += "#{split[i+1]} "} line += "\n" split = line.split else @last_explicit_ttl = split[1].to_i end # Now see if the clas is included. If not, then we should default to the last class used. begin klass = Classes.new(split[2]) @last_explicit_class = klass rescue ArgumentError # Wasn't a CLASS # So add the last explicit class in line = "" (2).times {|i| line += "#{split[i]} "} line += " #{@last_explicit_class} " (split.length - 2).times {|i| line += "#{split[i+2]} "} line += "\n" split = line.split rescue Error => e end # Add the type so we can load the zone one RRSet at a time. type = Types.new(split[3].strip) is_soa = (type == Types::SOA) type_was = type if (type == Types.RRSIG) # If this is an RRSIG record, then add the TYPE COVERED rather than the type - this allows us to load a complete RRSet at a time type = Types.new(split[4].strip) end type_string=prefix_for_rrset_order(type, type_was) @last_name = name if !([Types::NAPTR, Types::TXT].include?type_was) line.sub!("(", "") line.sub!(")", "") end if (is_soa) if (@soa_ttl) # Replace the %MISSING_TTL% text with the SOA TTL from the config line.sub!(" %MISSING_TTL% ", " #{@soa_ttl} ") else # Can we try the @last_explicit_ttl? if (@last_explicit_ttl) line.sub!(" %MISSING_TTL% ", " #{@last_explicit_ttl} ") end end line = replace_soa_ttl_fields(line) if (!@last_explicit_ttl) soa_rr = Dnsruby::RR.create(line) @last_explicit_ttl = soa_rr.minimum end end line = line.strip if (stored_line && stored_line != "") line += " " + stored_line.strip end # We need to fix up any non-absolute names in the RR # Some RRs have a single name, at the end of the string - # to do these, we can just check the last character for "." and add the # "." + origin string if necessary if ([Types::MX, Types::NS, Types::AFSDB, Types::NAPTR, Types::RT, Types::SRV, Types::CNAME, Types::MB, Types::MG, Types::MR, Types::PTR, Types::DNAME].include?type_was) # if (line[line.length-1, 1] != ".") if (!(/\.\z/ =~ line)) line = line + "." + @origin.to_s end end # Other RRs have several names. These should be parsed by Dnsruby, # and the names adjusted there. if ([Types::MINFO, Types::PX, Types::RP].include?type_was) parsed_rr = Dnsruby::RR.create(line) case parsed_rr.type when Types::MINFO if (!parsed_rr.rmailbx.absolute?) parsed_rr.rmailbx = parsed_rr.rmailbx.to_s + "." + @origin.to_s end if (!parsed_rr.emailbx.absolute?) parsed_rr.emailbx = parsed_rr.emailbx.to_s + "." + @origin.to_s end when Types::PX if (!parsed_rr.map822.absolute?) parsed_rr.map822 = parsed_rr.map822.to_s + "." + @origin.to_s end if (!parsed_rr.mapx400.absolute?) parsed_rr.mapx400 = parsed_rr.mapx400.to_s + "." + @origin.to_s end when Types::RP if (!parsed_rr.mailbox.absolute?) parsed_rr.mailbox = parsed_rr.mailbox.to_s + "." + @origin.to_s end if (!parsed_rr.txtdomain.absolute?) parsed_rr.txtdomain = parsed_rr.txtdomain.to_s + "." + @origin.to_s end end line = parsed_rr.to_s end if (do_prefix_hack) return line + "\n", type_string, @last_name end return line+"\n" end # Get the TTL in seconds from the m, h, d, w format def get_ttl(ttl_text_in) # If no letter afterwards, then in seconds already # Could be e.g. "3d4h12m" - unclear if "4h5w" is legal - best assume it is # So, search out each letter in the string, and get the number before it. ttl_text = ttl_text_in.downcase index = ttl_text.index(/[whdms]/) if (!index) return ttl_text.to_i end last_index = -1 total = 0 while (index) letter = ttl_text[index] number = ttl_text[last_index + 1, index-last_index-1].to_i new_number = 0 case letter when 115 then # "s" new_number = number when 109 then # "m" new_number = number * 60 when 104 then # "h" new_number = number * 3600 when 100 then # "d" new_number = number * 86400 when 119 then # "w" new_number = number * 604800 end total += new_number last_index = index index = ttl_text.index(/[whdms]/, last_index + 1) end return total end def replace_soa_ttl_fields(line) # Replace any fields which evaluate to 0 split = line.split 4.times {|i| x = i + 7 split[x].strip! split[x] = get_ttl(split[x]).to_s } return split.join(" ") + "\n" end # This method is included only for OpenDNSSEC support. It should not be # used otherwise. # Frig the RR type so that NSEC records appear last in the RRSets. # Also make sure that DNSKEYs come first (so we have a key to verify # the RRSet with!). def prefix_for_rrset_order(type, type_was) # :nodoc: all # Now make sure that NSEC(3) RRs go to the back of the list if ['NSEC', 'NSEC3'].include?type.string if (type_was == Types::RRSIG) # Get the RRSIG first type_string = "ZZ" + type.string else type_string = "ZZZ" + type.string end elsif type == Types::DNSKEY type_string = "0" + type.string elsif type == Types::NS # Make sure that we see the NS records first so we know the delegation status type_string = "1" + type.string else type_string = type.string end return type_string end end end dnsruby-1.61.3/lib/dnsruby.rb0000644000004100000410000001510213607400362016101 0ustar www-datawww-data# -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby/code_mappers' require 'dnsruby/message/message' require 'dnsruby/ipv4' require 'dnsruby/ipv6' require 'timeout' require 'dnsruby/the_log' require 'dnsruby/version' require 'dnsruby/cache' require 'dnsruby/DNS' require 'dnsruby/hosts' require 'dnsruby/update' require 'dnsruby/zone_transfer' require 'dnsruby/dnssec' require 'dnsruby/zone_reader' require 'dnsruby/resolv' # = Dnsruby library # Dnsruby is a thread-aware DNS stub resolver library written in Ruby. # # It is based on resolv.rb, the standard Ruby DNS implementation, # but gives a complete DNS implementation, including DNSSEC. # # The Resolv class can be used to resolve addresses using /etc/hosts and /etc/resolv.conf, # or the DNS class can be used to make DNS queries. These interfaces will attempt to apply # the default domain and searchlist when resolving names. # # The Resolver and SingleResolver interfaces allow finer control of individual messages. # The Resolver class sends queries to multiple resolvers using various retry mechanisms. # The SingleResolver class is used by Resolver to send individual Messages to individual # resolvers. # # Resolver queries return Dnsruby::Message objects. Message objects have five # sections: # # * The header section, a Dnsruby::Header object. # # * The question section, a list of Dnsruby::Question objects. # # * The answer section, a list of Dnsruby::Resource objects. # # * The authority section, a list of Dnsruby::Resource objects. # # * The additional section, a list of Dnsruby::Resource objects. # # # == example # res = Dnsruby::Resolver.new # System default # ret = res.query("example.com") # print "#{ret.anwer.length} answer records returned, #{ret.answer.rrsets.length} RRSets returned in aswer section\n" # # p Dnsruby::Resolv.getaddress("www.ruby-lang.org") # p Dnsruby::Resolv.getname("210.251.121.214") # # Dnsruby::DNS.open {|dns| # p dns.getresources("www.ruby-lang.org", Dnsruby::Types.A).collect {|r| r.address} # p dns.getresources("ruby-lang.org", 'MX').collect {|r| [r.exchange.to_s, r.preference]} # } # # == exceptions # # * ResolvError < StandardError # # * ResolvTimeout < Timeout::Error # # * NXDomain < ResolvError # # * FormErr < ResolvError # # * ServFail < ResolvError # # * NotImp < ResolvError # # * Refused < ResolvError # # * NotZone < ResolvError # # * YXDomain < ResolvError # # * YXRRSet < ResolvError # # * NXRRSet < ResolvError # # * NotAuth < ResolvError # # * OtherResolvError < ResolvError # # == I/O # Dnsruby implements a pure Ruby event loop to perform I/O. # Support for EventMachine has been deprecated. # # == DNSSEC # Dnsruby supports DNSSEC and NSEC(3). # DNSSEC support is on by default - but no trust anchors are configured by default. # See Dnsruby::Dnssec for more details. # # == Codes # Dnsruby makes extensive use of several different types of codes. These are implemented # in the form of subclasses of CodeMapper and are located in lib/code_mappers.rb. They are: # # * OpCode - e.g. Query, Status, Notify # * RCode - e.g. NOERROR, NXDOMAIN # * ExtendedRCode - currently only BADVERS # * Classes - IN, CH, HS, NONE, ANY # * Types - RR types, e.g. A, NS, SOA # * QTypes - IXFR, AXFR, MAILB, MAILA, ANY # * MetaTypes - TKEY, TSIG, OPT # * Algorithms - e.g. RSAMD5, DH, DSA # * Nsec3HashAlgorithms - currently only SHA-1 # == Bugs # * NIS is not supported. # * /etc/nsswitch.conf is not supported. # * NSEC3 validation still TBD module Dnsruby def Dnsruby.version return VERSION end @@logger = Logger.new(STDOUT) @@logger.level = Logger::FATAL # Get the log for Dnsruby # Use this to set the log level # e.g. Dnsruby.log.level = Logger::INFO def Dnsruby.log @@logger end # Logs (error level) and raises an error. def log_and_raise(object, error_class = RuntimeError) if object.is_a?(Exception) error = object Dnsruby.log.error(error.inspect) raise error else message = object.to_s Dnsruby.log.error(message) raise error_class.new(message) end end; module_function :log_and_raise # An error raised while querying for a resource class ResolvError < StandardError attr_accessor :response end # A timeout error raised while querying for a resource class ResolvTimeout < Timeout::Error end # The requested domain does not exist class NXDomain < ResolvError end # A format error in a received DNS message class FormErr < ResolvError end # Indicates a failure in the remote resolver class ServFail < ResolvError end # The requested operation is not implemented in the remote resolver class NotImp < ResolvError end # The requested operation was refused by the remote resolver class Refused < ResolvError end # The update RR is outside the zone (in dynamic update) class NotZone < ResolvError end # Some name that ought to exist, does not exist (in dynamic update) class YXDomain < ResolvError end # Some RRSet that ought to exist, does not exist (in dynamic update) class YXRRSet < ResolvError end # Some RRSet that ought not to exist, does exist (in dynamic update) class NXRRSet < ResolvError end # The nameserver is not responsible for the zone (in dynamic update) class NotAuth < ResolvError end # Another kind of resolver error has occurred class OtherResolvError < ResolvError end # Socket was closed by server before request was processed class SocketEofResolvError < ResolvError end # An error occurred processing the TSIG class TsigError < OtherResolvError end # Sent a signed packet, got an unsigned response class TsigNotSignedResponseError < TsigError end # Indicates an error in decoding an incoming DNS message class DecodeError < ResolvError attr_accessor :partial_message end # Indicates an error encoding a DNS message for transmission class EncodeError < ResolvError end # Indicates an error verifying class VerifyError < ResolvError end # Indicates a zone transfer has failed due to SOA serial mismatch class ZoneSerialError < ResolvError end end dnsruby-1.61.3/demo/0000755000004100000410000000000013607400362014245 5ustar www-datawww-datadnsruby-1.61.3/demo/mresolv.rb0000755000004100000410000000505213607400362016266 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # mresolv [ -d ] [ -n number ] [ -t timeout ] [ filename... ] # # mresolv performs multiple DNS lookups in parallel. Names to query # are read from the list of files given on the command line, or from the # standard input. # # = OPTIONS # # *-d : Turn on debugging output. # # *-n number : Set the number of queries to have in progress at any time. # # *-t timeout : Set the query timeout for each name in seconds. # Examples for running: # # echo my-domain.com | ./mresolv.rb # or # ./mresolv.rb # then type domain name(s) separated by new lines and then ctrl-D require 'dnsruby' require 'getoptLong' opts = GetoptLong.new( ['-d', GetoptLong::NO_ARGUMENT], ['-n', GetoptLong::REQUIRED_ARGUMENT], ['-t', GetoptLong::REQUIRED_ARGUMENT]) max_outstanding = 32 # number of requests to have outstanding at any time timeout = 15 # timeout (seconds) debug = false opts.each do |opt, arg| case opt when '-d' Dnsruby.log.level = Logger::INFO debug = true when '-n' max_outstanding = arg.to_i when '-t' timeout = arg end end resolver = Dnsruby::Resolver.new resolver.query_timeout = timeout # We want to have a rolling window of max_outstanding queries. in_progress = 0 q = Queue.new eof = false until eof # Have the thread loop round, send queries until max_num are outstanding. while !eof && in_progress < max_outstanding print('DEBUG: reading...') if debug unless (name = gets) print("EOF.\n") if debug eof = true break end name.chomp! resolver.send_async(Dnsruby::Message.new(name), q, name) in_progress += 1 print("name = #{name}, outstanding = #{in_progress}\n") if debug end # Keep receiving while the query pool is full, or the list has been queried while in_progress >= max_outstanding || (eof && in_progress > 0) id, result, error = q.pop in_progress -= 1 if error print("#{id}:\t#{error}\n") else print("#{result.answer.join("\n")}\n") end end end dnsruby-1.61.3/demo/check_soa.rb0000755000004100000410000001172313607400362016520 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # check_soa - Check a domain's nameservers # # = SYNOPSIS # # check_soa domain # # = DESCRIPTION # # check_soa queries each of a domain's nameservers for the Start # of Authority (SOA) record and prints the serial number. Errors # are printed for nameservers that couldn't be reached or didn't # answer authoritatively. # # = AUTHOR # # The original Bourne Shell and C versions were printed in # "DNS and BIND" by Paul Albitz & Cricket Liu. # # The Perl version was written by Michael Fuhr . # # = SEE ALSO # # axfr, check_zone, mresolv, mx, perldig, Net::DNS require 'dnsruby' NO_DOMAIN_SPECIFIED = -1 NO_NAMESERVERS = -2 def fatal_error(message, exit_code) puts message exit(exit_code) end def usage fatal_error("Usage: #{$0} domain", NO_DOMAIN_SPECIFIED) end def create_resolver resolver = Dnsruby::Resolver.new resolver.retry_times = 2 resolver.recurse = 0 # Send out non-recursive queries # disable caching otherwise SOA is cached from first nameserver queried resolver.do_caching = false resolver end def get_ns_response(resolver, domain) ns_response = resolver.query(domain, 'NS') if ns_response.header.ancount == 0 fatal_error("No nameservers found for #{domain}.", NO_NAMESERVERS) end ns_response end # Finds all the nameserver domains for the domain. def get_ns_domains(resolver, domain) ns_response = get_ns_response(resolver, domain) ns_answers = ns_response.answer.select { |r| r.type == 'NS'} ns_answers.map(&:domainname) end def process_ns_domain(resolver, domain, ns_domain) a_response = begin # In order to lookup the IP(s) of the nameserver, we need a Resolver # object that is set to our local, recursive nameserver. So we create # a new object just to do that. local_resolver = Dnsruby::Resolver.new local_resolver.query(ns_domain, 'A') rescue Exception => e puts "Cannot find address for #{ns_domain}: #{e}" return end a_answers = a_response.answer.select {|r| r.type == 'A'} a_answers.each do |a_answer| # ---------------------------------------------------------------------- # Ask this IP. # ---------------------------------------------------------------------- ip_address = a_answer.address resolver.nameserver = ip_address.to_s print "#{ns_domain} (#{ip_address}): " # ---------------------------------------------------------------------- # Get the SOA record. # ---------------------------------------------------------------------- soa_response = begin resolver.query(domain, 'SOA', 'IN') rescue Exception => e puts "Error : #{e}" return end # ---------------------------------------------------------------------- # Is this nameserver authoritative for the domain? # ---------------------------------------------------------------------- unless soa_response.header.aa print "isn't authoritative for #{domain}\n" return end # ---------------------------------------------------------------------- # We should have received exactly one answer. # ---------------------------------------------------------------------- unless soa_response.header.ancount == 1 puts "expected 1 answer, got #{soa_response.header.ancount}." return end # ---------------------------------------------------------------------- # Did we receive an SOA record? # ---------------------------------------------------------------------- answer_type = soa_response.answer[0].type unless answer_type == 'SOA' puts "expected SOA, got #{answer_type}" return end # ---------------------------------------------------------------------- # Print the serial number. # ---------------------------------------------------------------------- puts "has serial number #{soa_response.answer[0].serial}" end end def main # Get domain from command line, printing usage and exiting if none provided: domain = ARGV.fetch(0) { usage } resolver = create_resolver ns_domains = get_ns_domains(resolver, domain) # ------------------------------------------------------------------------------ # Check the SOA record on each nameserver. # ------------------------------------------------------------------------------ ns_domains.each do |ns_domain_name| process_ns_domain(resolver, domain, ns_domain_name) end end main dnsruby-1.61.3/demo/axfr.rb0000755000004100000410000001273313607400362015543 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # axfr - Perform a DNS zone transfer # # = SYNOPSIS # # axfr [ -fqs ] [ -D directory ] [ @nameserver ] zone # # = DESCRIPTION # # axfr performs a DNS zone transfer, prints each record to the standard # output, and stores the zone to a file. If the zone has already been # stored in a file, axfr will read the file instead of performing a # zone transfer. # # Zones will be stored in a directory hierarchy. For example, the # zone transfer for foo.bar.com will be stored in the file # HOME/.dns-zones/com/bar/foo/axfr. The directory can be changed # with the B<-D> option. # # This programs requires that the Storable module be installed. # # = OPTIONS # # * -f Force a zone transfer, even if the zone has already been stored # in a file. # # * -q Be quiet -- don't print the records from the zone. # # * -s Perform a zone transfer if the SOA serial number on the nameserver # is different than the serial number in the zone file. # # * -D directory Store zone files under I instead of the default directory (see "FILES") # # * nameserver Query nameserver instead of the default nameserver. # # = FILES # # * ${HOME}/.dns-zones Default directory for storing zone files. # # = AUTHOR # # Michael Fuhr # unless (1..2).include?(ARGV.length) puts "Usage: #{$0} [ -fqs ] [ -D directory ] [ @nameserver ] zone" exit(-1) end require 'getoptLong' require 'dnsruby' # ------------------------------------------------------------------------------ # Read any command-line options and check syntax. # ------------------------------------------------------------------------------ # getopts("fqsD:"); opts = GetoptLong.new(["-f", GetoptLong::NO_ARGUMENT], ["-q", GetoptLong::NO_ARGUMENT], ["-D", GetoptLong::REQUIRED_ARGUMENT], ["-s", GetoptLong::NO_ARGUMENT]) opt_q = false opt_f = false opt_s = false opt_d = nil opts.each do |opt, arg| case opt when '-q' opt_q=true when '-f' opt_f = true when '-s' opt_s = true when '-D' opt_d = arg end end # ------------------------------------------------------------------------------ # Get the nameserver (if specified) and set up the zone transfer directory # hierarchy. # ------------------------------------------------------------------------------ nameserver = (ARGV[0] =~ /^@/) ? ARGV.shift : '' nameserver = nameserver.sub(/^@/, '') resolver = nameserver ? Dnsruby::Resolver.new(nameserver) : Dnsruby::Resolver.new zone = ARGV.shift basedir = opt_d || File.join((ENV['HOME'] || ''), '.dns-zones') zonedir = zone.split(/\./).reverse.join("/") zonefile = File.join(basedir, zonedir, 'axfr') # Don't worry about the 0777 permissions here - the current umask setting # will be applied. # NOTE: mkdir will raise an error on failure so I don't think 'or' works here. unless FileTest.directory?(basedir) Dir.mkdir(basedir, 0777) or raise RuntimeError, "can't mkdir #{basedir}: #{$!}\n" end dir = basedir # NOTE: What are we doing here? Could this be replaced by mkdir_p? zonedir.split('/').each do |subdir| dir += '/' + subdir unless FileTest.directory?(dir) Dir.mkdir(dir, 0777) or raise RuntimeError, "can't mkdir #{dir}: #{$!}\n" end end # ------------------------------------------------------------------------------ # Get the zone. # ------------------------------------------------------------------------------ if FileTest.exist?(zonefile) && !opt_f zoneref = Marshal.load(File.open(zonefile)) if zoneref.nil? raise RuntimeError, "couldn't retrieve zone from #{zonefile}: #{$!}\n" end # ---------------------------------------------------------------------- # Check the SOA serial number if desired. # ---------------------------------------------------------------------- if opt_s serial_file = serial_zone = nil zoneref.each do |rr| if (rr.type == 'SOA') serial_file = rr.serial break end end if serial_file.nil? raise RuntimeError, "no SOA in #{zonefile}\n" end soa = resolver.query(zone, 'SOA') if soa.nil? raise RuntimeError, "couldn't get SOA for #{zone}: " + resolver.errorstring + "\n" end soa.answer.each do |rr| if rr.type == 'SOA' serial_zone = rr.serial break end end if serial_zone != serial_file opt_f = true end end else opt_f = true end if opt_f print "nameserver = #{nameserver}, zone=#{zone}" zt = Dnsruby::ZoneTransfer.new zt.server = nameserver if nameserver != '' zoneref = zt.transfer(zone) if zoneref.nil? raise RuntimeError, "couldn't transfer zone\n" end Marshal.dump(zoneref, File.open(zonefile, File::CREAT | File::RDWR)) end # ------------------------------------------------------------------------------ # Print the records in the zone. # ------------------------------------------------------------------------------ unless opt_q zoneref.each { |z| puts z } end dnsruby-1.61.3/demo/digdlv.rb0000755000004100000410000000465013607400362016053 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # digdlv - Ruby script to perform DNS queries, validated against the ISC DLV # registry. # # = SYNOPSIS # # digdlv name [ type [ class ] ] # # = DESCRIPTION # # Performs a DNS query on the given name. The record type # and class can also be specified; if left blank they default # to A and IN. # The program firstly loads the DLV zone signing key. Then, the # requested DNS query is performed recursively. The response is then validated # - the DLV registry is searched for the keys of the closest ancestor # of the query name, and the chain of trust is followed to prove # that the DNSSEC records are correct, or that we do not expect the # response to be signed. # # = AUTHOR # # Michael Fuhr # Alex D require 'dnsruby' def fatal_error(message) puts message exit -1 end unless (1..3).include?(ARGV.length) fatal_error("Usage: #{$0} name [ type [ class ] ]") end resolver = Dnsruby::Recursor.new zone_transfer = Dnsruby::ZoneTransfer.new dlv_key = Dnsruby::RR.create("dlv.isc.org. IN DNSKEY 257 3 5 BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh") Dnsruby::Dnssec.add_dlv_key(dlv_key) name, type, klass = ARGV type ||= 'A' klass ||= 'IN' if type.upcase == 'AXFR' rrs = zone_transfer.transfer(name) # , klass) if rrs rrs.each { |rr| puts rr } else fatal_error("Zone transfer failed: #{resolver.errorstring}.") end else begin answer = resolver.query(name, type, klass) puts answer rescue Exception => e fatal_error("query failed: #{e}") end end dnsruby-1.61.3/demo/rubydig.rb0000755000004100000410000000370213607400362016244 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # rubydig - Ruby script to perform DNS queries # # = SYNOPSIS # # rubydig [ @nameserver ] name [ type [ class ] ] # # = DESCRIPTION # # Performs a DNS query on the given name. The record type # and class can also be specified; if left blank they default # to A and IN. # # = AUTHOR # # Michael Fuhr # def fatal_error(message) puts message exit(-1) end unless (1..3).include?(ARGV.length) fatal_error("Usage: #{$0} [ @nameserver ] name [ type [ class ] ]") end require 'dnsruby' resolver = Dnsruby::Resolver.new zone_transfer = Dnsruby::ZoneTransfer.new if ARGV[0] =~ /^@/ nameserver = ARGV.shift if nameserver == '@auth' resolver = Dnsruby::Recursor.new else puts "Setting nameserver : #{nameserver}" resolver.nameserver = (nameserver.sub(/^@/, '')) puts "nameservers = #{resolver.config.nameserver}" zone_transfer.server = (nameserver.sub(/^@/, '')) end end name, type, klass = ARGV type ||= 'A' klass ||= 'IN' if type.upcase == 'AXFR' rrs = zone_transfer.transfer(name) # , klass) if rrs rrs.each { |rr| puts rr } else fatal_error("Zone transfer failed: #{resolver.errorstring}") end else # Dnsruby::TheLog.level=Logger::DEBUG begin answer = resolver.query(name, type, klass) puts answer rescue Exception => e fatal_error("Query failed: #{e}") end end dnsruby-1.61.3/demo/to_resolve.txt0000644000004100000410000011404213607400362017171 0ustar www-datawww-dataddcsweden.se ddd-direkt.se eat.se eat-house.se msn.se msn-kontakt.se ccc.se ccc-bild.se rrr.se rrrab.se ibm.se ibma.se 443366.se 4444.se jah.se jaha.se l9.se la-bella.se the.se the-archer.se eng.se eng-el.se dot.se dot-ab.se uuu.se uv.se bat.se bat-att-hyra.se jjj.se jjk.se ying.se yings.se jahaa.se the-art-of-planning.se hypermind.se hypermotion.se jerenvik.se jerfs.se bayramband.se bayrol.se yingying.se echotech.se echset.se jahadesign.se bohdesign.se bohed.se uplife.se uplight.se 0591.se 05kakelochklinker.se yeast2003.se yebo.se 0y.se 0z.se bohedberg.se effu.se effusio.se battidningar.se battillbehor.se yeezgaming.se yeguadafavorito.se jerfsten.se beme.se bemek.se bat-consulting.se eci.se broome.se broomtowncats.se 05nvd.se 1-0.se jewel.se jeweliamovie.se effy.se xylocain.se xylon.se bememusik.se broomwade.se 05studios.se upline.se 0-0.se 1-0-0.se bemi.se bourn.se bourneultimatum.se bemba.se bemc.se effyh.se 0-0-0.se xylophane.se booksellers.se bookship.se blastmanager.se blastolac.se brionvega.se briotoys.se benie.se benilla.se yesstyle.se yesterday.se bemce.se 0-0-1.se yeguadakarisma.se benedictine.se benedictum.se bookshop.se 0-1.se benema.se bener.se blastolen.se elevenconsulting.se elevengroup.se yelia.se yell.se benima.se bemcon.se bemus.se bemyguest.se benerotts.se bemo-tunnel.se bemora.se bemda.se emmen.se emmens.se bengtzon.se bengy.se benevolence.se benexa.se benhogan.se yelles.se bemi-service.se 0-360.se efg.se bemoredog.se benimar.se bournonville.se yentreve.se yeomans.se benevent.se benevia.se electronix.se electroparts.se bourns.se xxtreme.se xxx.se yek.se yekonomi.se boursbet.se benesch.se benevinum.se electropirate.se yellowsub.se yellowtaxi.se yeksungems.se yeos.se xxx-cam.se yellowjello.se yellowline.se yemmi.se yen.se benfico.se yeninc.se yenisofra.se benesign.se yellowtech.se yellowguide.se yellowhat.se benevo.se yellowstonepark.se yellowstrom.se 0-3sixty.se yellowspider.se xxx-camgirls.se yellia.se 0-8.se benhoganmusik.se beni.se benget.se bengmark.se yellowlounge.se xview.se xvis.se yenco.se benestadkeramik.se yelp.se yemanja.se yenny.se yello-gas.se electroaudio.se electrocity.se yello-strom.se benestamgolfarchitecture.se yenom.se yendo.se yellowhouse.se yellowjack.se yellowtree.se xxiii.se xxk590.se yellowhelmet.se yellowtel.se yellowbandit.se yellowbf.se benestamgolfarkitektur.se efg-financial-product.se xxl.se electroclash.se xxx-cams.se benic.se efg-financial-products.se yellowtown.se yellowhomeservices.se electroclass.se xq28.se xql.se benice.se xxxvideos.se xxxwebmaster.se electromekano.se electromontage.se beniced.se xylene.se xylix.se xqlusive.se xxxtv.se xxxvideo.se xxxgirls.se xxxit.se electron.se xylem.se xylencr3w.se electroclean.se xoro.se xosexshop.se electroconsult.se benestamgolfcoursedesign.se xushi.se xux.se xqs.se electrocontrol.se electrona.se electrocry.se xterlogistics.se xtern.se benestamgolfdesign.se xr.se electrondimmer.se xn-taxialingss-68a.se xna.se benestravel.se electrodesign.se xradio.se xterna.se benet.se electronet.se xn-alingsstaxi-28a.se xn-blhuset-fxa.se xramvision.se electroedholm.se electroluxservicelund.se electromark.se benetalnil.se benidorm.se electroenoc.se electromecano.se electromedia.se electrojunkie.se electrokit.se xterra.se electrokontrols.se electroline.se xosys.se xxx-dvd.se xxx-video.se xrank.se electrolube.se xxx4you.se electrolux.se electrofishing.se electrographic.se electroklubben.se electroguide.se electrohelios.se electrohype.se xxx666.se xterragear.se lll.se lllb.se data.se data-akut.se hat.se hat-system.se bbb.se bbb-ss.se 777.se 777-host.se ooo.se ooonicsecontrolzoneefuqasdfajewkfdgyyfd.se ttt.se tttak.se kkk.se kkk2007.se electrohead.se 111.se 1111.se eee.se eeee.se mat.se mat-dryck.se mmm.se mmmab.se 555.se 5555.se latab.se latar.se hhgs.se hhhh.se 666.se 666-666.se iii.se iii-development.se cat.se cat-clean.se ggfx.se gggnicsecontrolzonehalskjdfhakjlsdfaskd.se 220volt.se 2222.se nnn.se nnnnicsecontrolzoneahsdqibwbercvhufasbd.se af.se af-belfrage.se oat.se oatly.se ectopic.se ectrading.se effer.se effero.se praxairyara.se praxia.se la-bild.se 11111.se electrolux-at-home.se deputamadre.se depuy.se insyseur.se int-idea.se ccc-c3.se yodavision.se yodesign.se infanterit.se infantiltinferno.se davvo.se davys.se satanic.se satanigatan.se stagno.se stagos.se 0u.se 0v.se ibmalpin.se uncommonsense.se uncover.se 0-tidsflytt.se jjkommunikation.se begat.se begavia.se 444444.se gonic.se gonis.se 3a-advokaterna.se 3a-konsult.se eeemetallform.se 06.se 1-0-0-0.se xxxarkitekter.se 222222.se fitzpatrick.se fiung.se 4456575.se stagreus.se negrete.se negut.se 1-1.se specific-diets.se specifikationskonsult.se befriends.se befuktning.se yinochyang.se beanie.se beans.se 225200.se 3aab.se eat-it.se triphase.se tripike.se lafor.se laforma.se infarb.se jjkonst.se laforza.se bat-kusthandel.se yaukungmun.se yava.se eat-sweden.se uncoveredmusic.se cccaters.se boutiqueannecy.se boutiquebla.se dataswitch.se datasynapse.se oooo.se cccc.se llldata.se jerfstenstrale.se eci-ab.se baystar.se brinkmann.se brinkmotorsport.se xxxbio.se bourses.se 3abyggdelen.se 2299.se datasynergi.se ycdbsoya.se yco.se biskit.se biskop.se yestravel.se yesway.se brootak.se bazooka.se bazookaboys.se ebonite.se ebonnera.se satansgloria.se tripinvest.se yangcreators.se yangs.se baystone.se lafoto.se neh.se ebonus.se eat-web.se yet.se yets.se booksload.se fiv.se yacine.se yaco.se yawin.se yawn.se boheden.se blastorp.se xxxkatalogen.se yelah.se bemireklam.se bemkonsult.se xxxl.se datasystem.se yodii.se bemic.se yawnmedia.se batra.se batracing.se lactolite.se lacuarta.se electropix.se 3album.se 22andberg.se bemman.se rrrf.se eateknik.se eater.se beansprout.se yeh.se bypasset.se byportalen.se 22aug.se yepinvest.se yeppcom.se five.se bray.se brazil.se egestammarketing.se eget.se yep.se yellowmagic.se bengmartin.se eatertainment.se jjkonsult.se booksondemand.se batraco.se bluebirds.se bluebit.se llm.se broprodukter.se 0w.se formanspc.se formany.se yokel.se yoker.se benforlangning.se octv.se oculos.se yodoi.se bemico.se bemindful.se eatab.se eatathome.se ebony.se beming.se fluorcarbon.se fluorplast.se bundle.se bundolo.se brouer.se brouwers.se flexografi.se flexokliche.se ooopps.se booksonline.se yogini.se yogiochyogini.se bitchig.se bitchtour.se yei.se olifant.se oliglobal.se 0wnd.se 22augusti.se llmarkconsult.se brinknet.se benfoto.se bengnet.se brady.se bradykardi.se xxl-video.se xxlpix.se brouzell.se llmaudio.se fleddeflux.se fleece.se judaica.se judako.se yavar.se blastring.se bemichtools.se oatrading.se yolk.se yolo.se bundy.se borealisgroup.se borebro.se bengali.se bengaliweb.se benfurman.se eat2day.se jergelin.se brovag.se efg-financialproduct.se 0x.se yangtorp.se judas.se yeppcongress.se flexolvit.se xxl-adventure.se boutiqueblaze.se beng.se yellowbird.se boothill-linedancers.se bootjacks.se bengt.se yourself.se yourserver.se formapg.se yeppmedical.se llmd.se 22inc.se bengt-h.se bengt-lotta.se 0x539.se 0xdeadbeef.se obesitasfonden.se obeyme.se 0x0.se yanken.se yankyard.se bootleg.se 0xe.se xxxx.se fluiddynamik.se fluidinventor.se uncovers.se bengalkatt.se xuxuca.se jestyle.se jesukristikyrka.se xxlsport.se bengalkatten.se eci-se.se obf.se bracommunity.se brad.se egalitar.se egalite.se fluidosol.se fluidspaces.se floodland.se floodnet.se boyinra-stiftelsen.se boyner.se ebon.se yohanzon.se yohli.se borebyran.se yard.se yardsale.se formaplast.se eates.se braccoitaliano.se brachyspira.se electronetto.se ebbster.se ebbtide.se fluidity.se jericho.se jerico.se jergen.se xv.se yankee.se yankeecandle.se yohoo.se yogurt.se yoguza.se yapyap.se yaquiserver.se oligo25.se underscore.se undersidan.se jergill.se yoh.se bengt-martin.se boulevart.se boulkizz.se yankeecarclub.se jerkules.se jerky.se llmedia.se 0xff.se yoigo.se eav.se eavrop.se bradab.se eatatwork.se bracketurism.se brackvattensakvariet.se llmonitor.se xraptor.se yoj.se yellowmail.se oligophant.se xrate.se electronicenvironment.se electronicgovernment.se bengall.se fluke.se bengt-ake.se yellowmate.se yogioh.se yaniro.se borbo.se borbos.se xotec.se obscuramagica.se obscurity.se bordercollies.se borderkanalen.se boutiquedermonie.se bootlegs.se egero.se egerot.se eawop2007.se eaz.se obfab.se boreco.se bows.se bowt.se idioma.se idiot.se kuenkel.se kuess.se 7777.se ecliving.se eclub.se catheter.se cathie.se cat-electric.se fluke-sthlm.se positronstudios.se positus.se electroheat.se 111111.se boric.se borikt.se aratron.se arauco.se boureliusbygg.se bourghardt.se electronia.se latbaten.se flunk.se flunordic.se xrated.se yankeehouse.se yankeeparts.se xotek.se jeriksson.se jerixson.se nonetwork.se nonex.se xxxxii.se the-attic.se adeptsecurity.se adeqvat.se bootmusik.se the-basement.se depuyacromed.se stagsegelsskogsservice.se the-beach.se pottodds.se pottsork.se 0x1.se jerkholmens.se jerkland.se bracitat.se adventuresolutions.se adventuresports.se ibmanagement.se imptob.se impul.se bordellen.se border.se discusklubben.se discussion.se eazmo.se potzscher.se begbag.se flotutrask.se flour.se eclator.se eclectic.se jergis.se jeriko.se eclipsemedia.se eclipze.se bordeauxer.se bordell.se ambitionuppsala.se ambitiousone.se yarn.se yarps.se kutang.se kuten.se floodosoner.se bourgogne.se bourjois.se xyience.se tttnicsecontrolzonefqwgeufyqewyefygasdf.se alife.se alig.se yankees.se adventurestyle.se eng-johnsson.se neg-micon.se negativ.se ambitus.se bouncers.se bouncingbox.se 22q11.se tttparkettslip.se bourghardt-retorikutbildning.se depzi.se addako.se addan.se xxxxx.se yankeecars.se 060.se ggh.se cathis.se yatack.se yataka.se border-wines.se borderbroder.se bowwows.se addecco.se addeco.se border-rangers.se bouquetgarnimix.se bourbon.se eng-tex.se specifique.se infarkt.se bazoo.se bazook.se bridgwater.se briding.se beninca.se yankeedoodledandy.se boviaoss.se bovidhavet.se flubber.se flubby.se yesterdaycars.se yaw.se yawd.se crosswise.se crosswood.se naturique.se naturism.se praesum.se praetorelab.se engagemang.se eazy.se befuktningssystem.se yesterdaymusic.se eazyup.se eb.se impelmedia.se impentab.se ambitus-teknik.se kutlu.se naturist.se yataz.se yatf.se posthistoria.se posthuma.se bowflex.se bowhead.se nonfire.se borile.se begbanken.se yojimbo.se yatingstudio.se boviksbadet.se bovin.se yogiontheroad.se braclub.se begbat.se crossworks.se kuesschen.se boredtodeath.se borefelt.se posthumandreams.se ooops.se aligerum.se improva.se improvakliniken.se blacarat.se black.se gonisstad.se lafquist.se collectum.se collecture.se theoremascandinavia.se theorganicpharmacy.se yey.se brightness.se brightnessreef.se oli.se olibra.se boxgraphix.se boxhill.se alight.se bridion.se box.se postemballage.se posten.se flow07.se flowart.se yavari.se eb-hedlund.se yesterdaysnews.se yesto.se possengineering.se possepsykoterapi.se yogisat.se odo.se odon.se yavin4.se oestrogen.se oet.se bower.se bowers-wilkins.se black-birdie.se neglige.se neglinge.se adiento.se adifferentaspect.se kwn.se kwon.se bourdon-haenni.se fluns.se odonet.se stablo.se stabshuset.se flourish.se flourtant.se yeyyey.se pradobygg.se praegel.se befwe.se begbatar.se yestosomn.se trabearbetningsutrustning.se trabenet.se specimen.se negative.se immensus.se immersion.se jergo.se yatara.se gghandel.se blam.se blamagasinet.se pou.se nehagen.se 4466000.se baystoneconsulting.se ecstasy.se ecstatics.se alliator.se allidator.se blackcube.se blackdecker.se immunforsvar.se immunit.se oooups.se begadi.se begagnad-cykel.se cccdalarna.se efmobil.se efmsverige.se yinoyang.se la-bilder.se improved-reading.se improvefond.se eb-konsult.se eb85.se naturistbad.se blackshield.se blackshore.se flucktare.se fludent.se adera.se cat-rental-store.se bazaarfood.se bazaarmovement.se blackjack.se blackjackguide.se immunit-secure.se 446verksamhetsstyrning.se fluor.se yinshu.se gossipgirl.se gossipnews.se negativeoutlook.se begroup.se begrunda.se oculus.se rrs.se byppja.se theorganicshop.se yippi.se yit.se blackjackguiden.se bovine.se eba.se adiuvo.se adiva.se underdogs.se underfin.se bedford.se bedfordstuyvesant.se bluebits.se eget-tryck.se glossbonaturprodukter.se glossip.se bat-maskintjanst.se addanny.se 11111111.se 1112.se yardtech.se patebosmedja.se patek.se rrstudio.se improveit.se postfolket.se postforskott.se yogiskhalsa.se yintang.se yinyang.se beanthere.se buggemala.se buggeroff.se malacoleaf.se malafolkdanslag.se blowoutproductions.se blowtech.se blandgodis.se blandio.se praes.se yarin.se yawp.se lacucaracha.se datatal.se black-box.se 060921.se 060online.se praesentis.se yeigo.se boredesign.se negativt.se negawatt.se eciab.se black-boy.se odell-jarlemyr.se odelli.se odibonk.se odido.se chimneycorners.se chimo.se immobilienscout24.se immofield.se adventureteam.se la-bygg.se blanchard.se blanche.se odells-signalmontage.se aderaborstahusen.se ladstrom.se ladu.se bypresenten.se ebonyivory.se yofa.se adivarsson.se blackjackinfo.se efg-financialproducts.se blastringsab.se xxxlankar.se five-by-five.se bengabus.se bearsafari.se bearshare.se eb-index.se daugaard.se daughter.se odontlar.se odontolog.se infart.se blackboard.se blackbone.se ocun.se negativet.se gonix.se ecceavis.se eccehomo.se 0611.se bearab.se bearb.se blandis.se blandkobbaroskar.se lacucina.se beeswax.se beetagg.se jergovic.se blackjacksm.se black-bruin.se biskopen.se jerhammar.se yoko.se brovagen.se burberry.se burchardi.se fluortanten.se flowertwig.se flowey.se magento.se mageras.se oatsfield.se dauksz.se bloodshed-nihil.se bloodtide.se blameit.se blamesen.se kylentreprenader.se kylfalt.se xxxlofsweden.se bedow.se bedr.se blackboots.se donkeyshot.se donki.se postfoto.se datateam.se eccell.se bradbolaget.se bradcentralen.se unmei.se unn.se accus.se accusort.se beewe.se beez.se ebonyporr.se xxxmedia.se onesec.se oneset.se flim.se flimmer.se belabelbutik.se belach.se thirstforknowledge.se thirty.se dauphinance.se daurang.se tolero.se tolerud.se yatsy.se blame.se yez.se eckecammen.se eckenscafe.se ecstay.se brovakten.se flourtanten.se flow.se dauns.se daup.se yinsikt.se neger.se bune.se nehdforever.se black-diamond.se adrina.se adris.se practiceworks.se practico.se immunitet.se ymca.se ymdrift.se flinc.se flinckafingrar.se accutone.se gonk.se daun.se jeth.se jeti.se blancheb.se unna.se ecic.se borgland.se borglin.se negerboll.se timeunit.se timeus.se nitrohelmets.se nitromedia.se bleach.se bleachme.se cat-tech.se gonny.se improvit.se improvo.se jerhamre.se blastringsmaskin.se tractionbil.se tractis.se xxxxxx.se blaman.se befa.se cat-web.se xxxmodels.se eftec.se eftefesten.se yohan.se bored.se oau.se oax.se blackbirdsnest.se immersis.se burchardt.se onoga.se onomatepoetry.se chibit.se chibratz.se flimmr.se eclair.se eclaser.se flow-natural.se alinet.se alingetexas.se yatta.se underfire.se ebur.se ebusiness.se eccemundus.se kylfirman.se improwiseconsulting.se imps.se 7777777.se transmissionsteamet.se transmit.se flimrigt.se flin.se 060-123808.se daundesign.se trimchip.se trimcut.se ambius.se yara.se burcharth.se practicum.se practise.se align.se aligning.se traceur.se trachoma2010.se blacksilver.se odonnell.se odont.se postguard.se posthantering.se bengalmagic.se eatfresh.se bouganim.se bougicord.se yogitea.se cat5.se immi.se oligovation.se boukefsprivatskola.se daune.se gonordic.se trackster-rmbyran.se tracktech.se onoof.se jerleke.se underflow.se jesukristikyrkaavsistadagarsheliga.se begsaab.se eclient.se jevinger.se jevor.se eckhardt.se eckhell.se obstinat.se obstinatemotion.se dauner.se bengt-martins.se yara-praxair.se datazoo.se datcomp.se aliprot.se aliquantum.se stahab.se nonpaperbooks.se nonroam.se borglund.se floodprotection.se obscurum-per-obscurius.se bellicus.se bellin.se boulliant.se boulodromen.se catchlight.se catchpress.se eathouse.se eatit.se brazil-living.se border-shop.se adivo.se bordercollie.se briellbygg.se briesch.se kxmedia.se ky.se posityd.se yattayatta.se obskyrt.se obsolete.se gorahemsida.se goran.se yleg.se yler.se boving-kinnmark.se nongfin.se blottbleck.se blou.se praetoreslab.se obfb.se postgallerian.se inivero.se inizia.se behaviorism.se behaviorworks.se eckerberg.se addapartner.se araucotours.se eazycm.se bat-motor.se beetlecabriolet.se beetree.se date.se daunfeldt.se odontbok.se kuester.se bradag.se yobro.se yoc.se alingfeldt.se kkk2008.se immiflex.se kworld.se inflectionpoint.se inflecto.se flukenetworks.se praetoreslaboratory.se jesus.se occismarsvin.se occlutech.se oogabooga.se oogle.se obstecare.se bradagis.se yocal.se engagemangscentralen.se msn-messenger.se eclip.se odeon.se oderland.se xxxmovie.se goran-dehlin.se eatomato.se eatonholec.se engagement.se catenafastigheter.se catenas.se bengt-nilsson.se trackworld.se traco.se eatons.se bovidkusten.se stahberg.se posix.se posk.se yit-dts.se yit-sverige.se aliorient.se aliothfenrir.se dauber.se daude.se flattv.se flava.se catad.se catagon.se immoimmo.se obsd.se cathkidston.se flowcess.se brightoffice.se yatzees.se davitron.se davlar.se tracsolutions.se tractatus.se five-dayweek.se alipang.se alipour.se impster.se begsajten.se boulevarden.se boulevardmagazine.se advexa.se advfa-la.se yavis.se bowersandwilkins.se nons.se davlens.se trimediastockholm.se trimera.se informativa.se informativmedia.se jewab.se traci.se octillion.se octintranet.se bowhunting.se beeuty.se boullan.se infarten.se flina.se alipack.se catahoulas.se blackjackturnering.se 060-172250.se bourelius.se fluortanterna.se obsti.se beetbox.se beethalin.se bloomen.se bloomfield.se bottlebrothers.se bottles.se tractive.se briesch-consulting.se catholic.se blotbostad.se blotf.se 060-194800.se adix.se eclipping.se davenport.se davenportsmusik.se odontdr.se inizio.se five-elements.se imminent.se immittio.se blackboule.se obsense.se collectus.se jerikos.se bootradgard.se adifferentday.se iiiee.se briese.se oogoto.se brazil-resort.se beetronic.se eatout.se impsys.se ontario.se ontec.se behindthecamera.se behm.se ontoday.se ontologia.se addax.se addaxinnovations.se uncus.se bazaartorget.se daunproject.se catholica.se cat6.se octo.se ymer.se bazar.se baystream.se yinyoga.se yip.se addarsnas.se efnet.se bluebiz.se posthuset.se yawyd.se uncut.se blossing.se blossom.se bovingroup.se blackguide.se blackharborband.se improvaplastik.se manfred.se manfredgruppen.se flue.se davanti.se davator.se yinyangshop.se brigade.se brigaden.se imptec.se blackdiamond.se aderadok.se floods.se odio.se odis.se naturistcamping.se catchrelax.se malafrakt.se blackhat.se pot-limit-omaha.se potapoff.se briex.se briforsinstrument.se uncutdiamond.se daus.se burna.se burnaid.se oetiker.se impeo.se odellsel.se ontologix.se malaga.se eazycredit.se blackguard.se flowchart.se addbridge.se boaz.se boazul.se blindboy.se blinddate.se bloodymary.se catchthedog.se naturisten.se bloomframe.se adflex.se adfontes.se advfirmastadig.se tetek.se tetenoir.se beguided.se beguns.se eatl.se informatix.se ooh.se behave.se behavio.se blustep.se blutt.se blossom-nordic.se davco.se ymerab.se brigadens.se 061124426.se blot.se odellselservice.se yijie.se yikes.se borglund-byggk.se catech.se cateco.se underbarungen.se underberg.se begbatprylar.se bowell.se bowen.se yazdanfar.se yazmina.se gonorre.se yaragas.se aestheticexperience.se aesthetics.se 1111111.se blota.se jesus-acute.se eclips.se yawh.se brifus.se blurpa.se blurum.se improvaplastikkirurgi.se improve.se tetens.se flowfamily.se iiii.se blackhawk.se goran-nilsson.se immortalis.se immortals.se laducale.se spiegelberg.se spiegelstockholm.se theoria.se yezpher.se davens.se alinonline.se alinterest.se adibris.se adibus.se aestim.se boazul-medical.se bouldersinc.se boule.se kyon.se kyoob.se specimenhunters.se blatunga.se blau.se informator.se imita.se imitera.se behaviometrics.se bottleshop.se kylfokus.se biskopenfastighetsab.se tolfesbo.se batinvestmarina.se batir.se imitthem.se blinddatemusic.se instrumentpolen.se instruments.se tracing.se flindall.se fling.se nivis.se nivita.se flavet.se impeomarkets.se flum.se flumma.se immix.se jesus-christ.se blv.se bedragen.se imitthuvud.se sponsnet.se sponsor.se laerum.se laesker.se advfn.se understreet.se undersvik.se postgodis.se postgresql.se childactivitycentre.se childcare.se squashportal.se squashreklam.se burglar.se burgler.se jesus-kristus.se beardrex.se bearflight.se poul.se adminis.se administration.se flincks.se tractel.se traction.se odik.se odima.se flavourspraydiet.se flawless.se brigante.se pratsugen.se pratus.se instrumentservice.se addc.se eatmydust.se blount-pool.se bazar1.se informatorer.se spinspiration.se spinstitut.se ecit.se eck.se boul.se flummer.se flaviano.se improveitsystems.se alimentumwines.se alimta.se davidbergstrom.se davidbjork.se goodbye.se goodcar.se immigration-sydafrika.se immigrationverket.se catchword.se catchwork.se briggspowerproducts.se bright.se eazygun.se efw.se efx.se bung.se yohanna.se influenza.se influera.se trackit.se tracklistan.se bearinn.se bearlaw.se ymir.se ymkkonsult.se posten6.se transmitit.se bradatorer.se immola.se oetker.se eazykredit.se boatmangbg.se boatmeet.se boulder.se dooright.se doorlin.se oop.se underskog.se blastrix.se blazingsevens.se blb.se blotta.se sprintline.se sprintxohm.se postenab.se doorman.se yeildsystem.se bearfootzoo.se odier.se uncutdvd.se flawless-design.se burckar.se brigantia.se catchy.se ymsyd.se ymusic.se thetford.se thethaiway.se trackmagazine.se adixen.se msn-stuff.se boulemedical.se boulensdag.se improve-it.se insurrection.se insurvey.se dausmedia.se adic.se immigrantinstitutet.se immigration.se blinddater.se catcon.se catdata.se blackjackturneringar.se sponsor-el.se odelros.se odeltorp.se belastningsskadecentrum.se belatron.se praty-bet.se spina.se spinalbalans.se flaxy.se flay.se bearglue.se bejerstrand.se bejfred.se biskops-arn.se date-it.se posthem.se befab.se oceanobservations.se oceanoptics.se tractocile.se tracealyzer.se tracecode.se nivla.se bright-arkitekter.se immoscout.se buonocafe.se bup.se adicast.se blacklead.se blacklevels.se daudistel.se goritas.se gorji-persson.se adesto.se adesys.se befab-trofen.se infored.se inforema.se davenso.se filibuster.se filicaja.se practica.se practicaljokes.se bazar2.se bright-europe.se catdesign.se immix-gaming.se immo.se flopp.se floppen.se aestino.se yax.se tetens-hantverk.se goran-utbult.se powerhouse.se powerhousemc.se blowfly.se blowin.se colleen.se adetto.se glamorous.se glamour.se gorjus.se adidassverige.se adiels.se datatrygghet.se dataunit.se ggi.se ky-akademien.se trinicom.se trinitas.se underglad.se bradcommunications.se adjungo.se adjust.se blatand.se blaterrine.se blowjob.se immoserver.se bazola.se bupb.se rrt.se flooradur.se floorandcarpet.se dataunitserver.se bathso.se bathusboken.se engagera.se catchytunes.se datateater.se catalogue.se catalys.se bengalskatten.se ungtval.se ungutanpung.se boatstream.se boattaxi.se engagerad.se adicio.se bldesign.se bldk.se immigrant.se 777dragon.se goran61.se blastro.se blastunder.se kyphi-parfymeri.se kypros.se catalysator.se blacklight.se floc.se flock.se undertak.se boatweb.se boavista.se inherit.se inhoc.se oceanpeople.se flukta.se prader-willi.se pradit.se blownfuse.se undertaker.se flayme.se blowjobfilmer.se transcendentalism.se transcendentgroup.se kkknicsecontrolzonentvsadfksajdshfajsdd.se ymerbacken.se tracolor.se yli.se eckhoff.se tracka.se trackalyzer.se bathuset.se bat-protector.se adicon.se datautbildare.se tracom.se glamour4ever.se boulevardmedia.se davli.se transmitransmitreceive.se aeproduktion.se aequitas.se goosewood.se goossens.se boots.se transmitreceive.se brigaplat.se multivac.se multivan.se onset-paintball.se onseven.se batbottentvatt.se batboyslim.se bowes.se kyoto.se kyotorecordings.se bldr.se improve-your-golf.se immostreet.se immovario.se trackdown.se trackers.se glamourama.se aeger.se aegid.se catalinafastigheter.se catalinakliniken.se floostajaktlag.se flop.se bazar3.se flocken.se blottare.se aestockholm.se immo-immo.se catalinastranden.se kyornskoldsvik.se practive.se yit285.se boozhoundz.se boozo.se doormanbill.se davidbjorkman.se flavona.se flavongroup.se borglunda.se borile-atv.se ylife.se ylinen.se odensemarsipan.se odensfors.se five4fun.se odellsforsakring.se adeu.se triax.se tribal.se trackerschool.se tracingsolutions.se track.se datautbildarna.se yezz.se bellinga.se adjustable.se aemedia.se aemkei.se immunbrist.se blata.se impera.se ky-akademierna.se iisab.se iistiftelsen.se aer-lingus.se eatrade.se tribal-x.se blaton.se tricolorscreen.se tricom.se cate.se folkshoppen.se folkskam.se olihn.se nonsen.se adiz.se aerodyn.se aeroenergi.se gorilla-safaris.se gorillacam.se gordian.se gordic.se bowlcircus.se bowler.se omina.se ominkasso.se belteknik.se beltman.se borglundsmek.se travertine.se travessen.se iiiii.se myonly.se myopus.se immonen.se immore.se blowkart.se blowme.se bunga.se bathyra.se batia.se behaviosec.se jewander.se blasvanen.se yield.se yieldmanager.se blackbox.se gorillagang.se flaxracing.se flaxxar.se odensglomda.se mulligtochgulligt.se mullinmallin.se efsab.se efsflight.se follatech.se follin.se cathrine.se blackdiamonds.se alkakonst.se alkalon.se beetween.se burnball.se immosky.se foreignexchange.se foreignministry.se oneill.se oneinteractive.se thetheater.se immoasis.se blackmartiniz.se blackmesa.se yazoo.se postia.se borgmalmen.se blowmoulding.se ylinentalo.se alison.se aliss.se allabookmakers.se allabostader.se trinitasfastigheter.se aegir.se improveme.se catalog.se kyangla.se kyansailing.se onshop.se onside.se naboer.se naboforetagspartner.se immobile.se bldscan.se brietling.se bluepointsolutions.se bluepower.se onskinunderskin.se onslundabyalag.se brightofsweden.se dave.se ojf.se ojinegras.se ebook.se aerie.se aeriksson.se adifone.se la-byggkonsult.se undelater.se undeman.se batista.se squashstege.se blus.se kylfrakt.se onstage07.se onstahunddagis.se ontology.se blotbergsboden.se addcall.se floorandmore.se blowjobporr.se bowie.se allabostadsannonser.se blackline.se chickenfoot.se chickenhouse.se traberliga7.se alignit.se gordin.se imitz.se catchup.se blackburst.se blackbycenturion.se ecko.se folkskola.se nabomarketing.se beigt.se beija.se efynd.se yn.se oneiros.se foresee.se foresight.se batic.se flamingfox.se flamingo.se spigotti.se spihalland.se efsgullanget.se onore.se blvab.se eftel.se blb-bostader.se ylianttila.se boax.se gordinegenbok.se odinspack.se poularde.se spelkassan.se spelkiosken.se yaragasab.se burco.se foresightlaboratory.se ky-akademin.se jewaru.se eckonsult.se chiclay.se chiclit.se eatmyhouse.se brazil-resorts.se blacklist.se la-cantina.se flowco-retorik.se underskoterska.se boatname.se flavour.se flavourevents.se daustryckeri.se boattransport.se boatvideos.se brazilboliger.se brightpark.se boralv.se borang.se bunkra.se bunn.se bearharddesign.se lactal.se lactamin.se blvd.se odensholm.se floorballrink.se floorcare.se uncutversion.se adforaid.se gonorth.se bradagisval.se bureau.se bureaudesign.se catedia.se blacklodge.se burgman.se trace.se tracead.se onrunsfjallby.se ons.se olinab.se olinda.se chicane.se chicanodesign.se spectronic.se spectroscopy.se ebook-store.se iiik.se burden.se burdenofsin.se spectre.se spectro.se aeronet.se aeropc.se aera-sense.se blearning.se blechert.se spinstore.se aerasense.se aercrete.se catalysis.se aegrafiskform.se aegsverige.se bouleochbar.se traconi.se olinder-westerberg.se flaviola.se odensjoby.se stabilisator.se stabilisera.se bleak.se blackhawknetwork.se kyansokningar.se oneitis.se addcap.se onside-kommunikation.se aerts.se aerwhy.se naturister.se adeus.se collega.se bootshaus.se onstep.se multimeter.se multimetrix.se tribalddb.se boulevardteatern.se informatorn.se informatrix.se aegis.se blackboxab.se alir.se advolill.se advona.se undemar.se eckounltd.se ecka.se blinddaters.se posterinitiative.se posterism.se laesoe.se blackknights.se occlutechinternational.se ggif.se odigos.se blackisbeautiful.se aeracing.se transcom.se onslundafoto.se yliniemi.se stabilisering.se iin.se iingeborg.se boattracker.se multiverket.se unden.se undenas.se bradconnectivity.se chiburai.se adviseit.se adviseor.se olympiakonferens.se olympianutrition.se onside24.se adh.se adhd.se improvement.se 061128.se blundstone.se blunt.se occoinvent.se occra.se flaviositet.se ylitalo.se posthemlis.se aliraqi.se imivision.se immomaxx.se brightpeople.se blackhawks.se bradcontrol.se flawless-guild.se yarblek.se 0612.se adev.se alin.se traceinface.se batchim.se batcofra.se flinda.se bedre.se lactec.se posterus.se posterverkstaden.se forestahotell.se forestandardagar.se occo.se blackoutboys.se blackoutmc.se catasa.se catasus.se oceanpoker.se ylivainio.se blackboy.se trackmania.se tribalism.se aerys.se trainersonline.se trainformation.se administrationen.se 777mobile.se boraz.se borbird.se ylivirta.se blushing.se glamourbloggen.se blackmoney.se blackmountain.se aerophoto.se efyran.se chickan.se chicken.se onsidekonsult.se posterland.se postero.se aerosoles.se aerosoltrap.se aerzen.se floofilter.se chiclitt.se batik.se batinfo.se postherpetiskneuralgi.se aesmaskinservice.se aesp.se track-it.se brigby.se onos.se aesthesis.se aesthetic.se tractor.se trabetongteknik.se blacklabel.se iik.se adessepraktiken.se adesso.se bupgranskning.se ecigarett.se onsight.se bowlerdesign.se belt-buckles.se beltbuckles.se training.se bowest.se powerboat.se powerboatracing.se oetker-food-service.se blurb.se blurid.se spectrumcases.se speda.se bowesystecsverige.se baysystems.se flinga.se flavourofindia.se oceanprodukter.se trimning.se trimpulse.se iinternet.se flowcoaching.se blackbruin.se blackbugs.se foresor.se foress.se catalysisconsulting.se beltbucklesno1.se beltek.se goophone.se goorb.se blackheart.se posterverkstan.se laesoe-saltcare.se posteronline.se blowjobs.se catamaran.se catana.se flindal.se blacklabelgames.se eckran.se bluride.se beejodd.se beeline.se immovision.se postgate.se tremor.se tren.se aestheticanova.se iikoto.se bowesterdahl.se catchcomm.se catching.se thethinktank.se brigbys.se unipalabra.se unipars.se aemotorsport.se daver.se transferprint.se transfers.se eatmyphoto.se improvisator.se transus.se transvea.se bedsonnet.se bedstepornofilm.se yithome.se datautbildning.se fordonbyte.se fordonet.se poulenc.se immobilia.se belaconte.se blov.se firmament.se firmamoppen.se uniquemoments.se uniquenorth.se tracksdirect.se trackslistan.se baywest.se baz.se immortal.se aesseal.se bellinger.se trackexperience.se olikt.se olimp.se chickenfarm.se eckstein.se olika.se catcit.se imma.se immag.se fischbach.se fischeninschweden.se blackbridge.se pour.se pourbon.se gorillakillarna.se kyh.se kyhl.se olympiaspirit.se eckto.se blackbull.se occt.se kyosho.se blacklabelsociety.se floom.se floor-and-more.se adjustablebeds.se yazza.se catec.se alingfelt.se alirev.se catal.se foresite.se bowesterlund.se trimeresurus.se trimevent.se borax.se poulsens.se pound.se trabiten.se yieldsystem.se chicnuts.se blastzone.se 7799.se eftours.se eftsweden.se immanent.se immanuel.se batbranschensriksforbund.se lafayette.se lafayetteradio.se fischer.se fordongas.se spelklader.se advhr.se aderagroup.se onstore.se efu.se catator.se posterix.se onsightautomation.se bupi-cleaner.se olinderredovisning.se tribalmedia.se onsvala.se immortalart.se onsign.se naturistforbundet.se onslundaif.se yb.se ungvanster.se blackheartband.se aems.se daverev.se prado.se spihelsingborg.se bellinicasino.se bellinisalltjanst.se chic.se traditionfastighetsmakleri.se traditionochhantverk.se aerospace-kth.se aerospinningcenter.se unipartner.se iit.se catalyst.se onsvalabro.se baysystems-northerneurope.se tractor-pulling.se powerboats.se filicorizecchini.se laesy.se laettbyggteknik.se trackandfield.se tractechnology.se efo.se bejab.se bejan.se ynad.se aerialclothing.se aerialwork.se bowk.se bowl.se filidontens.se trialformsupport.se trialog.se goodink.se goodiz.se undertakservice.se olaform.se olafs.se boranta.se transmitrecieve.se treper.se trepira.se davidzzon.se davies-co.se aeroplane.se iitala.se yndegarden.se burgmann.se trimsoft.se bathusets.se blackdiamondsforlife.se bathusetdesign.se bellingham.se bellings.se blueit.se bluejay.se omax.se omb.se trackfix.se trackme.se onsvalagard.se tresuger.se tresund.se blackdoor.se onsite.se dauta.se instrumentteknik.se blissful.se blissgroup.se aesska.se bellini.se triogrande.se triogruppen.se bungalow.se transcend.se yitprojekt.se spedab.se lafdata.se transurance.se adject.se adjektiv.se behmfredin.se odin.se floorball.se catanna.se catapult.se la-casita.se aegis-data.se trackart.se stability.se stabilizer.se catco.se yf.se gordinegentshirt.se lacouronne.se lacream.se bupi-solvent-cleaner.se goodcars.se spinalis.se spectrochrom.se traumata.se traume.se adjo.se tractorpower.se unipol.se unipoll.se burdus.se adgood.se adgrip.se aegswitchgear.se postiad.se catcoab.se illuminet.se illuminum.se iitalaab.se aerostat.se aerostructure.se adfunnel.se adfuturum.se catalin.se thethirdeye.se oetker-fs.se onsaddle.se advonaut.se bowmaker.se bowmoore.se eckankar.se aeromedic.se aeromix.se chic-online.se immox.se trimform.se fordonjobs.se spinifex.se spinit.se borat.se boratjr.se trafsys.se tragardh.se unipatatas.se powerful.se powerfx.se laetus.se eckworks.se bowlers.se bowlindermarin.se undra.se undran.se catcoagenturer.se stabilo.se trackback.se chicamagazine.se kyarrangemang.se ecl.se powerbody.se chicago75.se chicagoblower.se aen.se undertaksfirman.se fischer-co.se adjoin.se adjoint.se knubbs.se knucklehead.se goodwin.se goodwind.se forecast.se forecom.se poweric.se eckard.se chico.se pourcrime.se ky-guiden.se chickenpox.se goodwine.se fischer-reklam.se unipipe.se forhenne.se forhim.se powergamer.se findconsulting.se findea.se goodwines.se bootsinabag.se bluepride.se spihk.se boratjunior.se adjovi.se adizes.se firmamsvensson.se burkar.se burkarna.se olin.se kyas.se blume.se blumenberg.se transientskydd.se transima.se yaraindustrial.se ebookcreator.se forestberry.se adviser.se odin-fonder.se chicola.se fischercat.se traceland.se traceline.se alist.se immpuls.se labi.se labil.se firmamt.se insupport.se insurance.se forhonom.se bellux.se bellwox.se trenad.se gooster.se bure.se onsitegroup.se gorillapod.se yitrading.se spelkliniken.se posterxxl.se blackmetal.se thigereye.se thii.se davidblank.se olin1.se buraker.se burar.se multiverktyg.se lacenter.se laces.se addcapital.se undecem.se undefeated.se onekligen.se spiky.se spila.se transferdesign.se transferens.se gorenewable.se gorengsmattor.se findeasy.se laestander.se floor54.se transmode.se illustro.se illutel.se burea.se insurgency.se triokawa.se unipath.se unipet.se naboo.se bradrycker.se bradspel.se brigge.se tracopower.se chieftain.se chieftaintrailers.se bleck.se flintis.se flintmastaren.se bungalowhomes.se blackhill.se bedrehelse.se spilab.se bellino.se buppie.se bups.se alin-co.se blushed.se imbecile.se imber.se dav.se track-guard.se powwownow.se pox.se traditions.se goodwell.se goodwill.se ggik.se beers.se beershop.se ecinema.se necesse.se nechrivanbarzani.se stabilit.se braddjup.se onsitemedia.se aerea.se oceanquest.se musicofsweden.se musicom.se bootstrap.se booty.se ungvard.se ebukonsult.se ebum.se imbera.se advocate-online.se advocateonline.se efsovik.se efsroknas.se poupon.se bradsportforbundet.se davey.se davgat.se advobo.se advocard.se burley.se burlid.se goorep.se bearhill.se tracentrum.se catapult-consulting.se blackmicas.se blacknred.se blacknuss.se catchingclouds.se ontomtid.se gorenje.se knuckles.se behrensaenergi.se behrensgroup.se bureabostader.se undefined.se davero.se ybjj.se ybm.se illumit.se postendalaro.se belaggio.se belaieff.se ylkraft.se powergarnet.se been.se beenhouse.se bedstesexfilm.se lactogen.se 061210.se 0613.se bureauk.se aeroplast.se imberg.se beiersdorf.se eftab.se eftandlakeri.se yarapraxair.se specka.se specma.se flawlessart.se behnn.se fis.se fisch.se tragardhfalkenborn.se beinteractive.se beirenfuji.se ebookers.se bejaro.se postenintro.se olikabilder.se specitek.se poznejte.se pozt.se occuhealth.se occupied.se bowl-inn.se traceit.se 0612-717700.se pourhomme.se forfew.se forflex.se trendz.se trengtan.se insurance-it.se kyhla.se transitions.se transitmodels.se natalisfond.se natalplaza.se nameclient.se namedrive.se burlin.se fishskin.se fishtank.se adessobygg.se imms.se flavours.se namedrop.se bating.se chicagopneumatic.se powergear.se bearleague.se adja.se adjackets.se bupsjobo.se daverock.se spectrum.se lafdesigns.se catchingeye.se chiendouceur.se undefinedsounds.se speel.se speeron.se namedropping.se natrligansiktslyftning.se natrom.se kyoshobutiken.se lactiferm.se absolvo.se absonet.se spiik.se bootybay.se speedworks.se speedxpert.se goot.se trailer-store.se trailerbengt.se laesser.se buratti.se burb.se boatnav.se advocate.se undacamping.se undae.se datautbildningar.se boaxelsson.se transitor.se okeli.se oken.se trackstar.se chicbaby.se uniqueparts.se blacknusshairandcare.se blockhane.se blockhus.se spill-tech.se spillan.se onshare.se trioquinta.se trioredovisning.se folkskolan.se trailercam.se goot2b.se imbuecommunications.se imbumba.se floor724.se absonic.se onoterade.se behold.se blackbrilliants.se transferfactor.se stablelafleur.se stablematt.se yaravi.se burmese.se burmester.se trimfriskvard.se behome.se aderakommunikation.se iio.se bloodhounds.se bloodline.se gorillaresor.se glair.se glaj.se trabroar.se trabtech.se 77racing.se gginfo.se yitsverige.se alinband.se alinco.se forebo.se foreby.se trapersiennspec.se trapets.se posterprint.se labildesign.se speechpower.se speechtime.se glajal.se adjob.se advony.se blissing.se bradstone.se stabilometer.se kkkonst.se flavourspray.se efoa.se glam.se chica-gaming.se bejas.se gorillasafaris.se belaew.se natron.se uniprocess.se unipump.se efshelsingborg.se trailerfynd.se trailerhou.se aeropoxy.se aeros.se catch22.se catchafire.se trinitec.se behrenskennel.se batbryggan.se catchit.se 0620.se datautohus.se chiccita.se chicagency.se instrumenttekniker.se gginvest.se triokok.se stabilotherm.se trackster.se traduc.se traduco.se onsjo.se transvestit.se transvision.se beirholm.se batbutiken.se addcare.se immobilien.se unipost.se goodwood.se aeredovisning.se labindustries.se necinfrontia.se goosebay.se goosegreen.se adj.se stabilproduktion.se triolen.se triolog.se blinddates.se burbage.se spiikensbacke.se speedy.se imc.se blueprint.se trackmypicture.se fireshow.se firesite.se bluebliz.se aerograd.se aerogym.se ynef.se offtrail.se offworld.se davincy.se davinyl.se postenlogistikab.se labora.se laboratech.se traducta.se kooneva.se koop.se foreach.se forebergsmissionsforsamling.se bellini-casino.se powerice.se onsjosag.se munin39.se munk.se bowl4joy.se goorglad.se colttotalplus.se colubris.se ocho.se ochpetra.se davi.se firmitas.se firmngro.se blissresto.se onoterat.se uncutvideo.se burab.se bearline.se goodcause.se boratt.se forebyggamigran.se trabjerg.se adjustables.se gorillaz.se advoqat.se trablas.se bootylicious.se triol.se burkart.se blackmountains.se speedogliid.se speedol.se alireza.se aliria.se labeldesign.se labelinks.se aedifico.se aedo.se speedyasia.se blinddating.se odin6.se speed.se aderavaluemanagement.se olympiatandlakarna.se bo.se dnsruby-1.61.3/demo/example_recurse.rb0000755000004100000410000000203013607400362017753 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # Example usage for Net::DNS::Resolver::Recurse # Performs recursion for a query. require 'dnsruby' unless (1..3).include?(ARGV.length) puts "Usage: #{$0} domain [type [ class ]]" exit(-1) end resolver = Dnsruby::Recursor.new resolver.hints = '198.41.0.4' # A.ROOT-SERVER.NET. Dnsruby::TheLog.level = Logger::DEBUG name, type, klass = ARGV type ||= 'A' klass ||= 'IN' packet = resolver.query(name, type, klass) puts packet dnsruby-1.61.3/demo/mx.rb0000755000004100000410000000231413607400362015221 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby' # = NAME # # mx - Print a domain's MX records # # = SYNOPSIS # # mx domain # # = DESCRIPTION # # mx prints a domain's MX records, sorted by preference. # # = AUTHOR # # Michael Fuhr # (Ruby port AlexD, Nominet UK) # def fatal_error(message) puts message exit -1 end unless ARGV.length == 1 fatal_error("Usage: #{$0} name") end domain = ARGV[0] resolver = Dnsruby::DNS.new begin resolver.each_resource(domain, 'MX') do |rr| print rr.preference, "\t", rr.exchange, "\n" end rescue Exception => e fatal_error("Can't find MX hosts for #{domain}: #{e}") end dnsruby-1.61.3/demo/trace_dns.rb0000755000004100000410000000332113607400362016536 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ require 'dnsruby' # e.g. ruby trace_dns.rb example.com unless (1..2).include?(ARGV.length) puts "Usage: #{$0} domain [type]" exit(-1) end # Load DLV key dlv_key = Dnsruby::RR.create("dlv.isc.org. IN DNSKEY 257 3 5 BEAAAAPHMu/5onzrEE7z1egmhg/WPO0+juoZrW3euWEn4MxDCE1+lLy2 brhQv5rN32RKtMzX6Mj70jdzeND4XknW58dnJNPCxn8+jAGl2FZLK8t+ 1uq4W+nnA3qO2+DL+k6BD4mewMLbIYFwe0PG73Te9fZ2kJb56dhgMde5 ymX4BI/oQ+cAK50/xvJv00Frf8kw6ucMTwFlgPe+jnGxPPEmHAte/URk Y62ZfkLoBAADLHQ9IrS2tryAe7mbBZVcOwIeU/Rw/mRx/vwwMCTgNboM QKtUdvNXDrYJDSHZws3xiRXF1Rf+al9UmZfSav/4NWLKjHzpT59k/VSt TDN0YUuWrBNh") Dnsruby::Dnssec.add_dlv_key(dlv_key) resolver = Dnsruby::Recursor.new # TheLog.level = Logger::DEBUG resolver.recursion_callback = Proc.new do |packet| packet.additional.each { |a| puts a } puts(";; Received #{packet.answersize} bytes from #{packet.answerfrom}. Security Level = #{packet.security_level.string}\n") puts "\n#{'-' * 79}\n" end domain = ARGV[0] type = ARGV[1] || Types.A begin response = resolver.query(domain, type) puts "\nRESPONSE : #{response}" rescue Dnsruby::NXDomain puts "Domain '#{domain}' doesn't exist" end dnsruby-1.61.3/demo/check_zone.rb0000755000004100000410000001063213607400362016707 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # check_zone - Check a DNS zone for errors # # = SYNOPSIS # # check_zone [ -r ] # # = DESCRIPTION # # Checks a DNS zone for errors. Current checks are: # # * Checks that all A records have corresponding PTR records. # # * Checks that hosts listed in NS, MX, and CNAME records have # A records. # # = OPTIONS # # * -r Perform a recursive check on subdomains. # # = AUTHOR # # Michael Fuhr # (Ruby version AlexD, Nominet UK) # def fatal_error(message) puts message exit(-1) end unless (1..2).include?(ARGV.length) fatal_error("Usage: #{$0} domain [ class ]") end require 'dnsruby' require 'getoptLong' def check_domain(args) domain = args[0] klass = args[1] || 'IN' puts "----------------------------------------------------------------------" puts "#{domain} (class #{klass}\n" puts "----------------------------------------------------------------------" resolver = Dnsruby::Resolver.new resolver.retry_times = 2 nspack = begin resolver.query(domain, 'NS', klass) rescue Exception => e print "Couldn't find nameservers for #{domain}: #{e}\n" return end print "nameservers (will request zone from first available):\n" ns_answers = nspack.answer.select {|r| r.type == 'NS' } ns_domain_names = ns_answers.map(&:domainname) ns_domain_names.each { |name| puts "\t#{name}" } puts '' resolver.nameserver = ns_domain_names zt = Dnsruby::ZoneTransfer.new zt.server = ns_domain_names zone = zt.transfer(domain) # , klass) unless zone fatal_error("Zone transfer failed: #{resolver.errorstring}") end puts "checking PTR records" check_ptr(domain, klass, zone) puts "\nchecking NS records" check_ns(domain, klass, zone) puts "\nchecking MX records" check_mx(domain, klass, zone) puts "\nchecking CNAME records" check_cname(domain, klass, zone) print "\n" if @recurse puts 'checking subdomains' subdomains = Hash.new # foreach (grep { $_->type eq 'NS' and $_->name ne $domain } @zone) { zone.select { |i| i.type == 'NS' && i.name != domain }.each do |z| subdomains[z.name] = 1 end # foreach (sort keys %subdomains) { subdomains.keys.sort.each do |k| check_domain([k, klass]) end end end def check_ptr(domain, klass, zone) resolver = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq 'A' } @zone) { zone.select { |z| z.type == 'A' }.each do |rr| host = rr.name addr = rr.address ans = nil begin ans = resolver.query(addr.to_s, 'A') #, klass) puts "\t#{host} (#{addr}) has no PTR record" if ans.header.ancount < 1 rescue Dnsruby::NXDomain puts "\t#{host} (#{addr}) returns NXDomain" end end end def check_ns(domain, klass, zone) resolver = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "NS" } @zone) { zone.select { |z| z.type == 'NS' }.each do |rr| ans = resolver.query(rr.nsdname, 'A', klass) puts "\t", rr.nsdname, ' has no A record' if (ans.header.ancount < 1) end end def check_mx(domain, klass, zone) resolver = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "MX" } @zone) { zone.select { |z| z.type == 'MX' }.each do |rr| ans = resolver.query(rr.exchange, 'A', klass) print "\t", rr.exchange, " has no A record\n" if (ans.header.ancount < 1) end end def check_cname(domain, klass, zone) resolver = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "CNAME" } @zone) zone.select { |z| z.type == 'CNAME' }.each do |rr| ans = resolver.query(rr.cname, 'A', klass) print "\t", rr.cname, " has no A record\n" if (ans.header.ancount < 1) end end def main opts = GetoptLong.new(['-r', GetoptLong::NO_ARGUMENT]) @recurse = false opts.each do |opt, arg| case opt when '-r' @recurse = true end end check_domain(ARGV) end main dnsruby-1.61.3/demo/digroot.rb0000755000004100000410000000325013607400362016244 0ustar www-datawww-data#! /usr/bin/env ruby # -- # Copyright 2007 Nominet UK # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. # ++ # = NAME # # digitar - Ruby script to perform DNS queries, validated against the IANA TAR # (trust anchor repository). # # = SYNOPSIS # # digroot name [ type [ class ] ] # # = DESCRIPTION # # Performs a DNS query on the given name. The record type # and class can also be specified; if left blank they default # to A and IN. The program firstly performs the requested DNS # query. The response is then validated from the signed root. # # = AUTHOR # # Michael Fuhr # Alex D require 'dnsruby' def fatal_error(message) puts message exit -1 end unless (1..3).include?(ARGV.length) fatal_error("Usage: #{$0} name [ type [ class ] ]") end inner_resolver = Dnsruby::Resolver.new inner_resolver.do_validation = true inner_resolver.dnssec = true resolver = Dnsruby::Recursor.new(inner_resolver) resolver.dnssec = true # Dnsruby::TheLog.level=Logger::DEBUG name, type, klass = ARGV type ||= 'A' klass ||= 'IN' begin answer = resolver.query(name, type, klass) print answer rescue Exception => e fatal_error("query failed: #{e}") end dnsruby-1.61.3/Gemfile0000644000004100000410000000010413607400362014607 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem "addressable", "~> 2.5" dnsruby-1.61.3/DNSSEC0000644000004100000410000000430513607400362014225 0ustar www-datawww-dataDNSSEC support in Dnsruby ========================= DNSSEC defines a set of security extensions to DNS which provide a way for a resolver to verify cryptographically the DNS RRSets returned by an upstream resolver. The main standard is defined in RFCs 4033, 4034 and 4035. Dnsruby provides a recursive, validating security-aware stub resolver which maintains a cache of trusted keys and verifies RRSIG-signed messages with those keys (adding new trusted keys from signed DNSKEY RRSets and DS records). If dnsruby does not currently have the required key, it will attempt to walk the tree from the nearest known trusted key. The dnssec security status of a message is stored in Message#security_level (defined by Message::SecurityLevel). It is possible to tell Dnsruby to use a Recursor or a defined (or system default) Resolver to perform the validation. The default is to use a Recursor, as many systems are behind dodgy servers which mangle the DNS records. Using a Recursor means that only authoritative nameservers are queried for the DNSSEC records. In the absence of a signed root, Dnsruby has no trust anchor to validate messages against. It is possible to manually configure dnsruby with individual trust ancors. It is also possible to import a trust anchor repository (such as the one maintained by IANA), and configure the ISC DLV registry. Dnsruby contains basic methods to do this, although they are not currently secured. Clients are recommended to develop their own means of obtaining the initial trust anchors. It is possible to turn off dnssec validation on a per-message basis. Simply set Message#do_validation to false. DNSSEC is on by default - if desired, you can turn it off with the dnssec flag in Dnsruby::(Single)Resolver if desired. EDNS0 support is also enabled by default - if desired, you can turn this off by setting the Dnsruby::(Single)Resolver#udp_packet_size property to be 512. There should generally be no need to do this. Dnsruby maintains a cache of responses, and a cache of trusted keys. Once the initial keys have been downloaded, and a set of trusted keys built up, very little overhead is required to enjoy the benefits of DNSSEC. There is, however, some initial cost (to build up the caches). dnsruby-1.61.3/.coveralls.yml0000644000004100000410000000010613607400362016111 0ustar www-datawww-dataservice_name: travis-ci repo_token: 99b5l6CKFt3CC4rxYUetvDQvKtaTgCJW2