resolve.c
1b71270a
 /* $Id$*/
 
 /* #include <arpa/nameser.h> -- included from resolve.h*/
 #include <netinet/in.h>
 #include <resolv.h>
 #include <string.h>
 
 #include "resolve.h"
 #include "dprint.h"
 #include "mem/mem.h"
 
 
 
 /* mallocs for local stuff */
 #define local_malloc pkg_malloc
 #define local_free   pkg_free
 
 
 
  /*  skips over a domain name in a dns message
  *  (it can be  a sequence of labels ending in \0, a pointer or
  *   a sequence of labels ending in a pointer -- see rfc1035
  *   returns pointer after the domain name or null on error*/
 unsigned char* dns_skipname(unsigned char* p, unsigned char* end)
 {
 	while(p<end){
 		/* check if \0 (root label length) */
 		if (*p==0){
 			p+=1;
 			break;
 		}
 		/* check if we found a pointer */
 		if (((*p)&0xc0)==0xc0){
 			/* if pointer skip over it (2 bytes) & we found the end */
 			p+=2;
 			break;
 		}
 		/* normal label */
 		p+=*p+1;
 	}
 	return (p>end)?0:p;
 }
 
 
 
 /* parses the srv record into a srv_rdata structure
  *   msg   - pointer to the dns message
  *   end   - pointer to the end of the message
  *   rdata - pointer  to the rdata part of the srv answer
  * returns 0 on error, or a dyn. alloc'ed srv_rdata structure */
 /* SRV rdata format:
  *            111111
  *  0123456789012345
  * +----------------+
  * |     priority   |
  * |----------------|
  * |     weight     |
  * |----------------|
  * |   port number  |
  * |----------------|
  * |                |
  * ~      name      ~
  * |                |
  * +----------------+
  */
 struct srv_rdata* dns_srv_parser( unsigned char* msg, unsigned char* end,
 								  unsigned char* rdata)
 {
 	struct srv_rdata* srv;
 	int len;
 	
 	srv=0;
 	if ((rdata+6)>end) goto error;
 	srv=(struct srv_rdata*)local_malloc(sizeof(struct srv_rdata));
 	if (srv==0){
4c55b6cf
 		LOG(L_ERR, "ERROR: dns_srv_parser: out of memory\n");
1b71270a
 		goto error;
 	}
 	
 	memcpy((void*)&srv->priority, rdata, 2);
 	memcpy((void*)&srv->weight,   rdata+2, 2);
 	memcpy((void*)&srv->port,     rdata+4, 2);
 	rdata+=6;
 	srv->priority=ntohs(srv->priority);
 	srv->weight=ntohs(srv->weight);
 	srv->port=ntohs(srv->port);
4c55b6cf
 	if ((len=dn_expand(msg, end, rdata, srv->name, MAX_DNS_NAME-1))==-1)
1b71270a
 		goto error;
4c55b6cf
 	/* add terminating 0 ? (warning: len=compressed name len) */
1b71270a
 	return srv;
 error:
 	if (srv) local_free(srv);
 	return 0;
 }
 
 
 
4c55b6cf
 /* parses a CNAME record into a cname_rdata structure */
 struct cname_rdata* dns_cname_parser( unsigned char* msg, unsigned char* end,
 									  unsigned char* rdata)
 {
 	struct cname_rdata* cname;
 	int len;
 	
 	cname=0;
 	cname=(struct cname_rdata*)local_malloc(sizeof(struct cname_rdata));
 	if(cname==0){
 		LOG(L_ERR, "ERROR: dns_cname_parser: out of memory\n");
 		goto error;
 	}
 	if ((len=dn_expand(msg, end, rdata, cname->name, MAX_DNS_NAME-1))==-1)
 		goto error;
 	return cname;
 error:
 	if (cname) local_free(cname);
 	return 0;
 }
 
 
 
