/*
 * Copyright (C) 2002 scott campbell <axonpotential@yahoo.com>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 *
 * --------------------------------------------------------------------------------
 * sp_template sp_dns_head.c version 1.1 07/08/02
 * 
 * Purpose:
 * The sp_dns_head plugin defines an api to create alerts based on DNS
 * header and data structure information.
 *
 * Arguments:
 * See the documentation web page for a complete listing of plugin 
 * options.  it can be found at http://www.geocities.com/axonpotential/snort/index.html
 *
 * Comments:
 * Be careful with the selection of operator.  Those marked as unusually resource consuming
 * will indeed do that and should be used with great care.
 * Potential problem with the DnsRecord data type - I assume that there will be 24 or less
 * records returned in the packet.  In all reality this should be safe.
 */

/* put the name of your pluging header file here */
#include "sp_dns_head.h"

extern char *file_name;  /* this is the file name from rules.c, generally used
                            for error messages */
extern int file_line;    /* this is the file line number from rules.c that is
                            used to indicate file lines for error messages */
static const char     digits[] = "0123456789";

/****************************************************************************
 * 
 * Function: SetupDNSTemplate()
 *
 * Purpose: Generic detection engine plugin template.  Registers the
 *          configuration function and links it to a rule keyword.  This is
 *          the function that gets called from InitPlugins in plugbase.c.
 *
 * Arguments: None.
 *
 * Returns: void function
 *
 ****************************************************************************/
void SetupDNSTemplate()
{
    /* map the keyword to an initialization/processing function */
    RegisterPlugin("dns", DNSTemplateInit);

#ifdef DEBUG
    printf("Plugin: DNS Template Setup\n");
#endif
}


/****************************************************************************
 * 
 * Function: DNSTemplateInit(char *, OptTreeNode *)
 *
 * Purpose: Generic rule configuration function.  Handles parsing the rule 
 *          information and attaching the associated detection function to
 *          the OTN.
 *
 * Arguments: data => rule arguments/data
 *            otn => pointer to the current rule option list node
 *
 * Returns: void function
 *
 ****************************************************************************/
void DNSTemplateInit(char *data, OptTreeNode *otn, int protocol)
{
    /* be sure to check that the protocol that is passed in matches the
       transport layer protocol that you're using for this rule! */
    //if(protocol != IPPROTO_UDP)
    if((protocol != IPPROTO_UDP) && (protocol != IPPROTO_TCP))
    {
	    FatalError("ERROR %s(%d) => Bad protocol in DNS header rule...\n", file_name, file_line);
    }

    /* allocate the data structure and attach it to the
       rule's data struct list */
    otn->ds_list[PLUGIN_DNS_HEAD] = (DnsHeadData *) calloc(sizeof(DnsHeadData), sizeof(char));

    /* this is where the keyword arguments are processed and placed into the 
       rule option's data structure */
    ParseDNS(data, otn);

    /* finally, attach the option's detection function to the rule's 
       detect function pointer list */
    AddOptFuncToList(DnsDetectorFunction, otn);
}



/****************************************************************************
 * 
 * Function: ParseDNS(char *, OptTreeNode *)
 *
 * Purpose: This is the function that is used to process the option keyword's
 *          arguments and attach them to the rule's data structures.
 *
 * Arguments: data => argument data
 *            otn => pointer to the current rule's OTN
 *
 * Returns: void function
 *
 ****************************************************************************/
