/*
 * 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.
 *
 * -----------------------------------------------------------------------
 * for the bind routines used in the preprocessor, the following holds:
 *
 * Copyright (c) 1996,1999 by Internet Software Consortium.
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND INTERNET SOFTWARE CONSORTIUM DISCLAIMS
 * ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL INTERNET SOFTWARE
 * CONSORTIUM BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
 * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
 * PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS
 * ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
 * SOFTWARE.
 * -----------------------------------------------------------------------
 *
 * spp_dns_session.c version 1.3, 07/08/02
 *
 * Purpose: It is not unusual for firewalls to allow DNS traffic
 *          to connect from a host (pc) to an arbitrary server on
 *          the internet.  This makes it an ideal vehical for unregulated
 *          traffic.  This plugin operates on two levels.  The first
 *          verifies that UDP port 53 traffic is of the correct structure.
 *          The second watches the connections between client and server, and
 *          makes sure that each request is getting a single response.
 *
 * Arguments:
 *           -nosession : disables session monitoring.  Enabled by default.
 *           -noclean   : disables traffic verification.  Enabled by default.
 *           -timeout   : modifies session timeout.  Default is 20 seconds.
 *
 * Effect: Non standard traffic and state violations are logged.
 *         Traffic remains untouched.
 *
 * Comments: In addition to the usual modifications to plugbase.h/c and the Makefile,
 *           the following need to be added to the generators.h file:
 *
 *           #define GENERATOR_SPP_DNS       114
 *           #define     DNS_REC_EXPAND_FAIL                 1
 *           #define     DNS_XDATA_IN_PKT                    2
 *           #define     DNS_SESSION_UNKNOWN                 3
 *           #define     DNS_SESSION_MULTIPLE                4
 *
 */
#define DEBUG
/* your preprocessor header file goes here */
#include "spp_dns_session.h"

/* external globals from rules.c */
LISTPTR first = NULL;	   /* head list member */
u_long SESSION = 1;		   /* default session on */
u_long CLEAN = 1;		   /* default clean on */
u_long TIMEOUT = 40;	   /* default timeout = 20 seconds */
u_long ALLOW_INSTANCE = 3;  /* number of session attempts */
static const char     digits[] = "0123456789";

/****************************************************************************
 * Function: SetupTemplate()
 *
 * Purpose: Registers the preprocessor keyword and initialization
 *          function into the preprocessor list.  This is the function that
 *          gets called from InitPreprocessors() in plugbase.c.
 *
 * Arguments: None.
 *
 * Returns: void function
 *
 ****************************************************************************/
void SetupDnsSession()
{
    /* link the preprocessor keyword to the init function in
       the preproc list */
    RegisterPreprocessor("dns_session", DnsSessionInit);

#ifdef DEBUG
    printf("Preprocessor: dns_session is setup...\n");
#endif
}


/****************************************************************************
 * Function: DnsSessionInit(u_char *)
 *
 * Purpose: Calls the argument parsing function, performs final setup on data
 *          structs, links the preproc function into the function list.
 *
 * Arguments: args => ptr to argument string
 *
 * Returns: void function
 *
 *****************************************************************************/
void DnsSessionInit(u_char *args)
{
#ifdef DEBUG
    printf("Preprocessor: DnsSessionInit Initialized\n");
#endif

    /* parse the argument list from the rules file */
    ParseTemplateArgs(args);

    /* Set the preprocessor function into the function list */
    AddFuncToPreprocList(PreprocFunction);
}


/****************************************************************************
 * Function: ParseTemplateArgs(char *)
 *
 * Purpose: Process the preprocessor arguements from the rules file and
 *          initialize the preprocessor's data struct.  This function doesn't
 *          have to exist if it makes sense to parse the args in the init
 *          function.
 *
 * Arguments: args => argument list
 *
 * Returns: void function
 *
 ****************************************************************************/
void ParseTemplateArgs(char *args)
{
    /*
     *  the default behavior is to run with both packet cleaning and state monitoring
     *
     *  this needs to be made more robust...
     */

         char **toks;
	 int num_toks = 4;
	 int i = 0;

	     /* tokenize the argument list */
	     toks = mSplit(args, " ", 4, &num_toks, '\\');

	     /* loop through each token */
	while(i < num_toks)
         {
		printf("looping in token reader ... for %s\n", toks[i]);
		 if(!strcasecmp(toks[i], "-nosession"))
                {
                    	SESSION = 0;
                }
                else if(!strcasecmp(toks[i], "-noclean"))
                {
			CLEAN = 0;
		}
		else if (!strcasecmp(toks[i], "-timeout"))
		{
			 TIMEOUT = atol(toks[i+1]);
			 i++;
		}
		 i++;
	 }
}