1b71270a
 /* parses an A record rdata into an a_rdata structure
  * returns 0 on error or a dyn. alloc'ed a_rdata struct
  */
 struct a_rdata* dns_a_parser(unsigned char* rdata, unsigned char* end)
 {
 	struct a_rdata* a;
 	
 	if (rdata+4>end) goto error;
 	a=(struct a_rdata*)local_malloc(sizeof(struct a_rdata));
 	if (a==0){
 		LOG(L_ERR, "ERROR: dns_a_parser: out of memory\n");
 		goto error;
 	}
 	memcpy(a->ip, rdata, 4);
 	return a;
 error:
 	return 0;
 }
 
 
 
 /* parses an AAAA (ipv6) record rdata into an aaaa_rdata structure
  * returns 0 on error or a dyn. alloc'ed aaaa_rdata struct */
 struct aaaa_rdata* dns_aaaa_parser(unsigned char* rdata, unsigned char* end)
 {
 	struct aaaa_rdata* aaaa;
 	
 	if (rdata+16>end) goto error;
 	aaaa=(struct aaaa_rdata*)local_malloc(sizeof(struct aaaa_rdata));
 	if (aaaa==0){
 		LOG(L_ERR, "ERROR: dns_aaaa_parser: out of memory\n");
 		goto error;
 	}
 	memcpy(aaaa->ip6, rdata, 16);
 	return aaaa;
 error:
 	return 0;
 }
 
 
 
 /* frees completely a struct rdata list */
 void free_rdata_list(struct rdata* head)
 {
 	struct rdata* l;
 	for(l=head; l; l=l->next){
 		/* free the parsed rdata*/
 		if (l->rdata) local_free(l->rdata);
 		local_free(l);
 	}
 }
 
 
 
 /* gets the DNS records for name:type
  * returns a dyn. alloc'ed struct rdata linked list with the parsed responses
  * or 0 on error
  * see rfc1035 for the query/response format */
 struct rdata* get_record(char* name, int type)
 {
 	int size;
 	int qno, answers_no;
5bc22c81
 	int r;
1b71270a
 	int ans_len;
 	static union dns_query buff;
 	unsigned char* p;
 	unsigned char* t;
 	unsigned char* end;
 	static unsigned char answer[ANS_SIZE];
 	unsigned short rtype, class, rdlength;
 	unsigned int ttl;
 	struct rdata* head;
 	struct rdata** crt;
 	struct rdata* rd;
 	struct srv_rdata* srv_rd;
 	struct srv_rdata* crt_srv;
 	
 	
 	
 	head=rd=0;
 	crt=&head;
 	size=res_search(name, C_IN, type, buff.buff, sizeof(buff));
072cead0
 	if (size<0) {
 		LOG(L_ERR, "ERROR: get_record: size<0\n");
 		goto error;
 	}
1b71270a
 	else if (size > sizeof(buff)) size=sizeof(buff);
 	
 	p=buff.buff+DNS_HDR_SIZE;
 	end=buff.buff+size;
 	if (p>end) goto error_boundary;
 	qno=ntohs((unsigned short)buff.hdr.qdcount);
 
 	for (r=0; r<qno; r++){
 		/* skip the name of the question */
072cead0
 		if ((p=dns_skipname(p, end))==0) {
 			LOG(L_ERR, "ERROR: get_record: skipname==0\n");
 			goto error;
 		}
1b71270a
 		p+=2+2; /* skip QCODE & QCLASS */
 	#if 0
 		for (;(p<end && (*p)); p++);
 		p+=1+2+2; /* skip the ending  '\0, QCODE and QCLASS */
 	#endif
072cead0
 		if (p>end) {
 			LOG(L_ERR, "ERROR: get_record: p>end\n");
 			goto error;
 		}
1b71270a
 	};
 	answers_no=ntohs((unsigned short)buff.hdr.ancount);
 	ans_len=ANS_SIZE;
 	t=answer;
 	for (r=0; (r<answers_no) && (p<end); r++){
 		/*  ignore it the default domain name */
072cead0
 		if ((p=dns_skipname(p, end))==0) {
 			LOG(L_ERR, "ERROR: get_record: skip_name=0 (#2)\n");
 			goto error;
 		}
1b71270a
 		/*
 		skip=dn_expand(buff.buff, end, p, t, ans_len);
 		p+=skip;
 		*/
 		/* check if enough space is left fot type, class, ttl & size */
 		if ((p+2+2+4+2)>end) goto error_boundary;
 		/* get type */
 		memcpy((void*) &rtype, (void*)p, 2);
 		rtype=ntohs(rtype);
 		p+=2;
 		/* get  class */
 		memcpy((void*) &class, (void*)p, 2);
 		class=ntohs(class);
 		p+=2;
 		/* get ttl*/
 		memcpy((void*) &ttl, (void*)p, 4);
 		ttl=ntohl(ttl);
 		p+=4;
 		/* get size */
 		memcpy((void*)&rdlength, (void*)p, 2);
 		rdlength=ntohs(rdlength);
 		p+=2;
 		/* check for type */
4c55b6cf
 		/*
1b71270a
 		if (rtype!=type){
5415e40b
 			LOG(L_ERR, "WARNING: get_record: wrong type in answer (%d!=%d)\n",
 					rtype, type);
1b71270a
 			p+=rdlength;
 			continue;
 		}
 		*/
4c55b6cf
 		/* expand the "type" record  (rdata)*/
 		
1b71270a
 		rd=(struct rdata*) local_malloc(sizeof(struct rdata));
 		if (rd==0){
 			LOG(L_ERR, "ERROR: get_record: out of memory\n");
 			goto error;
 		}
 		rd->type=rtype;
 		rd->class=class;
 		rd->ttl=ttl;
4c55b6cf
 		rd->next=0;
 		switch(rtype){
1b71270a
 			case T_SRV:
 				srv_rd= dns_srv_parser(buff.buff, end, p);
 				rd->rdata=(void*)srv_rd;
 				if (srv_rd==0) goto error_parse;
 				
 				/* insert sorted into the list
 				 * crt reused */
 				for (crt=&head; *crt; crt= &((*crt)->next)){
 					crt_srv=(struct srv_rdata*)(*crt)->rdata;
 					if ((srv_rd->priority <  crt_srv->priority) ||
 					   ( (srv_rd->priority == crt_srv->priority) && 
 							 (srv_rd->weight > crt_srv->weight) ) ){
 						/* insert here */
 						break;
 					}
 				}
 				/* insert here */
 				rd->next=*crt;
 				*crt=rd;
 				
 				break;
 			case T_A:
 				rd->rdata=(void*) dns_a_parser(p,end);
 				if (rd->rdata==0) goto error_parse;
 				*crt=rd; /* crt points to the last "next" or the list head*/
 				crt=&(rd->next);
 				break;
 			case T_AAAA:
 				rd->rdata=(void*) dns_aaaa_parser(p,end);
 				if (rd->rdata==0) goto error_parse;
 				*crt=rd;
 				crt=&(rd->next);
 				break;
4c55b6cf
 			case T_CNAME:
 				rd->rdata=(void*) dns_cname_parser(buff.buff, end, p);
 				if(rd->rdata==0) goto error_parse;
 				*crt=rd;
 				crt=&(rd->next);
 				break;
1b71270a
 			default:
4c55b6cf
 				LOG(L_ERR, "WARNING: get_record: unknown type %d\n", rtype);
1b71270a
 				rd->rdata=0;
4c55b6cf
 				*crt=rd;
 				crt=&(rd->next);
1b71270a
 		}
 		
 		p+=rdlength;
 		
 	}
 	return head;
 error_boundary:
 		LOG(L_ERR, "ERROR: get_record: end of query buff reached\n");
 		return 0;
 error_parse:
 		LOG(L_ERR, "ERROR: get_record: rdata parse error \n");
 		if (rd) local_free(rd); /* rd->rdata=0 & rd is not linked yet into
 								   the list */
 error:
 		LOG(L_ERR, "ERROR: get_record \n");
 		if (head) free_rdata_list(head);
 	return 0;
 }
 