void ParseDNS(char *data, OptTreeNode *otn)
{
    DnsHeadData *ds_ptr;  /* data struct pointer */
    char **toks;
    int num_toks = 4;
    int i = 0;

    /* set the ds pointer to make it easier to reference the option's
       particular data struct */
    ds_ptr = otn->ds_list[PLUGIN_DNS_HEAD];

    /* strip out white space */
    while(isspace((int)*data)) data++;

	toks = mSplit(data, ",", 4, &num_toks, '\\');

	while ( i < num_toks)
	{
		//printf("looping in token reader... %s \n", toks[i]);
		/* start the compare functions.  This us ugly.  Real ugly. */
		if(!strcasecmp(toks[i], "QUERY"))
		{
			ds_ptr->value = DNS_QUERY;
			if(!strcasecmp(toks[i+1], "Q"))
				ds_ptr->type = DNS_QUERY_Q;
			else if(!strcasecmp(toks[i+1], "R"))
				ds_ptr->type = DNS_QUERY_R;
			else FatalError("ERROR %s(%d) - dns_header rule.\n", file_name, file_line);
		}
		else if(!strcasecmp(toks[i], "OPCODE"))
		{
			ds_ptr->value = DNS_OPCODE;
			if(!strcasecmp(toks[i+1], "SQ"))
				ds_ptr->type = DNS_OPCODE_SQ;
			else if(!strcasecmp(toks[i+1], "IQ"))
				ds_ptr->type = DNS_OPCODE_IQ;
			else if(!strcasecmp(toks[i+1], "SS"))
				ds_ptr->type = DNS_OPCODE_SS;
			else if(!strcasecmp(toks[i+1], "OTHER"))
				ds_ptr->type = DNS_OPCODE_OTHER;
			else FatalError("ERROR %s(%d) - dns_header rule.\n", file_name, file_line);
		}
		else if(!strcasecmp(toks[i], "AA"))
		{
			ds_ptr->value = DNS_AA;
			if(!strcasecmp(toks[i+1], "N"))
				ds_ptr->type = DNS_AA_N;
			if(!strcasecmp(toks[i+1], "Y"))
				ds_ptr->type = DNS_AA_Y;
		}
		else if(!strcasecmp(toks[i], "TC"))
		{
			ds_ptr->value = DNS_TC;
			if(!strcasecmp(toks[i+1], "N"))
				ds_ptr->type = DNS_TC_N;
			if(!strcasecmp(toks[i+1], "Y"))
				ds_ptr->type = DNS_TC_Y;
		}
		else if(!strcasecmp(toks[i], "RD"))
		{
			ds_ptr->value = DNS_RD;
			if(!strcasecmp(toks[i+1], "N"))
				ds_ptr->type = DNS_RD_N;
			if(!strcasecmp(toks[i+1], "Y"))
				ds_ptr->type = DNS_RD_Y;
		}
		else if(!strcasecmp(toks[i], "RA"))
		{
			ds_ptr->value = DNS_RA;
			if(!strcasecmp(toks[i+1], "N"))
				ds_ptr->type = DNS_RA_N;
			if(!strcasecmp(toks[i+1], "Y"))
				ds_ptr->type = DNS_RA_Y;
		}
		else if(!strcasecmp(toks[i], "RCODE"))
		{
			ds_ptr->value = DNS_RCODE;
			if(!strcasecmp(toks[i+1], "NAMEERR"))
				ds_ptr->type = DNS_RCODE_NAMEERR;
			else FatalError("ERROR %s(%d) - dns_header rule.\n", file_name, file_line);
		}
		else if(!strcasecmp(toks[i], "QTYPE"))
		{
			ds_ptr->value = DNS_QTYPE;
			if(!strcasecmp(toks[i+1], "A"))
				ds_ptr->type = DNS_QTYPE_A;
			else if(!strcasecmp(toks[i+1], "NS"))
				ds_ptr->type = DNS_QTYPE_NS;
			else if(!strcasecmp(toks[i+1], "PTR"))
				ds_ptr->type = DNS_QTYPE_PTR;
			else if(!strcasecmp(toks[i+1], "HINFO"))
				ds_ptr->type = DNS_QTYPE_HINFO;
			else if(!strcasecmp(toks[i+1], "MX"))
				ds_ptr->type = DNS_QTYPE_MX;
			else if(!strcasecmp(toks[i+1], "AXFR"))
				ds_ptr->type = DNS_QTYPE_AXFR;
			else if(!strcasecmp(toks[i+1], "ANY"))
				ds_ptr->type = DNS_QTYPE_ANY;
			else if(!strcasecmp(toks[i+1], "SOA"))
				ds_ptr->type = DNS_QTYPE_SOA;
			else FatalError("ERROR %s(%d) - dns_header rule.\n", file_name, file_line);
		}
		else if(!strcasecmp(toks[i], "QCLASS"))
		{
			ds_ptr->value = DNS_QCLASS;
			if(!strcasecmp(toks[i+1], "I"))
				ds_ptr->type = DNS_QCLASS_I;
			else if(!strcasecmp(toks[i+1], "X"))
				ds_ptr->type = DNS_QCLASS_X;
		}
		else if(!strcasecmp(toks[i], "TTL"))
		{
			ds_ptr->value = DNS_TTL;
			ds_ptr->type = atoi(toks[i+1]);
		}

		i++;
	} //end of token loop

}