/****************************************************************************
 * Function: PreprocFunction(Packet *)
 *
 * Purpose: Heavy lifting.
 *
 * Arguments: p => pointer to the current packet data struct
 *
 * Returns: void function
 *
 ****************************************************************************/
void PreprocFunction(Packet *p)
{
	u_char *read_ptr;
	u_char *endOfData;
	DNS_HEADER *dnsheader;
	/* LISTPTR search = NULL; */


	if(!PacketIsUDP(p))
	{
		return;
	}

	/* traffic to and from udp:53 */
	if(p->dp != 53 && p->sp != 53)
	{
	        return;
	}

	/* setup the pointers */
	read_ptr = (u_char*)p->data;
	endOfData = (u_char*)(p->data + p->dsize);
	dnsheader = (DNS_HEADER *) read_ptr;

	if (CLEAN == 1)
	{
		/* check to see if it is unclean */
		packetClean(read_ptr, endOfData, dnsheader, p);
	}
	/* if packet was dirty, we would have exited by now */

	if (SESSION == 1)
	{
		if ( search_list(dnsheader, p, first) == 0 )
		{
			/* the packet was not found in the list so we add it */
			first = add_to_list(dnsheader, p, first);
		}

		/* for maximum debugging, uncomment the following function */
		/* show_list(first); */

		first = time_scan_list(first);
	}

	return;
}

/****************************************************************************
 * Function: packetClean(u_char *, u_char *, DNS_HEADER *, Packet *)
 *
 * Purpose: Make sure dns packet is of the correct form
 *
 * Arguments: u_char *read_ptr: read pointer to dns data struct
 *            u_char *endOfData: pointer to end of dns data
 *            DNS_HEADER *dnsheader: pointer to dns structure
 *            Packet *p: pointer to the current packet data struct
 *
 * Returns: void function
 *
 ****************************************************************************/
void packetClean(u_char *read_ptr, u_char *endOfData, DNS_HEADER *dnsheader, 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;

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

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

	while ( (--q_count >= 0) && (cp < endOfData ) )
	{
		ret = skipName(read_ptr, cp, endOfData) + QFIXEDSZ;
		if ( ret - QFIXEDSZ <= 0 )
		{
			/* error condition DNS_REC_EXPAND_FAIL */
			dnsErrorHand(1,p);
			PreprocCleanExitFunction(1);
		}
		cp += ret;
	}

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

	while ( (--rr_count >= 0) && (cp < endOfData ) )
	{
		ret =skipToData(read_ptr, cp, &type, &class, &ttl, &dlen, endOfData);
		if ( ret - QFIXEDSZ <= 0 )
		{
			/* error condition DNS_REC_EXPAND_FAIL */
			dnsErrorHand(1,p);
			PreprocCleanExitFunction(1);
		}
/* added HERE!*/
printf("TTL VALUE = %d\n", ttl);
		cp += ret;

	}

	/* record should be fully processed.  if there is any extra data sitting around we are in trouble */
	ret = endOfData - cp;

	if ( ret != 0 )
	{
		/* error condition DNS_XDATA_IN_PKT */
		dnsErrorHand(2,p);
		PreprocCleanExitFunction(1);
	}

	PreprocCleanExitFunction(0);
}

/****************************************************************
 * 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);
}


/****************************************************************************
 * Function: dnsErrorHand(int, Packet *)
 *
 * Purpose: Report back errors in packet form and state
 *
 * Arguments: int et: error event identifier
 *            Packet *p: pointer to the current packet data struct
 *
 * Returns: void function
 *
 ****************************************************************************/
void dnsErrorHand(int et, Packet *p)
{
char outstring[STD_BUF];
Event event;
	switch(et)
	{
		case 1:
			/* DNS_REC_EXPAND_FAIL */
			SetEvent(&event, GENERATOR_SPP_DNS, DNS_REC_EXPAND_FAIL,  1, 0, 0, 0);
			snprintf(outstring, STD_BUF-1, "spp_dns(%d): resource record expansion failure",et);
			CallAlertFuncs(p, outstring, NULL, &event);
			break;

		case 2:
			/* DNS_XDATA_IN_PKT */
			SetEvent(&event, GENERATOR_SPP_DNS, DNS_XDATA_IN_PKT,  1, 0, 0, 0);
			snprintf(outstring, STD_BUF-1, "spp_dns(%d): extra data in dns packet",et );
			CallAlertFuncs(p, outstring, NULL, &event);
			break;

		case 3:
			/* DNS_SESSION_UNKNOWN */
			SetEvent(&event, GENERATOR_SPP_DNS, DNS_SESSION_UNKNOWN,  1, 0, 0, 0);
			snprintf(outstring, STD_BUF-1, "spp_dns(%d): unknown DNS session traffic",et );
			CallAlertFuncs(p, outstring, NULL, &event);
			break;

		default:
			/* unknown problem, clean up and exit out */
			break;
	}

	return;

}

