#!/usr/bin/perl # # dig.pl my $version = 1.09; # 9-19-07 Michael Robinton # # Copyright 2003, Michael Robinton # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. # use strict; use IO::Socket 1.18; use Net::DNS::Codes qw(:all); use Net::DNS::ToolKit qw( get16 put16 get_ns newhead gethead strip gettimeofday ); use Net::DNS::ToolKit::Debug qw( print_buf print_head ); use Net::DNS::ToolKit::RR; my $DEBUG2FILE = 0; # save response records to file sub usage { print STDERR "\n",@_ if @_; print STDERR q| Syntax: dig.pl [@server] [+tcp] [-d] [-h] [-p port#] [-t type] name server is the name or IP address of the name server to query. An IPv4 address can be provided in dotted-decimal notation. When the supplied server argument is a hostname, dig resolves that name before querying that name server. +tcp only use TCP protocol -d print the query to the console -h print the header to the console -p port# is the port number that dig.pl will send its queries instead of the standard DNS port number 53. -t indicates what type of query is required. This script supports only A, AAAA, MX, NS, CNAME, SOA, TXT, and ANY queries as well as AXFR record transfers. If no type argument is supplied, dig.pl will perform a lookup for an A record name is the name of the resource record that is to be looked up. |; exit 1; } my $tcp = 0; # default my $debug = 0; # default my $headbug = 0; # default my $Type = T_A; # default my $port = 53; # default my $server = get_ns(); # default to first ns on list my $name = ''; my $server = ($server) ? inet_ntoa($server) : '127.0.0.1'; my $sname = $server; my %allowed = ( A => T_A, AAAA => T_AAAA, MX => T_MX, NS => T_NS, CNAME => T_CNAME, SOA => T_SOA, AXFR => T_AXFR, ANY => T_ANY, TXT => T_TXT, PTR => T_PTR, ); my($class); &usage unless (@_ = @ARGV); # exit with message if no args my $cmdline = join(' ',@ARGV); # parse args while ($_ = shift) { if ($_ =~ /^@(.+)/) { # new server $sname = $1; $server = (gethostbyname($sname))[4]; # use first address &usage("could not find server $name") unless $server; $server = inet_ntoa($server); } elsif ($_ =~ /^\-d/) { # debug? $debug = 1; } elsif ($_ =~ /^\-h/) { # header? $headbug = 1; } elsif ($_ =~ /^\-t/) { # type? $Type = uc shift; # must be next item &usage("bad type $Type") unless exists $allowed{$Type}; $Type = $allowed{$Type}; } elsif ($_ =~ /^\-p/) { # port? &usage("invalid port number $port") if ($port = shift) =~ /\D/; } elsif ($_ =~ /^\+tcp/) { $tcp = 1; } elsif ($_ =~ /^\-/) { &usage("unknown option $_"); } else { # must be a name chop if $_ =~ /\.$/; $name = $_; } } # get start timer my @time = gettimeofday; # construct query my $buffer; my $offset = newhead(\$buffer, $$, # pid is always unique BITS_QUERY | RD, # query, recursion desired 1,0,0,0, # one question ); &usage('you must supply a name') unless $name; my ($get,$put,$parse) = new Net::DNS::ToolKit::RR; $offset = $put->Question(\$buffer,$offset, $name,$Type,C_IN); print_head(\$buffer) if $headbug; # show header if header debug print_buf(\$buffer) if $debug; # show query if debug my $response; my $head = qq| ; <<>> dig.pl $version <<>> $cmdline ;; |; my $timeout = 15; # seconds eval { local $SIG{ALRM} = sub {die "connection time out, no servers could be reached"}; alarm $timeout; if (! $tcp && $Type != T_AXFR) { # UDP for all except AXFR ##### open socket my $socket = IO::Socket::INET->new( PeerAddr => $server, PeerPort => $port, Proto => 'udp', Type => SOCK_DGRAM, ) or die "connection timed out, no servers could be reached"; ##### send UDP query syswrite $socket, $buffer, $offset; # offset contains length of packet ##### read UDP answer my $urcv; die "failed to get UDP message" unless defined ($urcv = sysread($socket, $response, NS_PACKETSZ)); # get response, size limited close $socket; if($DEBUG2FILE) { open(T,'>./tmpu.tmp'); syswrite T, $response, $urcv; close T; print "UDP RCV=$urcv\n"; } } # UDP only if ($tcp || $Type == T_AXFR || ! response2text(\$response)) { my $socket = IO::Socket::INET->new( PeerAddr => $server, PeerPort => $port, Proto => 'tcp', Type => SOCK_STREAM, ) or die "connection timed out, no servers could be reached"; ##### send TCP query my $msglen; put16(\$msglen,0,$offset); # offset contains the length of message syswrite $socket, $msglen, 2; syswrite $socket, $buffer, $offset; # offset contains length of message ##### read TCP answer open(T,'>./tmpt.tmp') if $DEBUG2FILE; my $soaCount = 0; while ($soaCount < 2) { $soaCount = 2 unless $Type == T_AXFR; alarm $timeout; if (sysread $socket, $response, 2) { my $rcvdtot = 0;; my($rcvd,$buf); $msglen = get16(\$response,0); $response = ''; my $nleft = $msglen; while($rcvd = sysread($socket, $buf, $nleft)) { $response .= $buf; $nleft -= $rcvd; alarm $timeout; } die "failed to read TCP message $msglen" unless defined $rcvd; alarm 0; syswrite(T,$response,$msglen) if $DEBUG2FILE; response2text(\$response,\$soaCount); } else { print "; Transfer failed.\n"; alarm 0; close $socket; exit 1; last; } close T if $DEBUG2FILE; } close $socket; } # using TCP alarm 0; }; # end eval if ($@) { print STDERR $head,';; ',$@,"\n"; exit 1; } sub response2text { my($bp,$soap) = @_; my $type; if ($headbug) { print "\n\n"; print_head($bp); # show header if header debug } if ($debug) { print "\n\n"; print_buf($bp); # show answer if debug } ## decipher the answer my ($newoff,$id,$qr,$opcode,$aa,$tc,$rd,$ra,$mbz,$ad,$cd,$rcode, $qdcount,$ancount,$nscount,$arcount) = gethead($bp); ## adjust format for print my $flags .= 'qr ' if $qr; $flags .= 'aa ' if $aa; $flags .= 'tc ' if $tc; $flags .= 'rd ' if $rd; $flags .= 'ra ' if $ra; $flags .= 'z ' if $mbz; $flags .= 'ad ' if $ad; $flags .= 'cd ' if $cd; chop $flags; $opcode = OpcodeTxt->{$opcode}; $rcode = RcodeTxt->{$rcode}; print $head; print qq |;; Got answer. ;; ->>HEADER<<- opcode: $opcode, status: $rcode, id: $id ;; flags: $flags; QUERY: $qdcount, ANSWER: $ancount, AUTHORITY: $nscount, ADDITIONAL: $arcount | unless $soap; print q| ;; QUESTION SECTION: | unless $Type == T_AXFR; foreach(0..$qdcount -1) { ($newoff,$name,$type,$class) = $get->Question($bp,$newoff); ($name,$type,$class) = $parse->Question($name,$type,$class); $type = strip($type); $class = strip($class); print ";$name\t\t$class\t$type\n" unless $soap; } print q| ;; ANSWER SECTION: | unless $Type == T_AXFR; $newoff = nextsect($bp,$newoff,$ancount,$soap); print q| ;; AUTHORITY SECTION: | unless $Type == T_AXFR;; $newoff = nextsect($bp,$newoff,$nscount,$soap); print q| ;; ADDITIONAL SECTION: | unless $Type == T_AXFR;; $newoff = nextsect($bp,$newoff,$arcount,$soap); # end timer @time[2,3] = gettimeofday; my $msec = sprintf("%0.0f",elapsed(@time)); $_ = $qdcount + $ancount + $nscount + $arcount; print qq| ;; Query time: $msec ms ;; SERVER: $server#$port($sname) ;; WHEN: | . scalar localtime() . qq| ;; MSG SIZE rcvd: $newoff -- XFR size: $_ records |; 1; # true if no errors } sub nextsect { my($bp,$off,$count,$soap) = @_; foreach(0..$count -1) { ($off, my($name,$type,$class,$ttl,$rdlength,@rdata)) = $get->next($bp,$off); ++$$soap if $soap && $type == T_SOA; # bump soa counter if it exists ($name,$type,$class,$ttl,$rdlength,@rdata) = $parse->RR($name,$type,$class,$ttl,$rdlength,@rdata); $type =~ s/T_//; $class =~ s/C_//; print "$name\t$ttl\t$class\t$type\t"; foreach(@rdata) { print $_,' '; } print "\n"; } return $off; } sub elapsed { my ($startsec,$startusec,$endsec,$endusec) = @_; if ($endusec < $startusec) { $endusec += 1000000; $endsec -= 1; } my $msec = ($endusec - $startusec)/1000; $msec += ($endsec - $startsec) * 1000; }