/****************************************************************************
 * 
 * Function: DnsDetectorFunction(char *, OptTreeNode *)
 *
 * Purpose: Use this function to perform the particular detection routine
 *          that this rule keyword is supposed to encompass.
 *
 * Arguments: data => argument data
 *            otn => pointer to the current rule's OTN
 *
 * Returns: If the detection test fails, this function *must* return a zero!
 *          On success, it calls the next function in the detection list 
 *
 ****************************************************************************/
int DnsDetectorFunction(Packet *p, struct _OptTreeNode *otn, OptFpList *fp_list)
{
	DnsHeadData	*ds_ptr;
	int scs = 0; /* have single return value ... 1 = success*/

	ds_ptr=otn->ds_list[PLUGIN_DNS_HEAD];

	/* quick check - packets must be of sane size to continue */
	if(p->dsize < (sizeof(DNS_HEADER) + QFIXEDSZ))
	{
		//printf("TOO SHORT!\n");
		return 0;
	}

	/* fill in the p->dns_head structure */
	p->dns_head = (DNS_HEADER *) p->data;

	/* want to see the packet headers go by?? Uncomment the folowing line */
	//dumpPacket(p);

	if(ds_ptr->value == DNS_QUERY)
	{
		if((ds_ptr->type == DNS_QUERY_Q) && (ntohs(p->dns_head->qr) == 0))
		{ scs = 1; }
		else if((ds_ptr->type == DNS_QUERY_R) && (ntohs(p->dns_head->qr) == 1))
		{ scs = 1; }
	}
	else if(ds_ptr->value == DNS_OPCODE)
	{
		if((ds_ptr->type == DNS_OPCODE_SQ) && (ntohs(p->dns_head->opcode) == 0))
		{ scs = 1; }
		else if((ds_ptr->type == DNS_OPCODE_IQ) && (ntohs(p->dns_head->opcode) == 1))
		{ scs = 1; }
		else if((ds_ptr->type == DNS_OPCODE_SS) && (ntohs(p->dns_head->opcode) == 2))
		{ scs = 1; }
		else if((ds_ptr->type == DNS_OPCODE_OTHER) && (ntohs(p->dns_head->opcode) > 2))
			scs = 1;
	}
	else if(ds_ptr->value == DNS_RCODE)
	{
		if((ds_ptr->type == DNS_RCODE_NAMEERR) && (ntohs(p->dns_head->rcode) == 3))
		{ scs = 1; }
	}
	else if((ds_ptr->value == DNS_TTL) || (ntohs(ds_ptr->value) == DNS_QTYPE) || 
			(ds_ptr->value == DNS_QCLASS))
	{
		/* we need to do a little digging at this point to
		 * get the required information.  All the crunching
		 * and variable decloration (etc) will be moved out
		 * of this loop in the name of speed.
		 */
		int count;
		DnsRecord	in_data;
		in_data = GatherInnerData(p); /* in_data contains all the data we need */

		/* check to see if the loop crashed out on bad data.  don't want to kill 
		 * snort from data streaming down port 53 
		 */
		if(in_data.error == 1)
		{
			//printf("AIEEEEE!!!\n");
			return 0;
		}

		for(count=0; count < (ntohs(p->dns_head->ancount) + ntohs(p->dns_head->nscount) + 
					ntohs(p->dns_head->arcount)); count++)
		{
			/* this is set up so that the loop runs only once.
			 * and and all tests are done at the same time
			 * in case there is more than one out of this group
			 */
			if(ds_ptr->value == DNS_TTL)
			{
				if(in_data.ttl[count] < ds_ptr->type)
					scs = 1;
			}
			else if(ds_ptr->value == DNS_QCLASS)
			{
				if((ds_ptr->type == DNS_QCLASS_I) && (in_data.class[count] == 1))
				{
					scs = 1;
				}
				else if((ds_ptr->type == DNS_QCLASS_X) && (in_data.class[count] != 1))
				{
					scs = 1;
				}
			}
			else if(ds_ptr->value == DNS_QTYPE)
			{
				if((ds_ptr->type == DNS_QTYPE_A) && (in_data.type[count] == 1))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_NS) && (in_data.type[count] == 2))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_CNAME) && (in_data.type[count] == 5))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_PTR) && (in_data.type[count] == 12))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_HINFO) && (in_data.type[count] == 13))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_MX) && (in_data.type[count] == 15))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_AXFR) && (in_data.type[count] == 252))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_ANY) && (in_data.type[count] == 255))
					scs = 1;
				else if((ds_ptr->type == DNS_QTYPE_SOA) && (in_data.type[count] == 6))
					scs = 1;
			}
		} // end of loop
	}

    /* your detection function tests go here */
    if (scs == 1)
    {
        /* call the next function in the function list recursively */
        /* THIS CALL *MUST* BE IN THE PLUGIN, OTHERWISE YOU WILL BREAK
           SNORT'S DETECTION ENGINE!!! */
        return fp_list->next->OptTestFunc(p, otn, fp_list->next);
    }