/* 
 * NOTE: From this point on we are only dealing with the stateful end of
 * things.  We are done with the hideous mess of DNS unpacking
 */


/****************************************************************************
 * Function: TcpStreamTime()
 *
 * Note: cut and paste from spp_tcp_stream.c .  Thanks.
 *
 * Arguments: none
 *
 * Returns: u_long number of seconds since beginning of time (unix-centric)
 *
 ****************************************************************************/
u_long TcpStreamTime()
{
    struct timeval t;
    gettimeofday(&t, (struct timezone *)NULL);
    return(t.tv_sec);
}


/****************************************************************************
 * Function: add_to_list(DNS_HEADER *dnsheader, Packet *p, LISTPTR first)
 *
 * Purpose: Add an entry to the front of the session list
 *
 * Arguments: DNS_HEADER *dnsheader; pointer to current dns hdr struct
 *            Packet *p: pointer to the current packet data struct
 *            LISTPTR first: first entry in link list
 *
 * Returns: link list data type
 *
 ****************************************************************************/
LISTPTR add_to_list(DNS_HEADER *dnsheader, Packet *p, LISTPTR first)
{
	LISTPTR new_rec = NULL;

	/* allocate memory */
	new_rec = (LISTPTR)malloc(sizeof(LIST));
	if (!new_rec)
	{
		#ifdef DEBUG
			printf("\nunable to alocate mem for spp_dns_session\n");
		#endif
		exit;
	}

	/* set new link's data */
	new_rec->id = dnsheader->id;
	new_rec->srcip =  p->iph->ip_src.s_addr;
	new_rec->dstip =  p->iph->ip_dst.s_addr;
	new_rec->time = TcpStreamTime();
	new_rec->session = dnsheader->qr;
	new_rec->instance = 1;
	new_rec->next_rec = NULL;
	new_rec->prev_rec = NULL;

	if (first != NULL)
	{
		new_rec->next_rec = first;
		first->prev_rec = new_rec;
	}

	first = new_rec;

	#ifdef DEBUG
		printf("Adding DNS record to new_rec->next_rec = %x at %d\n", (int)new_rec->next_rec,(int)TcpStreamTime());
	#endif

	return(first);
}


/****************************************************************************
 * Function: show_list( LISTPTR )
 *
 * Purpose: Provide debug information by printing the entire linked list
 *
 * Arguments: LISTPTR first: first entry in linked list
 *
 * Returns: void function
 *
 ****************************************************************************/
void show_list( LISTPTR first )
{
	LISTPTR cur_ptr = first;

	printf("\n\n      ID       SrcIP     DstIP         Time        Session\n");
	printf("    ========   ========  =======    ============   ===== \n");

	while (cur_ptr != NULL )
	{
		printf("      %X  ",(int)cur_ptr->id);
		printf("   %X  ", (int)cur_ptr->srcip);
		printf("  %X   ", (int)cur_ptr->dstip);
		printf("      %d", (int)(TcpStreamTime() - cur_ptr->time));
		printf("          %d\n",(int)cur_ptr->session);
		cur_ptr = cur_ptr->next_rec;
	}
}


/****************************************************************************
 * Function: time_scan_list( LISTPTR )
 *
 * Purpose: Scan list and remove session entries that are older than the
 *          value set by TIMEOUT.  Will return the (possibly redefined) head of
 *          the list.
 *
 * Arguments: LISTPTR first: first entry in linked list
 *
 * Returns: LISTPTR data type
 *
 ****************************************************************************/
