[alt.sources] A domain name library in perl

yukngo@obelix.gaul.csd.uwo.ca (Cheung Yukngo) (08/07/90)

A few months ago I wrote a program called pns and posted it in
comp.lang.perl. I then realised that the program could be a lot more
useful as a library. So, this is the library version of pns. The
documentation is very bad (as usual) but you probably need to read
RFC1035 which contains all the information about domain name service
if you ask more than just IP addresses from hostnames.

Please let me know if you find a bug.

clipper@csd.uwo.ca

#! /bin/sh
# This is a shell archive, meaning:
# 1. Remove everything above the #! /bin/sh line.
# 2. Save the resulting text in a file.
# 3. Execute the file with /bin/sh (not csh) to create the files:
#	nslib.doc
#	nslib.perl
#
if test -f 'nslib.doc'
then
	echo shar: will not over-write existing file "'nslib.doc'"
else
	echo x - 'nslib.doc'
	sed 's/^X//' >'nslib.doc' << 'SHAR_EOF'
X# Copyright 1990 Khun Yee Fung <clipper@csd.uwo.ca>
X#
X# Permission to use, copy, modify, and distribute, this software and its
X# documentation for any purpose is hereby granted without fee, provided that
X# the above copyright notice appear in all copies and that both that
X# copyright notice and this permission notice appear in supporting
X# documentation, and that the name of the copyright holders be used in
X# advertising or publicity pertaining to distribution of the software with
X# specific, written prior permission, and that no fee is charged for further
X# distribution of this software, or any modifications thereof.  The copyright
X# holder make no representations about the suitability of this software for
X# any purpose.  It is provided "as is" without express or implied warranty.
X#
X# THE COPYRIGHT HOLDER DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
X# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
X# EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
X# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
X# DATA, PROFITS, QPA OR GPA, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 
X# OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
X# PERFORMANCE OF THIS SOFTWARE.
X
Xnslib is a library for Perl providing domain name client services. If
Xyou find any errors, please let me know.
X
XPlease give the name server you use in the line marked by # *** in the
Xpackage.
X
XThe library consists of three subroutines callable from the main
Xprogram. The first one is called nsinit(). This subroutine initialises
Xthe server connection. The second one is called nsend(). This should
Xbe used to shut down the server connection. The third one is called
Xnsquery().
X
Xnsinit() 
X
X  Parameter - optional one parameter to give the server address. The
X  IP address of the server should be known by the machine you are
X  using (either its IP address is in /etc/hosts or it is given by its
X  IP address).
X
X  Error codes:
X	255 - More than one paramter
X	254 - nsinit can't find the IP address of the server
X	253 - can't use socket
X	252 - can't use connect
X
Xnsend()
X
X	shut down the server. No error code.
X
Xnsquery()
X
X  Parameters: exactly two parameters
X    option - the type of query required. These include
X      Address - returns the IP address
X      Name server - the name server for the host given
X      Mail destination - no idea what this is used for
X      Mail forwarder - ditto
X      Canonical name - returns the canonical name for the host name given
X      Zone of authority - no idea
X      Mailbox - dunno
X      Mail group - dunno
X      Mail rename - dunno
X      Null - apparently nothing
X      Services - well known services provided by the host given
X      Domain name - return the host name given an IP address
X      Host info - CPU and OS of the host
X      Mailbox info - beats me
X      Mail exchange - for the mail wizard
X      Text - dunno
X      Transfer of zone - no idea
X      Mail record - dunno
X      Mail agent - no idea
X      Everything - returns everything the name server knows about the host
X
X    If you are more curious than me, read RFC1035 for the full
X    information. I actually implemented this program using the
X    information there but just too lazy to fill in the infotmation I
X    never care to know. If you are interested, feel free to fill in
X    the information and send me a copy too.
X  
X    Hostname: The second parameter. This should either be a hostname
X    or a IP address, depending whether the name server is expecting a
X    hostname or a IP address. If you want to know the IP address of
X    tut.cis.ohio-state.edu, for example, you should give
X    "tut.cis.ohio-state.edu" as the hostname. But if you want to know
X    the hostname of 128.64.18.60, you should give "128.64.18.60". Yes,
X    the program will reverse the IP address and add "in-addr.arpa" for
X    you.
X
X  Output: nsquery returns an ASSOCIATE ARRAY. So be prepared to catch
X  an associative array. The possible keys for the array are the same
X  as the options for nsquery with the following additional keys:
X    Error - 0 if nothing is wrong. Else, error.
X      251 - parameter number is not 2
X      250 - no such option
X      1   - format error
X      2   - server failure
X      3   - name error
X      4   - not implemented
X      5   - refused
X    Message - the string version of Error.
X    AA - whether the answer is authoritative
X    QR - what kind of query it was
X    Truncation - whether the answer has been truncated or not
X    Recursion desired - yes for this program
X    Recursion available - whether the name server has recursion
X    Address - the value of this key contains all the hosts with IP
X      addresses found in the query. The hostnames will be used as keys
X      to store the IP addresses.
X
X  Examples:
X    &nsquery('Host info', 'tut.cis.ohio-state.edu');
X    &nsquery('Domain name', '129.100.11.2');
X
X
XWarning: I assume $[ to be 0. It is also the first time I used
Xpackage, I have no idea whether I used it right or wrong. You must
Xalso initialise the $server variable in nslib.perl. I don't want to
Xinclude the domain name server we use.
X
XWish list:
X  1. Some sort of documentation for the library (want to take a shot
X     at this?)
X  2. Multiple queries at the same time.
X
XExample program:
X#!/u3/thesis/clipper/pl/perl
X
Xdo 'nslib.perl || die "Can't do nslib.perl";
X
Xif(&nsinit() != 0) {
X    print "Can't open server\n";
X    exit(1);
X}
X%reply1 = &nsquery('Host info', 'tut.cis.ohio-state.edu');
X%reply2 = &nsquery('Domain name', '129.100.11.2');
X&nsend();
Xprint "reply 1\n";
X@keys = keys(%reply1);
Xforeach $key (@keys) {
X    print $key, '	:', $reply1{$key}, "\n";
X}
Xprint "reply 2\n";
X@keys = keys(%reply2);
Xforeach $key (@keys) {
X    print $key, ' ', $reply2{$key}, "\n";
X}
SHAR_EOF
if test 5791 -ne "`wc -c < 'nslib.doc'`"
then
	echo shar: error transmitting "'nslib.doc'" '(should have been 5791 characters)'
fi
fi
if test -f 'nslib.perl'
then
	echo shar: will not over-write existing file "'nslib.perl'"
else
	echo x - 'nslib.perl'
	sed 's/^X//' >'nslib.perl' << 'SHAR_EOF'
X#!/u3/thesis/clipper/pl/perl
X# Copyright 1990 by Khun Yee Fung <clipper@csd.uwo.ca>
X# See nslib.doc for warranty information
X# $Source: /u3/thesis/clipper/pl/RCS/pns,v $
X# $Id: pns,v 1.3 90/05/12 16:29:58 clipper Exp $
X
Xpackage domainname;
X
X$sockaddr = 'S n a4 x8';
X$port = 53;
X%question = ('Address', 1, 'Name server', 2, 'Mail destination', 3,
X	     'Mail forwarder', 4, 'Canonical name', 5, 'Zone of authority',
X	     6, 'Mailbox', 7, 'Mail group', 8, 'Mail rename', 9, 'Null', 10,
X	     'Services', 11, 'Domain name', 12, 'Host info', 13,
X	     'Mailbox info', 14, 'Mail exchange', 15, 'Text', 16,
X	     'Transfer of zone', 252, 'Mail record', 253, 'Mail agent', 254,
X	     'Everything', 255);
X@question = ('', 'Address', 'Name server', 'Mail destination',
X	     'Mail forwarder', 'Canonical name', 'Zone of authority',
X	     'Mailbox', 'Mail group', 'Mail rename', 'Null', 'Services',
X	     'Domain name', 'Host info', 'Mailbox info', 'Mail exchange',
X	     'Text');
X
X@bits = (0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100,
X  0x0080, 0x0040, 0x0020, 0x0010, 0x0008, 0x0004, 0x0002, 0x0001);
X$PORTS[5] = "RJE"; $PORTS[7] = "ECHO"; $PORTS[9] = "DISCARD";
X$PORTS[11] = "USERS"; $PORTS[13] = "DAYTIME"; $PORTS[17] = "QUOTE";
X$PORTS[19] = "CHARGEN"; $PORTS[20] = "FTP-DATA"; $PORTS[21] = "FTP";
X$PORTS[23] = "TELNET"; $PORTS[25] = "SMTP"; $PORTS[27] = "NSW-FE";
X$PORTS[29] = "MSG-ICP"; $PORTS[31] = "MSG-AUTH"; $PORTS[33] = "DSP";
X$PORTS[37] = "TIME"; $PORTS[39] = "RLP"; $PORTS[41] = "GRAPHICS";
X$PORTS[42] = "NAMESERVER"; $PORTS[43] = "NICNAME"; $PORTS[44] = "MPM-FLAGS";
X$PORTS[45] = "MPM"; $PORTS[46] = "MPM-SND"; $PORTS[47] = "NI-FTP";
X$PORTS[49] = "LOGIN"; $PORTS[51] = "LA-MAINT"; $PORTS[53] = "DOMAIN";
X$PORTS[55] = "ISI-GL"; $PORTS[61] = "NI-MAIL"; $PORTS[63] = "VIA-FTP";
X$PORTS[65] = "TACACS-DS"; $PORTS[67] = "BOOTPS"; $PORTS[68] = "BOOTPC";
X$PORTS[69] = "TFTP"; $PORTS[71] = "NETRJS-1"; $PORTS[72] = "NETRJS-2";
X$PORTS[73] = "NETRJS-3"; $PORTS[64] = "NETRJS-4"; $PORTS[79] = "FINGER";
X$PORTS[81] = "HOSTS-NS"; $PORTS[83] = "MIT-ML-DEV"; $PORTS[85] = "MIT-ML-DEV";
X$PORTS[89] = "SU-MIT-TG"; $PORTS[91] = "MIT-DOV"; $PORTS[93] = "DCP";
X$PORTS[95] = "SUPDUP"; $PORTS[97] = "SWIFT-RVF"; $PORTS[98] = "TACNEWS";
X$PORTS[99] = "METAGRAM"; $PORTS[101] = "HOSTNAME"; $PORTS[102] = "ISO-TSAP";
X$PORTS[103] = "X400"; $PORTS[104] = "X400-SND"; $PORTS[105] = "CSNET-NS";
X$PORTS[107] = "RTELNET"; $PORTS[109] = "POP2"; $PORTS[111] = "SUNRPC";
X$PORTS[113] = "AUTH"; $PORTS[115] = "SFTP"; $PORTS[117] = "UUCP-PATH";
X$PORTS[119] = "NNTP"; $PORTS[121] = "ERPC"; $PORTS[123] = "NTP";
X$PORTS[125] = "LOCUS-MAP"; $PORTS[127] = "LOCUS-CON"; $PORTS[129] = "PWDGEN";
X$PORTS[130] = "CISCO-FNA"; $PORTS[131] = "CISCO-TNA"; 
X$PORTS[132] = "CISCO-SYS"; $PORTS[133] = "STATSRV"; $PORTS[134] = "INGRES-NET";
X$PORTS[135] = "LOC-SRV"; $PORTS[136] = "PROFILE"; $PORTS[137] = "NETBIOS-NS";
X$PORTS[138] = "NETBIOS-DGM"; $PORTS[139] = "NETBIOS-SSN"; 
X$PORTS[140] = "EMFIS-DATA"; $PORTS[141] = "EMFIS-CNTL"; $PORTS[142] = "BL-IDM";
X
X@QR = ("query", "response");
X@OPCODE = ("QUERY", "IQUERY", "STATUS");
X@AA = ("Non-Authoritive", "Authoritive");
X@TC = ("Not Truncated", "Truncated");
X@RD = ("Don't recurse", "Do Recurse");
X@RA = ("No recursion", "Has recursion");
X@RCODE = ("No error", "Format error", "Server Failure", "Name error",
X	  "Not implemented", "Refused");
X@PROTOCOL = ("", "ICMP", "IGMP", "GGP", "", "ST", "TCP", "UCL", "EGP",
X  "IGP", "BBN-RCC-MON", "NVP-II", "PUP", "ARGUS", "EMCON", "XNET", "CHAOS",
X  "UDP", "MUX", "DCN-MEAS", "HMP", "PRM", "XNS-IDP", "TRUNK1", "TRUNK2",
X  "LEAF1", "LEAF2", "RDP", "IRTP", "ISO-TP4", "NETBLT", "MFE-NSP", "MERIT-INP",
X  "SEP");
X$PROTOCOL[62] = "CFTP"; $PROTOCOL[64] = "SAT-EXPAK";
X$PROTOCOL[65] = "MIT-SUBNET"; $PROTOCOL[66] = "RVD"; $PROTOCOL[67] = "IPPC";
X$PROTOCOL[69] = "SAT-MON"; $PROTOCOL[71] = "IPCV"; 
X$PROTOCOL[76] = "BR-SAT-MON"; $PROTOCOL[78] = "WB-MON";
X$PROTOCOL[79] = "WB-EXPAK";
X
Xsub main'nsinit {
X    if ($#_ == $[) {
X	$server = $_[0];
X    }
X    elsif ($#_ > $[) {
X	return 255;
X    }
X    else {
X	# *** Give the name server you use
X	$server = 'ria';
X    }
X    if ($server =~ /(\d+)\.(\d+)\.(\d+)\.(\d+)/) {
X	$saddr = pack("CCCC", $1, $2, $3, $4);
X    }
X    elsif (!(($name, $dummy, $type, $len, $saddr) = gethostbyname($server))) {
X	return 254;
X    }
X
X    $sin = pack($sockaddr, 2, $port, $saddr);
X
X    socket(SERVER, 2, 1, 0) || return 253;
X    connect(SERVER, $sin) || return 252;
X
X    select(SERVER); $| = 1; select(STDOUT);
X    0;
X}
X
Xsub main'nsquery {
X    undef(%reply);
X    if ($#_ != 1) {
X	return (("Error", 251, "Message", "parameter size should be 2"));
X    }
X    local($hostname) = $_[1];
X    if(($question = $question{$_[0]}) eq '') {
X	return (("Error", 250, "Message", "No such option"));
X    }
X    $len = 17;
X    @names = split('\.', $hostname);
X    if ($question == 12) {
X	@names = reverse(@names);
X	push(@names, ("in-addr", "arpa"));
X    }
X    foreach $arg (@names) {
X	$len += 1 + length($arg);
X    }
X    print SERVER pack('S', $len);
X    print SERVER pack('S6', 319, 256, 1, 0, 0, 0);
X    foreach $arg (@names) {
X	print SERVER pack('C', length($arg));
X	print SERVER $arg;
X    }
X    print SERVER pack('C', 0);
X    print SERVER pack('S2', $question, 1);
X
X    read(SERVER, $len, 2);
X    read(SERVER, $response, unpack('S', $len));
X    @shead = unpack('S6', $response);
X    $reply{'Error'} = $shead[1] & 0x000F;
X    $reply{'Message'} = $RCODE[$shead[1] & 0x000F];
X    $reply{'AA'} = $AA[($shead[1] & 0x0400) >> 10];
X    $reply{'QR'} = $QR[($shead[1] & 0x8000) >> 15];
X    $reply{'Truncation'} = $TC[($shead[1] & 0x0200) >> 9];
X    $reply{'Recursion desired'} = $RD[($shead[1] & 0x0100) >> 8];
X    $reply{'Recursion available'} = $RA[($shead[1] & 0x0080) >> 7];
X
X    $in = 12;
X    $ans = $shead[2];
X    while ($ans > 0) {
X	&label();
X	$in += 4;
X	$ans--;
X    }
X
X    foreach $index ((3, 4, 5)) {
X	$ans = $shead[$index];
X	while ($ans > 0) {
X	    $label = &label();
X	    $type = substr($response, $in, 2); $in += 8;
X	    $rdlength = substr($response, $in, 2); $in += 2;
X	    $rdata = substr($response, $in, unpack('n', $rdlength));
X	    &process($label);
X	}
X	continue {
X	    $ans--;
X	}
X    }
X    %reply;
X}
X
X
Xsub label {
X    $qname = "";
X    $c = substr($response, $in, 1); $in++;
X    $offset = $in;
X    $forward = 1;
X  loop: while ($c ne "\000") {
X      $cc = ord($c);
X      if (($cc & 0xc0) == 0xc0) {
X	  if ($forward) {
X	      $forward = 0;
X	      $in = $offset + 1;
X	  }
X	  $offset = ($cc - 192) * 256 + ord(substr($response, $offset, 1));
X      }
X      else {
X	  $qname = $qname . substr($response, $offset, $cc) . "."; 
X	  $offset += $cc;
X      }
X      $c = substr($response, $offset, 1); $offset++;
X    }
X    if ($forward) {
X	$in = $offset;
X    }
X    chop($qname);
X    $qname =~ y/A-Z/a-z/;
X    $qname;
X}
X
Xsub process {
X    local($label) = $_[0];
X    local($RR);
X    $RR = unpack('S', $type);
X    if ($RR == 1) {
X	@host = unpack('C4', $rdata);
X	if($reply{'Address'} eq '') {
X	    $reply{'Address'} = $label;
X	}
X	else {
X	    $reply{'Address'} = $reply{'Address'} . ' ' . $label;
X	}
X	$reply{$label} = "$host[0].$host[1].$host[2].$host[3]";
X	$in += 4;
X    }
X    elsif ($RR == 6) {
X	$serial = substr($response, $in, 4); $in += 4;
X	$refresh = substr($response, $in, 4); $in += 4;
X	$retry = substr($response, $in, 4); $in += 4;
X	$expire = substr($response, $in, 4); $in += 4;
X	$minimum = substr($response, $in, 4); $in += 4;
X	$reply{'Zone of authority'} = &label() . " " . &label() . 
X	     " " . unpack('N', $serial) . " " .  unpack('N', $refresh) . 
X	     " " . unpack('N', $retry) . " " . unpack('N', $expire) . 
X	     " " . unpack('N', $minimum);
X    }
X    elsif ($RR == 10) {
X	$in += unpack('n', $rdlength);
X	$reply{'Null'} = 'Nothing';
X    }
X    elsif ($RR == 11) {
X	$limit = $in + unpack('n', $rdlength);
X	$ip = substr($response, $in, 4); $in += 4;
X	$protocol = substr($response, $in, 1); $in++;
X	$bitmap = substr($response, $in, $limit - $in);
X	$in = $limit;
X	$result = "$PROTOCOL[ord($protocol)] ";
X	$result .= &ipaddr($ip) . " ";
X	$result .= &protocols();
X	$reply{'Services'} = $result;
X    }
X    elsif ($RR ==13) {
X	$len = substr($response, $in, 1); $in++;
X	$CPU = substr($response, $in, ord($len)); $in += ord($len);
X	$len = substr($response, $in, 1); $in++;
X	$OS = substr($response, $in, ord($len)); $in += ord($len);
X	$reply{'Host info'} = "$CPU $OS";
X    }
X    elsif ($RR == 15) {
X	$prefer = unpack("n", substr($response, $in, 2)); $in += 2;
X	$reply{'Mail exchange'} = "$prefer" . &label();
X    }
X    elsif ($RR == 16) {
X	$limit = $in + unpack('n', $rdlength);
X	$string = '';
X	while ($in < $limit) {
X	    $len = substr($response, $in, 1); $in++;
X	    $string .= substr($response, $in, ord($len)); $in += ord($len);
X	}
X	$reply{'Text'} = $string;
X    }
X    else {
X	$lab = &label();
X	if ($reply{$question[$RR]} eq '') {
X	    $reply{$question[$RR]} = $lab;
X	}
X	else {
X	    $reply{$question[$RR]} .= ' ' . $lab;
X	}
X    }
X}
X
Xsub ipaddr {
X    local(@host) = unpack('C4', $_[0]);
X    "$host[0].$host[1].$host[2].$host[3]";
X}
X
Xsub protocols {
X    local($i, $j, $k, $result);
X    $k = length($bitmap);
X    @portsnum = unpack('n10', $bitmap);
X    for ($i = 0; $i <= $k; $i++) {
X	for ($j = 0; $j < 16; $j++) {
X	    if (($portsnum[$i] & $bits[$j]) != 0) {
X		$result .= "$PORTS[$i * 16 + $j] ";
X	    }
X	}
X    }
X    $result;
X}
X
Xsub main'nsend {
X    close SERVER;
X}
SHAR_EOF
if test 9328 -ne "`wc -c < 'nslib.perl'`"
then
	echo shar: error transmitting "'nslib.perl'" '(should have been 9328 characters)'
fi
fi
echo Done
exit 0