#ifdef DEBUG
    else
    {
        /* you can put debug comments here or not */
        printf("No match\n");
    }
#endif

    /* if the test isn't successful, this function *must* return 0 */
    return 0;
}

void dumpPacket(Packet * p)
{
	/* a quick little shim to dump the header values in question */
	printf("DNS PACKET:\n");
	printf("\t id: %d\n", p->dns_head->id);
	printf("\t qr: %x\n", p->dns_head->qr);
	printf("\t opcode: %x\n", p->dns_head->opcode);
	printf("\t aa: %x\n", p->dns_head->aa);
	printf("\t tc: %x\n", p->dns_head->tc);
	printf("\t rd: %x\n", p->dns_head->rd);
	printf("\t ra: %x\n", p->dns_head->ra);
	printf("\t unused: %x\n", p->dns_head->unused);
	printf("\t ad: %x\n", p->dns_head->ad);
	printf("\t cd: %x\n", p->dns_head->cd);
	printf("\t rcode: %x\n", p->dns_head->rcode);
	printf("\t qdcount: %x\n", p->dns_head->qdcount);
	printf("\t ancount: %x\n", p->dns_head->ancount);
	printf("\t nscount: %x\n", p->dns_head->nscount);
	printf("\t arcount: %x\n", p->dns_head->arcount);
}

DnsRecord GatherInnerData(Packet * p)
{
	int q_count, rr_count, ret=0;
	u_short class;
	u_short type;
	u_int32_t ttl;
	u_short dlen;
	u_char *cp;
	u_char *read_ptr;
	u_char *endOfData;
	DnsRecord return_data;
	int i = 0;

	read_ptr = (u_char*)p->data;
	endOfData = (u_char*)(p->data + p->dsize);

	cp = read_ptr + sizeof(DNS_HEADER);	/* set the initial file pointer start */

	q_count = ntohs(p->dns_head->qdcount);	/* number of question records */

	/* here we loop through the (query) names */
	while ( (--q_count >= 0) && (cp < endOfData ) )
	{
		ret = skipName(read_ptr, cp, endOfData) + QFIXEDSZ;
		if ( ret - QFIXEDSZ <= 0 )
		{
			/* error condition - this may be non-DNS data */
			return_data.error = 1;
			/* now reset cp to ensure that we will not go through this
			 * here or int he loop below
			 */
			cp = (endOfData -1);
		}
		else
		{
			cp += ret;
		}
	}
	/* there is a potential mass of data here from a large return
	 * we will fill up the holding data struct by looping through 
	 * all the parts ... I told you these functions were slow
	 */

	/* number of answer + authority + additional records presented in packet - can be 0 */
	rr_count = ntohs(p->dns_head->ancount) + ntohs(p->dns_head->nscount) + ntohs(p->dns_head->arcount);

	while ( (--rr_count >= 0) && (cp < endOfData ) )
	{
		ret =skipToData(read_ptr, cp, &type, &class, &ttl, &dlen, endOfData);
		if ( ret - QFIXEDSZ <= 0 )
		{
			/* something is wrong, set error and get out of loop */
			return_data.error = 1;
			cp = (endOfData - 1);
		}
		else
		{
		cp += ret;
		return_data.type[i] = ntohs(type);
		return_data.class[i] = ntohs(class);
		return_data.ttl[i] = ntohl(ttl);
		return_data.dlen[i] = ntohs(dlen);
		i++;
		}

	}
	return(return_data);
}