LISTPTR time_scan_list( LISTPTR first )
{
	LISTPTR cur_ptr = first;     /* where we are */
	LISTPTR tmp_ptr = NULL;

	u_long c_time = TcpStreamTime();

	while (cur_ptr != NULL )
	{
		if ((c_time - cur_ptr->time ) > TIMEOUT )
		{
				if ( cur_ptr->prev_rec == NULL) // first member
				{
					/* first record.  bad form with the return I know ... */
					first = NULL;
					free(cur_ptr);
					return(first);
				}
				else
				{
					if (cur_ptr->next_rec) // !end of line
					{
						#ifdef DEBUG
							printf("\tclean: setting cp->nr->pr %x = cp->pr %x\n", (int)cur_ptr->next_rec->prev_rec, (int)cur_ptr->prev_rec);
						#endif
						cur_ptr->next_rec->prev_rec = cur_ptr->prev_rec;
					}
						#ifdef DEBUG
							printf("\tclean: setting cp->pr->nr %x = cp->nr %x\n", (int)cur_ptr->prev_rec->next_rec, (int)cur_ptr->next_rec);
						#endif
						cur_ptr->prev_rec->next_rec = cur_ptr->next_rec; // adjust prev rec forward

					tmp_ptr = cur_ptr;
					free(cur_ptr);
					cur_ptr = tmp_ptr->next_rec;

				}

		}
		else
		{
			cur_ptr = cur_ptr->next_rec;
		}

	} // end while
	#ifdef DEBUG
		printf("end time_scan_list()\n");
	#endif
	return(first);

}





/****************************************************************************
 * Function: search_list(DNS_HEADER *, Packet *, LISTPTR )
 *
 * Purpose: Search the session list for dns id value matching up to the
 *          given client and server IP values
 *
 * Arguments: DNS_HEADER *dnsheader: pointer to dns structure
 *            Packet *p: pointer to the current packet data struct
 *            LISTPTR first: first entry in session linked list
 *
 * Returns: integer , 0=add to list, 1= !add to list.  note, return has
 *          nothing to do with the error condition.
 *
 ****************************************************************************/
int search_list(DNS_HEADER *dnsheader, Packet *p, LISTPTR first)
{
	LISTPTR c_ptr = first;
	int response = 0;
	int found = 0;

	while (c_ptr != NULL && found == 0 )
	{
		switch(c_ptr->session)
		{
			case 0:
			// printf("case 0 looking for %x, seeing %x with %x\n", dnsheader->id, c_ptr->id, c_ptr->session );
				/* here we have a question.  if another exists
				 * this is an error situation
				 */
				if ((c_ptr->id == dnsheader->id) &&
				    (c_ptr->srcip == p->iph->ip_src.s_addr) &&
				    (c_ptr->dstip == p->iph->ip_dst.s_addr))
				{
					response = 1;
					found = 1;

					if (c_ptr->instance <= ALLOW_INSTANCE) /* we are being less trigger happy here. on a slow          */
					{						  /* link, a client might spit out several requests before    */
						c_ptr->instance++;    /* the lethargic server on the other end picks up.  here we */
					}						  /* allow for three                                          */
					else
					{

						#ifdef DEBUG
							printf("\nQUESTION ERROR CONDITION\n");
						#endif

						dnsErrorHand(3,p);
					}

				}

			case 1:
			// printf("case 1 looking for %x, seeing %x with %x\n", dnsheader->id, c_ptr->id, c_ptr->session );
				/* there ought to be an question here for our answer */
				if ((c_ptr->id == dnsheader->id) &&
				    (c_ptr->dstip == p->iph->ip_src.s_addr) &&
				    (c_ptr->srcip == p->iph->ip_dst.s_addr))
				{
					/* found the match */
					c_ptr->session = RESPONSE;
					response = 1;
					found = 1;
				}
		}
		c_ptr = c_ptr->next_rec;
	}

		if (dnsheader->qr == 1 && found != 1)
		{
			/* there was *not* a question for our answer.  bummer. add anyway.  */
			/* if this error is flooding the logs, move the timeout up slightly */
				#ifdef DEBUG
					printf("\nANSWER ERROR CONDITION\n");
				#endif
				dnsErrorHand(3,p);
		}

	return(response);

}


/****************************************************************************
 * Function: PreprocCleanExitFunction(int signal)
 *
 * Purpose: function to provide a way out
 *
 * Arguments: int signal: currently ignored
 *
 * Returns: void function
 *
 ****************************************************************************/
void PreprocCleanExitFunction(int signal)
{
       /* clean exit code goes here */
	return;
}

void runSession(LISTPTR first)
{
	LISTPTR cur_ptr = first;

	while (cur_ptr != NULL )
	{
		printf(".");
		cur_ptr = cur_ptr->next_rec;
	}

	printf("\n");

}


