dnsruby-1.54/0000755000175000017500000000000012206575435012466 5ustar ondrejondrejdnsruby-1.54/README0000644000175000017500000000337212206575435013353 0ustar ondrejondrejDnsruby ======= 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. Dnsruby presents a new 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 : o Implemented RRs : A, AAAA, AFSDB, ANY, CERT, CNAME, DNAME, HINFO, ISDN, LOC, MB, MG, MINFO, MR, MX, NAPTR, NS, NSAP, OPT, PTR, PX, RP, RT, SOA, SPF, SRV, TKEY, TSIG, TXT, WKS, X25, DNSKEY, RRSIG, NSEC, NSEC3, NSEC3PARAM, DS, DLV o Generic RR types supported (RFC3597) o (Signed) Zone transfer (AXFR and IXFR) supported o (Signed) Dyamic updates supported o 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. Online tests ============ Nominet operate a test server which the Dnsruby test code queries. If this server is not available then some of the online tests will not be run. Contact ======= Use dnsruby rubyforge forums, or contact : alexd@nominet.org.uk dnsruby-1.54/DNSSEC0000644000175000017500000000430512206575435013372 0ustar ondrejondrejDNSSEC 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.54/demo/0000755000175000017500000000000012206575435013412 5ustar ondrejondrejdnsruby-1.54/demo/example_recurse.rb0000644000175000017500000000160212206575435017121 0ustar ondrejondrej#-- #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' res = Dnsruby::Recursor.new Dnsruby::TheLog.level = Logger::DEBUG name, type, klass = ARGV type ||= "A" klass ||= "IN" res.hints=("198.41.0.4") # A.ROOT-SERVER.NET. packet = res.query(name, type, klass) print packet.to_s dnsruby-1.54/demo/mresolv.rb0000644000175000017500000000452412206575435015433 0ustar ondrejondrej#-- #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. 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 res = Dnsruby::Resolver.new res.query_timeout=timeout # We want to have a rolling window of max_outstanding queries. in_progress = 0 q = Queue.new eof = false while (!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! res.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.54/demo/check_zone.rb0000644000175000017500000001053012206575435016046 0ustar ondrejondrej#-- #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) # require 'dnsruby' require 'getoptLong' def check_domain(args) domain = args[0] klass = "IN" if (args.length > 1) klass = args[1] end print "----------------------------------------------------------------------\n" print "#{domain} (class #{klass}\n" print "\n" res = Dnsruby::Resolver.new res.retry_times=(2) nspack = nil begin nspack = res.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="" (nspack.answer.select {|r| r.type == "NS"}).each do |ns| print "\t", ns.domainname, "\n" end print "\n" res.nameserver= (nspack.answer.select {|i| i.type == "NS"}).collect {|i| i.domainname.to_s} zt = Dnsruby::ZoneTransfer.new zt.server=(nspack.answer.select {|i| i.type == "NS"}).collect {|i| i.domainname.to_s} zone = zt.transfer(domain) # , klass) unless (zone) print "Zone transfer failed: ", res.errorstring, "\n" return end print "checking PTR records\n" check_ptr(domain, klass, zone) print "\n" print "checking NS records\n" check_ns(domain, klass, zone) print "\n" print "checking MX records\n" check_mx(domain, klass, zone) print "\n" print "checking CNAME records\n" check_cname(domain, klass, zone) print "\n" if (@recurse) print "checking subdomains\n\n" 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) res = 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 = res.query(addr.to_s, "A") #, klass) print "\t#{host} (#{addr}) has no PTR record\n" if (ans.header.ancount < 1) rescue Dnsruby::NXDomain print "\t#{host} (#{addr}) returns NXDomain\n" end end end def check_ns(domain, klass, zone) res = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "NS" } @zone) { (zone.select { |z| z.type == "NS" }).each do |rr| ans = res.query(rr.nsdname, "A", klass) print "\t", rr.nsdname, " has no A record\n" if (ans.header.ancount < 1) end end def check_mx(domain, klass, zone) res = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "MX" } @zone) { zone.select {|z| z.type == "MX"}.each do |rr| ans = res.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) res = Dnsruby::Resolver.new # foreach $rr (grep { $_->type eq "CNAME" } @zone) zone.select {|z| z.type == "CNAME"}.each do |rr| ans = res.query(rr.cname, "A", klass) print "\t", rr.cname, " has no A record\n" if (ans.header.ancount < 1) end end opts = GetoptLong.new(["-r", GetoptLong::NO_ARGUMENT]) @recurse = false opts.each do |opt, arg| case opt when '-r' @recurse=true end end if (ARGV.length >=1 && ARGV.length <=2) check_domain(ARGV) exit else print "Usage: #{$0} [ -r ] domain [ class ]\n" end dnsruby-1.54/demo/trace_dns.rb0000644000175000017500000000313012206575435015676 0ustar ondrejondrej#-- #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' include Dnsruby # e.g. ruby trace_dns.rb example.com # 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) res = Dnsruby::Recursor.new #TheLog.level = Logger::DEBUG res.recursion_callback=(Proc.new { |packet| packet.additional.each { |a| print a.to_s + "\n" } print(";; Received #{packet.answersize} bytes from #{packet.answerfrom}. Security Level = #{packet.security_level.string}\n\n") }) type = ARGV[1] if (type == nil) type = Types.A end begin ret = res.query(ARGV[0], type) print "\nRESPONSE : #{ret}\n" rescue NXDomain print "Domain doesn't exist\n" end dnsruby-1.54/demo/digdlv.rb0000644000175000017500000000466312206575435015221 0ustar ondrejondrej#-- #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 begin require 'rubygems' rescue LoadError end require 'dnsruby' include Dnsruby raise RuntimeError, "Usage: #{$0} name [ type [ class ] ]\n" unless (ARGV.length >= 1) && (ARGV.length <= 3) res = Dnsruby::Recursor.new zt=Dnsruby::ZoneTransfer.new 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) name, type, klass = ARGV type ||= "A" klass ||= "IN" if (type.upcase == "AXFR") rrs = zt.transfer(name) # , klass) if (rrs) rrs.each do |rr| print rr.to_s + "\n" end else raise RuntimeError, "zone transfer failed: ", res.errorstring, "\n" end else # Dnsruby::TheLog.level=Logger::DEBUG begin answer = nil answer = res.query(name, type, klass) print answer rescue Exception => e print "query failed: #{e}\n" end end dnsruby-1.54/demo/mx.rb0000644000175000017500000000213412206575435014363 0ustar ondrejondrej#-- #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) # if ARGV.length == 1 dname = ARGV[0] res = Dnsruby::DNS.new begin res.each_resource(dname, 'MX') { |rr| print rr.preference, "\t", rr.exchange, "\n" } rescue Exception => e print "Can't find MX hosts for #{dname}: ", e, "\n" end else print "Usage: #{$0} domain\n" end dnsruby-1.54/demo/to_resolve.txt0000644000175000017500000012206212206575435016337 0ustar ondrejondrejddcsweden.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.54/demo/check_soa.rb0000644000175000017500000001151512206575435015661 0ustar ondrejondrej#-- #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. # #This Perl version was written by Michael Fuhr . # #= SEE ALSO # #axfr, check_zone, mresolv, mx, perldig, Net::DNS require 'dnsruby' #------------------------------------------------------------------------------ # Get the domain from the command line. #------------------------------------------------------------------------------ if ARGV.length ==1 domain = ARGV[0] #------------------------------------------------------------------------------ # Find all the nameservers for the domain. #------------------------------------------------------------------------------ res = Dnsruby::Resolver.new # res.defnames=(0) res.retry_times=(2) ns_req = nil begin ns_req = res.query(domain, "NS") rescue Exception => e print "Error : #{e}" return end if (ns_req.header.ancount == 0) print "No nameservers found for #{domain}\n" return end # Send out non-recursive queries res.recurse=(0) #------------------------------------------------------------------------------ # Check the SOA record on each nameserver. #------------------------------------------------------------------------------ (ns_req.answer.select {|r| r.type == "NS"}).each do |nsrr| #---------------------------------------------------------------------- # Set the resolver to query this nameserver. #---------------------------------------------------------------------- ns = nsrr.domainname # 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_res = Dnsruby::Resolver.new a_req=nil begin a_req = local_res.query(ns, 'A') rescue Exception => e print "Can not find address for #{ns}: #{e}\n" next end (a_req.answer.select {|r| r.type == 'A'}).each do |r| ip = r.address #---------------------------------------------------------------------- # Ask this IP. #---------------------------------------------------------------------- res.nameserver=(ip.to_s) print "#{ns} (#{ip}): " #---------------------------------------------------------------------- # Get the SOA record. #---------------------------------------------------------------------- soa_req=nil begin soa_req = res.query(domain, 'SOA', 'IN') rescue Exception => e print "Error : #{e}\n" next end #---------------------------------------------------------------------- # Is this nameserver authoritative for the domain? #---------------------------------------------------------------------- unless (soa_req.header.aa) print "isn't authoritative for #{domain}\n" next end #---------------------------------------------------------------------- # We should have received exactly one answer. #---------------------------------------------------------------------- unless (soa_req.header.ancount == 1) print "expected 1 answer, got ", soa_req.header.ancount, "\n" next end #---------------------------------------------------------------------- # Did we receive an SOA record? #---------------------------------------------------------------------- unless ((soa_req.answer)[0].type == "SOA") print "expected SOA, got ", (soa_req.answer)[0].type, "\n" next end #---------------------------------------------------------------------- # Print the serial number. #---------------------------------------------------------------------- print "has serial number ", (soa_req.answer)[0].serial, "\n" end end else print "Usage: #{$0} domain\n" end dnsruby-1.54/demo/rubydig.rb0000644000175000017500000000357412206575435015415 0ustar ondrejondrej#-- #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 # require 'dnsruby' include Dnsruby res = Dnsruby::Resolver.new zt=Dnsruby::ZoneTransfer.new if (ARGV && (ARGV[0] =~ /^@/)) nameserver = ARGV.shift if (nameserver == "@auth") res = Dnsruby::Recursor.new else print "Setting nameserver : #{nameserver}\n" res.nameserver=(nameserver.sub(/^@/, "")) print "nameservers = #{res.config.nameserver}\n" zt.server=(nameserver.sub(/^@/, "")) end end raise RuntimeError, "Usage: #{$0} [ \@nameserver ] name [ type [ class ] ]\n" unless (ARGV.length >= 1) && (ARGV.length <= 3) name, type, klass = ARGV type ||= "A" klass ||= "IN" if (type.upcase == "AXFR") rrs = zt.transfer(name) # , klass) if (rrs) rrs.each do |rr| print rr.to_s + "\n" end else raise RuntimeError, "zone transfer failed: ", res.errorstring, "\n" end else # Dnsruby::TheLog.level=Logger::DEBUG begin answer = nil answer = res.query(name, type, klass) print answer rescue Exception => e print "query failed: #{e}\n" end end dnsruby-1.54/demo/digroot.rb0000644000175000017500000000312112206575435015403 0ustar ondrejondrej#-- #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 begin require 'rubygems' rescue LoadError end require 'dnsruby' include Dnsruby raise RuntimeError, "Usage: #{$0} name [ type [ class ] ]\n" unless (ARGV.length >= 1) && (ARGV.length <= 3) resolver = Dnsruby::Resolver.new() resolver.do_validation = true res = Dnsruby::Recursor.new(resolver) # Dnsruby::TheLog.level=Logger::DEBUG name, type, klass = ARGV type ||= "A" klass ||= "IN" begin answer = nil answer = res.query(name, type, klass) print answer rescue Exception => e print "query failed: #{e}\n" end dnsruby-1.54/demo/axfr.rb0000644000175000017500000001313512206575435014702 0ustar ondrejondrej#-- #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 # 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 if (ARGV.length < 1) || (ARGV.length > 2) print "Usage: #{$0} [ -fqs ] [ -D directory ] [ @nameserver ] zone\n" else #------------------------------------------------------------------------------ # Get the nameserver (if specified) and set up the zone transfer directory # hierarchy. #------------------------------------------------------------------------------ nameserver = (ARGV[0] =~ /^@/) ? ARGV.shift : "" nameserver = nameserver.sub(/^@/, "") res = nil if nameserver res = Dnsruby::Resolver.new(nameserver) else res = Dnsruby::Resolver.new end zone = ARGV.shift basedir = opt_d!=nil ? opt_d : (ENV["HOME"]!=nil ? ENV["HOME"] : "") + "/.dns-zones" zonedir = zone.split(/\./).reverse.join("/") zonefile = basedir + "/" + zonedir + "/axfr" # Don't worry about the 0777 permissions here - the current umask setting # will be applied. if !(FileTest.directory?basedir) Dir.mkdir(basedir, 0777) or raise RuntimeError, "can't mkdir #{basedir}: #{$!}\n" end dir = basedir zonedir.split("/").each do |subdir| dir += "/" + subdir if (!FileTest.directory?dir) Dir.mkdir(dir, 0777) or raise RuntimeError, "can't mkdir #{dir}: #{$!}\n" end end #------------------------------------------------------------------------------ # Get the zone. #------------------------------------------------------------------------------ zonearray = nil 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 = res.query(zone, "SOA") if soa==nil raise RuntimeError, "couldn't get SOA for #{zone}: " + res.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. #------------------------------------------------------------------------------ if (!opt_q) zoneref.each do |z| print z.to_s + "\n" end end end dnsruby-1.54/EXAMPLES0000644000175000017500000001062112206575435013627 0ustar ondrejondrej# 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.54/EVENTMACHINE0000644000175000017500000000013512206575435014256 0ustar ondrejondrejDnsruby no longer supports EventMachine - the inbuilt select loop now works on all platforms.dnsruby-1.54/metadata.yml0000644000175000017500000001003112206575435014764 0ustar ondrejondrej--- !ruby/object:Gem::Specification name: dnsruby version: !ruby/object:Gem::Version prerelease: false segments: - 1 - 54 version: "1.54" platform: ruby authors: - AlexD autorequire: dnsruby bindir: bin cert_chain: [] date: 2013-06-12 00:00:00 +01:00 default_executable: dependencies: [] description: email: alexd@nominet.org.uk executables: [] extensions: [] extra_rdoc_files: - DNSSEC - EXAMPLES - README - EVENTMACHINE files: - Rakefile - test/custom.txt - test/resolv.conf - test/tc_axfr.rb - test/tc_cache.rb - test/tc_dlv.rb - test/tc_dns.rb - test/tc_dnskey.rb - test/tc_dnsruby.rb - test/tc_ds.rb - test/tc_escapedchars.rb - test/tc_header.rb - test/tc_hip.rb - test/tc_ipseckey.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_packet.rb - test/tc_packet_unique_push.rb - test/tc_question.rb - test/tc_queue.rb - test/tc_recur.rb - test/tc_res_config.rb - test/tc_res_env.rb - test/tc_res_file.rb - test/tc_res_opt.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.rb - test/tc_soak_base.rb - test/tc_sshfp.rb - test/tc_tcp.rb - test/tc_tkey.rb - test/tc_tsig.rb - test/tc_update.rb - test/tc_validator.rb - test/tc_verifier.rb - test/ts_dnsruby.rb - test/ts_offline.rb - test/ts_online.rb - lib/Dnsruby/Cache.rb - lib/Dnsruby/code_mapper.rb - lib/Dnsruby/Config.rb - lib/Dnsruby/DNS.rb - lib/Dnsruby/dnssec.rb - lib/Dnsruby/Hosts.rb - lib/Dnsruby/ipv4.rb - lib/Dnsruby/ipv6.rb - lib/Dnsruby/key_cache.rb - lib/Dnsruby/message.rb - lib/Dnsruby/name.rb - lib/Dnsruby/PacketSender.rb - lib/Dnsruby/Recursor.rb - lib/Dnsruby/Resolver.rb - lib/Dnsruby/resource/A.rb - lib/Dnsruby/resource/AAAA.rb - lib/Dnsruby/resource/AFSDB.rb - lib/Dnsruby/resource/CERT.rb - lib/Dnsruby/resource/DHCID.rb - lib/Dnsruby/resource/DLV.rb - lib/Dnsruby/resource/DNSKEY.rb - lib/Dnsruby/resource/domain_name.rb - lib/Dnsruby/resource/DS.rb - lib/Dnsruby/resource/generic.rb - lib/Dnsruby/resource/HINFO.rb - lib/Dnsruby/resource/HIP.rb - lib/Dnsruby/resource/IN.rb - lib/Dnsruby/resource/IPSECKEY.rb - lib/Dnsruby/resource/ISDN.rb - lib/Dnsruby/resource/KX.rb - lib/Dnsruby/resource/LOC.rb - lib/Dnsruby/resource/MINFO.rb - lib/Dnsruby/resource/MX.rb - lib/Dnsruby/resource/NAPTR.rb - lib/Dnsruby/resource/NSAP.rb - lib/Dnsruby/resource/NSEC.rb - lib/Dnsruby/resource/NSEC3.rb - lib/Dnsruby/resource/NSEC3PARAM.rb - lib/Dnsruby/resource/OPT.rb - lib/Dnsruby/resource/PX.rb - lib/Dnsruby/resource/resource.rb - lib/Dnsruby/resource/RP.rb - lib/Dnsruby/resource/RRSIG.rb - lib/Dnsruby/resource/RT.rb - lib/Dnsruby/resource/SOA.rb - lib/Dnsruby/resource/SPF.rb - lib/Dnsruby/resource/SRV.rb - lib/Dnsruby/resource/SSHFP.rb - lib/Dnsruby/resource/TKEY.rb - lib/Dnsruby/resource/TSIG.rb - lib/Dnsruby/resource/TXT.rb - lib/Dnsruby/resource/X25.rb - lib/Dnsruby/select_thread.rb - lib/Dnsruby/single_verifier.rb - lib/Dnsruby/SingleResolver.rb - lib/Dnsruby/TheLog.rb - lib/Dnsruby/update.rb - lib/Dnsruby/validator_thread.rb - lib/Dnsruby/zone_reader.rb - lib/Dnsruby/zone_transfer.rb - lib/dnsruby.rb - demo/axfr.rb - demo/check_soa.rb - demo/check_zone.rb - demo/digdlv.rb - demo/digroot.rb - demo/example_recurse.rb - demo/mresolv.rb - demo/mx.rb - demo/rubydig.rb - demo/to_resolve.txt - demo/trace_dns.rb - DNSSEC - EXAMPLES - README - EVENTMACHINE has_rdoc: true homepage: http://rubyforge.org/projects/dnsruby/ licenses: [] post_install_message: rdoc_options: [] require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 0 version: "0" required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version segments: - 0 version: "0" requirements: [] rubyforge_project: dnsruby rubygems_version: 1.3.6 signing_key: specification_version: 3 summary: Ruby DNS(SEC) implementation test_files: - test/ts_offline.rb dnsruby-1.54/lib/0000755000175000017500000000000012206575435013234 5ustar ondrejondrejdnsruby-1.54/lib/dnsruby.rb0000644000175000017500000004232212206575435015252 0ustar ondrejondrej#-- #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_mapper' require 'Dnsruby/ipv4' require 'Dnsruby/ipv6' require 'timeout' require 'Dnsruby/TheLog' #= 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 < TimeoutError # #* 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. # #== Bugs #* NIS is not supported. #* /etc/nsswitch.conf is not supported. #* NSEC3 validation still TBD module Dnsruby # @TODO@ Remember to update version in dnsruby.gemspec! VERSION = 1.54 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 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 HIP = 55 # RFC 5205 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 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 ECC = 4 RSASHA1 = 5 RSASHA256 = 8 RSASHA512 = 10 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) 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 #An error raised while querying for a resource class ResolvError < StandardError end #A timeout error raised while querying for a resource class ResolvTimeout < TimeoutError 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 #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 #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. class Resolv #Looks up the first IP address for +name+ def self.getaddress(name) DefaultResolver.getaddress(name) end #Looks up all IP addresses for +name+ def self.getaddresses(name) DefaultResolver.getaddresses(name) end #Iterates over all IP addresses for +name+ def self.each_address(name, &block) DefaultResolver.each_address(name, &block) end #Looks up the first hostname of +address+ def self.getname(address) DefaultResolver.getname(address) end #Looks up all hostnames of +address+ def self.getnames(address) DefaultResolver.getnames(address) end #Iterates over all hostnames of +address+ def self.each_name(address, &proc) DefaultResolver.each_name(address, &proc) end #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) each_address(name) {|address| return address} raise ResolvError.new("no address for #{name}") end #Looks up all IP addresses for +name+ def getaddresses(name) ret = [] each_address(name) {|address| ret << address} return ret end #Iterates over all IP addresses for +name+ def each_address(name) if AddressRegex =~ name yield name return end yielded = false @resolvers.each {|r| r.each_address(name) {|address| yield address.to_s yielded = true } return if yielded } end #Looks up the first hostname of +address+ def getname(address) each_name(address) {|name| return name} raise ResolvError.new("no name for #{address}") end #Looks up all hostnames of +address+ def getnames(address) ret = [] each_name(address) {|name| ret << name} return ret end #Iterates over all hostnames of +address+ def each_name(address) yielded = false @resolvers.each {|r| r.each_name(address) {|name| yield name.to_s yielded = true } return if yielded } end require 'Dnsruby/Cache' require 'Dnsruby/DNS' require 'Dnsruby/Hosts' require 'Dnsruby/message' require 'Dnsruby/update' require 'Dnsruby/zone_transfer' require 'Dnsruby/dnssec' require 'Dnsruby/zone_reader' #Default Resolver to use for Dnsruby class methods DefaultResolver = self.new #Address RegExp to use for matching IP addresses AddressRegex = /(?:#{IPv4::Regex})|(?:#{IPv6::Regex})/ end end dnsruby-1.54/lib/Dnsruby/0000755000175000017500000000000012206575435014662 5ustar ondrejondrejdnsruby-1.54/lib/Dnsruby/zone_transfer.rb0000644000175000017500000003142112206575435020067 0ustar ondrejondrej#-- #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 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 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 = SingleResolver.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 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 = TCPSocket.new(server, @port) 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 enddnsruby-1.54/lib/Dnsruby/Config.rb0000644000175000017500000003350412206575435016421 0ustar ondrejondrej#-- #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 #-- #@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 (Fixnum) # # 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 @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 @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("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 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 open(filename) {|f| f.each {|line| line.sub!(/[#;].*/, '') keyword, *args = line.split(/\s+/) args.each { |arg| arg.untaint } next unless keyword case keyword 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 } 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}" 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) #:nodoc: all if !@configured parse_config end candidates = [] name = Name.create(name) if name.absolute? candidates = [name] else if (@apply_domain) if @ndots > name.length - 1 candidates.push(Name.create(name.to_a+@domain)) 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.54/lib/Dnsruby/zone_reader.rb0000644000175000017500000003610312206575435017507 0ustar ondrejondrej#-- #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 and attempts to load a zone. Returns a list # of RRs if successful, nil otherwise. def process_file(file) line_num = 0 zone = nil IO.foreach(file) { |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 #{line_num} of #{file} : [#{line}]") 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] == ";") return nil if (line.strip.length == 0) return nil if (!line || (line.length == 0)) @in_quoted_section = false if !@continued_line line = strip_comments(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.54/lib/Dnsruby/Cache.rb0000644000175000017500000001065712206575435016223 0ustar ondrejondrej#-- #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 @mutex = Mutex.new end def cache @cache end def clear() @mutex.synchronize { @cache = Hash.new } end def length return @cache.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 } 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) @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.54/lib/Dnsruby/name.rb0000644000175000017500000003006212206575435016130 0ustar ondrejondrej#-- #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 # 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 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.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([0..9a..fA..F][0..9a..fA..F])/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 enddnsruby-1.54/lib/Dnsruby/PacketSender.rb0000644000175000017500000005765112206575435017575 0ustar ondrejondrej#-- #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 @@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 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 # 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 # * :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 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 query_packet = make_query_packet(msg, use_tcp) 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 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 socket = TCPSocket.new(@server, @port, src_address, src_port) 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 socket.bind(src_address, src_port) socket.connect(@server, @port) end runnextportloop = false rescue Exception => e if (socket!=nil) begin socket.close 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=IOError.new("dnsruby can't connect 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 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) 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=IOError.new("Send failed to #{@server}:#{@port} from #{src_address}:#{src_port}, use_tcp=#{use_tcp}, exception : #{e}") Dnsruby.log.error{"#{err}"} st.push_exception_to_select(client_query_id, client_queue, err, nil) begin socket.close 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 Fixnum 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 Fixnum 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 Fixnum 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 # 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 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 enddnsruby-1.54/lib/Dnsruby/Recursor.rb0000644000175000017500000007162012206575435017021 0ustar ondrejondrej#-- #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 # 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 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 @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() 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 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) 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 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}) 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 ((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 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.54/lib/Dnsruby/validator_thread.rb0000644000175000017500000001026712206575435020531 0ustar ondrejondrej#-- #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 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.54/lib/Dnsruby/SingleResolver.rb0000644000175000017500000001255412206575435020161 0ustar ondrejondrej#-- #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, :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 enddnsruby-1.54/lib/Dnsruby/select_thread.rb0000644000175000017500000006246512206575435020032 0ustar ondrejondrej#-- #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 'singleton' require 'Dnsruby/validator_thread.rb' module Dnsruby Thread::abort_on_exception = true 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 = [] # @@notified] @@timeouts = Hash.new # @@mutex.synchronize do @@query_hash = Hash.new @@socket_hash = Hash.new @@observers = Hash.new @@tcp_buffers=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 # 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] end 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]=[query_settings.client_query_id] # @todo@ If we use persistent sockets then we need to update this array @@timeouts[query_settings.client_query_id]=query_settings.endtime @@sockets.push(query_settings.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 = false @@mutex.synchronize { sockets = @@sockets timeouts = @@timeouts.values has_observer = !@@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 => e# Don't worry if the socket was closed already # print "IO Error =: #{e}\n" 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) # proces the timeouts process_timeouts unused_loop_count+=1 else process_ready(ready) unused_loop_count=0 # process_error(errors) end @@mutex.synchronize{ if (unused_loop_count > 10 && @@query_hash.empty? && @@observers.empty?) Dnsruby.log.debug{"Stopping select loop"} return 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 # @@query_hash[query_settings.client_query_id]=query_settings # @@socket_hash[query_settings.socket]=[query_settings.client_query_id] # @todo@ If we use persistent sockets then we need to update this array def process_ready(ready) ready.each do |socket| query_settings = nil @@mutex.synchronize{ # Can do this if we have a query per socket, but not otherwise... c_q_id = @@socket_hash[socket][0] # @todo@ If we use persistent sockets then this won't work query_settings = @@query_hash[c_q_id] } next if !query_settings udp_packet_size = query_settings.udp_packet_size msg, bytes = get_incoming_data(socket, udp_packet_size) if (msg!=nil) # Check that the IP we received from was the IP we sent to! answerip = msg.answerip.downcase answerfrom = msg.answerfrom.downcase dest_server = query_settings.dest_server 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 ready.delete(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] } # 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 = (socket.class == TCPSocket) # 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 remove_id(id) socket=nil @@mutex.synchronize{ socket = @@query_hash[id].socket @@timeouts.delete(id) @@query_hash.delete(id) @@socket_hash.delete(socket) @@sockets.delete(socket) # @TODO@ Not if persistent! } Dnsruby.log.debug{"Closing socket #{socket}"} begin socket.close # @TODO@ Not if persistent! rescue IOError # Don't worry if the socket was closed already end end def process_timeouts time_now = Time.now timeouts={} @@mutex.synchronize { timeouts = @@timeouts } 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=="") TheLog.info("Bad response from server - no bytes read - ignoring") # @TODO@ Should we do anything about this? 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 begin if (socket.class == TCPSocket) # @todo@ Ruby Bug #9061 stops this working right # We'd like to do a socket.recvfrom, but that raises an Exception # on Windows for TCPSocket for Ruby 1.8.5 (and 1.8.6). # So, we need to do something different for TCP than UDP. *sigh* # @TODO@ This workaround will only work if there is exactly one socket per query # - *not* ideal TCP use! @@mutex.synchronize{ client_id = @@socket_hash[socket][0] answerfrom = @@query_hash[client_id].dest_server answerip = answerfrom answerport = @@query_hash[client_id].dest_port } # 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) 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.54/lib/Dnsruby/Resolver.rb0000644000175000017500000013172512206575435017021 0ustar ondrejondrej#-- #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/PacketSender" 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]]) # #=== 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 DefaultPort = 53 DefaultDnssec = true 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 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: if (!@configured) add_config_nameservers end return @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 return send_message(msg) 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 return 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) # # @TODO@ Add new queue tuples, e.g. : # event_type = EventType::RECEIVED # reply = nil # while (event_type == EventType::RECEIVED) # id, event_type, reply, error = q.pop # Dnsruby.log.debug{"Resolver : result received"} # if ((error != nil) && (event_type == EventType::ERROR)) # raise error # end # print "Reply = #{reply}\n" # end # print "Reply = #{reply}\n" # return reply id, result, error = q.pop if (error != nil) raise error else return result 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 return [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(*args) # msg, client_queue, client_query_id) if (!@configured) add_config_nameservers end # @single_res_mutex.synchronize { if (!@resolver_ruby) # @TODO@ Synchronize this? @resolver_ruby = ResolverRuby.new(self) end # } client_query_id = @resolver_ruby.send_async(*args) if (@single_resolvers.length == 0) Thread.start { sleep(@query_timeout == 0 ? 1 : @query_timeout) args[1].push([client_query_id, nil, ResolvTimeout.new("Query timed out - no nameservers configured")]) } end return 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 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 # if (@single_resolvers==[]) # add_config_nameservers # end update # ResolverRegister::register_resolver(self) end def add_config_nameservers # :nodoc: all if (!@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, :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.push(res) if res end } end def set_config_nameserver(n) # @TODO@ Should we allow NS RRSet here? If so, then .sort_by {rand} if (!@configured) @config.get_ready end @configured = true if (n).kind_of?String @config.nameserver=[n] else @config.nameserver=n end add_config_nameservers end def reset_attributes #:nodoc: all if (@resolver_ruby) @resolver_ruby.reset_attributes end # 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 @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 { @single_resolvers.delete(nil) # Just in case... @single_resolvers.each do |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) raise ArgumentError.new("Can't create server #{server}") if !res update_internal_res(res) @single_res_mutex.synchronize { @single_resolvers.push(res) } end def update_internal_res(res) [:port, :use_tcp, :no_tcp, :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 Fixnum 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 Fixnum 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 Fixnum 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 update end def Resolver.check_port(p, src_port=[]) if (p.class != Fixnum) tmp_src_ports = Array.new(src_port) p.each do |x| if (!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)) if ((p == 0) && (src_port.length > 0)) return false end return true else Dnsruby.log.error("Illegal port (#{p})") raise ArgumentError.new("Illegal port #{p}") end end def Resolver.port_in_range(p) if ((p == 0) || ((p >= 50000) && (p <= 65535))) # @TODO@ IANA port bitmap - use 50000 - 65535 for now # ((Iana::IANA_PORTS.index(p)) == nil && # (p > 1024) && (p < 65535))) return true end return false end def Resolver.get_ports_from(p) a = [] if (p.class == Fixnum) a = [p] else p.each do |x| a.push(x) end end return a 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 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) if (args[0].length > 2) tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0][0], :key => args[0][1], :algorithm => args[0][2]}) else tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0][0], :key => args[0][1]}) end end else # Dnsruby.log.debug{"TSIG signing switched off"} return nil end elsif (args.length ==2) tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0], :key => args[1]}) elsif (args.length ==3) tsig = RR.new_from_hash({:type => Types.TSIG, :klass => Classes.ANY, :name => args[0], :key => args[1], :algorithm => args[2]}) else raise ArgumentError.new("Wrong number of arguments to tsig=") end Dnsruby.log.info{"TSIG signing now using #{tsig.name}, key=#{tsig.key}"} return 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 return @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)) Dnsruby.log.error{"Duplicate timeout key!"} raise RuntimeError.new("Duplicate timeout key!") end timeouts[base+retry_delay+offset]=[res, retry_count] end end end # } return 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(*args) # msg, client_queue, client_query_id=nil) msg=args[0] client_queue=nil client_query_id=nil client_queue=args[1] if (args.length > 2) client_query_id = args[2] end # 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 if (!client_queue.kind_of?Queue) Dnsruby.log.error{"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 if (!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 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 return 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[ time_now = Time.now timeouts=@parent.generate_timeouts(time_now) return timeouts 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 if (error != nil) # client_queue.push([client_query_id, Resolver::EventType::ERROR, msg, error]) client_queue.push([client_query_id, msg, error]) else # client_queue.push([client_query_id, Resolver::EventType::VALIDATED, msg, error]) client_queue.push([client_query_id, msg, error]) end 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 do |t| timeouts.delete(t) end 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?) Dnsruby.log.fatal{"Queue empty in handle_queue_event!"} raise RuntimeError.new("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) Dnsruby.log.error{"Serious internal error!! #{id} expected, #{event_id} received"} raise RuntimeError.new("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) if (!outstanding.include?id) Dnsruby.log.error{"Query id not on outstanding list! #{outstanding.length} items. #{id} not on #{outstanding}"} # raise RuntimeError.new("Query id not on outstanding!") end outstanding.delete(id) end # } if (event_type == Resolver::EventType::RECEIVED) # if (event.kind_of?(Exception)) if (error != nil) 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 != nil) 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) if (!response.cached) stop_querying(client_query_id) # @TODO@ Does the client want notified at this point? 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] if (!(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) if (!response.cached) # @mutex.synchronize{ query, client_queue, s_queue, outstanding = @query_list[client_query_id] if (s_queue != select_queue) Dnsruby.log.error{"Serious internal error : expected select queue #{s_queue}, got #{select_queue}"} raise RuntimeError.new("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) Dnsruby.log.error{"Serious internal error : expected select queue #{s_queue}, got #{select_queue}"} raise RuntimeError.new("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) Dnsruby.log.error{"Serious internal error : expected select queue #{s_queue}, got #{select_queue}"} raise RuntimeError.new("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/SingleResolver"dnsruby-1.54/lib/Dnsruby/resource/0000755000175000017500000000000012206575435016511 5ustar ondrejondrejdnsruby-1.54/lib/Dnsruby/resource/AFSDB.rb0000644000175000017500000000411712206575435017660 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/SOA.rb0000644000175000017500000000642112206575435017463 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/SPF.rb0000644000175000017500000000177612206575435017501 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/MX.rb0000644000175000017500000000374412206575435017372 0ustar ondrejondrej#-- #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(" ") @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.54/lib/Dnsruby/resource/generic.rb0000644000175000017500000001060312206575435020452 0ustar ondrejondrej#-- #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/RRSIG' require 'Dnsruby/resource/NSEC' require 'Dnsruby/resource/DS' 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'dnsruby-1.54/lib/Dnsruby/resource/TSIG.rb0000644000175000017500000005430612206575435017614 0ustar ondrejondrej#-- #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.") 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) 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 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; else raise ArgumentError.new("Invalid TSIG algorithm") end elsif (alg.class == Name) if (alg!=HMAC_MD5 && alg!=HMAC_SHA1 && alg!=HMAC_SHA256) 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 enddnsruby-1.54/lib/Dnsruby/resource/RP.rb0000644000175000017500000000433612206575435017365 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/RT.rb0000644000175000017500000000401312206575435017361 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/NSEC3.rb0000644000175000017500000002631512206575435017660 0ustar ondrejondrej#-- #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 return false end def check_name_in_wildcard_range(name) # @TODO@ Check if the name is covered by this record return false end def calculate_hash return 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 (0..iterations).each { 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 return 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) if (t && t.length > 0) @types = NSEC.get_types(t) else @types = [] end 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? return (@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") 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) if (input == "-") return "" end return [input].pack("H*") end def NSEC3.encode_salt(s) if (!s || s.length == 0) return "-" end return 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 = 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!=nil) type_strings = [] @types.each do |t| type_strings.push(t.string) end # salt = NSEC3.encode_salt(@salt) salt = salt() next_hashed = encode_next_hashed(@next_hashed) types = type_strings.join(" ") return "#{@hash_alg.code} #{@flags} #{@iterations} #{salt} ( #{next_hashed} #{types} )" else return "" 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 enddnsruby-1.54/lib/Dnsruby/resource/OPT.rb0000644000175000017500000001450312206575435017503 0ustar ondrejondrej#-- #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 # @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=size end def options(args) if (args==nil) return @options elsif args.kind_of?Fixnum # 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 to_s ret = "OPT pseudo-record : payloadsize #{payloadsize}, xrcode #{xrcode.code}, version #{version}, flags #{flags}" if @options @options.each do |opt| ret = ret + " " + opt.to_s 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_bytes(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([options]) end class Option attr_accessor :code, :data def initialize(code, data) @code = code @data = data end end end end enddnsruby-1.54/lib/Dnsruby/resource/CERT.rb0000644000175000017500000000674412206575435017606 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/HINFO.rb0000644000175000017500000000377412206575435017714 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/IPSECKEY.rb0000644000175000017500000001044612206575435020257 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/DLV.rb0000644000175000017500000000154112206575435017464 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/resource.rb0000644000175000017500000004700612206575435020674 0ustar ondrejondrej#-- #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 # 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 #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) # class RR include Comparable def <=>(other) # return 1 if ((!other) || !(other.name) || !(other.type)) # return -1 if (!@name) if (@name.canonical == other.name.canonical) if (@type.code == other.type.code) return (@rdata <=> other.rdata) else return @type.code <=> other.type.code end else return @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 return rdata.length end def name=(newname) if (!(newname.kind_of?Name)) @name=Name.create(newname) else @name = newname end 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 if (klass.class == Classes) @klass = klass else @klass = Classes.new("CLASS#{klass}") end end end def clone MessageDecoder.new(MessageEncoder.new {|msg| msg.put_rr(self, true)}.to_s) {|msg| r = msg.get_rr return r } 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 end if (rec.type == Types.RRSIG) && (@type == Types.RRSIG) return rec.type_covered == self.type_covered end [rec, self].each { |rr| if (rr.type == Types::RRSIG) return ((@type == rr.type_covered) || (rec.type == rr.type_covered)) end } return (@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.to_s+"=", 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 = hash[:klass] || Classes::IN ttl = hash[:ttl] || 0 recordclass = get_class(type, klass) record = recordclass.new record.name=hash[:name] if !(record.name.kind_of?Name) record.name = Name.create(record.name) end record.ttl=ttl record.type = type record.klass = klass hash.delete(:name) hash.delete(:type) hash.delete(:ttl) hash.delete(:klass) record.from_hash(hash) return 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(/(\? 0) return @rdata else return "no rdata" end 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 = self.instance_variables s_ivars = [] ivars.each {|i| s_ivars << i.to_s} # Ruby 1.9 s_ivars.delete "@ttl" # RFC 2136 section 1.1 s_ivars.delete "@rdata" if (self.type == Types.DS) s_ivars.delete "@digest" end s_ivars.sort! ivars = other.instance_variables o_ivars = [] ivars.each {|i| o_ivars << i.to_s} # Ruby 1.9 o_ivars.delete "@ttl" # RFC 2136 section 1.1 o_ivars.delete "@rdata" if (other.type == Types.DS) o_ivars.delete "@digest" end o_ivars.sort! return s_ivars == o_ivars && s_ivars.collect {|name| self.instance_variable_get name} == o_ivars.collect {|name| other.instance_variable_get name} end def eql?(other) #:nodoc: return self == other end def hash # :nodoc: h = 0 vars = self.instance_variables vars.delete "@ttl" vars.each {|name| h ^= self.instance_variable_get(name).hash } return h end def self.find_class(type_value, class_value) # :nodoc: all klass = nil 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) end if (type_value.class == Class) type_value = type_value.const_get(:TypeValue) return find_class(type_value, Classes.to_code(class_value)) else if (type_value.class == Types) type_value = type_value.code else type_value = Types.new(type_value).code end if (class_value.class == Classes) class_value = class_value.code else class_value = Classes.new(class_value).code end return find_class(type_value, class_value) end return ret 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) if (args.length == 1) && (args[0].class == String) return new_from_string(args[0]) elsif (args.length == 1) && (args[0].class == Hash) return new_from_hash(args[0]) else return new_from_data(args) end end def self.get_num(bytes) ret = 0 shift = (bytes.length-1) * 8 bytes.each_byte {|byte| ret += byte.to_i << shift shift -= 8 } return ret end end end require 'Dnsruby/resource/domain_name' require 'Dnsruby/resource/generic' require 'Dnsruby/resource/IN' dnsruby-1.54/lib/Dnsruby/resource/ISDN.rb0000644000175000017500000000350512206575435017576 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/NSAP.rb0000644000175000017500000001257512206575435017611 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/KX.rb0000644000175000017500000000370412206575435017364 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/LOC.rb0000644000175000017500000002136112206575435017456 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/NSEC3PARAM.rb0000644000175000017500000001057612206575435020443 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/TKEY.rb0000644000175000017500000001205512206575435017615 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/DNSKEY.rb0000644000175000017500000002542112206575435020037 0ustar ondrejondrej#-- #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 pkey.e = exponent pkey.n = modulus 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 pkey.p = p pkey.q = q pkey.g = g pkey.pub_key = y pkey end end end enddnsruby-1.54/lib/Dnsruby/resource/HIP.rb0000644000175000017500000000755212206575435017467 0ustar ondrejondrej #-- #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.54/lib/Dnsruby/resource/IN.rb0000644000175000017500000000576412206575435017360 0ustar ondrejondrej#-- #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::DNSKEY => DNSKEY, 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::DS => DS, Types::NSEC3 => NSEC3, Types::NSEC3PARAM => NSEC3PARAM, Types::DLV => DLV, Types::SSHFP => SSHFP, Types::IPSECKEY => IPSECKEY, Types::HIP => HIP, Types::DHCID => DHCID } #: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' dnsruby-1.54/lib/Dnsruby/resource/MINFO.rb0000644000175000017500000000410312206575435017704 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/NAPTR.rb0000644000175000017500000000613012206575435017722 0ustar ondrejondrej#-- #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_accessor :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.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 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 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.54/lib/Dnsruby/resource/TXT.rb0000644000175000017500000001323312206575435017517 0ustar ondrejondrej#-- #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[0] 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 enddnsruby-1.54/lib/Dnsruby/resource/A.rb0000644000175000017500000000322612206575435017221 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/PX.rb0000644000175000017500000000446012206575435017371 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/SRV.rb0000644000175000017500000000765412206575435017524 0ustar ondrejondrej#-- #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_int end if hash[:weight] @weight = hash[:weight].to_int end if hash[:port] @port = hash[:port].to_int 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.54/lib/Dnsruby/resource/AAAA.rb0000644000175000017500000000322412206575435017522 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/NSEC.rb0000644000175000017500000002454212206575435017575 0ustar ondrejondrej#-- #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 if (@name.wild?) return check_name_in_wildcard_range(n) end if (name.canonically_before(n) && (n.canonically_before(next_domain))) return true end return false end def check_name_in_wildcard_range(n) # Check if the name is covered by this record return false if !@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) return false if n.canonically_before(n2) return true end def types=(t) if (t && t.length > 0) @types = NSEC.get_types(t) else @types = [] end end def self.get_types(t) types = nil if (t.instance_of?Array) # from the wire, already decoded types =t elsif (t.instance_of?String) if (index = t.index";") t = t[0, index] end if (index = t.index")") t = t[0, index] end # List of mnemonics types=[] mnemonics = t.split(" ") mnemonics.each do |m| type = Types.new(m) types.push(type) end else raise DecodeError.new("Unknown format of types for Dnsruby::RR::NSEC") end return 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 0..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. if (!([Types::OPT, Types::TSIG].include?(type))) types.push(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 do |type| type_codes.push(type.code) end 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.push(type_codes[0]) # And delete them from type_codes type_codes=type_codes.last(type_codes.length-1) break if (type_codes.empty?) end if (!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 (2+bitmap.length).times do output += " " end 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 return 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 = 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!=nil) type_strings = [] @types.each do |t| type_strings.push(t.string) end types = type_strings.join(" ") return "#{@next_domain.to_s(true)} ( #{types} )" else return "" 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.54/lib/Dnsruby/resource/RRSIG.rb0000644000175000017500000002561312206575435017733 0ustar ondrejondrej#-- #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?Fixnum) 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.54/lib/Dnsruby/resource/SSHFP.rb0000644000175000017500000000461612206575435017730 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/domain_name.rb0000644000175000017500000000277712206575435021322 0ustar ondrejondrej#-- #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 msg.put_name(@domainname, canonical) end def self.decode_rdata(msg) #:nodoc: all return self.new(msg.get_name) end end end end dnsruby-1.54/lib/Dnsruby/resource/DHCID.rb0000644000175000017500000000314212206575435017651 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/X25.rb0000644000175000017500000000272212206575435017417 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/resource/DS.rb0000644000175000017500000002045612206575435017353 0ustar ondrejondrej#-- #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-2", 2 ) 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 = Digest::SHA256.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.54/lib/Dnsruby/update.rb0000644000175000017500000002260312206575435016474 0ustar ondrejondrej#-- #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, 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 = 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#absent") 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 resource = RR.create("#{args[0]} #{ttl} IN #{args[1]} #{args[2]}") resource.klass = Classes.NONE add_update(resource) end return resource end end enddnsruby-1.54/lib/Dnsruby/dnssec.rb0000644000175000017500000003025512206575435016473 0ustar ondrejondrej#-- #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 >= ALWAYS_ROOT_ONY) && (p <= ALWAYS_LOCAL_ANCHORS)) @@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) @@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 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, 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 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].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 enddnsruby-1.54/lib/Dnsruby/key_cache.rb0000644000175000017500000000563012206575435017126 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/TheLog.rb0000644000175000017500000000245712206575435016401 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/DNS.rb0000644000175000017500000002372612206575435015645 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/ipv4.rb0000644000175000017500000000376112206575435016100 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/Hosts.rb0000644000175000017500000000766012206575435016320 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/message.rb0000644000175000017500000010506612206575435016643 0ustar ondrejondrej#-- #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 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 if (!(/\.\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 return 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 if (!found_rrset) ret.push(RRSet.new(rr)) end end return ret end def ==(other) return false unless (other.instance_of?Message::Section) return false if (other.rrsets(nil, true).length != self.rrsets(nil, true).length) otherrrsets = other.rrsets(nil, true) self.rrsets(nil, true).each {|rrset| return false unless otherrrsets.include?rrset } return 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 #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 return exception end def ==(other) ret = false if (other.kind_of?Message) ret = @header == other.header && @question[0] == other.question[0] && @answer == other.answer && @authority == other.authority && @additional == other.additional end return ret 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 return 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 return 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 = {} ["answer", "authority", "additional"].each do |section| ret[section] = self.send(section).rrsets(type, include_opt) end return 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) if (!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) #:nodoc: all if (!@answer.include?rr) @answer << rr update_counts end end def each_answer @answer.each {|rec| yield rec } end def add_authority(rr) #:nodoc: all if (!@authority.include?rr) @authority << rr update_counts end end def each_authority @authority.each {|rec| yield rec } end def add_additional(rr) #:nodoc: all if (!@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 return 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? return (@tsigstate == :Signed || @tsigstate == :Verified || @tsigstate == :Failed) end #If this message was signed by a TSIG, was the TSIG verified? def verified? return (@tsigstate == :Verified) end def get_opt each_additional do |r| if (r.type == Types::OPT) return r end end return nil end def rcode rcode = @header.get_header_rcode opt = get_opt if (opt != nil) rcode = rcode.code + (opt.xrcode.code << 4) rcode = RCode.new(rcode) end return rcode; end def 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.to_s else retval = retval + @header.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 return 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 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) } } }.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 return o 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 #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)} return header end def data return 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) {|msg| header.decode(msg) header_end = msg.index } header.arcount = header.arcount - 1 bytes[0,header_end]=MessageEncoder.new {|msg| header.encode(msg)}.to_s return bytes end def ==(other) return @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 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 return retval; end def decode(msg) @id, flag, @qdcount, @ancount, @nscount, @arcount = msg.get_unpack('nnnnnn') @qr = (((flag >> 15)&1)==1)?true:false @opcode = OpCode.new((flag >> 11) & 15) @aa = (((flag >> 10)&1)==1)?true:false @tc = (((flag >> 9)&1)==1)?true:false @rd = (((flag >> 8)&1)==1)?true:false @ra = (((flag >> 7)&1)==1)?true:false @ad = (((flag >> 5)&1)==1)?true:false @cd = (((flag >> 4)&1)==1)?true:false @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 class MessageDecoder #:nodoc: all attr_reader :index def initialize(data) @data = data @index = 0 @limit = data.length yield self end def has_remaining return @limit-@index > 0 end def get_length16 len, = self.get_unpack('n') save_limit = @limit @limit = @index + len d = yield(len) if @index < @limit raise DecodeError.new("junk exists") elsif @limit < @index raise DecodeError.new("limit exceeded") end @limit = save_limit return d end def get_bytes(len = @limit - @index) d = @data[@index, len] @index += len return d end def get_unpack(template) len = 0 littlec = ?c bigc = ?C littleh = ?h bigh = ?H littlen = ?n bign = ?N star = ?* if (littlec.class != Fixnum) # We're using Ruby 1.9 - convert the codes littlec = littlec.getbyte(0) bigc = bigc.getbyte(0) littleh = littleh.getbyte(0) bigh = bigh.getbyte(0) littlen = littlen.getbyte(0) bign = bign.getbyte(0) star = star.getbyte(0) end template.each_byte {|byte| case byte when littlec, bigc len += 1 when littleh, bigh len += 1 when littlen len += 2 when bign len += 4 when star len = @limit-@index else raise StandardError.new("unsupported template: '#{byte.chr}' in '#{template}'") end } raise DecodeError.new("limit exceeded") if @limit < @index + len arr = @data.unpack("@#{@index}#{template}") @index += len return arr end def get_string len = @data[@index] if (len.class == String) len = len.getbyte(0) end raise DecodeError.new("limit exceeded\nlimit = #{@limit}, index = #{@index}, len = #{len}\n") if @limit < @index + 1 + (len ? len : 0) d = @data[@index + 1, len] @index += 1 + len return d end def get_string_list strings = [] while @index < @limit strings << self.get_string end strings end def get_name return Name.new(self.get_labels) end def get_labels(limit=nil) limit = @index if !limit || @index < limit d = [] while true temp = @data[@index] if (temp.class == String) temp = (temp.getbyte(0)) end case temp # @data[@index] when 0 @index += 1 return d when 192..255 idx = self.get_unpack('n')[0] & 0x3fff if limit <= idx raise DecodeError.new("non-backward name pointer") end save_index = @index @index = idx d += self.get_labels(limit) @index = save_index return d else d << self.get_label end end return d end def get_label begin # label = Name::Label.new(Name::decode(self.get_string)) label = Name::Label.new(self.get_string) return label # return Name::Label::Str.new(self.get_string) rescue ResolvError => e raise DecodeError.new(e) # Turn it into something more suitable end end def get_question name = self.get_name type, klass = self.get_unpack("nn") q = Question.new(name, type, klass) return q end def get_rr name = self.get_name type, klass, ttl = self.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 rec = self.get_length16 {typeclass.decode_rdata(self)} rec.name=name rec.ttl=ttl rec.type = type rec.klass = klass return rec end end class MessageEncoder #:nodoc: all def initialize @data = '' @names = {} yield self end def to_s return @data end def put_bytes(d) @data << d end def put_pack(template, *d) @data << d.pack(template) 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) self.put_pack("C", d.length) @data << d 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! if (downcase) d = d.downcase end put_labels(d.to_a, canonical) end def put_labels(d, do_canonical) d.each_index {|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 } @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 #A Dnsruby::Question object represents a record in the #question section of a DNS packet. # #RFC 1035 Section 4.1.2 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(*args) @qtype = Types::A @qclass = Classes::IN type_given = false if (args.length > 0) if (args.length > 1) @qtype = Types.new(args[1]) type_given = true if (args.length > 2) @qclass = Classes.new(args[2]) end end else raise ArgumentError.new("Must pass at least a name!") end # If the name looks like an IP address then do an appropriate # PTR query, unless the user specified the qtype @qname=args[0] if (!type_given) case @qname.to_s when IPv4::Regex @qname = IPv4.create(@qname).to_name @qtype = Types.PTR when IPv6::Regex @qname = IPv6.create(@qname).to_name @qtype = Types.PTR when Name when IPv6 @qtype = Types.PTR when IPv4 @qtype = Types.PTR else @qname = Name.create(@qname) end else case @qtype when Types.PTR case @qname.to_s when IPv4::Regex @qname = IPv4.create(@qname).to_name when IPv6::Regex @qname = IPv6.create(@qname).to_name when IPv6 when IPv4 else @qname = Name.create(@qname) end else @qname = Name.create(@qname) end end end def qtype=(qtype) @qtype = Types.new(qtype) end def qclass=(qclass) @qclass = Classes.new(qclass) end def qname=(qname) case qname when IPv4::Regex @qname = IPv4.create(qname).to_name @qtype = Types.PTR when IPv6::Regex @qname = IPv6.create(qname).to_name @qtype = Types.PTR when Name when IPv6 @qtype = Types.PTR when IPv4 @qtype = Types.PTR else @qname = Name.create(qname) end end #Returns a string representation of the question record. def to_s return "#{@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.54/lib/Dnsruby/ipv6.rb0000644000175000017500000001110412206575435016070 0ustar ondrejondrej#-- #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.54/lib/Dnsruby/code_mapper.rb0000644000175000017500000001152612206575435017472 0ustar ondrejondrej#-- #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 def CodeMapper.maxcode return @maxcode 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?Fixnum) 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 #{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 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?Fixnum) return arg else return @@arrays[self].stringsdown[arg.downcase] end end def <=>(other) if (other.class == Fixnum) 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.54/lib/Dnsruby/single_verifier.rb0000644000175000017500000015204412206575435020371 0ustar ondrejondrej#-- #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 return @@recursor end def get_dlv_resolver # :nodoc: # if (Dnssec.do_validation_with_recursor?) # return Recursor.new # else if (Dnssec.default_resolver) return Dnssec.default_resolver else return Resolver.new end # end 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 # 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 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 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 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 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 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 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 end end def get_nameservers_for(name, res = nil) # :nodoc: # @TODO@ !!! if (Dnssec.do_validation_with_recursor?) return get_recursor else if (Dnssec.default_resolver) return Dnssec.default_resolver else return Resolver.new end 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) 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.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 = follow_chain(closest_anchor, qname) 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.54/Rakefile0000644000175000017500000000063712206575435014141 0ustar ondrejondrejrequire 'rake/testtask' require 'rake/rdoctask' 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 task :test => :install do require 'rake/runtest' Rake.run_tests 'test/ts_dnsruby.rb' end task :default => :install do end task :install do sh "ruby setup.rb" end dnsruby-1.54/test/0000755000175000017500000000000012206575435013445 5ustar ondrejondrejdnsruby-1.54/test/ts_offline.rb0000644000175000017500000000362612206575435016131 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'dnsruby' Dnsruby.log.level = Logger::FATAL require "test/unit" require "test/tc_header.rb" require "test/tc_name.rb" require "test/tc_misc.rb" require "test/tc_packet.rb" require "test/tc_packet_unique_push.rb" require "test/tc_question.rb" require "test/tc_res_file.rb" require "test/tc_res_opt.rb" require "test/tc_res_config.rb" #require "test/tc_res_env.rb" require "test/tc_rr-txt.rb" require "test/tc_rr-unknown.rb" require "test/tc_rr.rb" require "test/tc_rrset.rb" require "test/tc_tkey.rb" require "test/tc_update.rb" require "test/tc_escapedchars.rb" require "test/tc_dnskey.rb" require "test/tc_rrsig.rb" require "test/tc_nsec.rb" require "test/tc_nsec3.rb" require "test/tc_nsec3param.rb" require "test/tc_ipseckey.rb" require "test/tc_naptr.rb" 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 DS digest test" puts "-----------------------------------------------------------------------" end if (have_openssl) require "test/tc_ds.rb" end dnsruby-1.54/test/tc_packet.rb0000644000175000017500000002752112206575435015736 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestPacket < Test::Unit::TestCase 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 end dnsruby-1.54/test/tc_nsec3.rb0000644000175000017500000001134512206575435015477 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class Nsec3Test < Test::Unit::TestCase INPUT = "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 aabbccdd ( " + "2vptu5timamqttgl4luu9kg21e0aor3s A RRSIG )" INPUT2 = "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example. 3600 IN NSEC3 1 1 12 aabbccdd " + "2vptu5timamqttgl4luu9kg21e0aor3s" include Dnsruby def test_nsec_from_string nsec = Dnsruby::RR.create(INPUT) # assert_equal(H("x.y.w.example"), nsec.next_hashed.to_s) assert_equal([Types.A, Types.RRSIG], nsec.types) assert(nsec.opt_out?) assert_equal(12, nsec.iterations) assert_equal("aabbccdd", nsec.salt) assert_equal(Dnsruby::Nsec3HashAlgorithms.SHA_1, nsec.hash_alg) nsec2 = Dnsruby::RR.create(nsec.to_s) assert(nsec2.to_s == nsec.to_s) nsec = Dnsruby::RR.create(INPUT2) assert_equal([], nsec.types) assert(nsec.opt_out?) assert_equal(12, nsec.iterations) assert_equal("aabbccdd", nsec.salt) assert_equal(Dnsruby::Nsec3HashAlgorithms.SHA_1, nsec.hash_alg) nsec2 = Dnsruby::RR.create(nsec.to_s) assert(nsec2.to_s == nsec.to_s) end def test_base32 inputs = [["",""], ["f","CO======"], ["fo","CPNG===="], ["foo", "CPNMU==="], ["foob", "CPNMUOG="], ["fooba", "CPNMUOJ1"], ["foobar", "CPNMUOJ1E8======"]] inputs.each {|dec, enc| assert(Base32.encode32hex(dec) == enc, "Failed encoding #{dec}") assert(Base32.decode32hex(enc) == dec, "Failed decoding #{enc}") } 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_calculate_hash input = [ [ "example" , "0p9mhaveqvm6t7vbl5lop2u3t2rp3tom"], [ "a.example" , "35mthgpgcu1qg68fab165klnsnk3dpvl"], [ "ai.example" , "gjeqe526plbf1g8mklp59enfd789njgi"], [ "ns1.example" , "2t7b4g4vsa5smi47k61mv5bv1a22bojr"], [ "ns2.example" , "q04jkcevqvmu85r014c7dkba38o0ji5r"], [ "w.example" , "k8udemvp1j2f7eg6jebps17vp3n8i58h"], [ "*.w.example" , "r53bq7cc2uvmubfu5ocmm6pers9tk9en"], [ "x.w.example" , "b4um86eghhds6nea196smvmlo4ors995"], [ "y.w.example" , "ji6neoaepv8b5o6k4ev33abha8ht9fgc"], [ "x.y.w.example" , "2vptu5timamqttgl4luu9kg21e0aor3s"], [ "xx.example" , "t644ebqk9bibcna874givr6joj62mlhv"], [ "2t7b4g4vsa5smi47k61mv5bv1a22bojr.example" , "kohar7mbb8dc2ce8a9qvl8hon4k53uhi"] ] input.each {|name, hash| nsec3 = Dnsruby::RR.create({:type => 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 def test_rfc_examples print "IMPLEMENT NSEC3 validation!\n" return end enddnsruby-1.54/test/tc_escapedchars.rb0000644000175000017500000003535212206575435017115 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestEscapedChars < Test::Unit::TestCase 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.54/test/tc_tkey.rb0000644000175000017500000000477512206575435015451 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' require "digest/md5" class TestTKey < Test::Unit::TestCase 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.54/test/tc_name.rb0000644000175000017500000000542112206575435015402 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestName < Test::Unit::TestCase 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.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.54/test/tc_ds.rb0000644000175000017500000000711012206575435015065 0ustar ondrejondrej#-- #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 'test/unit' require 'openssl' require 'digest/sha2' require 'dnsruby' class DsTest < Test::Unit::TestCase 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 )" include Dnsruby 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.54/test/tc_res_opt.rb0000644000175000017500000001500312206575435016132 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestResOpt < Test::Unit::TestCase 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.54/test/tc_sshfp.rb0000644000175000017500000000256012206575435015606 0ustar ondrejondrej #-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestSSHFP < Test::Unit::TestCase 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.54/test/tc_rrsig.rb0000644000175000017500000000601612206575435015611 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class RrsigTest < Test::Unit::TestCase 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.54/test/tc_dnsruby.rb0000644000175000017500000000300212206575435016141 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestDnsruby < Test::Unit::TestCase def test_dnsruby a = Resolv.getaddress("www.ruby-lang.org") assert_equal(a.to_s, "221.186.184.68") a = Resolv.getaddresses("www.ruby-lang.org") assert(a.length==1) assert_equal(a[0].to_s, "221.186.184.68") Resolv.each_address("www.ruby-lang.org") {|address| assert_equal(address, "221.186.184.68")} n = Resolv.getname("210.251.121.214") assert_equal(n, "ci.ruby-lang.org") begin ret = Resolv.getname("www.ruby-lang.org") assert(false, ret) rescue Exception => e assert(e.kind_of?(ResolvError)) end n = Resolv.getnames("210.251.121.214") assert(n.length==1) assert_equal(n[0], "ci.ruby-lang.org") Resolv.each_name("210.251.121.214") {|name| assert_equal(name, "ci.ruby-lang.org")} end enddnsruby-1.54/test/tc_soak_base.rb0000644000175000017500000001130312206575435016405 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' class TestSoakBase # < Test::Unit::TestCase include Dnsruby Rrs = [ { :type => Types.A, :name => 'a.t.dnsruby.validation-test-servers.nominet.org.uk', :address => '10.0.1.128' }, { :type => Types::MX, :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.dnsruby.validation-test-servers.nominet.org.uk', :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk' }, { :type => Types.TXT, :name => 'txt.t.dnsruby.validation-test-servers.nominet.org.uk', :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 p "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 p "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.54/test/custom.txt0000644000175000017500000000036712206575435015526 0ustar ondrejondrej# $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.54/test/tc_res_env.rb0000644000175000017500000000402412206575435016121 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestResolverEnv < Test::Unit::TestCase # @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.54/test/tc_hip.rb0000644000175000017500000000651612206575435015250 0ustar ondrejondrej #-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestHIP < Test::Unit::TestCase 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.54/test/tc_rr-opt.rb0000644000175000017500000001160112206575435015702 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' require 'socket' include Dnsruby class TestRrOpt < Test::Unit::TestCase 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") 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.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 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.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.54/test/tc_rr.rb0000644000175000017500000002671512206575435015116 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestRR < Test::Unit::TestCase 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', }, ] #------------------------------------------------------------------------------ # 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_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 end dnsruby-1.54/test/tc_soak.rb0000644000175000017500000001434712206575435015426 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' begin require 'test/tc_single_resolver' rescue LoadError require 'tc_single_resolver' end begin require 'test/tc_soak_base' rescue LoadError require 'tc_soak_base' end include Dnsruby # This class tries to soak test the Dnsruby library. # It can't do this very well, owing to the small number of sockets allowed to be open simultaneously. # @TODO@ Future versions of dnsruby will allow random streaming over a fixed number of (cycling) random sockets, # so this test can be beefed up considerably at that point. # @todo@ A test DNS server running on localhost is really needed here class TestSingleResolverSoak < Test::Unit::TestCase def test_many_asynchronous_queries_one_single_resolver run_many_asynch_queries_test_single_res(1) end def test_many_asynchronous_queries_many_single_resolvers run_many_asynch_queries_test_single_res(50) end def run_many_asynch_queries_test_single_res(num_resolvers) q = Queue.new resolvers = [] timed_out = 0 query_count = 0 num_resolvers.times do |n| resolvers.push(SingleResolver.new) resolvers[n].packet_timeout=4 end res_pos = 0 start = Time.now # @todo@ 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. 63.times do |i| rr_count = 0 TestSoakBase::Rrs.each do |data| rr_count+=1 res = resolvers[res_pos] res_pos=+1 if (res_pos >= num_resolvers) res_pos = 0 end res.send_async(Message.new(data[:name], data[:type]), q, [i,rr_count]) # p "Sent #{i}, #{rr_count}, Queue #{q}" query_count+=1 end end query_count.times do |i| id,ret, error = q.pop if (error.class == ResolvTimeout) timed_out+=1 elsif (ret.class != Message) p "ERROR RETURNED : #{error}" end end stop=Time.now time_taken=stop-start p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken" assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!") end def test_many_threads_on_one_single_resolver_synchronous # Test multi-threaded behaviour # Check the header IDs to make sure they're all different threads = Array.new res = SingleResolver.new ids = [] mutex = Mutex.new timed_out = 0 query_count = 0 res.packet_timeout=4 start=Time.now # Windows limits us to 256 sockets num_times=250 if (/java/ =~ RUBY_PLATFORM) # JRuby threads are native threads, so let's not go too mad! num_times=50 end num_times.times do |i| threads[i] = Thread.new{ 40.times do |j| TestSoakBase::Rrs.each do |data| mutex.synchronize do query_count+=1 end packet=nil begin packet = res.query(data[:name], data[:type]) rescue ResolvTimeout mutex.synchronize { timed_out+=1 } next end assert(packet) ids.push(packet.header.id) assert_equal(packet.question[0].qclass, 'IN', 'Class correct' ) end end } end threads.each do |thread| thread.join end stop=Time.now time_taken=stop-start p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken" # check_ids(ids) # only do this if we expect all different IDs - e.g. if we stream over a single socket assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!") end def check_ids(ids) ids.sort! count = 0 ids.each do |id| count+=1 if (count < ids.length-1) assert(ids[count+1] != id, "Two identical header ids used!") end end end def test_many_threads_on_many_single_resolvers # Test multi-threaded behaviour # @todo@ Check the header IDs to make sure they're all different threads = Array.new mutex = Mutex.new timed_out = 0 query_count = 0 start=Time.now num_times=250 if (/java/ =~ RUBY_PLATFORM) # JRuby threads are native threads, so let's not go too mad! num_times=50 end num_times.times do |i| threads[i] = Thread.new{ res = SingleResolver.new res.packet_timeout=4 40.times do |j| TestSoakBase::Rrs.each do |data| mutex.synchronize do query_count+=1 end q = Queue.new res.send_async(Message.new(data[:name], data[:type]), q, [i,j]) id, packet, error = q.pop if (error.class == ResolvTimeout) mutex.synchronize { timed_out+=1 } next elsif (packet.class!=Message) p "ERROR! #{error}" end assert(packet) assert_equal(packet.question[0].qclass, 'IN', 'Class correct' ) end end } end threads.each do |thread| thread.join end stop=Time.now time_taken=stop-start p "Query count : #{query_count}, #{timed_out} timed out. #{time_taken} time taken" assert(timed_out < query_count * 0.1, "#{timed_out} of #{query_count} timed out!") end enddnsruby-1.54/test/ts_dnsruby.rb0000644000175000017500000000117412206575435016171 0ustar ondrejondrej#-- #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 "test/ts_online.rb" require "test/ts_offline.rb" dnsruby-1.54/test/tc_ipseckey.rb0000644000175000017500000000547612206575435016310 0ustar ondrejondrej #-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestIPSECKEY < Test::Unit::TestCase 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.54/test/tc_tcp.rb0000644000175000017500000001322712206575435015253 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' require 'socket' class TestTcp < Test::Unit::TestCase 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 client_thread = Thread.new { res = Dnsruby::SingleResolver.new("127.0.0.1") 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 # 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::Message::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.54/test/tc_axfr.rb0000644000175000017500000000221512206575435015420 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' class TestAxfr < Test::Unit::TestCase def test_axfr 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") assert(zone.length > 0) assert(zt.last_tsigstate==nil) 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) enddnsruby-1.54/test/tc_misc.rb0000644000175000017500000001267112206575435015422 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' class TestMisc < Test::Unit::TestCase 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(!$@, 'No errors') assert(srv, 'SRV got made') #~ # 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.54/test/tc_rrset.rb0000644000175000017500000001074612206575435015627 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class RrsetTest < Test::Unit::TestCase 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.54/test/tc_cache.rb0000644000175000017500000001106012206575435015521 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' include Dnsruby class TestCache < Test::Unit::TestCase 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_resolver_do_caching # Get the records back from the test zone Dnsruby::PacketSender.clear_caches res = Resolver.new("ns0.validation-test-servers.nominet.org.uk.") res.do_caching = false assert(!res.do_caching) res.udp_size = 4096 ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT) # print "#{ret}\n" assert(!ret.cached) assert(ret.rcode == RCode.NoError) assert(ret.header.aa) # Store the ttls first_ttls = ret.answer.rrset( "overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT).ttl # Wait a while sleep(1) # Ask for the same records ret = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT) # print "#{ret}\n" 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("ns0.validation-test-servers.nominet.org.uk.") res.udp_size = 4096 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( "overflow.dnsruby.validation-test-servers.nominet.org.uk", Types.TXT).ttl # Wait a while sleep(1) # Ask for the same records 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( "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.54/test/resolv.conf0000644000175000017500000000140012206575435015621 0ustar ondrejondrej#-- #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.54/test/tc_rr-unknown.rb0000644000175000017500000000767512206575435016617 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestRrUnknown < Test::Unit::TestCase 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.54/test/tc_res_file.rb0000644000175000017500000000275012206575435016254 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' class TestAResolverFile < Test::Unit::TestCase 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.54/test/tc_nsec.rb0000644000175000017500000002331212206575435015411 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' include Dnsruby class NsecTest < Test::Unit::TestCase INPUT = "alfa.example.com. 86400 IN NSEC host.example.com. ( " + "A MX RRSIG NSEC TYPE1234 )" include Dnsruby 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.54/test/tc_question.rb0000644000175000017500000000302612206575435016330 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestQuestion < Test::Unit::TestCase 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.54/test/tc_rr-txt.rb0000644000175000017500000001147712206575435015732 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestRrTest < Test::Unit::TestCase #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.54/test/tc_resolver.rb0000644000175000017500000002000512206575435016316 0ustar ondrejondrej#-- #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 'dnsruby' require 'socket' require 'test/unit' include Dnsruby #@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 < Test::Unit::TestCase include Dnsruby Thread::abort_on_exception = true PORT = 42138 @@port = PORT def setup Dnsruby::Config.reset end def test_send_message res = Resolver.new ret = res.send_message(Message.new("example.com", Types.A)) assert(ret.kind_of?(Message)) end def test_send_plain_message res = Resolver.new response, error = res.send_plain_message(Message.new("example.com")) assert(response.kind_of?(Message)) m = Message.new("fgjkhsklfjedfiuaufewriuf.com") m.header.rd = true response, error = res.send_plain_message(m) # print "Response : #{response}\n" # print "Error : #{error}\n" assert(response.kind_of?(Message)) assert(error) assert(error.kind_of?(NXDomain)) end def test_query res = Resolver.new ret = res.query("example.com") assert(ret.kind_of?(Message)) end def test_query_async res = Resolver.new q = Queue.new res.send_async(Message.new("example.com", Types.A),q,q) id, ret, error = q.pop assert_equal(id, q, "Id wrong!") assert(ret.kind_of?(Message), "Ret wrong!") assert(error==nil) end def test_query_one_duff_server_one_good res = Resolver.new({:nameserver => ["localhost", "128.8.10.90"]}) res.retry_delay=1 q = Queue.new res.send_async(Message.new("example.com", Types.A),q,q) id, ret, error = q.pop assert_equal(id, q, "Id wrong!") assert(ret.kind_of?(Message), "Ret wrong! (#{ret.class}") assert(error==nil) 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("210.251.121.214", 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=~/ruby-lang/) 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 res=Resolver.new q = Queue.new res.send_async(Message.new("dklfjhdFHFHDVVUIEWRFDSAJKVCNASDLFJHN.com", Types.A), q, 1) id, m, err = q.pop assert(id==1) assert(m.rcode == RCode.NXDOMAIN) assert(NXDomain === err) 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 = 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 = 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 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 = 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 = 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 enddnsruby-1.54/test/tc_update.rb0000644000175000017500000002454512206575435015754 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestUpdate < Test::Unit::TestCase 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_txt update = Update.new() update.add("target_name", "TXT", 100, "test signed update") assert(update.to_s.index("test signed update")) 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.54/test/tc_packet_unique_push.rb0000644000175000017500000000531512206575435020360 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestPacketUniquePush < Test::Unit::TestCase # def test_packUniquePush # # # testProc('unique_push'); # end # ## def test_packetSafePush ## begin ## testProc('safe_push'); ## flunk("Shouldn't work!") ## rescue Exception ## end ## end # def testProc (method) def test_proc domain = 'example.com'; tests = [ [ 1, RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.1'), ], [ 2, RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('bar.example.com 60 IN A 10.0.0.1'), ], [ 1, # RFC 2136 section 1.1 RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 90 IN A 10.0.0.1'), ], [ 3, RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.2'), RR.create('foo.example.com 60 IN A 10.0.0.3'), ], [ 3, RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.2'), RR.create('foo.example.com 60 IN A 10.0.0.3'), RR.create('foo.example.com 60 IN A 10.0.0.1'), ], [ 3, RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.2'), RR.create('foo.example.com 60 IN A 10.0.0.1'), RR.create('foo.example.com 60 IN A 10.0.0.4'), ], ] methods = { 'add_answer' => '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.54/test/tc_dlv.rb0000644000175000017500000000610512206575435015247 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' include Dnsruby class TestDlv < Test::Unit::TestCase 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.54/test/tc_verifier.rb0000644000175000017500000003564612206575435016311 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class VerifierTest < Test::Unit::TestCase 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.54/test/tc_header.rb0000644000175000017500000000551112206575435015712 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestHeader < Test::Unit::TestCase 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'); # # Is header.inspect remotely sane? # assert(header.to_s =~ /opcode = Query/, 'inspect() has opcode correct'); assert(header.to_s =~ /ancount = 2/, 'inspect() has ancount correct'); 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.54/test/tc_dns.rb0000644000175000017500000001706012206575435015250 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestDNS < Test::Unit::TestCase 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")} 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")} 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, "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.dnsruby.validation-test-servers.nominet.org.uk', :address => '10.0.1.128' }, { :type => Types::MX, :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.dnsruby.validation-test-servers.nominet.org.uk', :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk' }, { :type => Types.TXT, :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, packet.header.ancount, '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.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.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(1, 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.dnsruby.validation-test-servers.nominet.org.uk',"Correct name (with persistent socket and #{method})") end end end dnsruby-1.54/test/tc_single_resolver.rb0000644000175000017500000002364612206575435017675 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestSingleResolver < Test::Unit::TestCase Thread::abort_on_exception = true # Dnsruby.log.level=Logger::DEBUG def setup Dnsruby::Config.reset end Rrs = [ { :type => Types.A, :name => 'a.t.dnsruby.validation-test-servers.nominet.org.uk', :address => '10.0.1.128' }, { :type => Types::MX, :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.dnsruby.validation-test-servers.nominet.org.uk', :domainname => 'a.t.dnsruby.validation-test-servers.nominet.org.uk' }, { :type => Types.TXT, :name => 'txt.t.dnsruby.validation-test-servers.nominet.org.uk', :strings => ['Net-DNS'] } ] def test_simple res = SingleResolver.new() m = res.query("a.t.dnsruby.validation-test-servers.nominet.org.uk") 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.dnsruby.validation-test-servers.nominet.org.uk") 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.dnsruby.validation-test-servers.nominet.org.uk") 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.port = port res.packet_timeout=1 q = Queue.new msg = Message.new("a.t.dnsruby.validation-test-servers.nominet.org.uk") 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, packet.header.ancount, '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(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.dnsruby.validation-test-servers.nominet.org.uk') ip = res.server assert_equal('10.0.1.128', ip.to_s, 'nameserver() looks up IP.') res.server=('cname.t.dnsruby.validation-test-servers.nominet.org.uk') 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.packet_timeout = 15 m = res.query("overflow.dnsruby.validation-test-servers.nominet.org.uk", 'txt') assert(m.header.ancount == 61, "61 answer records expected, got #{m.header.ancount}") assert(!m.header.tc, "Message was truncated!") 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 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("127.0.0.1", 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 thread2 = Thread.new { r = SingleResolver.new("127.0.0.1") r.port = port ret = r.query("example.com") } thread.join thread2.join 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 enddnsruby-1.54/test/tc_validator.rb0000644000175000017500000000542412206575435016452 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' include Dnsruby class TestValidator < Test::Unit::TestCase 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.54/test/tc_dnskey.rb0000644000175000017500000000576712206575435015774 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class DnskeyTest < Test::Unit::TestCase 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.54/test/tc_nsec3param.rb0000644000175000017500000000307512206575435016521 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class Nsec3ParamTest < Test::Unit::TestCase INPUT = "example. 3600 IN NSEC3PARAM 1 0 12 aabbccdd" include Dnsruby 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.54/test/ts_online.rb0000644000175000017500000000756012206575435015774 0ustar ondrejondrej#-- #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 'rubygems' rescue Exception end require 'dnsruby' Dnsruby.log.level = Logger::FATAL require "test/unit" # Disable these tests if we're not online require 'socket' sock = UDPSocket.new() online = false begin sock.connect('193.0.14.129', # k.root-servers.net. 25) online = true sock.close rescue Exception puts "----------------------------------------" puts "Cannot bind to socket:\n\t"+$!+"\n" puts "This is an indication you have network problems\n" puts "\n\nNo online tests will be run!!\n\n" puts "----------------------------------------" end if (online) # OK - online and ready to go print "Running online tests. These tests send UDP packets - some may be lost.\n" print "If you get the odd timeout error with these tests, try running them again.\n" print "It may just be that some UDP packets got lost the first time...\n" require "test/tc_resolver.rb" require "test/tc_dnsruby.rb" # require "test/tc_inet6.rb" # require "test/tc_recurse.rb" require "test/tc_tcp.rb" # require "test/tc_queue.rb" require "test/tc_recur.rb" # require "test/tc_soak.rb" # 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) server_up = false 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"+$!+"\n" puts "\n\nNo tests targetting this server will be run!!\n\n" puts "----------------------------------------" end if (server_up) require "test/tc_single_resolver.rb" require "test/tc_axfr.rb" require "test/tc_cache.rb" require "test/tc_dns.rb" require "test/tc_rr-opt.rb" require "test/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 "test/tc_tsig.rb" puts "------------------------------------------------------" puts "Running DNSSEC test - may fail if OpenSSL not complete" puts "------------------------------------------------------" require "test/tc_verifier.rb" require "test/tc_dlv.rb" require "test/tc_validator.rb" 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 end end dnsruby-1.54/test/tc_naptr.rb0000644000175000017500000000420112206575435015601 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' include Dnsruby class TestNAPTR < Test::Unit::TestCase 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.54/test/tc_tsig.rb0000644000175000017500000002124212206575435015427 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' require "digest/md5" include Dnsruby class TestTSig < Test::Unit::TestCase 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.54/test/tc_recur.rb0000644000175000017500000000205412206575435015601 0ustar ondrejondrej#-- #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' require 'test/unit' class TestRecur < Test::Unit::TestCase def test_recur Dnsruby::PacketSender.clear_caches r = Dnsruby::Recursor.new # Dnsruby::TheLog.level = Logger::DEBUG ret = r.query("uk-dnssec.nic.uk", Dnsruby::Types.DNSKEY) # print ret assert(ret && ret.answer.length > 0) # 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.54/test/tc_res_config.rb0000644000175000017500000000574612206575435016612 0ustar ondrejondrej#-- #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 'rubygems' rescue LoadError end require 'test/unit' require 'dnsruby' class TestResolverConfig < Test::Unit::TestCase 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({: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.54/test/tc_queue.rb0000644000175000017500000000205212206575435015603 0ustar ondrejondrej#-- #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 'test/unit' require 'dnsruby' class TestQueue < Test::Unit::TestCase 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