5bc22c81
 
 
 /* resolves a host name trying SRV lookup if *port==0 or normal A/AAAA lookup
  * if *port!=0.
  * returns: hostent struct & *port filled with the port from the SRV record;
  *  0 on error
  */
 struct hostent* sip_resolvehost(char* name, unsigned short* port)
 {
 	struct hostent* he;
 	struct rdata* head;
 	struct rdata* l;
 	struct srv_rdata* srv;
 	static char tmp[MAX_DNS_NAME]; /* tmp. buff. for SRV lookups */
 	int len;
 
 	/* try SRV if no port specified (draft-ietf-sip-srv-06) */
 	if ((port)&&(*port==0)){
 		*port=SIP_PORT; /* just in case we don't find another */
 		len=strlen(name);
 		if ((len+SRV_PREFIX_LEN+1)>MAX_DNS_NAME){
 			LOG(L_WARN, "WARNING: sip_resolvehost: domain name too long (%d),"
 						" unable to perform SRV lookup\n", len);
 		}else{
 			memcpy(tmp, SRV_PREFIX, SRV_PREFIX_LEN);
 			memcpy(tmp+SRV_PREFIX_LEN, name, len+1); /*include the ending 0*/
 			
 			head=get_record(tmp, T_SRV);
 			for(l=head; l; l=l->next){
 				if (l->type!=T_SRV) continue; /*should never happen*/
 				srv=(struct srv_rdata*) l->rdata;
 				if (srv==0){
 					LOG(L_CRIT, "sip_resolvehost: BUG: null rdata\n");
 					free_rdata_list(head);
 					break;
 				}
 				he=resolvehost(srv->name);
 				if (he!=0){
 					/* we found it*/
 					DBG("sip_resolvehost: SRV(%s) = %s:%d\n",
 							tmp, srv->name, srv->port);
 					*port=srv->port;
 					free_rdata_list(head); /*clean up*/
 					return he;
 				}
 			}
 			DBG("sip_resolvehost: not SRV record found for %s," 
 					" trying 'normal' lookup...\n", name);
 		}
 	}
 	he=resolvehost(name);
 	return he;
 }