dns.tcl

Go to the documentation of this file.
00001 /*  dns.tcl - Copyright (C) 2002 Pat Thoyts <patthoyts@users.sourceforge.net>*/
00002 /* */
00003 /*  Provide a Tcl only Domain Name Service client. See RFC 1034 and RFC 1035*/
00004 /*  for information about the DNS protocol. This should insulate Tcl scripts*/
00005 /*  from problems with using the system library resolver for slow name servers.*/
00006 /* */
00007 /*  This implementation uses TCP only for DNS queries. The protocol reccommends*/
00008 /*  that UDP be used in these cases but Tcl does not include UDP sockets by*/
00009 /*  default. The package should be simple to extend to use a TclUDP extension*/
00010 /*  in the future.*/
00011 /* */
00012 /*  Support for SPF (http://spf.pobox.com/rfcs.html) will need updating*/
00013 /*  if or when the proposed draft becomes accepted.*/
00014 /* */
00015 /*  Support added for RFC1886 - DNS Extensions to support IP version 6*/
00016 /*  Support added for RFC2782 - DNS RR for specifying the location of services*/
00017 /*  Support added for RFC1995 - Incremental Zone Transfer in DNS*/
00018 /* */
00019 /*  TODO:*/
00020 /*   - When using tcp we should make better use of the open connection and*/
00021 /*     send multiple queries along the same connection.*/
00022 /* */
00023 /*   - We must switch to using TCP for truncated UDP packets.*/
00024 /* */
00025 /*   - Read RFC 2136 - dynamic updating of DNS*/
00026 /* */
00027 /*  -------------------------------------------------------------------------*/
00028 /*  See the file "license.terms" for information on usage and redistribution*/
00029 /*  of this file, and for a DISCLAIMER OF ALL WARRANTIES.*/
00030 /*  -------------------------------------------------------------------------*/
00031 /* */
00032 /*  $Id: dns.tcl,v 1.35 2007/08/26 00:44:34 patthoyts Exp $*/
00033 
00034 package require Tcl 8.2;                /*  tcl minimum version*/
00035 package require logger;                 /*  tcllib 1.3*/
00036 package require uri;                    /*  tcllib 1.1*/
00037 package require uri::urn;               /*  tcllib 1.2*/
00038 package require ip;                     /*  tcllib 1.7*/
00039 
00040 namespace ::dns {
00041     variable version 1.3.2
00042     variable rcsid {$Id: dns.tcl,v 1.35 2007/08/26 00:44:34 patthoyts Exp $}
00043 
00044     namespace export configure resolve name address cname \
00045         status re wait =  cleanup errorcode
00046 
00047     variable options
00048     if {![info exists options]} {
00049         array  options =  {
00050             port       53
00051             timeout    30000
00052             protocol   tcp
00053             search     {}
00054             nameserver {localhost}
00055             loglevel   warn
00056         }
00057         variable log [logger::init dns]
00058         ${log}::level =  $options(loglevel)
00059     }
00060 
00061     /*  We can use either ceptcl or tcludp for UDP support.*/
00062     if {![catch {package require udp 1.0.4} msg]} { ;/*  tcludp 1.0.4+*/
00063         /*  If TclUDP 1.0.4 or better is available, use it.*/
00064          options = (protocol) udp
00065     } else {
00066         if {![catch {package require ceptcl} msg]} {
00067              options = (protocol) udp
00068         }
00069     }
00070 
00071     variable types
00072     array  types =  { 
00073         A 1  NS 2  MD 3  MF 4  CNAME 5  SOA 6  MB 7  MG 8  MR 9 
00074         NULL 10  WKS 11  PTR 12  HINFO 13  MINFO 14  MX 15  TXT 16
00075         SPF 16 AAAA 28 SRV 33 IXFR 251 AXFR 252  MAILB 253  MAILA 254
00076         ANY 255 * 255
00077     } 
00078 
00079     variable classes
00080     array  classes =  { IN 1  CS 2  CH  3  HS 4  * 255}
00081 
00082     variable uid
00083     if {![info exists uid]} {
00084          uid =  0
00085     }
00086 }
00087 
00088 /*  -------------------------------------------------------------------------*/
00089 
00090 /*  Description:*/
00091 /*   Configure the DNS package. In particular the local nameserver will need*/
00092 /*   to be set. With no options, returns a list of all current settings.*/
00093 /* */
00094 ret  ::dns::configure (type args) {
00095     variable options
00096     variable log
00097 
00098     if {[llength $args] < 1} {
00099         set r {}
00100         foreach opt [lsort [array names options]] {
00101             lappend r -$opt $options($opt)
00102         }
00103         return $r
00104     }
00105 
00106     set cget 0
00107     if {[llength $args] == 1} {
00108         set cget 1
00109     }
00110    
00111     while {[string match -* [lindex $args 0]]} {
00112         switch -glob -- [lindex $args 0] {
00113             -n* -
00114             -ser* {
00115                 if {$cget} {
00116                     return $options(nameserver) 
00117                 } else {
00118                     set options(nameserver) [Pop args 1] 
00119                 }
00120             }
00121             -po*  { 
00122                 if {$cget} {
00123                     return $options(port)
00124                 } else {
00125                     set options(port) [Pop args 1] 
00126                 }
00127             }
00128             -ti*  { 
00129                 if {$cget} {
00130                     return $options(timeout)
00131                 } else {
00132                     set options(timeout) [Pop args 1]
00133                 }
00134             }
00135             -pr*  {
00136                 if {$cget} {
00137                     return $options(protocol)
00138                 } else {
00139                     set proto [string tolower [Pop args 1]]
00140                     if {[string compare udp $proto] == 0 \
00141                             && [string compare tcp $proto] == 0} {
00142                         return -code error "invalid protocol \"$proto\":\
00143                             protocol must be either \"udp\" or \"tcp\""
00144                     }
00145                     set options(protocol) $proto 
00146                 }
00147             }
00148             -sea* { 
00149                 if {$cget} {
00150                     return $options(search)
00151                 } else {
00152                     set options(search) [Pop args 1] 
00153                 }
00154             }
00155             -log* {
00156                 if {$cget} {
00157                     return $options(loglevel)
00158                 } else {
00159                     set options(loglevel) [Pop args 1]
00160                     ${log}::setlevel $options(loglevel)
00161                 }
00162             }
00163             --    { Pop args ; break }
00164             default {
00165                 set opts [join [lsort [array names options]] ", -"]
00166                 return -code error "bad option [lindex $args 0]:\
00167                         must be one of -$opts"
00168             }
00169         }
00170         Pop args
00171     }
00172 
00173     return
00174 }
00175 
00176 /*  -------------------------------------------------------------------------*/
00177 
00178 /*  Description:*/
00179 /*   Create a DNS query and send to the specified name server. Returns a token*/
00180 /*   to be used to obtain any further information about this query.*/
00181 /* */
00182 ret  ::dns::resolve (type query , type args) {
00183     variable uid
00184     variable options
00185     variable log
00186 
00187     # get a guaranteed unique and non-present token id.
00188     set id [incr uid]
00189     while {[info exists [set token [namespace current]::$id]]} {
00190         set id [incr uid]
00191     }
00192     # FRINK: nocheck
00193     variable $token
00194     upvar 0 $token state
00195 
00196     # Setup token/state defaults.
00197     set state(id)          $id
00198     set state(query)       $query
00199     set state(qdata)       ""
00200     set state(opcode)      0;                   # 0 = query, 1 = inverse query.
00201     set state(-type)       A;                   # DNS record type (A address)
00202     set state(-class)      IN;                  # IN (internet address space)
00203     set state(-recurse)    1;                   # Recursion Desired
00204     set state(-command)    {};                  # asynchronous handler
00205     set state(-timeout)    $options(timeout);   # connection timeout default.
00206     set state(-nameserver) $options(nameserver);# default nameserver
00207     set state(-port)       $options(port);      # default namerservers port
00208     set state(-search)     $options(search);    # domain search list
00209     set state(-protocol)   $options(protocol);  # which protocol udp/tcp
00210 
00211     # Handle DNS URL's
00212     if {[string match "dns:*" $query]} {
00213         array set URI [uri::split $query]
00214         foreach {opt value} [uri::split $query] {
00215             if {$value != {} && [info exists state(-$opt)]} {
00216                 set state(-$opt) $value
00217             }   
00218         }
00219         set state(query) $URI(query)
00220         ${log}::debug "parsed query: $query"
00221     }
00222 
00223     while {[string match -* [lindex $args 0]]} {
00224         switch -glob -- [lindex $args 0] {
00225             -n* - ns -
00226             -ser* { set state(-nameserver) [Pop args 1] }
00227             -po*  { set state(-port) [Pop args 1] }
00228             -ti*  { set state(-timeout) [Pop args 1] }
00229             -co*  { set state(-command) [Pop args 1] }
00230             -cl*  { set state(-class) [Pop args 1] }
00231             -ty*  { set state(-type) [Pop args 1] }
00232             -pr*  { set state(-protocol) [Pop args 1] }
00233             -sea* { set state(-search) [Pop args 1] }
00234             -re*  { set state(-recurse) [Pop args 1] }
00235             -inv* { set state(opcode) 1 }
00236             -status {set state(opcode) 2}
00237             -data { set state(qdata) [Pop args 1] }
00238             default {
00239                 set opts [join [lsort [array names state -*]] ", "]
00240                 return -code error "bad option [lindex $args 0]: \
00241                         must be $opts"
00242             }
00243         }
00244         Pop args
00245     }
00246 
00247     if {$state(-nameserver) == {}} {
00248         return -code error "no nameserver specified"
00249     }
00250 
00251     if {$state(-protocol) == "udp"} {
00252         if {[llength [package provide ceptcl]] == 0 \
00253                 && [llength [package provide udp]] == 0} {
00254             return -code error "udp support is not available,\
00255                 get ceptcl or tcludp"
00256         }
00257     }
00258     
00259     # Check for reverse lookups
00260     if {[regexp {^(?:\d{0,3}\.){3}\d{0,3}$} $state(query)]} {
00261         set addr [lreverse [split $state(query) .]]
00262         lappend addr in-addr arpa
00263         set state(query) [join $addr .]
00264         set state(-type) PTR
00265     }
00266 
00267     BuildMessage $token
00268     
00269     if {$state(-protocol) == "tcp"} {
00270         TcpTransmit $token
00271         if {$state(-command) == {}} {
00272             wait $token
00273         }
00274     } else {
00275         UdpTransmit $token
00276     }
00277     
00278     return $token
00279 }
00280 
00281 /*  -------------------------------------------------------------------------*/
00282 
00283 /*  Description:*/
00284 /*   Return a list of domain names returned as results for the last query.*/
00285 /* */
00286 ret  ::dns::name (type token) {
00287     set r {}
00288     Flags $token flags
00289     array set reply [Decode $token]
00290 
00291     switch -exact -- $flags(opcode) {
00292         0 {
00293             # QUERY
00294             foreach answer $reply(AN) {
00295                 array set AN $answer
00296                 if {![info exists AN(type)]} {set AN(type) {}}
00297                 switch -exact -- $AN(type) {
00298                     MX - NS - PTR {
00299                         if {[info exists AN(rdata)]} {lappend r $AN(rdata)}
00300                     }
00301                     default {
00302                         if {[info exists AN(name)]} {
00303                             lappend r $AN(name)
00304                         }
00305                     }
00306                 }
00307             }
00308         }
00309 
00310         1 {
00311             # IQUERY
00312             foreach answer $reply(QD) {
00313                 array set QD $answer
00314                 lappend r $QD(name)
00315             }
00316         }
00317         default {
00318             return -code error "not supported for this query type"
00319         }
00320     }
00321     return $r
00322 }
00323 
00324 /*  Description:*/
00325 /*   Return a list of the IP addresses returned for this query.*/
00326 /* */
00327 ret  ::dns::address (type token) {
00328     set r {}
00329     array set reply [Decode $token]
00330     foreach answer $reply(AN) {
00331         array set AN $answer
00332 
00333         if {[info exists AN(type)]} {
00334             switch -exact -- $AN(type) {
00335                 "A" {
00336                     lappend r $AN(rdata)
00337                 }
00338                 "AAAA" {
00339                     lappend r $AN(rdata)
00340                 }
00341             }
00342         }
00343     }
00344     return $r
00345 }
00346 
00347 /*  Description:*/
00348 /*   Return a list of all CNAME results returned for this query.*/
00349 /* */
00350 ret  ::dns::cname (type token) {
00351     set r {}
00352     array set reply [Decode $token]
00353     foreach answer $reply(AN) {
00354         array set AN $answer
00355 
00356         if {[info exists AN(type)]} {
00357             if {$AN(type) == "CNAME"} {
00358                 lappend r $AN(rdata)
00359             }
00360         }
00361     }
00362     return $r
00363 }
00364 
00365 /*  Description:*/
00366 /*    Return the decoded answer records. This can be used for more complex*/
00367 /*    queries where the answer isn't supported byb cname/address/name.*/
00368 ret  ::dns::result (type token , type args) {
00369     array set reply [eval [linsert $args 0 Decode $token]]
00370     return $reply(AN)
00371 }
00372 
00373 /*  -------------------------------------------------------------------------*/
00374 
00375 /*  Description:*/
00376 /*   Get the status of the request.*/
00377 /* */
00378 ret  ::dns::status (type token) {
00379     upvar #0 $token state
00380     return $state(status)
00381 }
00382 
00383 /*  Description:*/
00384 /*   Get the error message. Empty if no error.*/
00385 /* */
00386 ret  ::dns::error (type token) {
00387     upvar #0 $token state
00388     if {[info exists state(error)]} {
00389     return $state(error)
00390     }
00391     return ""
00392 }
00393 
00394 /*  Description*/
00395 /*   Get the error code. This is 0 for a successful transaction.*/
00396 /* */
00397 ret  ::dns::errorcode (type token) {
00398     upvar #0 $token state
00399     set flags [Flags $token]
00400     set ndx [lsearch -exact $flags errorcode]
00401     incr ndx
00402     return [lindex $flags $ndx]
00403 }
00404 
00405 /*  Description:*/
00406 /*   Reset a connection with optional reason.*/
00407 /* */
00408 ret  ::dns::reset (type token , optional why =reset , optional errormsg ={)} {
00409     upvar #0 $token state
00410     set state(status) $why
00411     if {[string length $errormsg] > 0 && ![info exists state(error)]} {
00412          state = (error) $errormsg
00413     }
00414     catch {fileevent $state(sock) readable {}}
00415     Finish $token
00416 }
00417 
00418 /*  Description:*/
00419 /*   Wait for a request to complete and return the status.*/
00420 /* */
00421 ret  ::dns::wait (type token) {
00422     upvar #0 $token state
00423 
00424     if {$state(status) == "connect"} {
00425         vwait [subst $token](status)
00426     }
00427 
00428     return $state(status)
00429 }
00430 
00431 /*  Description:*/
00432 /*   Remove any state associated with this token.*/
00433 /* */
00434 ret  ::dns::cleanup (type token) {
00435     upvar #0 $token state
00436     if {[info exists state]} {
00437         catch {close $state(sock)}
00438         catch {after cancel $state(after)}
00439         unset state
00440     }
00441 }
00442 
00443 /*  -------------------------------------------------------------------------*/
00444 
00445 /*  Description:*/
00446 /*   Dump the raw data of the request and reply packets.*/
00447 /* */
00448 ret  ::dns::dump (type args) {
00449     if {[llength $args] == 1} {
00450         set type -reply
00451         set token [lindex $args 0]
00452     } elseif { [llength $args] == 2 } {
00453         set type [lindex $args 0]
00454         set token [lindex $args 1]
00455     } else {
00456         return -code error "wrong # args:\
00457             should be \"dump ?option? methodName\""
00458     }
00459 
00460     # FRINK: nocheck
00461     variable $token
00462     upvar 0 $token state
00463     
00464     set result {}
00465     switch -glob -- $type {
00466         -qu*    -
00467         -req*   {
00468             set result [DumpMessage $state(request)]
00469         }
00470         -rep*   {
00471             set result [DumpMessage $state(reply)]
00472         }
00473         default {
00474             error "unrecognised option: must be one of \
00475                     \"-query\", \"-request\" or \"-reply\""
00476         }
00477     }
00478 
00479     return $result
00480 }
00481 
00482 /*  Description:*/
00483 /*   Perform a hex dump of binary data.*/
00484 /* */
00485 ret  ::dns::DumpMessage (type data) {
00486     set result {}
00487     binary scan $data c* r
00488     foreach c $r {
00489         append result [format "%02x " [expr {$c & 0xff}]]
00490     }
00491     return $result
00492 }
00493 
00494 /*  -------------------------------------------------------------------------*/
00495 
00496 /*  Description:*/
00497 /*   Contruct a DNS query packet.*/
00498 /* */
00499 ret  ::dns::BuildMessage (type token) {
00500     # FRINK: nocheck
00501     variable $token
00502     upvar 0 $token state
00503     variable types
00504     variable classes
00505     variable options
00506 
00507     if {! [info exists types($state(-type))] } {
00508         return -code error "invalid DNS query type"
00509     }
00510 
00511     if {! [info exists classes($state(-class))] } {
00512         return -code error "invalid DNS query class"
00513     }
00514 
00515     set qdcount 0
00516     set qsection {}
00517     set nscount 0
00518     set nsdata {}
00519 
00520     # In theory we can send multiple queries. In practice, named doesn't
00521     # appear to like that much. If it did work we'd do this:
00522     #  foreach domain [linsert $options(search) 0 {}] ...
00523 
00524 
00525     # Pack the query: QNAME QTYPE QCLASS
00526     set qsection [PackName $state(query)]
00527     append qsection [binary format SS \
00528                          $types($state(-type))\
00529                          $classes($state(-class))]
00530     incr qdcount
00531 
00532     if {[string length $state(qdata)] > 0} {
00533         set nsdata [eval [linsert $state(qdata) 0 PackRecord]]
00534         incr nscount
00535     }
00536 
00537     switch -exact -- $state(opcode) {
00538         0 {
00539             # QUERY
00540             set state(request) [binary format SSSSSS $state(id) \
00541                 [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
00542                                     $qdcount 0 $nscount 0]
00543             append state(request) $qsection $nsdata
00544         }
00545         1 {
00546             # IQUERY            
00547             set state(request) [binary format SSSSSS $state(id) \
00548                 [expr {($state(opcode) << 11) | ($state(-recurse) << 8)}] \
00549                 0 $qdcount 0 0 0]
00550             append state(request) \
00551                 [binary format cSSI 0 \
00552                      $types($state(-type)) $classes($state(-class)) 0]
00553             switch -exact -- $state(-type) {
00554                 A {
00555                     append state(request) \
00556                         [binary format Sc4 4 [split $state(query) .]]
00557                 }
00558                 PTR {
00559                     append state(request) \
00560                         [binary format Sc4 4 [split $state(query) .]]
00561                 }
00562                 default {
00563                     return -code error "inverse query not supported for this type"
00564                 }
00565             }
00566         }
00567         default {
00568             return -code error "operation not supported"
00569         }
00570     }
00571 
00572     return
00573 }
00574 
00575 /*  Pack a human readable dns name into a DNS resource record format.*/
00576 ret  ::dns::PackName (type name) {
00577     set data ""
00578     foreach part [split [string trim $name .] .] {
00579         set len [string length $part]
00580         append data [binary format ca$len $len $part]
00581     }
00582     append data \x00
00583     return $data
00584 }
00585 
00586 /*  Pack a character string - byte length prefixed*/
00587 ret  ::dns::PackString (type text) {
00588     set len [string length $text]
00589     set data [binary format ca$len $len $text]
00590     return $data
00591 }
00592 
00593 /*  Pack up a single DNS resource record. See RFC1035: 3.2 for the format*/
00594 /*  of each type.*/
00595 /*  eg: PackRecord name wiki.tcl.tk type MX class IN rdata {10 mail.example.com}*/
00596 /* */
00597 ret  ::dns::PackRecord (type args) {
00598     variable types
00599     variable classes
00600     array set rr {name "" type A class IN ttl 0 rdlength 0 rdata ""}
00601     array set rr $args
00602     set data [PackName $rr(name)]
00603 
00604     switch -exact -- $rr(type) {
00605         CNAME - MB - MD - MF - MG - MR - NS - PTR {
00606             set rr(rdata) [PackName $rr(rdata)] 
00607         }
00608         HINFO { 
00609             array set r {CPU {} OS {}}
00610             array set r $rr(rdata)
00611             set rr(rdata) [PackString $r(CPU)]
00612             append rr(rdata) [PackString $r(OS)]
00613         }
00614         MINFO {
00615             array set r {RMAILBX {} EMAILBX {}}
00616             array set r $rr(rdata)
00617             set rr(rdata) [PackString $r(RMAILBX)]
00618             append rr(rdata) [PackString $r(EMAILBX)]
00619         }
00620         MX {
00621             foreach {pref exch} $rr(rdata) break
00622             set rr(rdata) [binary format S $pref]
00623             append rr(rdata) [PackName $exch]
00624         }
00625         TXT {
00626             set str $rr(rdata)
00627             set len [string length [set str $rr(rdata)]]
00628             set rr(rdata) ""
00629             for {set n 0} {$n < $len} {incr n} {
00630                 set s [string range $str $n [incr n 253]]
00631                 append rr(rdata) [PackString $s]
00632             }
00633         }          
00634         NULL {}
00635         SOA {
00636             array set r {MNAME {} RNAME {}
00637                 SERIAL 0 REFRESH 0 RETRY 0 EXPIRE 0 MINIMUM 0}
00638             array set r $rr(rdata)
00639             set rr(rdata) [PackName $r(MNAME)]
00640             append rr(rdata) [PackName $r(RNAME)]
00641             append rr(rdata) [binary format IIIII $r(SERIAL) \
00642                                   $r(REFRESH) $r(RETRY) $r(EXPIRE) $r(MINIMUM)]
00643         }
00644     }
00645 
00646     # append the root label and the type flag and query class.
00647     append data [binary format SSIS $types($rr(type)) \
00648                      $classes($rr(class)) $rr(ttl) [string length $rr(rdata)]]
00649     append data $rr(rdata)
00650     return $data
00651 }
00652 
00653 /*  -------------------------------------------------------------------------*/
00654 
00655 /*  Description:*/
00656 /*   Transmit a DNS request over a tcp connection.*/
00657 /* */
00658 ret  ::dns::TcpTransmit (type token) {
00659     # FRINK: nocheck
00660     variable $token
00661     upvar 0 $token state
00662 
00663     # setup the timeout
00664     if {$state(-timeout) > 0} {
00665         set state(after) [after $state(-timeout) \
00666                               [list [namespace origin reset] \
00667                                    $token timeout\
00668                                    "operation timed out"]]
00669     }
00670 
00671     # Sometimes DNS servers drop TCP requests. So it's better to
00672     # use asynchronous connect
00673     set s [socket -async $state(-nameserver) $state(-port)]
00674     fileevent $s writable [list [namespace origin TcpConnected] $token $s]
00675     set state(sock) $s
00676     set state(status) connect
00677 
00678     return $token
00679 }
00680 
00681 ret  ::dns::TcpConnected (type token , type s) {
00682     variable $token
00683     upvar 0 $token state
00684 
00685     fileevent $s writable {}
00686     if {[catch {fconfigure $s -peername}]} {
00687     # TCP connection failed
00688         Finish $token "can't connect to server"
00689     return
00690     }
00691 
00692     fconfigure $s -blocking 0 -translation binary -buffering none
00693 
00694     # For TCP the message must be prefixed with a 16bit length field.
00695     set req [binary format S [string length $state(request)]]
00696     append req $state(request)
00697 
00698     puts -nonewline $s $req
00699 
00700     fileevent $s readable [list [namespace current]::TcpEvent $token]
00701 }
00702 
00703 /*  -------------------------------------------------------------------------*/
00704 /*  Description:*/
00705 /*   Transmit a DNS request using UDP datagrams*/
00706 /* */
00707 /*  Note:*/
00708 /*   This requires a UDP implementation that can transmit binary data.*/
00709 /*   As yet I have been unable to test this myself and the tcludp package*/
00710 /*   cannot do this.*/
00711 /* */
00712 ret  ::dns::UdpTransmit (type token) {
00713     # FRINK: nocheck
00714     variable $token
00715     upvar 0 $token state
00716 
00717     # setup the timeout
00718     if {$state(-timeout) > 0} {
00719         set state(after) [after $state(-timeout) \
00720                               [list [namespace origin reset] \
00721                                    $token timeout\
00722                                   "operation timed out"]]
00723     }
00724     
00725     if {[llength [package provide ceptcl]] > 0} {
00726         # using ceptcl
00727         set state(sock) [cep -type datagram $state(-nameserver) $state(-port)]
00728         fconfigure $state(sock) -blocking 0
00729     } else {
00730         # using tcludp
00731         set state(sock) [udp_open]
00732         udp_conf $state(sock) $state(-nameserver) $state(-port)
00733     }
00734     fconfigure $state(sock) -translation binary -buffering none
00735     set state(status) connect
00736     puts -nonewline $state(sock) $state(request)
00737     
00738     fileevent $state(sock) readable [list [namespace current]::UdpEvent $token]
00739     
00740     return $token
00741 }
00742 
00743 /*  -------------------------------------------------------------------------*/
00744 
00745 /*  Description:*/
00746 /*   Tidy up after a tcp transaction.*/
00747 /* */
00748 ret  ::dns::Finish (type token , optional errormsg ="") {
00749     # FRINK: nocheck
00750     variable $token
00751     upvar 0 $token state
00752     global errorInfo errorCode
00753 
00754     if {[string length $errormsg] != 0} {
00755     set state(error) $errormsg
00756     set state(status) error
00757     }
00758     catch {close $state(sock)}
00759     catch {after cancel $state(after)}
00760     if {[info exists state(-command)] && $state(-command) != {}} {
00761     if {[catch {eval $state(-command) {$token}} err]} {
00762         if {[string length $errormsg] == 0} {
00763         set state(error) [list $err $errorInfo $errorCode]
00764         set state(status) error
00765         }
00766     }
00767         if {[info exists state(-command)]} {
00768             unset state(-command)
00769         }
00770     }
00771 }
00772 
00773 /*  -------------------------------------------------------------------------*/
00774 
00775 /*  Description:*/
00776 /*   Handle end-of-file on a tcp connection.*/
00777 /* */
00778 ret  ::dns::Eof (type token) {
00779     # FRINK: nocheck
00780     variable $token
00781     upvar 0 $token state
00782     set state(status) eof
00783     Finish $token
00784 }
00785 
00786 /*  -------------------------------------------------------------------------*/
00787 
00788 /*  Description:*/
00789 /*   Process a DNS reply packet (protocol independent)*/
00790 /* */
00791 ret  ::dns::Receive (type token) {
00792     # FRINK: nocheck
00793     variable $token
00794     upvar 0 $token state
00795 
00796     binary scan $state(reply) SS id flags
00797     set status [expr {$flags & 0x000F}]
00798 
00799     switch -- $status {
00800         0 {
00801             set state(status) ok
00802             Finish $token 
00803         }
00804         1 { Finish $token "Format error - unable to interpret the query." }
00805         2 { Finish $token "Server failure - internal server error." }
00806         3 { Finish $token "Name Error - domain does not exist" }
00807         4 { Finish $token "Not implemented - the query type is not available." }
00808         5 { Finish $token "Refused - your request has been refused by the server." }
00809         default {
00810             Finish $token "unrecognised error code: $err"
00811         }
00812     }
00813 }
00814 
00815 /*  -------------------------------------------------------------------------*/
00816 
00817 /*  Description:*/
00818 /*   file event handler for tcp socket. Wait for the reply data.*/
00819 /* */
00820 ret  ::dns::TcpEvent (type token) {
00821     variable log
00822     # FRINK: nocheck
00823     variable $token
00824     upvar 0 $token state
00825     set s $state(sock)
00826 
00827     if {[eof $s]} {
00828         Eof $token
00829         return
00830     }
00831 
00832     set status [catch {read $state(sock)} result]
00833     if {$status != 0} {
00834         ${log}::debug "Event error: $result"
00835         Finish $token "error reading data: $result"
00836     } elseif { [string length $result] >= 0 } {
00837         if {[catch {
00838             # Handle incomplete reads - check the size and keep reading.
00839             if {![info exists state(size)]} {
00840                 binary scan $result S state(size)
00841                 set result [string range $result 2 end]            
00842             }
00843             append state(reply) $result
00844             
00845             # check the length and flags and chop off the tcp length prefix.
00846             if {[string length $state(reply)] >= $state(size)} {
00847                 binary scan $result S id
00848                 set id [expr {$id & 0xFFFF}]
00849                 if {$id != [expr {$state(id) & 0xFFFF}]} {
00850                     ${log}::error "received packed with incorrect id"
00851                 }
00852                 # bug #1158037 - doing this causes problems > 65535 requests!
00853                 #Receive [namespace current]::$id
00854                 Receive $token
00855             } else {
00856                 ${log}::debug "Incomplete tcp read:\
00857                    [string length $state(reply)] should be $state(size)"
00858             }
00859         } err]} {
00860             Finish $token "Event error: $err"
00861         }
00862     } elseif { [eof $state(sock)] } {
00863         Eof $token
00864     } elseif { [fblocked $state(sock)] } {
00865         ${log}::debug "Event blocked"
00866     } else {
00867         ${log}::critical "Event error: this can't happen!"
00868         Finish $token "Event error: this can't happen!"
00869     }
00870 }
00871 
00872 /*  -------------------------------------------------------------------------*/
00873 
00874 /*  Description:*/
00875 /*   file event handler for udp sockets.*/
00876 ret  ::dns::UdpEvent (type token) {
00877     # FRINK: nocheck
00878     variable $token
00879     upvar 0 $token state
00880     set s $state(sock)
00881 
00882     set payload [read $state(sock)]
00883     append state(reply) $payload
00884 
00885     binary scan $payload S id
00886     set id [expr {$id & 0xFFFF}]
00887     if {$id != [expr {$state(id) & 0xFFFF}]} {
00888         ${log}::error "received packed with incorrect id"
00889     }
00890     # bug #1158037 - doing this causes problems > 65535 requests!
00891     #Receive [namespace current]::$id
00892     Receive $token
00893 }
00894     
00895 /*  -------------------------------------------------------------------------*/
00896 
00897 ret  ::dns::Flags (type token , optional varname ={)} {
00898     # FRINK: nocheck
00899     variable $token
00900     upvar 0 $token state
00901     
00902     if {$varname != {}} {
00903         upvar $varname flags
00904     }
00905 
00906     array  flags =  {query 0 opcode 0 authoritative 0 errorcode 0
00907         truncated 0 recursion_desired 0 recursion_allowed 0}
00908 
00909     binary scan $state(reply) SSSSSS mid hdr nQD nAN nNS nAR
00910 
00911      flags = (response)           [expr {($hdr & 0x8000) >> 15}]
00912      flags = (opcode)             [expr {($hdr & 0x7800) >> 11}]
00913      flags = (authoritative)      [expr {($hdr & 0x0400) >> 10}]
00914      flags = (truncated)          [expr {($hdr & 0x0200) >> 9}]
00915      flags = (recursion_desired)  [expr {($hdr & 0x0100) >> 8}]
00916      flafs = (recursion_allowed)  [expr {($hdr & 0x0080) >> 7}]
00917      flags = (errorcode)          [expr {($hdr & 0x000F)}]
00918 
00919     return [array get flags]
00920 }
00921 
00922 /*  -------------------------------------------------------------------------*/
00923 
00924 /*  Description:*/
00925 /*   Decode a DNS packet (either query or response).*/
00926 /* */
00927 ret  ::dns::Decode (type token , type args) {
00928     variable log
00929     # FRINK: nocheck
00930     variable $token
00931     upvar 0 $token state
00932 
00933     array set opts {-rdata 0 -query 0}
00934     while {[string match -* [set option [lindex $args 0]]]} {
00935         switch -exact -- $option {
00936             -rdata { set opts(-rdata) 1 }
00937             -query { set opts(-query) 1 }
00938             default {
00939                 return -code error "bad option \"$option\":\
00940                     must be -rdata"
00941             }
00942         }
00943         Pop args
00944     }
00945 
00946     if {$opts(-query)} {
00947         binary scan $state(request) SSSSSSc* mid hdr nQD nAN nNS nAR data
00948     } else {
00949         binary scan $state(reply) SSSSSSc* mid hdr nQD nAN nNS nAR data
00950     }
00951 
00952     set fResponse      [expr {($hdr & 0x8000) >> 15}]
00953     set fOpcode        [expr {($hdr & 0x7800) >> 11}]
00954     set fAuthoritative [expr {($hdr & 0x0400) >> 10}]
00955     set fTrunc         [expr {($hdr & 0x0200) >> 9}]
00956     set fRecurse       [expr {($hdr & 0x0100) >> 8}]
00957     set fCanRecurse    [expr {($hdr & 0x0080) >> 7}]
00958     set fRCode         [expr {($hdr & 0x000F)}]
00959     set flags ""
00960 
00961     if {$fResponse} {set flags "QR"} else {set flags "Q"}
00962     set opcodes [list QUERY IQUERY STATUS]
00963     lappend flags [lindex $opcodes $fOpcode]
00964     if {$fAuthoritative} {lappend flags "AA"}
00965     if {$fTrunc} {lappend flags "TC"}
00966     if {$fRecurse} {lappend flags "RD"}
00967     if {$fCanRecurse} {lappend flags "RA"}
00968 
00969     set info "ID: $mid\
00970               Fl: [format 0x%02X [expr {$hdr & 0xFFFF}]] ($flags)\
00971               NQ: $nQD\
00972               NA: $nAN\
00973               NS: $nNS\
00974               AR: $nAR"
00975     ${log}::debug $info
00976 
00977     set ndx 12
00978     set r {}
00979     set QD [ReadQuestion $nQD $state(reply) ndx]
00980     lappend r QD $QD
00981     set AN [ReadAnswer $nAN $state(reply) ndx $opts(-rdata)]
00982     lappend r AN $AN
00983     set NS [ReadAnswer $nNS $state(reply) ndx $opts(-rdata)]
00984     lappend r NS $NS
00985     set AR [ReadAnswer $nAR $state(reply) ndx $opts(-rdata)]
00986     lappend r AR $AR
00987     return $r
00988 }
00989 
00990 /*  -------------------------------------------------------------------------*/
00991 
00992 ret  ::dns::Expand (type data) {
00993     set r {}
00994     binary scan $data c* d
00995     foreach c $d {
00996         lappend r [expr {$c & 0xFF}]
00997     }
00998     return $r
00999 }
01000 
01001 
01002 /*  -------------------------------------------------------------------------*/
01003 /*  Description:*/
01004 /*   Pop the nth element off a list. Used in options processing.*/
01005 /* */
01006 ret  ::dns::Pop (type varname , optional nth =0) {
01007     upvar $varname args
01008     set r [lindex $args $nth]
01009     set args [lreplace $args $nth $nth]
01010     return $r
01011 }
01012 
01013 /*  -------------------------------------------------------------------------*/
01014 /*  Description:*/
01015 /*    Reverse a list. Code from http://wiki.tcl.tk/tcl/43*/
01016 /* */
01017 ret  ::dns::lreverse (type lst) {
01018     set res {}
01019     set i [llength $lst]
01020     while {$i} {lappend res [lindex $lst [incr i -1]]}
01021     return $res
01022 }
01023 
01024 /*  -------------------------------------------------------------------------*/
01025 
01026 ret  ::dns::KeyOf (type arrayname , type value , optional default ={)} {
01027     upvar $arrayname array
01028     set lst [array get array]
01029     set ndx [lsearch -exact $lst $value]
01030     if {$ndx != -1} {
01031         incr ndx -1
01032          r =  [lindex $lst $ndx]
01033     } else {
01034          r =  $default
01035     }
01036     return $r
01037 }
01038 
01039 
01040 /*  -------------------------------------------------------------------------*/
01041 /*  Read the question section from a DNS message. This always starts at index*/
01042 /*  12 of a message but may be of variable length.*/
01043 /* */
01044 ret  ::dns::ReadQuestion (type nitems , type data , type indexvar) {
01045     variable types
01046     variable classes
01047     upvar $indexvar index
01048     set result {}
01049 
01050     for {set cn 0} {$cn < $nitems} {incr cn} {
01051         set r {}
01052         lappend r name [ReadName data $index offset]
01053         incr index $offset
01054         
01055         # Read off QTYPE and QCLASS for this query.
01056         set ndx $index
01057         incr index 3
01058         binary scan [string range $data $ndx $index] SS qtype qclass
01059         set qtype [expr {$qtype & 0xFFFF}]
01060         set qclass [expr {$qclass & 0xFFFF}]
01061         incr index
01062         lappend r type [KeyOf types $qtype $qtype] \
01063                   class [KeyOf classes $qclass $qclass]
01064         lappend result $r
01065     }
01066     return $result
01067 }
01068         
01069 /*  -------------------------------------------------------------------------*/
01070 
01071 /*  Read an answer section from a DNS message. */
01072 /* */
01073 ret  ::dns::ReadAnswer (type nitems , type data , type indexvar , optional raw =0) {
01074     variable types
01075     variable classes
01076     upvar $indexvar index
01077     set result {}
01078 
01079     for {set cn 0} {$cn < $nitems} {incr cn} {
01080         set r {}
01081         lappend r name [ReadName data $index offset]
01082         incr index $offset
01083         
01084         # Read off TYPE, CLASS, TTL and RDLENGTH
01085         binary scan [string range $data $index end] SSIS type class ttl rdlength
01086 
01087         set type [expr {$type & 0xFFFF}]
01088         set type [KeyOf types $type $type]
01089 
01090         set class [expr {$class & 0xFFFF}]
01091         set class [KeyOf classes $class $class]
01092 
01093         set ttl [expr {$ttl & 0xFFFFFFFF}]
01094         set rdlength [expr {$rdlength & 0xFFFF}]
01095         incr index 10
01096         set rdata [string range $data $index [expr {$index + $rdlength - 1}]]
01097 
01098         if {! $raw} {
01099             switch -- $type {
01100                 A {
01101                     set rdata [join [Expand $rdata] .]
01102                 }
01103                 AAAA {
01104                     set rdata [ip::contract [ip::ToString $rdata]]
01105                 }
01106                 NS - CNAME - PTR {
01107                     set rdata [ReadName data $index off] 
01108                 }
01109                 MX {
01110                     binary scan $rdata S preference
01111                     set exchange [ReadName data [expr {$index + 2}] off]
01112                     set rdata [list $preference $exchange]
01113                 }
01114                 SRV {
01115                     set x $index
01116                     set rdata [list priority [ReadUShort data $x off]]
01117                     incr x $off
01118                     lappend rdata weight [ReadUShort data $x off]
01119                     incr x $off
01120                     lappend rdata port [ReadUShort data $x off]
01121                     incr x $off
01122                     lappend rdata target [ReadName data $x off]
01123                     incr x $off
01124                 }
01125                 TXT {
01126                     set rdata [ReadString data $index $rdlength]
01127                 }
01128                 SOA {
01129                     set x $index
01130                     set rdata [list MNAME [ReadName data $x off]]
01131                     incr x $off 
01132                     lappend rdata RNAME [ReadName data $x off]
01133                     incr x $off
01134                     lappend rdata SERIAL [ReadULong data $x off]
01135                     incr x $off
01136                     lappend rdata REFRESH [ReadLong data $x off]
01137                     incr x $off
01138                     lappend rdata RETRY [ReadLong data $x off]
01139                     incr x $off
01140                     lappend rdata EXPIRE [ReadLong data $x off]
01141                     incr x $off
01142                     lappend rdata MINIMUM [ReadULong data $x off]
01143                     incr x $off
01144                 }
01145             }
01146         }
01147 
01148         incr index $rdlength
01149         lappend r type $type class $class ttl $ttl rdlength $rdlength rdata $rdata
01150         lappend result $r
01151     }
01152     return $result
01153 }
01154 
01155 
01156 /*  Read a 32bit integer from a DNS packet. These are compatible with*/
01157 /*  the ReadName proc. Additionally - ReadULong takes measures to ensure */
01158 /*  the unsignedness of the value obtained.*/
01159 /* */
01160 ret  ::dns::ReadLong (type datavar , type index , type usedvar) {
01161     upvar $datavar data
01162     upvar $usedvar used
01163     set r {}
01164     set used 0
01165     if {[binary scan $data @${index}I r]} {
01166         set used 4
01167     }
01168     return $r
01169 }
01170 
01171 ret  ::dns::ReadULong (type datavar , type index , type usedvar) {
01172     upvar $datavar data
01173     upvar $usedvar used
01174     set r {}
01175     set used 0
01176     if {[binary scan $data @${index}cccc b1 b2 b3 b4]} {
01177         set used 4
01178         # This gets us an unsigned value.
01179         set r [expr {($b4 & 0xFF) + (($b3 & 0xFF) << 8) 
01180                      + (($b2 & 0xFF) << 16) + ($b1 << 24)}] 
01181     }
01182     return $r
01183 }
01184 
01185 ret  ::dns::ReadUShort (type datavar , type index , type usedvar) {
01186     upvar $datavar data
01187     upvar $usedvar used
01188     set r {}
01189     set used 0
01190     if {[binary scan [string range $data $index end] cc b1 b2]} {
01191         set used 2
01192         # This gets us an unsigned value.
01193         set r [expr {(($b2 & 0xff) + (($b1 & 0xff) << 8)) & 0xffff}] 
01194     }
01195     return $r
01196 }
01197 
01198 /*  Read off the NAME or QNAME element. This reads off each label in turn, */
01199 /*  dereferencing pointer labels until we have finished. The length of data*/
01200 /*  used is passed back using the usedvar variable.*/
01201 /* */
01202 ret  ::dns::ReadName (type datavar , type index , type usedvar) {
01203     upvar $datavar data
01204     upvar $usedvar used
01205     set startindex $index
01206 
01207     set r {}
01208     set len 1
01209     set max [string length $data]
01210     
01211     while {$len != 0 && $index < $max} {
01212         # Read the label length (and preread the pointer offset)
01213         binary scan [string range $data $index end] cc len lenb
01214         set len [expr {$len & 0xFF}]
01215         incr index
01216         
01217         if {$len != 0} {
01218             if {[expr {$len & 0xc0}]} {
01219                 binary scan [binary format cc [expr {$len & 0x3f}] [expr {$lenb & 0xff}]] S offset
01220                 incr index
01221                 lappend r [ReadName data $offset junk]
01222                 set len 0
01223             } else {
01224                 lappend r [string range $data $index [expr {$index + $len - 1}]]
01225                 incr index $len
01226             }
01227         }
01228     }
01229     set used [expr {$index - $startindex}]
01230     return [join $r .]
01231 }
01232 
01233 ret  ::dns::ReadString (type datavar , type index , type length) {
01234     upvar $datavar data
01235     set startindex $index
01236 
01237     set r {}
01238     set max [expr {$index + $length}]
01239 
01240     while {$index < $max} {
01241         binary scan [string range $data $index end] c len
01242         set len [expr {$len & 0xFF}]
01243         incr index
01244 
01245         if {$len != 0} {
01246             append r [string range $data $index [expr {$index + $len - 1}]]
01247             incr index $len
01248         }
01249     }
01250     return $r
01251 }
01252 
01253 /*  -------------------------------------------------------------------------*/
01254 
01255 /*  Support for finding the local nameservers*/
01256 /* */
01257 /*  For unix we can just parse the /etc/resolv.conf if it exists.*/
01258 /*  Of course, some unices use /etc/resolver and other things (NIS for instance)*/
01259 /*  On Windows, we can examine the Internet Explorer settings from the registry.*/
01260 /* */
01261 switch -exact $::tcl_platform(platform) {
01262     windows {
01263         ret  ::dns::nameservers () {
01264             package require registry
01265             set base {HKEY_LOCAL_MACHINE\System\CurrentControlSet\Services}
01266             set param "$base\\Tcpip\\Parameters"
01267             set interfaces "$param\\Interfaces"
01268             set nameservers {}
01269             if {[string equal $::tcl_platform(os) "Windows NT"]} {
01270                 AppendRegistryValue $param NameServer nameservers
01271                 AppendRegistryValue $param DhcpNameServer nameservers
01272                 foreach i [registry keys $interfaces] {
01273                     AppendRegistryValue "$interfaces\\$i" NameServer nameservers
01274                     AppendRegistryValue "$interfaces\\$i" DhcpNameServer nameservers
01275                 }
01276             } else {
01277                 set param "$base\\VxD\\MSTCP"
01278                 AppendRegistryValue $param NameServer nameservers
01279             }
01280             return $nameservers
01281         }
01282         ret  ::dns::AppendRegistryValue (type key , type val , type listName) {
01283             upvar $listName lst
01284             if {![catch {registry get $key $val} v]} {
01285                 foreach ns [split $v ", "] {
01286                     if {[lsearch -exact $lst $ns] == -1} {
01287                         lappend lst $ns
01288                     }
01289                 }
01290             }
01291         }
01292     }
01293     unix {
01294         ret  ::dns::nameservers () {
01295             set nameservers {}
01296             if {[file readable /etc/resolv.conf]} {
01297                 set f [open /etc/resolv.conf r]
01298                 while {![eof $f]} {
01299                     gets $f line
01300                     if {[regexp {^\s*nameserver\s+(.*)$} $line -> ns]} {
01301                         lappend nameservers $ns
01302                     }
01303                 }
01304                 close $f
01305             }
01306             if {[llength $nameservers] < 1} {
01307                 lappend nameservers 127.0.0.1
01308             }
01309             return $nameservers
01310         }
01311     }
01312     default {
01313         ret  ::dns::nameservers () {
01314             return -code error "command not supported for this platform."
01315         }
01316     }
01317 }
01318 
01319 /*  -------------------------------------------------------------------------*/
01320 /*  Possible support for the DNS URL scheme.*/
01321 /*  Ref: http://www.ietf.org/internet-drafts/draft-josefsson-dns-url-04.txt*/
01322 /*  eg: dns:target?class=IN;type=A*/
01323 /*      dns://nameserver/target?type=A*/
01324 /* */
01325 /*  URI quoting to be accounted for.*/
01326 /* */
01327 
01328 catch {
01329     uri::register {dns} {
01330          escape =      [ [namespace =  parent [namespace current]]::basic::escape]
01331          host =        [ [namespace =  parent [namespace current]]::basic::host]
01332          hostOrPort =  [ [namespace =  parent [namespace current]]::basic::hostOrPort]
01333 
01334          class =  [string map {* \\\\*} \
01335                        "class=([join [array names ::dns::classes] {|}])"]
01336          type =   [string map {* \\\\*} \
01337                        "type=([join [array names ::dns::types] {|}])"]
01338          classOrType =  "(?:${class}|${type})"
01339          classOrTypeSpec =  "(?:${class}|${type})(?:;(?:${class}|${type}))?"
01340 
01341          query =  "${host}(${classOrTypeSpec})?"
01342         variable schemepart "(//${hostOrPort}/)?(${query})"
01343         variable url "dns:$schemepart"
01344     }
01345 }
01346 
01347 namespace ::uri {} ;/*  needed for pkg_mkIndex.*/
01348 
01349 ret  ::uri::SplitDns (type uri) {
01350     upvar \#0 [namespace current]::dns::schemepart schemepart
01351     upvar \#0 [namespace current]::dns::class classOrType
01352     upvar \#0 [namespace current]::dns::class classRE
01353     upvar \#0 [namespace current]::dns::type typeRE
01354     upvar \#0 [namespace current]::dns::classOrTypeSpec classOrTypeSpec
01355 
01356     array set parts {nameserver {} query {} class {} type {} port {}}
01357 
01358     # validate the uri
01359     if {[regexp -- $dns::schemepart $uri r] == 1} {
01360 
01361         # deal with the optional class and type specifiers
01362         if {[regexp -indices -- "${classOrTypeSpec}$" $uri range]} {
01363             set spec [string range $uri [lindex $range 0] [lindex $range 1]]
01364             set uri [string range $uri 0 [expr {[lindex $range 0] - 2}]]
01365 
01366             if {[regexp -- "$classRE" $spec -> class]} {
01367                 set parts(class) $class
01368             }
01369             if {[regexp -- "$typeRE" $spec -> type]} {
01370                 set parts(type) $type
01371             }
01372         }
01373 
01374         # Handle the nameserver specification
01375         if {[string match "//*" $uri]} {
01376             set uri [string range $uri 2 end]
01377             array set tmp [GetHostPort uri]
01378             set parts(nameserver) $tmp(host)
01379             set parts(port) $tmp(port)
01380         }
01381         
01382         # what's left is the query domain name.
01383         set parts(query) [string trimleft $uri /]
01384     }
01385 
01386     return [array get parts]
01387 }
01388 
01389 ret  ::uri::JoinDns (type args) {
01390     array set parts {nameserver {} port {} query {} class {} type {}}
01391     array set parts $args
01392     set query [::uri::urn::quote $parts(query)]
01393     if {$parts(type) != {}} {
01394         append query "?type=$parts(type)"
01395     }
01396     if {$parts(class) != {}} {
01397         if {$parts(type) == {}} {
01398             append query "?class=$parts(class)"
01399         } else {
01400             append query ";class=$parts(class)"
01401         }
01402     }
01403     if {$parts(nameserver) != {}} {
01404         set ns "$parts(nameserver)"
01405         if {$parts(port) != {}} {
01406             append ns ":$parts(port)"
01407         }
01408         set query "//${ns}/${query}"
01409     }
01410     return "dns:$query"
01411 }
01412 
01413 /*  -------------------------------------------------------------------------*/
01414 
01415 catch {dns::configure -nameserver [lindex [dns::nameservers] 0]}
01416 
01417 package provide dns $dns::version
01418 
01419 /*  -------------------------------------------------------------------------*/
01420 /*  Local Variables:*/
01421 /*    indent-tabs-mode: nil*/
01422 /*  End:*/
01423 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1