/* ****************************************************************************
 * the following are routines taken directly out of the spp_dns_session.c
 * preprocessor.  In the immediate future, I will extract common defins,
 * data structs and functions and have a shared repository
 * ****************************************************************************
 */

/****************************************************************
 * skipName -- This routine expands a domain name and discards  *
 *     it.  If the domain name expansion fails, it reports an   *
 *     error and exits.  If the expansion succeeds, it returns  *
 *     the number of bytes to skip over.                        *
 *                                                              *
 *     This was taken from DNS and Bind, by Albitz and Liu      *
 ****************************************************************/
int
skipName(startOfMsg, cp, endOfMsg)
u_char *startOfMsg;
u_char *cp;
u_char *endOfMsg;
{
    char buf[MAXDNAME];  /* buffer to expand name into */
    int n;               /* number of bytes in compressed name */

    if((n = dn_expand(startOfMsg, endOfMsg, cp,
                                            buf, MAXDNAME)) < 0){
	#ifdef DEBUG
        	(void) fprintf(stderr, "dn_expand failed(n=%d)\n", n);
	#endif
    }
    return(n);
}


/****************************************************************
 * skipToData -- This routine advances the cp pointer to the    *
 *     start of the resource record data portion.  On the way,  *
 *     it fills in the type, class, ttl, and data length        *
 *                                                              *
 *     This was taken from DNS and Bind, by Albitz and Liu      *
 ****************************************************************/

int
skipToData(startOfMsg, cp, type, class, ttl, dlen, endOfMsg)
u_char     *startOfMsg;
u_char     *cp;
u_short    *type;
u_short    *class;
u_int32_t  *ttl;
u_short    *dlen;
u_char     *endOfMsg;
{
    u_char *tmp_cp = cp;  /* temporary version of cp */

    /* Skip the domain name; it matches the name we looked up */
    tmp_cp += skipName(startOfMsg, tmp_cp, endOfMsg);

    /*
     * Grab the type, class, and ttl.  GETSHORT and GETLONG
     * are macros defined in arpa/nameser.h.
     */
    GETSHORT(*type, tmp_cp);
    GETSHORT(*class, tmp_cp);
    GETLONG(*ttl, tmp_cp);
    GETSHORT(*dlen, tmp_cp);

    /* now add the actual data length to the return value */
    tmp_cp += *dlen;

    return(tmp_cp - cp);
}

/**********************************************************************
 * Expand compressed domain name 'comp_dn' to full domain name.       *
 * 'msg' is a pointer to the begining of the message,                 *
 * 'eomorig' points to the first location after the message,          *
 * 'exp_dn' is a pointer to a buffer of size 'length' for the result. *
 * Return size of compressed name or -1 if there was an error.        *
 *                                                                    *
 * taken from bind resolver source - see above for details            *
 *********************************************************************/
int
dn_expand(const u_char *msg, const u_char *eom, const u_char *src,
	  char *dst, int dstsiz)
{
	int n = ns_name_uncompress(msg, eom, src, dst, (size_t)dstsiz);

	if (n > 0 && dst[0] == '.')
		dst[0] = '\0';
	return (n);
}



/**********************************************************************
 * ns_name_uncompress(msg, eom, src, dst, dstsiz)                     *
 *	Expand compressed domain name to presentation format.         *
 * return:                                                            *
 *	Number of bytes read out of `src', or -1 (with errno set).    *
 * note:                                                              *
 *	Root domain returns as "." not "".                            *
 *                                                                    *
 * taken from bind resolver source - see above for details            *
 *********************************************************************/
