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:								//
    }										//
}										//