OpenVMS Source Code Demos
RES_SEARCH_DEMO
//==============================================================================
// title : res_search_demo.c
// author : Neil Rieck ( web: https://neilrieck.net )
// created : 2014-10-19
// references: 1) RFC-1034, RFC-1035
// 2) "DNS and BIND" published by O'Reilly
// 3) nslookup.c source code (all uncommented and I don't know why)
// 4) a few demos from Linux and Windows
// 5) RFC-3596 and RFC-3513 (for IPv6 addresses)
// platform : OpenVMS-8.4 Alpha with TCPIP Services v5.7
// compile : $cc res_search_demo/nopointer_size (a.k.a. 32-bit pointers)
// vms link : $link res_search_demo, tcpip$library:tcpip$lib.olb/lib
// cli symbol: $res_search_demo :== $path:res_search_demo
// cli run 1 : $res_search_demo www.google.com
// cli run 2 : $res_search_demo www.google.com [a|mx|any|ns|txt|soa|aaaa]
// history :
// ver who when what
// --- --- -------- ------------------------------------------------------------
// 100 NSR 20141010 1. original effort (before Thanksgiving/Ocktoberfest)
// 101 NSR 20141014 1. a few more hacks (after Thanksgiving/Ocktoberfest)
// 102 NSR 20141015 1. started adding code to parse the result buffer
// 103 NSR 20141016 1. added more code to parse the result
// 104 NSR 20141017 1. now allow querytype to be specified from the command line
// 2. added support for a few additional querytypes
// 105 NSR 20141018 1. started adding mx support to the answer analysis section
// NSR 20141020 2. added expansion support to dname extraction
// 106 NSR 20141022 1. improvments to dname expansion (collapsed two functions into one)
// 2. added support for processing/displaying ns responses
// 3. added support for processing/displaying txt responses
// 4. added support for processing/displaying soa responses
// 107 NSR 20141103 1. added support for processing/displaying aaaa responses (IPv6)
// NSR 20141105 2. replaced my IPv6 display-hack with call to inet_ntop()
//==============================================================================
#include <sys/types.h> //
#include <netinet/in.h> // sin_addr
#include <arpa/nameser.h> // name server
#include <resolv.h> // resolver
//
#include <errno.h> // errno (obviously)
#include <netdb.h> // h_errno
#include <stdio.h> //
#include <string.h> //
#include <stdlib.h> // needed for malloc
#include <inet.h> // s_addr and sin_addr
//
// DNS header structure
//
struct DNS_HEADER {
unsigned short id; // identification number 2 bytes
unsigned char rd :1; // recursion desired 1 byte
unsigned char tc :1; // truncated message
unsigned char aa :1; // authoritive answer
unsigned char opcode :4; // purpose of message
unsigned char qr :1; // query/response flag
unsigned char rcode :4; // response code 1 byte
unsigned char cd :1; // checking disabled
unsigned char ad :1; // authenticated data
unsigned char z :1; // its z! reserved
unsigned char ra :1; // recursion available
unsigned short q_count; // number of question entries 2 bytes
unsigned short ans_count; // number of answer entries 2 bytes
unsigned short auth_count; // number of authority entries 2 bytes
unsigned short add_count; // number of resource entries 2 bytes
};
struct QUESTION { // QUERY Record
unsigned short qtype; // query type 2 bytes
unsigned short qclass; // query class 2 bytes
}; //
#pragma pack(push, 1)
struct R_DATA{ // Resource Record (constant portion)
unsigned short type; // RR TYPE a=1, mx=15 (see: RFC-1035)
unsigned short _class; // RR CLASS IN=1 (see: RFC-1035)
unsigned int ttl; // time-to-live in cache
unsigned short data_len; // RDLENGTH (see: RFC-1035)
};
#pragma pack(pop)
//
struct RES_RECORD { // Resource Record (whole thing)
unsigned char *name; //
struct R_DATA *resource; // resource data (fixed size portion)
unsigned char *rdata; // resource data (variable length portion)
};
//
// prototypes
//
void my_fgets(char* a, int b, FILE* c); //
unsigned char* ReadResponseName (unsigned char*, unsigned char*, int*); //
//
//==============================================================================
// main
//==============================================================================
int main(int argc, char** argv) {
int rc; // return code
int rv; // return value
int i, j; //
char cli_domain[127]; //
char cli_query[4]; // big enough for: AAAA
unsigned char onec; // one char :-)
unsigned char nsbuf[4096]; // name server buffer
unsigned char *reader; //
unsigned char *qname; // query name
unsigned char junkc; //
unsigned short *ptr_s; //
unsigned long *ptr_l; //
unsigned char temp[256]; // used in data expansion
int qtype; //
int stop; //
unsigned char *soa_mname; // only one soa will ever be returned
unsigned char *soa_rname; // ''
unsigned long soa_serial; // ''
unsigned long soa_refresh; // ''
unsigned long soa_retry; // ''
unsigned long soa_expire; // ''
unsigned long soa_min_ttl; //
#define MAX_REPLIES 30 // I have seen as many as 21
struct RES_RECORD answers[MAX_REPLIES], //
auth[MAX_REPLIES], //
addit[MAX_REPLIES]; // DNS replies
unsigned short pref[MAX_REPLIES]; // only valid with MX answers
struct DNS_HEADER *dns = NULL; // init
//--------------------------------------------------------------------------
#if 0 // ------------------
printf("-i-args: %d\n",argc); // debug cli args
for (i=0; i<argc;i++) { //
printf("-i-arg%d: %s\n",i,argv[i]); //
} //
#endif // ------------------
//--------------------------------------------------------------------------
//
// test command line args (good form dictates we copy CLI args now)
//
switch(argc) { // how many cli args?
case 2: //
sprintf(cli_query,"%s","A"); // default to address
break; //
case 3: //
if (strlen(argv[2])<=sizeof(cli_query)) { // no buffer overflows allowed here
strcpy(cli_query,argv[2]); //
}else{ //
printf("-e-oops, argument 2 (querytype) is too large\n"); //
return -1; //
} //
break; //
default: //
printf("Usage1: %s <domain>\n", argv[0]); //
printf("Usage2: %s <domain> querytype\n", argv[0]); //
printf(" where querytype is one of: A, MX, ANY, NS, TXT, SOA, AAAA\n");
return -1; //
} //
//
if (strlen(argv[1])<sizeof(cli_domain)) { // no buffer overflows allowed here
strcpy(cli_domain,argv[1]); //
}else{ //
printf("-e-oops, argument 1 (domain) is too large\n"); //
return -1; //
}
//--------------------------------------------------------------------------
qtype = 0; // init
if (strcasecmp(cli_query,"A")==0) qtype = ns_t_a; // address (ipv4)
if (strcasecmp(cli_query,"MX")==0) qtype = ns_t_mx; // mail exchange
if (strcasecmp(cli_query,"ANY")==0) qtype = ns_t_any; // wildcard
if (strcasecmp(cli_query,"NS")==0) qtype = ns_t_ns; // name server
if (strcasecmp(cli_query,"TXT")==0) qtype = ns_t_txt; // text
if (strcasecmp(cli_query,"SOA")==0) qtype = ns_t_soa; // start of authority
if (strcasecmp(cli_query,"AAAA")==0)qtype = ns_t_aaaa; // address (ipv6)
if (qtype==0) { //
printf("-e-oops, querytype must be one of: A, MX, ANY, NS, TXT, SOA, AAAA\n");
return -1; //
} //
printf("-i-testing domain: %s\n",cli_domain); //
printf("-i-qtype %d\n",qtype); //
//--------------------------------------------------------------------------
for (i=0; i<sizeof(nsbuf);i++) { // init ns buffer for hacking
nsbuf[i] = '\0'; //
} //
//
// quote from "DNS and BIND - third edition - chapter 14 - C programming and the Resolver Library Routines":
// res_search is the "highest level" resolver routine, and is called by gethostbyname. res_search applies the search
// algorithm to the domain name passed to it. That is, it takes the domain name it receives (dname), "completes" the
// name (if it's not fully qualified) by adding the various domain names from the resolver search list, and calls
// res_query until it receives a successful response, indicating that it found a valid, fully qualified domain name.
// In addition to implementing the search algorithm, res_search looks in the file referenced by your HOSTALIASES
// environment variable. (The HOSTALIASES variable was described in Chapter 6, "Configuring Hosts".) So it also takes
// care of any "private" host aliases you might have. res_search returns the size of the response or fills in h_errno
// and returns -1 if there was an error or the answer count is zero. (h_errno is like errno, but for DNS lookups.)
//
rv = res_search(cli_domain, ns_c_in, qtype, nsbuf, sizeof(nsbuf)); //
printf("rv : %ld ",rv); // display the return value
if (rv<0){ //
printf("(error)\n"); //
printf("errno : %ld\n",errno); // hacking
printf("herrno: %ld\n",h_errno); // hacking
return -1;
}else{ //
printf("(ok)\n"); //
} //
//
// if anything was written to the buffer then parse it
//
if(rv > 0) { //
//
// display the buffer (x-files; the truth is in there)
//
for (i=0; i<=rv; i++) { // "<=" will output one extra
onec = nsbuf[i]; //
printf("-i-index: %3d %3d ",i,onec); //
if (onec < 32){ //
printf(".\n"); //
goto nexti; //
} //
if (onec < 127){ //
printf("%c\n",onec); //
goto nexti; //
} //
printf(".\n"); //
nexti: //
} //
} //
//
if (rv<=0) goto adios; // nothing to parse
//
// parse the nsbuf using this dns view of the contents
//
dns = (struct DNS_HEADER *) &nsbuf; // prep a "dns-header view" into nsbuf
qname = (unsigned char*) &nsbuf[sizeof(struct DNS_HEADER)]; // set qname to data just past header
reader = &nsbuf[ sizeof(struct DNS_HEADER) + // fixed size (12 bytes)
(strlen((const char*)qname)+1) + // variable length string + 1
sizeof(struct QUESTION) ]; // fixed size (4 bytes)
//
printf("\nThe response contains: "); // so nslookup-like summary
printf("\n %d Question(s)", ntohs(dns->q_count)); //
printf("\n %d Answer(s)", ntohs(dns->ans_count)); //
printf("\n %d Authoritative Server(s)", ntohs(dns->auth_count)); //
printf("\n %d Additional record(s)\n\n", ntohs(dns->add_count)); //
//
// read answers
//
stop=0; // init stop count
for(i=0;i<ntohs(dns->ans_count);i++) { //
answers[i].name=ReadResponseName(reader,nsbuf,&stop); // fetch the expanded name
reader = reader + stop; // move our extraction pointer
//
answers[i].resource = (struct R_DATA*)(reader); // copy fixed sized stuff (contents of reader)
reader = reader + sizeof(struct R_DATA); // jump to next section
//
answers[i].rdata = (unsigned char*)malloc(256); // need more space for dname expansion
pref[i]= 0; // init for all (mx will change)
switch(ntohs(answers[i].resource->type)) { //
case ns_t_mx: // if mail exchange
ptr_s = (unsigned short *) reader; // point at short
pref[i] = ntohs( *(ptr_s) ); // extract mx preference
reader = reader + 2; // skip over short
answers[i].rdata = ReadResponseName(reader,nsbuf,&stop); // fetch the expanded name
reader = reader + stop; // move our extraction pointer
break; //
case ns_t_soa: // if start-of-authority
soa_mname = ReadResponseName(reader,nsbuf,&stop); // fetch the expanded name
reader = reader + stop; // move our extraction pointer
soa_rname = ReadResponseName(reader,nsbuf,&stop); // fetch the expanded name
reader = reader + stop; // move our extraction pointer
ptr_l = (unsigned long *) reader; // point at long
soa_serial = ntohl( *(ptr_l) ); // extract soa_serial
ptr_l++; // move our extraction pointer
reader = reader + 4; // move our extraction pointer 4 bytes
soa_refresh = ntohl( *(ptr_l) ); // extract soa_refresh
ptr_l++; // move our extraction pointer
reader = reader + 4; // move our extraction pointer 4 bytes
soa_retry = ntohl( *(ptr_l) ); // soa_retry
ptr_l++; // move our extraction pointer
reader = reader + 4; // move our extraction pointer 4 bytes
soa_expire = ntohl( *(ptr_l) ); // soa_expire
ptr_l++; // move our extraction pointer
reader = reader + 4; // move our extraction pointer 4 bytes
soa_min_ttl = ntohl( *(ptr_l) ); // soa_min_ttl
ptr_l++; // move our extraction pointer
reader = reader + 4; // move our extraction pointer 4 bytes
break; //
case ns_t_txt: // if text record
junkc = (unsigned char) *reader++; // get string length
for (unsigned char x=0; x<junkc; x++){
answers[i].rdata[x] = *reader++;
answers[i].rdata[x+1] ='\0';
}
break;
case ns_t_ns: // if name server
answers[i].rdata = ReadResponseName(reader,nsbuf,&stop); // fetch the expanded name
reader = reader + stop; // move our extraction pointer
for (char x=0;x<=9;x++){
printf("-x- %d %d\n",x,reader[x]);
}
break; //
default: //
answers[i].rdata = (unsigned char*)malloc(ntohs(answers[i].resource->data_len));
for(j=0 ; j<ntohs(answers[i].resource->data_len) ; j++) { //
answers[i].rdata[j]=reader[j]; //
} //
answers[i].rdata[ntohs(answers[i].resource->data_len)] = '\0'; //
reader = reader + ntohs(answers[i].resource->data_len); //
} //
}
//
// read authorities
//
for(i=0;i<ntohs(dns->auth_count);i++) { //
auth[i].name=ReadResponseName(reader,nsbuf,&stop); //
reader+=stop; //
auth[i].resource=(struct R_DATA*)(reader); //
reader+=sizeof(struct R_DATA); //
auth[i].rdata=ReadResponseName(reader,nsbuf,&stop); //
reader+=stop; //
}
//
// read additional
//
for(i=0;i<ntohs(dns->add_count);i++) { //
addit[i].name=ReadResponseName(reader,nsbuf,&stop); //
reader+=stop; //
addit[i].resource=(struct R_DATA*)(reader); //
reader+=sizeof(struct R_DATA); //
addit[i].rdata = (unsigned char*)malloc(ntohs(addit[i].resource->data_len));
for(j=0;j<ntohs(addit[i].resource->data_len);j++) { //
addit[i].rdata[j]=reader[j]; //
} //
addit[i].rdata[ntohs(addit[i].resource->data_len)]='\0'; //
reader+=ntohs(addit[i].resource->data_len); //
} //
//
// display answers (style similar to nslookup.c)
//
printf("Answer Records: %d \n" , ntohs(dns->ans_count) ); //
for(i=0 ; i < ntohs(dns->ans_count) ; i++) { //
printf("Name: %s ",answers[i].name); //
char disp; //
disp = 0; // init
if ( ntohs(answers[i].resource->type ) == ns_t_a) { // if "IPv4 address" record
long *p; // need a pointer for the switcheroo
p=(long*)answers[i].rdata; // get the address of this response
struct sockaddr_in a; // declare an IPv4 socket
a.sin_addr.s_addr=(*p); // copy buffer data to socket
printf("has IPv4 address = %s",inet_ntoa(a.sin_addr)); //
disp = 1; // something was output
} //
if ( ntohs(answers[i].resource->type ) == ns_t_cname) { // if a "Canonical name for an alias"
printf("has alias name: %s",answers[i].rdata); //
disp = 1; // something was output
} //
if ( ntohs(answers[i].resource->type ) == ns_t_mx) { // if mail exchange
printf("preference %d name: %s",pref[i],answers[i].rdata); //
disp = 1; // something was output
}
if ( ntohs(answers[i].resource->type ) == ns_t_txt) { // if text
printf("text: %s",answers[i].rdata); //
disp = 1; // something was output
}
if ( ntohs(answers[i].resource->type ) == ns_t_soa) { // if start-of-authority
printf("\n soa mname : %s\n",soa_mname); //
printf(" soa mname : %s\n",soa_rname); //
printf(" soa serial : %ld\n",soa_serial ); //
printf(" soa refresh: %ld\n",soa_refresh ); //
printf(" soa retry : %ld\n",soa_retry ); //
printf(" soa expire : %ld\n",soa_expire ); //
printf(" soa min ttl: %ld\n",soa_min_ttl );
disp = 1; // something was output
}
if ( ntohs(answers[i].resource->type ) == ns_t_ns) { // if name server record
printf("nameserver = %s",answers[i].rdata); //
disp = 1; // something was output
}
//
// notes:
// 1) domain "google.com" had at least one IPv6 address the day I tested this
// 2) For version 107.1 I just scooped data directly from my buffer.
// 3) For version 107.2 I populate an IPv6 socket then called inet_ntop()
//
if ( ntohs(answers[i].resource->type ) == ns_t_aaaa) { // if "IPv6 address" record
/* ---- // scoop data directly from my buffer
printf("has IPv6 address = "); //
unsigned char *p; // need a pointer
p = answers[i].rdata; // get the address of this response
for(char i=0; i<=15; i++){ // network order
printf("%x", *(p+i));
if (i<15){
printf(":");
}else{
printf("\n");
}
}
--- */
long *p; // populate an IPv6 socket
p=(long*)answers[i].rdata; // get the address of this response
struct sockaddr_in6 a6; // declare an IPv6 socket
a6.sin6_addr.s6_laddr[0]=(*p++); // copy
a6.sin6_addr.s6_laddr[1]=(*p++); // copy
a6.sin6_addr.s6_laddr[2]=(*p++); // copy
a6.sin6_addr.s6_laddr[3]=(*p ); // copy
char yada[100]; // needed by inet_ntop()
printf("has IPv6 address = %s", //
inet_ntop(AF_INET6,&a6.sin6_addr,yada,sizeof(yada)) ); //
disp = 1; // something was output
} //
if (disp==0) { // if nothing output
printf("(no code written for resource type: %ld)",ntohs(answers[i].resource->type));
} //
printf("\n"); //
} //
//
// display authorities (style similar to nslookup.c)
//
printf("\nAuthoritive Records: %d \n" , ntohs(dns->auth_count) ); //
for( i=0 ; i < ntohs(dns->auth_count) ; i++) { //
printf("Name: %s ",auth[i].name); //
if(ntohs(auth[i].resource->type) == ns_t_ns) { // if a name server record
printf("has nameserver : %s",auth[i].rdata); //
} //
printf("\n"); //
} //
//
// display additional resource records (style similar to nslookup.c)
//
printf("\nAdditional Records: %d \n" , ntohs(dns->add_count) ); //
for(i=0; i < ntohs(dns->add_count) ; i++) { //
printf("Name: %s ",addit[i].name); //
char disp; //
disp = 0; //
if(ntohs(addit[i].resource->type) == ns_t_a) { // if an IPv4 address record
long *p; //
p=(long*)addit[i].rdata; //
struct sockaddr_in a; //
a.sin_addr.s_addr=(*p); //
printf("has IPv4 address = %s",inet_ntoa(a.sin_addr)); //
disp = 1; //
} //
if (disp==0) { // if nothing output
printf("(no code written for resource type: %ld)",ntohs(addit[i].resource->type));
} //
printf("\n"); //
printf("\n"); //
} //
adios:
return 1;
}
//==============================================================================
// read name of one dns response record
// 1) reader moves along (looking into buffer)
// 2) extracted data is copied to local variable 'name' which is returned
// 3) we only count data in this record; not data after a jump during expansion
// 4) data jumps offer a form of compression where one answer may contain a
// complete dname but subsequent answers may point at a fragment of a
// previously mentioned dname
//==============================================================================
u_char* ReadResponseName(unsigned char* reader, unsigned char* buffer, int* count) {
unsigned char *name; //
unsigned int p=0, jumped=0, offset; //
int i , j; //
*count = 1; // init
name = (unsigned char*)malloc(256); // allocate some memory
name[0]='\0'; // init
//
// read domain names
//
while (*reader!=0) { // if <> EOF
if (*reader>=192) { // if jump indicator (see rfc-1035)
offset = (((*(reader)-192))*256) + *(reader+1); // compute offset into buffer
reader = buffer + offset - 1; // jump to new location
jumped = 1; // remember that we jumped (stop counting)
}else{ //
name[p++]=*reader; //
} //
reader = reader+1; // advance pointer
if(jumped==0) { // if we didn't jump...
*count = *count + 1; // ...then okay to count
} //
} //
name[p]='\0'; // string complete
if(jumped==1) { // if we ever jumped ...
*count = *count + 1; // then count one jump as data
} //
// at this point, 'name' contains our dname without jumps
//
// convert: 3www6google3com0 to www.google.com
// +---+------+---+--- ascii values indicate stub length
//
for(i=0;i<(int)strlen((const char*)name);i++) { // scan string
p=name[i]; // isolate int (the stub length)
for(j=0;j<(int)p;j++) { // scan...
name[i]=name[i+1]; // ...and shift
i=i+1; //
} //
name[i]='.'; // need a dot here
}
name[i-1]='\0'; // remove the last dot
return name; //
}
//==============================================================================
// my fgets (read a string; don't overflow the variable; don't store trailing white space)
//==============================================================================
void my_fgets(char* a, int b, FILE* c) { //
int x; //
fgets(a,b,c); //
x = strlen(a); //
loop:;
switch (a[x-1]) { //
case '\t': // htab
case '\r': // carriage return
case '\n': // line-feed
a[x-1] = '\0'; //
x--;
goto loop;
default: //
} //
} //