DNS programming
Brian {Hamilton Kelly}
bhk at dsl.co.uk
Sun Sep 5 14:24:36 UTC 1999
On 1 Sep, in article <19990901.1629.27990snz at dsl.co.uk> I wrote:
> Just for amusement, I wrote something in REXX that would query a
> nameserver (a sort-of cut-down "nslookup"); I could e-mail you that
> source if you're interested. (I did this because I was writing an SMTP
> client in Rexx, and wanted to access the destinations' MX records from
> within the code, using neither a smarthost, nor calling the local
> resolver.) It was quite an "interesting" exercise getting the correct
> binary values into character strings (because the latter is the ONLY form
> of variable that Rexx uses). Nevertheless, it worked: I really must get
> around to completing the SMTP client (I haven't touched it for a year:-)
At the suggestiong of Joseph Yao, I'm posting the code here; please
remember that this is part of a work-in-progress, and not really ready to
see the light of day. There are many areas in which it could be
improved, but I was only writing it in the first place as a "feasibility
study". I've (today) edited in a few comments that indicate some of the
improvements that it urgently requires.
[Apologies to the person to whom I'd already mailed (at his request) a
copy: I should have checked it first to see how it operated, because it
turns out that one had an IP address built in to it for a nameserver that
is inaccessible from the 'Net at large, being behind a firewall.]
Anyway, here it is, for what it's worth. If anyone cares to come up with
some "really nifty" improvements, please e-mail them to me. I've cross-
posted this article to comp.land.rexx as well, so that the experienced
Rexx programmers there can suggest improvements.
One final caution: if you call the file nslookup.cmd, it's going to hide
the genuine nslookup.exe program from you --- I've been calling it
nsl.cmd instead.
/* NSLookup.cmd */
/* ~~~~~~~~~~~~ */
/* A REXX command procedure that emulates some features of the program */
/* nslookup, as an experiment to test the feasibility of querying a name */
/* server from REXX. */
/* */
/* This command procedure is a "work-in-progress"; eventually, it will */
/* form part of a suite of programs for handling various TCP/IP protocols */
/* which will be released together under the terms of the Gnu Public */
/* Licence. Therefore treat this fragment as being bound by those terms. */
/* */
/* Author: B Hamilton Kelly */
/* E-mail: bhk at dsl.co.uk */
Version = 0.23
/* Last-Modified: 05-Sep-1999 */
/*********************************************************/
/* Modification History */
/*********************************************************/
/* 0.10 980514 Initial scratchings in the dark! */
/* 0.20 980525 Got responses onto one line */
/* 0.21 980527 Get SOA, MX and ANY in 3 requests */
/* 0.22 980708 Write "> " prompt without trailing CRLF */
/* 0.23 990905 Minor tidying up for publication */
/*********************************************************/
/* An obvious improvement to the program would be for it to read the
resolv (or resolv2) file and determine the recommended nameservers that
way; for the time being, the IP address is built-in, through the
following assignment: if your nameserver is anywhere other than on the
local host, you'll have to modify this: */
nameserver = '127.0.0.1' /* IP address of NS as dotted-quad string */
parse arg DomainName
/* The program assumes that there is only one parameter; giving a second
to specify an alternate name server would be a handy improvement */
if DomainName == "" then
Interactive = 1
else
Interactive = 0
QType = c2x('00FF'X) /* QueryType = "ANY" */
QClass = c2x('00FF'x) /* QueryClass = "ANY" */
ZERO = c2x('0000'X) /* Number 0 in 16-bit word */
ONE = c2x('0001'X) /* Number 1 ditto */
TWO = c2x('0002'X) /* 2 ditto */
THREE = c2x('0003'X) /* 3 ditto */
NUMERIC DIGITS 11
NextSerial = 0
Message = "" /* GLOBAL, since it might contain, e.g. ',' characters */
Assembly = "" /* GLOBAL, since it's used recursively */
Name = "" /* GLOBAL, since passes back result */
Query = b2x('0000000100000000') /* QR = 0 --- this is a query
OP = 0000 --- a standard QUERY
AA = 0 --- (authoritative answer)
TC = 0 --- (truncated)
RD = 1 --- Recursion Desired
RA = 0 --- (recursion available)
Z = 000 --- (reserved field)
RC = 0000 --- (response code) */
Call RxFuncAdd 'SockLoadFuncs', 'RxSock', 'SockLoadFuncs'
Call SockLoadFuncs('QUIET')
/* Get a socket for initiating connections */
socket=SockSocket('AF_INET', 'SOCK_DGRAM', 0 )
If socket < 0 Then Do
Say 'SockSocket()' errno
Exit errno
End
Say 'Created socket' socket
NShost. = ""
NShost.!family = "AF_INET"
NShost.!port = 53 /* Well-known port for DNS */
NShost.!addr = nameserver
stat = SockConnect( socket, "NShost.!" )
If stat < 0 Then Do
Say 'SockConnect()' errno
Signal Halt
End
Say 'Connected' socket 'to' NShost.!addr || ':' || NShost.!port
Signal On Halt /* Trap Ctrl-Break */
Do forever
if Interactive > 0 then do
Call CHAROUT ,"> "
parse pull DomainName
parse upper var DomainName testVal
if testVal == "EXIT" then
Leave
end /* do */
NextSerial = NextSerial + 1
Header = x2c( d2x(NextSerial,4) ) || x2c(Query) || x2c( ONE ) /* QDCOUNT=1 */
Header = Header || x2c( ZERO ) || x2c( ZERO ) || x2c( ZERO )
LabelPart = MakeLabel( DomainName )
Say "LabelPart length =" length(LabelPart)
Question = LabelPart || x2c(c2x('0006'X)) || x2c(ONE) /* type=SOA class=IN */
Say "Question length =" length(Question)
Request = Header || Question
Say "Request length =" length(Request)
Call PrintMessage Request
stat = SockSend( socket, Request )
if stat < 0 then do
Call SockPsock_errno("SockSend")
Signal Halt
end /* do */
Say "Request sent OK"
reads.0 = 1; reads.1 = socket
stat = SockSelect( "reads.", "", "", 10 ) /* Wait up to 10 secs for response */
if stat = 0 then do /* Request timed out */
Say "Request timed out :-("
Signal Halt
end /* do */
Response = ""
received = SockRecv( socket, "Response", 1024 )
if received < 0 then do
SockPsock_errno("SockRecv")
Signal Halt
end /* do */
if length(Response) = 0 then do /* Nothing useful */
Say "Nothing in response buffer :-("
Signal Halt
end /* do */
Call PrintMessage Response
NextSerial = NextSerial + 1
Header = x2c( d2x(NextSerial,4) ) || x2c(Query) || x2c( ONE ) /* QDCOUNT=1 */
Header = Header || x2c( ZERO ) || x2c( ZERO ) || x2c( ZERO )
Question = LabelPart || x2c(c2x('000F'X)) || x2c(ONE) /* type=MX class=IN */
Say "Question length =" length(Question)
Request = Header || Question
Say "Request length =" length(Request)
Call PrintMessage Request
stat = SockSend( socket, Request )
if stat < 0 then do
Call SockPsock_errno("SockSend")
Signal Halt
end /* do */
Say "Request sent OK"
reads.0 = 1; reads.1 = socket
stat = SockSelect( "reads.", "", "", 10 ) /* Wait up to 10 secs for response */
if stat = 0 then do /* Request timed out */
Say "Request timed out :-("
Signal Halt
end /* do */
Response = ""
received = SockRecv( socket, "Response", 1024 )
if received < 0 then do
SockPsock_errno("SockRecv")
Signal Halt
end /* do */
if length(Response) = 0 then do /* Nothing useful */
Say "Nothing in response buffer :-("
Signal Halt
end /* do */
Call PrintMessage Response
NextSerial = NextSerial + 1
Header = x2c( d2x(NextSerial,4) ) || x2c(Query) || x2c( ONE ) /* QDCOUNT=1 */
Header = Header || x2c( ZERO ) || x2c( ZERO ) || x2c( ZERO )
Question = LabelPart || x2c(QType) || x2c(QClass) /* type=ANY class=ANY */
Say "Question length =" length(Question)
Request = Header || Question
Say "Request length =" length(Request)
Call PrintMessage Request
stat = SockSend( socket, Request )
if stat < 0 then do
Call SockPsock_errno("SockSend")
Signal Halt
end /* do */
Say "Request sent OK"
reads.0 = 1; reads.1 = socket
stat = SockSelect( "reads.", "", "", 10 ) /* Wait up to 10 secs for response */
if stat = 0 then do /* Request timed out */
Say "Request timed out :-("
Signal Halt
end /* do */
Response = ""
received = SockRecv( socket, "Response", 1024 )
if received < 0 then do
SockPsock_errno("SockRecv")
Signal Halt
end /* do */
if length(Response) = 0 then do /* Nothing useful */
Say "Nothing in response buffer :-("
Signal Halt
end /* do */
Call PrintMessage Response
Say ''
if Interactive = 0 then
Leave
end /* do */
/* To trap Ctrl-Break, or after other error with an open socket */
Halt:
Call SockShutDown socket, 2
Call SockClose socket
Exit
/* This function returns the "label" part for a DNS query; each element of */
/* the fully-qualified domain name is separated out, and preceded by a single*/
/* byte showing the length of that element. */
/* The complete label is terminated with a length byte of zero, representing */
/* the null element before the final period designating the root of the DNS. */
MakeLabel: Procedure
parse arg Adomain
Label = ""
if right(Adomain,1) \= '.' then /* Append final period */
Adomain = Adomain || '.'
parse var Adomain Element '.' Adomain
do while length(Element) > 0
Label = Label || D2C(length(Element)) || Element
parse var Adomain Element '.' Adomain
end /* do */
Label = Label || D2C(0)
return Label
/* For testing purposes, this prints out a "label" */
PrintLabel: Procedure Expose Message Assembly Name
parse arg ResultName, CurPos, Clear
if CurPos == "" then
CurPos = 1
if Clear = "" then
Assembly = ""
do forever
LenByte = C2D(substr(Message,CurPos,1)); CurPos = CurPos + 1
if LenByte > 63 then /* A "pointer" to a label fragment */
do
if LenByte < 192 then Do
Say "Illegal <label-length> marker (" || LenByte || ") at" CurPos-1
Exit
end /* do */
LenByte = (LenByte - 192)*256 + c2d(substr(Message,CurPos,1))
CurPos = CurPos+1; LenByte = LenByte + 1
INTERPRET 'Call PrintLabel "'ResultName'",' LenByte', "Keep"'
Return CurPos /* NB We do NOT "follow" the pointer */
end /* do */
if LenByte = 0 then do
Assembly = Assembly || '.'
INTERPRET ResultName '= Assembly'
Leave
end /* do */
Element = substr(Message,CurPos,LenByte); CurPos = CurPos + LenByte
if Assembly = "" then
Assembly = Element
else
Assembly = Assembly || '.' || Element
end /* do */
Return CurPos
/* Function GetString( posn ) return new_posn = string */
GetString: Procedure Expose Message
Parse Arg CurPos
LenByte = C2D(substr(Message,CurPos,1)); CurPos = CurPos + 1
Data = substr(Message,CurPos,LenByte)
NewPos = CurPos + LenByte
Return NewPos || ' = "' || Data || '"'
/* Subroutine PrintMessage (Message) */
/* Parses and prints out entire message structure */
/* There are great /swathes/ of debugging information printed here; a
useful modification would be to make these optional. */
PrintMessage: Procedure
Parse Arg Message
QDcount = 0; ANcount = 0; NScount = 0; ARcount = 0
/* This is where the raw bytes of a message are printed; this debugging
information can be omitted by altering the comment indicators that
preced and follow the block of code, such that it is commented out.
All the same, it would be better under some form of user control. */
/* */
Size = length(Message); Offset = 1
do while Size > 0
ShortWord = c2x(substr(Message,Offset,2))
Say Offset || ": '" || ShortWord || "'X"
Offset = Offset + 2; Size = Size - 2
end /* do */
/* */
NextPart = PrintHeader( Message )
if QDcount > 0 then
Say "Questions:"
NextPart = PrintQuestions( NextPart, QDcount )
if ANcount > 0 then
Say "Answers:"
NextPart = PrintAnswers( NextPart, ANcount )
if NScount > 0 then
Say "Authoritative information may be obtained from:"
NextPart = PrintAnswers( NextPart, NScount )
if ARcount > 0 then
Say "Additional Information:"
NextPart = PrintAnswers( NextPart, ARcount )
Return
/* Function PrintHeader (Message) returns pointer to next segment */
PrintHeader: Procedure Expose QDcount ANcount NScount ARcount
Parse Arg Message
CurPos = 1
Say "CurPos =" CurPos || "; Message length =" length(Message)
SeqNo = c2d(substr(Message,CurPos,2)); CurPos = CurPos + 2
Flags = x2b(c2x(substr(Message,CurPos,2))); CurPos = CurPos + 2
QDcount = c2d(substr(Message,CurPos,2)); CurPos = CurPos + 2
ANcount = c2d(substr(Message,CurPos,2)); CurPos = CurPos + 2
NScount = c2d(substr(Message,CurPos,2)); CurPos = CurPos + 2
ARcount = c2d(substr(Message,CurPos,2)); CurPos = CurPos + 2
QR = substr(Flags,1,1)
Opcode = x2d(b2x(substr(Flags,2,4)))
AA = substr(Flags,6,1)
TC = substr(Flags,7,1)
RD = substr(Flags,8,1)
RA = substr(Flags,9,1)
Z = substr(Flags,10,3)
Rcode = substr(Flags,13,4)
Say "Sequence number:" SeqNo
if QR = '1' then
Say " is a response."
else
Say " is a query."
select
when Opcode = 0 then
Say "Standard QUERY"
when Opcode = 1 then
Say "Inverse IQUERY"
when Opcode = 2 then
Say "Server STATUS request"
otherwise
Say "Unexpected OPcode" Opcode "in query"
end /* select */
if AA = '0' then
Auth = "Non-authoritative"
else
Auth = "Authoritative"
Say Auth "response"
if TC = '1' then
Say "Response was truncated :-("
if RD = '1' then
Say "Recursion requested"
if RA = '1' then
Say "Recursion available"
if Z \= 0 then
Say "Reserved field Z is non-zero! (" || Z || ")"
select
when Rcode = 0 then
Say "No error condition"
when Rcode = 1 then
Say "Format ERROR"
when Rcode = 2 then
Say "Server failure"
when Rcode = 3 then
Say "Name ERROR; no such domain"
when Rcode = 4 then
Say "Server does not support requested query"
when Rcode = 5 then
Say "Request refused by server"
otherwise
Say "Unexpected Response Code (" || Rcode || ")"
end /* select */
Say "There are" QDcount "questions"
Say "There are" ANcount "answers"
Say "There are" NScount "NS resource records in authority records section"
Say "There are" ARcount "additional records"
Return CurPos
/* Function PrintQuestions ( Pointer, Number) return next pointer */
PrintQuestions: Procedure Expose Message Name
Parse Arg CurPos, Count
do Qct = 1 to Count
Say "Question" Qct
CurPos = PrintQuestion( CurPos )
end /* do */
Return CurPos
/* Function PrintQuestion ( Pointer) return pointer to next question */
PrintQuestion: Procedure Expose Message Name
Parse Arg CurPos
CurPos = PrintLabel("Name",CurPos)
Say Name
Qtype =c2d(substr(Message,CurPos,2)); Qclass =c2d(substr(Message,CurPos+2,2))
select
when Qtype = 252 then
Say "qtype = AXFR"
when Qtype = 253 then
Say "qtype = MAILB"
when Qtype = 254 then
Say "qtype = MAILA"
when Qtype = 255 then
Say "qtype = ANY"
otherwise
Call PrintType Qtype
end /* select */
select
when Qclass = 255 then
Say "query class = ANY"
otherwise
Call PrintClass Qclass
end /* select */
Return CurPos + 4
/* Subroutine PrintType (type) */
PrintType: Procedure
Parse Arg TypeFlag
select
when TypeFlag = 1 then
Say "type = A"
when TypeFlag = 2 then
Say "type = NS"
when TypeFlag = 3 then
Say "type = MD"
when TypeFlag = 4 then
Say "type = MF"
when TypeFlag = 5 then
Say "type = CNAME"
when TypeFlag = 6 then
Say "type = SOA"
when TypeFlag = 7 then
Say "type = MB"
when TypeFlag = 8 then
Say "type = MG"
when TypeFlag = 9 then
Say "type = MR"
when TypeFlag = 10 then
Say "type = NULL"
when TypeFlag = 11 then
Say "type = WKS"
when TypeFlag = 12 then
Say "type = PTR"
when TypeFlag = 13 then
Say "type = HINFO"
when TypeFlag = 14 then
Say "type = MINFO"
when TypeFlag = 15 then
Say "type = MX"
when TypeFlag = 16 then
Say "type = TXT"
otherwise
Say "ERROR: unknown type =" TypeFlag
end /* select */
Return
/* Subroutine PrintClass (class) */
PrintClass: Procedure
Parse Arg Class
select
when Class = 1 then
Say "class = IN"
when Class = 2 then
Say "class = CSNET"
when Class = 3 then
Say "class = CHAOS"
when Class = 4 then
Say "class = HESIOD"
otherwise
Say "ERROR: unknown Class =" Class
end /* select */
Return
/* Function PrintAnswers ( Pointer, Number) return next pointer */
PrintAnswers: Procedure Expose Message Name
Parse Arg CurPos, Count
do Act = 1 to Count
CurPos = PrintRR( CurPos )
end /* do */
Return CurPos
/* Function PrintRR( Pointer) return pointer to next Resource Record */
PrintRR: Procedure Expose Message Name
Parse Arg CurPos
CurPos = PrintLabel("Name",CurPos)
RRtype = c2d(substr(Message,CurPos,2))
RRclass = c2d(substr(Message,CurPos+2,2))
TTL = c2d(substr(Message,CurPos+4,4))
RdLength = c2d(substr(Message,CurPos+8,2))
CurPos = CurPos + 10
Call PrintRdata Name, TTL, RRtype, RRclass, CurPos, RdLength
Return CurPos + RdLength
/* Subroutine PrintRdata( label, TTL, type, class, posn, count ) */
PrintRdata: Procedure Expose Message Name
Parse Arg ThisLabel, TTL, Rtype, Rclass, Posn, Count
select
when Rclass = 1 then
Class = "IN"
when Rclass = 2 then
Class = "CSNET"
when Rclass = 3 then
Class = "CHAOS"
when Rclass = 4 then
Class = "HESIOD"
otherwise
Say "ERROR: unknown Class =" Rclass
Return
end /* select */
select
when Rtype = 5 then do /* CNAME */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "CNAME" Name
end /* do */
when Rtype = 13 then do /* HINFO */
Parse Value GetString( Posn ) with Posn "=" CPU
Parse Value GetString( Posn ) with Posn "=" OS
Say ThisLabel TTL Class "HINFO" "CPU:" CPU "OS:" OS
end /* do */
when Rtype = 7 then do /* MB (experimental) */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "MB" Name
end /* do */
when Rtype = 3 then do /* MD (Obsolete) */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "MD" Name
end /* do */
when Rtype= 4 then do /* MF (Obsolete) */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "MF" Name
end /* do */
when Rtype = 8 then do /* MG (Experimental) */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "MG" Name
end /* do */
when Rtype = 14 then do /* MINFO (Experimental) */
Posn = PrintLabel( "Name", Posn )
mailbox = Name
Call PrintLabel "Name", Posn
mailhost = Name
Say ThisLabel TTL Class "MINFO" mailbox || "@" || mailhost
end /* do */
when Rtype = 9 then do /* MR (Experimental) */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "MR" Name
end /* do */
when Rtype = 15 then do /* MX */
Preference = c2d(substr(Message,Posn,2))
Call PrintLabel "Name", Posn+2
Say ThisLabel TTL Class "MX" Preference Name
end /* do */
when Rtype = 10 then /* NULL */
Say ThisLabel TTL Class "NULL"
when Rtype = 2 then do /* NS */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "NS" Name
end /* do */
when Rtype = 12 then do /* PTR */
Call PrintLabel "Name", Posn
Say ThisLabel TTL Class "PTR" Name
end /* do */
when Rtype = 6 then do /* SOA */
Posn = PrintLabel( "Name", Posn ) /* Primary server */
Say ThisLabel TTL Class "SOA" Name
Posn = PrintLabel( "Name", Posn ) /* Responsible person */
Say " " Name '('
Serial = c2d( substr(Message,Posn,4), 5 ); Posn = Posn + 4
Say " " Serial || '; Serial'
Refresh = c2d(substr(Message,Posn,4)); Posn = Posn + 4
Say " " Refresh || '; Refresh'
Retry = c2d(substr(Message,Posn,4)); Posn = Posn + 4
Say " " Retry || '; Retry'
Expire = c2d(substr(Message,Posn,4)); Posn = Posn + 4
Say " " Expire || '; Expire'
Minimum = c2d(substr(Message,Posn,4)); Posn = Posn + 4
Say " " Minimum || '; Minimum)'
end /* do */
when Rtype = 16 then do /* TXT */
Parse Value GetString(Posn) with Posn '=' Text
Say ThisLabel TTL Class "TXT" Text
end /* do */
when Rtype = 1 then do /* A */
a = c2d(substr(Message,Posn,1)); b = c2d(substr(Message,Posn+1,1))
c = c2d(substr(Message,Posn+2,1)); d = c2d(substr(Message,Posn+3,1))
Say ThisLabel TTL Class "A" a || '.' || b || '.' || c || '.' || d
end /* do */
when Rtype = 11 then do /* WKS */
a = c2d(substr(Message,Posn,1)); b = c2d(substr(Message,Posn+1,1))
c = c2d(substr(Message,Posn+2,1)); d = c2d(substr(Message,Posn+3,1))
protocol = c2d(substr(Message,Posn+4,2))
Say ThisLabel TTL Class "WKS" a || '.' || b || '.' || c || '.',
|| d || ':' || protocol
bitmap = x2b(c2x(substr(Message,Posn+6,Count-6)))
Say bitmap
end /* do */
otherwise
Say "Unknown RR Type =" Rtype
end /* select */
Return
--
Brian {Hamilton Kelly} bhk at dsl.co.uk
"But we're a university. We /have/ to have a library!..."said Ridcully,
"What sort of people would we be if we didn't go into the library?"
"Students", said the Senior Wrangler, morosely. [TP: The Last Continent]
More information about the bind-users
mailing list