int
ns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src,
		   char *dst, size_t dstsiz)
{
	u_char tmp[NS_MAXCDNAME];
	int n;
	
	if ((n = ns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1)
		return (-1);
	if (ns_name_ntop(tmp, dst, dstsiz) == -1)
		return (-1);
	return (n);
}


/************************************************************************
 * ns_name_unpack(msg, eom, src, dst, dstsiz)                           *
 *	Unpack a domain name from a message, source may be compressed.  *
 * return:                                                              *
 *	-1 if it fails, or consumed octets if it succeeds.              *
 *                                                                      *
 *   taken from bind resolver source - see above for details            *
 ***********************************************************************/
int
ns_name_unpack(const u_char *msg, const u_char *eom, const u_char *src,
	       u_char *dst, size_t dstsiz)
{
	const u_char *srcp, *dstlim;
	u_char *dstp;
	int n, len, checked, l;

	len = -1;
	checked = 0;
	dstp = dst;
	srcp = src;
	dstlim = dst + dstsiz;
	if (srcp < msg || srcp >= eom) {
		errno = EMSGSIZE;
		return (-1);
	}
	/* Fetch next label in domain name. */
	while ((n = *srcp++) != 0) {
		/* Check for indirection. */
		switch (n & NS_CMPRSFLGS) {
		case 0:
		case NS_TYPE_ELT:
			/* Limit checks. */
			if ((l = labellen(srcp - 1)) < 0) {
				errno = EMSGSIZE;
				return(-1);
			}
			if (dstp + l + 1 >= dstlim || srcp + l >= eom) {
				errno = EMSGSIZE;
				return (-1);
			}
			checked += l + 1;
			*dstp++ = n;
			memcpy(dstp, srcp, l);
			dstp += l;
			srcp += l;
			break;

		case NS_CMPRSFLGS:
			if (srcp >= eom) {
				errno = EMSGSIZE;
				return (-1);
			}
			if (len < 0)
				len = srcp - src + 1;
			srcp = msg + (((n & 0x3f) << 8) | (*srcp & 0xff));
			if (srcp < msg || srcp >= eom) {  /* Out of range. */
				errno = EMSGSIZE;
				return (-1);
			}
			checked += 2;
			/*
			 * Check for loops in the compressed name;
			 * if we've looked at the whole message,
			 * there must be a loop.
			 */
			if (checked >= eom - msg) {
				errno = EMSGSIZE;
				return (-1);
			}
			break;

		default:
			errno = EMSGSIZE;
			return (-1);			/* flag error */
		}
	}
	*dstp = '\0';
	if (len < 0)
		len = srcp - src;
	return (len);
}


/****************************************************************************
 * ns_name_ntop(src, dst, dstsiz)                                           *
 *	Convert an encoded domain name to printable ascii as per RFC1035.   *
 * return:                                                                  * 
 *	Number of bytes written to buffer, or -1 (with errno set)           *
 * notes:                                                                   *
 *	The root is returned as "."                                         *
 *	All other domains are returned in non absolute form                 *
 *                                                                          *
 *   taken from bind resolver source - see above for details                *
 ***************************************************************************/
int
ns_name_ntop(const u_char *src, char *dst, size_t dstsiz)
{
	const u_char *cp;
	char *dn, *eom;
	u_char c;
	u_int n;
	int l;

	cp = src;
	dn = dst;
	eom = dst + dstsiz;

	while ((n = *cp++) != 0) {
		if ((n & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
			/* Some kind of compression pointer. */
			errno = EMSGSIZE;
			return (-1);
		}
		if (dn != dst) {
			if (dn >= eom) {
				errno = EMSGSIZE;
				return (-1);
			}
			*dn++ = '.';
		}
		if ((l = labellen(cp - 1)) < 0) {
			errno = EMSGSIZE; /* XXX */
			return(-1);
		}
		if (dn + l >= eom) {
			errno = EMSGSIZE;
			return (-1);
		}
		if ((n & NS_CMPRSFLGS) == NS_TYPE_ELT) {
			int m;

			if (n != DNS_LABELTYPE_BITSTRING) {
				/* XXX: labellen should reject this case */
				errno = EINVAL;
				return(-1);
			}
			if ((m = decode_bitstring((const char **)&cp, dn, eom)) < 0)
			{
				errno = EMSGSIZE;
				return(-1);
			}
			dn += m; 
			continue;
		}
		for ((void)NULL; l > 0; l--) {
			c = *cp++;
			if (special(c)) {
				if (dn + 1 >= eom) {
					errno = EMSGSIZE;
					return (-1);
				}
				*dn++ = '\\';
				*dn++ = (char)c;
			} else if (!printable(c)) {
				if (dn + 3 >= eom) {
					errno = EMSGSIZE;
					return (-1);
				}
				*dn++ = '\\';
				*dn++ = digits[c / 100];
				*dn++ = digits[(c % 100) / 10];
				*dn++ = digits[c % 10];
			} else {
				if (dn >= eom) {
					errno = EMSGSIZE;
					return (-1);
				}
				*dn++ = (char)c;
			}
		}
	}
	if (dn == dst) {
		if (dn >= eom) {
			errno = EMSGSIZE;
			return (-1);
		}
		*dn++ = '.';
	}
	if (dn >= eom) {
		errno = EMSGSIZE;
		return (-1);
	}
	*dn++ = '\0';
	return (dn - dst);
}

 /*********************************************************************
 *                                                                    * 
 * taken from bind resolver source - see above for details            *
 *                                                                    * 
 *********************************************************************/
int
decode_bitstring(const char **cpp, char *dn, const char *eom)
{
	const char *cp = *cpp;
	char *beg = dn, tc;
	int b, blen, plen;

	if ((blen = (*cp & 0xff)) == 0)
		blen = 256;
	plen = (blen + 3) / 4;
	plen += sizeof("\\[x/]") + (blen > 99 ? 3 : (blen > 9) ? 2 : 1);
	if (dn + plen >= eom)
		return(-1);

	cp++;
	dn += SPRINTF((dn, "\\[x"));
	for (b = blen; b > 7; b -= 8, cp++)
		dn += SPRINTF((dn, "%02x", *cp & 0xff));
	if (b > 4) {
		tc = *cp++;
		dn += SPRINTF((dn, "%02x", tc & (0xff << (8 - b))));
	} else if (b > 0) {
		tc = *cp++;
		dn += SPRINTF((dn, "%1x",
			       ((tc >> 4) & 0x0f) & (0x0f << (4 - b)))); 
	}
	dn += SPRINTF((dn, "/%d]", blen));

	*cpp = cp;
	return(dn - beg);
}



/**********************************************************************
 * special(ch)                                                        *
 *	Thinking in noninternationalized USASCII (per the DNS spec),  *
 *	is this characted special ("in need of quoting") ?            *
 * return:                                                            *
 *	boolean.                                                      *
 * taken from bind resolver source - see above for details            *
 *********************************************************************/
int
special(int ch) {
	switch (ch) {
	case 0x22: /* '"' */
	case 0x2E: /* '.' */
	case 0x3B: /* ';' */
	case 0x5C: /* '\\' */
	case 0x28: /* '(' */
	case 0x29: /* ')' */
	/* Special modifiers in zone files. */
	case 0x40: /* '@' */
	case 0x24: /* '$' */
		return (1);
	default:
		return (0);
	}
}


/**********************************************************************
 * printable(ch)                                                      *
 *	Thinking in noninternationalized USASCII (per the DNS spec),  *
 *	is this character visible and not a space when printed ?      *
 * return:                                                            *
 *	boolean.                                                      *
 * taken from bind resolver source - see above for details            *
 *********************************************************************/
int
printable(int ch) {
	return (ch > 0x20 && ch < 0x7f);
}


/**********************************************************************
 *                                                                    *
 * taken from bind resolver source - see above for details            *
 *                                                                    *
 *********************************************************************/
int
labellen(const u_char *lp)
{
	int bitlen;
	u_char l = *lp;

	if ((l & NS_CMPRSFLGS) == NS_CMPRSFLGS) {
		/* should be avoided by the caller */
		return(-1);
	}

	if ((l & NS_CMPRSFLGS) == NS_TYPE_ELT) {
		if (l == DNS_LABELTYPE_BITSTRING) {
			if ((bitlen = *(lp + 1)) == 0)
				bitlen = 256;
			return((bitlen + 7 ) / 8 + 1);
		}
		return(-1);	/* unknwon ELT */
	}
	return(l);
}
