/****************************************************************************
 * Copyright (C) 1998 WIDE Project. All rights reserved.
 * Copyright (C) 1999,2000,2001,2002 University of Tromso. All rights reserved.
 * Copyright (C) 2002 Invenia Innovation AS. All rights reserved.
 *
 * Author: Feike W. Dillema, feico@pasta.cs.uit.no.
 *         based on newbie code by Yusuke DOI, Keio Univ. Murai Lab.
 ****************************************************************************/

/*
 * <$Id: ne_mesg.c,v 3.49 2005/07/04 09:09:22 dillema Exp $>
 */

#include "totd.h"

static int write_dname (u_char *, u_char *, uint16_t *, int, u_char *, u_char *);
static int dname_copy (u_char *, u_char *, int);
static u_char *dname_redirect (u_char *, u_char *);
static u_char *mesg_read_sec (G_List *, u_char *, int, u_char *, int);

uint16_t mesg_id (void) {
	static uint16_t id = 0;

	if (!id) {
		srandom (time (NULL));
		id = random ();
	}
	id++;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "mesg_id() = %d", id);
	return id;
}

int mesg_make_query (u_char *qname, uint16_t qtype, uint16_t qclass,
		     uint32_t id, int rd, u_char *buf, int buflen) {
	char *fn = "mesg_make_query()";
	u_char *ucp;
	int i, written_len;
	Mesg_Hdr *hdr;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: (qtype: %s, id: %d): start", fn,
			string_rtype (qtype), id);

	hdr = (Mesg_Hdr *) buf;

	/* write header */
	hdr->id = id;
	hdr->opcode = OP_QUERY;
	hdr->rcode = RC_OK;
	hdr->rd = rd;
	hdr->qr = hdr->aa = hdr->tc = hdr->ra = hdr->zero = 0;
	hdr->qdcnt = ntohs (1);
	hdr->ancnt = hdr->nscnt = hdr->arcnt = ntohs (0);

	written_len = sizeof (Mesg_Hdr);
	ucp = (u_char *) (hdr + 1);

	/* write qname */
	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: qname offset = %td", fn, ucp - buf);

	i = dname_copy (qname, ucp, buflen - written_len);
	if (i < 0)
		return -1;

	written_len += i;
	ucp += i;

	/* write qtype / qclass */
	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: qtype/qclass offset = %td",
			fn, ucp - buf);

	written_len += sizeof (uint16_t) * 2;
	if (written_len > buflen)
		return -1;

	PUTSHORT (qtype, ucp);
	PUTSHORT (qclass, ucp);

	return written_len;
}

int labellen (const u_char *cp) {
	uint i;

	i = *cp;
	if ((i & DNCMP_MASK) == 0)
		return(i);
	else if ((i & DNCMP_MASK) == EDNS0_MASK) {
		uint bitlen;

		if (i != EDNS0_ELT_BITLABEL)
			return -1;

		bitlen = *(cp + 1);
		if (bitlen == 0)
			bitlen = 256;

		return (((bitlen + 7) / 8) + 1);
	} else
		return -1;
}

u_char *mesg_skip_dname (u_char *dname, u_char *end) {
	int l;

	if (dname >= end)
		return NULL;

	while(*dname) {
		if ((*dname & DNCMP_MASK) == DNCMP_MASK) {
			dname += 2;	/* redirection */
			return dname;
		}
		if (dname + 2 > end) /* we need at least 2 bytes */
			return NULL;

		l = labellen(dname);
		if (l < 0)
			return NULL;
		dname += l + 1;

		if (dname >= end)
			return NULL;
	}
	dname++;	/* push away null terminator */
	return dname;
}

int mesg_dname_cmp (u_char *msg, u_char *dname_mesg, u_char *dname) {

	/*
	 * compare dname_mesg and dname label by label, while doing
	 * decompression of the dname_mesg of the message msg.
	 */
	dname_mesg = dname_redirect (dname_mesg, msg);
	while (*dname_mesg != '\0' && (*dname == *dname_mesg)) {
		int len;

		len  = labellen(dname_mesg);
		if (len != labellen(dname))
			return -1;

		if (*dname == EDNS0_ELT_BITLABEL) {
			if (memcmp (dname_mesg + 1, dname + 1,  len))
				return -1;
		} else if (strncasecmp (dname_mesg + 1, dname + 1, len))
				return -1;

		dname += len + 1;
		dname_mesg += len + 1;
		dname_mesg = dname_redirect (dname_mesg, msg);
	}

	if (*dname != *dname_mesg)
		return -1;
	else
		return 0;
}

int mesg_write_rrset_list (G_List *rrls, u_char *msg, u_char *msg_tail,
			   uint16_t *dnames, int dnames_len, u_char **wp,
			   uint16_t *cnt) {
	char *fn = "mesg_write_rrset_list()";
	u_char *wp_start, *wp_period;
	Mesg_Hdr *hdr;
	uint16_t us;
	uint32_t ul;
	RRset *rrsp;
	RR *rrp;
	int i, ret;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: start.", fn);

	if (!rrls)
		return 0;

	wp_start = *wp;
	hdr = (Mesg_Hdr *) msg;

	for (rrls = rrls->next; rrls->list_data; rrls = rrls->next) {
		if (T.debug > 4)
			syslog (LOG_DEBUG, "%s: write a record", fn);

		rrsp = (RRset *) rrls->list_data;
		for (i = 0; i < rrsp->data.d->data_cnt; i++) {
			wp_period = *wp;

			/* write the owner name */
			ret = write_dname (msg, msg_tail, dnames,
				dnames_len, rrset_owner (rrsp), *wp);
			if (ret < 0) {
				syslog (LOG_DEBUG, "write ownername failed");
				*wp = wp_period;
				return wp_period - wp_start;
			}
			*wp += ret;

			/* write RR field and data */
			rrp = (RR *) (rrsp->data.p +
				      data_offset (i, rrsp->data.p));
			if (*wp + sizeof (uint16_t) * 3 + sizeof (uint32_t)
			    + rrp->rd_len > msg_tail) {
				syslog (LOG_DEBUG, "write rdata failed");
				*wp = wp_period;
				return wp_period - wp_start;
			}

			PUTSHORT (rrsp->key.info->r_type, *wp);
			PUTSHORT (rrsp->key.info->r_class, *wp);
			ul = rrp->ttl;
			PUTLONG (ul, *wp);

			/* XXX RDATA COMPRESSION NOT IMPLEMENTED */
			PUTSHORT (rrp->rd_len, *wp);
			memcpy (*wp, rr_rdata (rrp), rrp->rd_len);
			*wp += rrp->rd_len;

			/* update header */
			us = ntohs (*cnt) + 1;
			/* and caller's counter */
			*cnt = htons (us);

			if (T.debug > 4)
				syslog (LOG_DEBUG, "%s: now counter = %u",
					fn, us);
		}
	}

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: return %td", fn, *wp - wp_start);

	return (*wp - wp_start);
}

#define MESG_ASSEMBLE_OFFSET_LEN 64

int mesg_assemble (G_List *an_list, G_List *ns_list, G_List *ar_list,
		   u_char *buf, uint16_t buflen, u_char *mesg, int mesg_len) {
	uint16_t dnames[MESG_ASSEMBLE_OFFSET_LEN];
	u_char *ucp, *ucp_tmp;
	int written_len, ret;
	Mesg_Hdr *hdr;

	/* check if header is already present */
	if (mesg)
		memcpy (buf, mesg, mesg_len);
	else
		memset (buf, 0, buflen);

	hdr = (Mesg_Hdr *) buf;

	written_len = 0;

	/* check and reset header */
	hdr->qr = 1;
	hdr->ancnt = 0;
	hdr->nscnt = 0;
	hdr->arcnt = 0;

	if (hdr->qdcnt) {
		int qdcnt = ntohs(hdr->qdcnt);

		/*
		 * Register question name to compression name list
		 * Note that for simplicity we only register
		 * the first, and ignore multiple question
		 */
		dnames[0] = (uint16_t) (sizeof (Mesg_Hdr));
		dnames[1] = 0;	/* terminator */

		/*
		 * Skip the question section.
		 */
		ucp = buf + sizeof(Mesg_Hdr);
		while (qdcnt--) {
			/* skip QNAME */
			if (!(ucp = mesg_skip_dname(ucp, buf + mesg_len)) ||
			    ucp + 2 * sizeof(uint16_t) > buf + mesg_len) {
				syslog (LOG_NOTICE, "query message overrun");
				return -1;
			}
			/* skip QTYPE and QCLASS */
			ucp += (2 * sizeof(uint16_t));
		}
		written_len = ucp - buf;
	} else {
		/* there mustnot be any question */
		written_len = sizeof (Mesg_Hdr);
		ucp = buf + written_len;
		dnames[0] = 0;
	}

	/* write answers */
	ucp_tmp = ucp;
	ret = mesg_write_rrset_list (an_list, buf, buf + buflen, dnames,
				     MESG_ASSEMBLE_OFFSET_LEN, &ucp,
				     &(hdr->ancnt));
	if (ret < 0) {
		/* truncated message */
		hdr->tc = 1;
		return ucp_tmp - buf;
	}
	written_len += ret;

	/* write ns */
	ucp_tmp = ucp;
	ret = mesg_write_rrset_list (ns_list, buf, buf + buflen, dnames,
				     MESG_ASSEMBLE_OFFSET_LEN, &ucp,
				     &(hdr->nscnt));
	if (ret < 0) {
		/* truncated message */
		hdr->tc = 1;
		return ucp_tmp - buf;
	}
	written_len += ret;

	/* write additonal records */
	ucp_tmp = ucp;
	ret = mesg_write_rrset_list (ar_list, buf, buf + buflen, dnames,
				     MESG_ASSEMBLE_OFFSET_LEN, &ucp,
				     &(hdr->arcnt));
	/*
	 * ignore if error -- we may leave out additional
	 * records if we do not have room for them
	 */
	if (ret < 0)
		return ucp_tmp - buf;
	else
		return written_len + ret;
}

int mesg_extract_rr (u_char *mesg, u_char *msg_end, uint16_t r_type,
	uint16_t r_class, u_char *rrp, u_char *buf, int buflen) {
	int i, written_len;
	u_char *rp, *wp;

	written_len = 0;

	switch (r_type) {
	case RT_NS:
	case RT_CNAME:
	case RT_PTR:
		/* just extract domain name */
		if (!dname_decompress (buf, buflen, rrp, mesg, msg_end,
					    &written_len)) {
			syslog (LOG_INFO, "record invalid -- %s",
				string_rtype (r_type));
			return -1;
		}
		break;
	case RT_SOA:
		rp = rrp;
		wp = buf;
		rp = dname_decompress (wp, buflen, rp, mesg, msg_end, &i);
		if (!rp) {
			syslog (LOG_INFO, "record invalid -- SOA MNAME");
			return -1;
		}

		wp += i;
		rp = dname_decompress (wp, buflen - (wp - buf), rp,
					    mesg, msg_end, &i);
		if (!rp) {
			syslog (LOG_INFO, "record invalid -- SOA RNAME");
			return -1;
		}

		wp += i;
		memcpy (wp, rp, (i = sizeof (uint32_t) * 5));
		wp += i;
		written_len = wp - buf;
		break;
	case RT_MX:
		rp = rrp;
		wp = buf;
		memcpy (wp, rp, (i = sizeof (uint16_t) * 1));	/* PREFERENCE */
		wp += i;
		rp += i;
		if (!dname_decompress (wp, buflen - (wp - buf), rp,
					    mesg, msg_end, &i)) {
			syslog (LOG_INFO, "record invalid -- MX EXCHANGE");
			return -1;
		}
		wp += i;
		written_len = wp - buf;
		break;
	case RT_RP:
		/* two domain names */
		rp = rrp;
		wp = buf;
		rp = dname_decompress (wp, buflen, rp, mesg, msg_end, &i);
		if (!rp) {
			syslog (LOG_INFO, "record invalid -- RP MBOX-DNAME");
			return -1;
		}
		wp += i;
		rp = dname_decompress (wp, buflen - (wp - buf), rp,
					    mesg, msg_end, &i);
		if (!rp) {
			syslog (LOG_INFO, "record invalid -- RP TXT-DNAME");
			return -1;
		}
		wp += i;
		written_len = wp - buf;
		break;
	case RT_A:
	case RT_HINFO:
	case RT_AAAA:
	case RT_A6:
	case RT_SRV:
	case RT_TXT:
		/* no modification */
		return 0;
	default:
		syslog (LOG_INFO, "unknown resource type %d", r_type);
		return 0;
	}

	return written_len;
}

int mesg_parse (u_char *msg, int msg_len, G_List *an_list, G_List *ns_list,
		G_List *ar_list) {
	char *fn = "mesg_parse()";
	Mesg_Hdr *hdr;
	u_char *rp;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: start", fn);

	if (msg_len < sizeof (*hdr))
		return -1;

	hdr = (Mesg_Hdr *) msg;
	rp = (u_char *) (hdr + 1);	/* read next of the header */

	if (hdr->qdcnt) {
		/* skip question section */
		rp = mesg_skip_dname (rp, msg + msg_len);
		rp += 4;	/* sizeof qtype + qclass */
		if (rp > msg + msg_len)
			return -1;
	}

	rp = mesg_read_sec (an_list, rp, ntohs(hdr->ancnt), msg, msg_len);
	if (!rp)
		return -1;

	rp = mesg_read_sec (ns_list, rp, ntohs(hdr->nscnt), msg, msg_len);
	if (!rp)
		return -1;

	rp = mesg_read_sec (ar_list, rp, ntohs(hdr->arcnt), msg, msg_len);
	if (!rp)
		return -1;

	return 0;
}

u_char *dname_decompress (u_char *buf, int buflen, u_char *dname,
			  u_char *m_head, u_char *m_tail, int *written) {
	int token_len, written_len, iter;
	u_char *cp, *next;
	int pktsiz = m_tail - m_head;

	next = NULL;
	written_len = token_len = 0;
	for (cp = dname; *cp; cp += token_len) {
		iter = 0;
	  top:
		if ((*cp & DNCMP_MASK) == DNCMP_MASK) {
			uint16_t ui;

			if (iter++ >= pktsiz) /* we're probably in a loop. */
				return NULL;

			if (!m_head || !m_tail) /* irregular redirect */
				return NULL;

			/* redirect */
			next = cp + 2;
			GETSHORT (ui, cp);
			ui = ui & ~DNCMP_MASK_INT16T;

			cp = m_head + ui;
			if (cp < m_head || m_tail < cp)
				return NULL;

			goto top;
		}

		token_len = labellen(cp);
		if (token_len < 0)
			return NULL;
		else
			token_len++;

		if (T.debug > 4)
			syslog (LOG_DEBUG, "token_len: %d", token_len);

		if (written_len + token_len >= buflen)
			return NULL; /* buffer overrun */
		if (cp + token_len > m_tail)
			return NULL; /* out of bounds */

		if (written) {
			/* non-printable dname string */
			memcpy (buf, cp, token_len);
			written_len += token_len;
			buf += token_len;
		} else {
			/* write printable string */
			if ((*cp & DNCMP_MASK) != EDNS0_MASK) {
				memcpy (buf, cp + 1, token_len - 1);
				*(buf + (token_len - 1)) = DNAME_DELIM;
				written_len += token_len;
				buf += token_len;
			} else if (*cp == EDNS0_ELT_BITLABEL) {
				int bitlength, i;
				u_char *wp;

				/* a bit conservative test, but simple */
				if (written_len + token_len*2 + 7 >= buflen)
					return NULL; /* buffer overrun */

				wp = buf;
				wp += sprintf(wp, "\\[x");
				for (i = 1; i < token_len-1; i++) {
					u_char d1, d2;
					uint b;

					b = (int) *(cp + 1 + i);
					d1 = hex[(b >> 4) & 0x0f];
					d2 = hex[b & 0x0f];
					wp += sprintf(wp, "%c%c", d1, d2);
				}
				bitlength = *(cp + 1) ? *(cp + 1) : 256;
				wp += sprintf(wp, "/%u].", bitlength);

				written_len += (wp - buf);
				buf += written_len;
			}
		}
	}

	*buf = '\0';
	if (written)
		*written = written_len + 1;

	if (!next)
		next = cp+1;

	return next;
}


char *string_rclass (uint16_t rclass) {
	switch (rclass) {
		case C_IN:
		return "IN";
	case C_NONE:
		return "NONE";
	case C_ANY:
		return "ANY";
	default:
		syslog (LOG_NOTICE, "Unknown resource class(%d)", rclass);
		return "UNKNOWN";
	}
}

char *string_rtype (uint16_t rtype) {
	switch (rtype) {
		case RT_VOID:
		return "(void)";
	case RT_A:
		return "A";
	case RT_NS:
		return "NS";
	case RT_MD:
		return "MD";
	case RT_MF:
		return "MF";
	case RT_CNAME:
		return "CNAME";
	case RT_SOA:
		return "SOA";
	case RT_MB:
		return "MB";
	case RT_MG:
		return "MG";
	case RT_MR:
		return "MR";
	case RT_NULL:
		return "NULL";
	case RT_WKS:
		return "WKS";
	case RT_PTR:
		return "PTR";
	case RT_HINFO:
		return "HINFO";
	case RT_MINFO:
		return "MINFO";
	case RT_MX:
		return "MX";
	case RT_TXT:
		return "TXT";
	case RT_RP:
		return "RP";
	case RT_AAAA:
		return "AAAA";
	case RT_SRV:
		return "SRV";
	case RT_A6:
		return "A6";
	case RT_UINFO:
		return "UINFO";
	case RT_TSIG:
		return "TSIG";
	case RT_IXFR:
		return "IXFR";
	case RT_AXFR:
		return "AXFR";
	case RT_ALL:
		return "ANY";
	default:
		syslog (LOG_NOTICE, "Unknown resource type(%d)", rtype);
		return "UNKNOWN";
	}
}

static int dname_copy (u_char *from, u_char *to, int tolen) {
	int skip, written_len;

	written_len = 0;
	while (*from) {
		skip = labellen(from) + 1;
		written_len += skip;
		if (written_len >= tolen)
			return -1;

		memcpy (to, from, skip);
		from += skip;
		to += skip;
	}
	*to = '\0';
	written_len++;

	return written_len;
}

static u_char *dname_redirect (u_char *label, u_char *msg) {
	uint16_t us;

	if (msg && (*label & DNCMP_MASK) == DNCMP_MASK) {
		GETSHORT (us, label);
		us = us & (~DNCMP_MASK_INT16T);
		label = msg + us;
	}
	return label;
}

static int write_dname (u_char *msg, u_char *msg_tail, uint16_t *dnames,
		      int dnames_len, u_char *dname, u_char *wp) {
	char *fn = "write_dname()";
	u_char *bestmatch_rpd = NULL;
	u_char *bestmatch_rpm = NULL;
	int bestmatch_len;
	u_char *rpd, *rpm;
	int written_len;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: start", fn);

	/*
	 * Check if some (part of) dname has already appeared in message
	 * 
	 * Start with full name, and chop first name label off for each
	 * iteration.
	 */
	bestmatch_len = 0;
	for (rpd = dname; *rpd && !bestmatch_len; rpd += labellen(rpd) + 1) {
		int i;

		/*
		 * The dnames array contains a list of pointers to domainnames
		 * in the message that we will try to match our name against.
		 */
		for (i = 0; dnames[i] != 0 && i < dnames_len; i++) {
			/*
	 		 * Again, start with full name, and chop first name label
			 * off for each iteration. Meanwhile, we follow
			 * redirections.
			 */
			for (rpm = dname_redirect (msg + dnames[i], msg); *rpm;
			     rpm = dname_redirect (labellen(rpm)+rpm+1, msg)) {
				u_char *cpd, *cpm;
				int match_len;

				if (rpm < msg || msg_tail < rpm)
					return -1; /* out of bounds */

				/* comparison pointers */
				cpd = rpd;
				cpm = rpm;

				match_len = 0;
				while (*cpm && *cpm == *cpd) {
					int mlen;

					mlen = labellen(cpm);
					if (mlen != labellen(cpd))
						break;

					/* binary comparison */
					if (*cpm == EDNS0_ELT_BITLABEL &&
					    memcmp (cpm+1, cpd+1, mlen))
							break;

					/* case-insensitive comparison */
					if (*cpm != EDNS0_ELT_BITLABEL &&
					    strncasecmp (cpm+1, cpd+1, *cpm))
						break;
					
					/* a label matched, move to next one */
					cpm += mlen + 1;
					cpd += mlen + 1;

					/* check redirection */
					cpm = dname_redirect (cpm, msg);
					if (cpm < msg || msg_tail < cpm)
						return -1; /* out of bounds */

					match_len++;
				}

				/*
				 * matched parts have to be postfixes, i.e.
				 * both need to be NULL terminated. We record
				 * the match if it is better than the best one
				 * we found so far.
				 */
				if (*cpm == '\0' && *cpd == '\0' &&
				    match_len > bestmatch_len) {
					bestmatch_rpd = rpd;
					bestmatch_rpm = rpm;
					bestmatch_len = match_len;
				}
			}
		}
	}

	/* register this name if not complete match */
	if (bestmatch_rpd != dname) {
		int i;
		/* we didn't find ourselves */
		for (i = 0; dnames[i] != 0; i++);

		if (i + 1 < dnames_len) {
			/* we still have room for one more in the table */
			if (((uint16_t) (bestmatch_rpm - msg)
			    < DNCMP_REDIRECT_LIMIT)) {
				/* It is within range */

				dnames[i] = (uint16_t) (wp - msg);
				dnames[i + 1] = 0;
			}
		}
	}

	/* write dname */
	written_len = 0;
	rpd = dname;

	/* write first unique part */
	while (*rpd && rpd != bestmatch_rpd) {
		int i;

		i = labellen(rpd) + 1;
		if (wp + i > msg_tail)
			return -1; /* overflow! */

		memcpy (wp, rpd, i);
		written_len += i;
		rpd += i;
		wp += i;
	}

	/* write second redirected part, if any found, or terminate */
	if (rpd == bestmatch_rpd) {
		uint16_t us;

		/* write redirection pointer */
		if (wp + sizeof (uint16_t) > msg_tail)
			return -1; /* overflow */

		us = (uint16_t) (bestmatch_rpm - msg) | DNCMP_MASK_INT16T;
		PUTSHORT (us, wp);
		written_len += sizeof (uint16_t);
	} else {
		*wp = '\0';
		written_len++;
	}

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: return (written_len = %d)",
			fn, written_len);

	return written_len;
}

static u_char *mesg_read_sec (G_List *target_list, u_char *section, int count,
			      u_char *mesg, int mesg_len) {
	char *fn = "mesg_read_sec()";
	G_List *rc_list, *gl;
	u_char buf[MAX_PACKET];
	u_char *msg_end, *rp;
	int i;

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: start", fn);

	/* initialize */
	rc_list = list_init ();
	if (!rc_list)
		return NULL;

	rp = section;
	msg_end = mesg + mesg_len;
	for (i = 0; i < count; i++) {
		u_char *rname, *rp_ex, *rdp;
		uint16_t r_type, r_class;
		uint16_t rdlen, rdlen_ex;
		uint32_t r_ttl;
		RR_List *rrl;

		/* parse header */
		rname = rp;
		rp = mesg_skip_dname (rp, msg_end);
		if (!rp)
			goto error;

		if (rp + sizeof(uint16_t)*3 + sizeof(uint32_t) > msg_end)
			goto error;

		GETSHORT (r_type, rp);
		GETSHORT (r_class, rp);
		GETLONG (r_ttl, rp);
		GETSHORT (rdlen, rp);

		rdp = rp;
		rp += rdlen;
		if (rp > msg_end)
			goto error;

		/* seek for matching RRset_Couple */
		for (gl = rc_list->next; gl->list_data; gl = gl->next) {
			RRset_Couple *rc;

			rc = (RRset_Couple *) (gl->list_data);
			if ((rc->rrs->key.info->r_type == r_type) &&
			    (rc->rrs->key.info->r_class == r_class) &&
			    !mesg_dname_cmp (mesg, rname, rrset_owner (rc->rrs))) {
				if (T.debug > 4)
					syslog (LOG_DEBUG, "%s: matching record \
found rrs->dname = %s / rname = %s", fn, rrset_owner (rc->rrs), rname);
				break;
			}
		}

		/* if no match, create a new RRset_Couple */
		if (!gl->list_data) {
			RRset_Couple *rc;
			int dname_len;

			if (!dname_decompress (buf, sizeof (buf), rname, mesg,
					       msg_end, &dname_len))
				goto error;

			rc = malloc (sizeof (RRset_Couple));
			if (!rc)
				goto error;

			rc->rrl = rr_list_alloc ();
			rc->rrs = rrset_create (r_type, r_class, dname_len, buf, NULL);

			/* if ok, add it to RRset_Couple list */
			if (!rc->rrl || !rc->rrs || list_add (rc_list, rc)) {
				rrset_couple_free(rc);
				goto error;
			}

			/* point it to the one we just added */
			gl = rc_list->next;
		}

		/* extract resource record */
		if (rdlen) {
			int ret;

			ret = mesg_extract_rr (mesg, msg_end, r_type, r_class,
					       rdp, buf, sizeof (buf));
			if (ret < 0)
				goto error;

			if (!ret) {
				rp_ex = rdp;
				rdlen_ex = rdlen;
			} else {
				rp_ex = buf;
				rdlen_ex = ret;
			}
		} else {
			rp_ex = NULL;
			rdlen_ex = 0;
		}

		/* add the extracted RR to matching RR_List */
		rrl = rr_list_add (((RRset_Couple *) (gl->list_data))->rrl,
				   r_ttl, rdlen_ex, rp_ex);
		if (!rrl)
			goto error;

		((RRset_Couple *) (gl->list_data))->rrl = rrl;
	}

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: make each RRset from list.", fn);

	rc_list->list_data = NULL;
	for (gl = rc_list->next; gl->list_data; gl = gl->next) {
		RRset_Couple *rc;
		RRset *rrs;

		/* create complete RRset */
		rc = (RRset_Couple *) (gl->list_data);
		rrs = rrset_create (rc->rrs->key.info->r_type,
				    rc->rrs->key.info->r_class,
				    rc->rrs->key.info->owner_len,
				    rrset_owner (rc->rrs), rc->rrl);
		if (!rrs)
			goto error;

		if (target_list) {
			if (list_add (target_list, rrset_copy (rrs)) < 0) {
				rrset_free (rrs);
				goto error;
			}
		}

		rrset_free (rrs);
	}

	/* free unused resources */
	list_destroy (rc_list, rrset_couple_freev);

	if (T.debug > 4)
		syslog (LOG_DEBUG, "%s: end", fn);

	return rp;  /* byte after of the section we just processed */

error:
	syslog (LOG_INFO, "%s: message extraction failed", fn);
	list_destroy (rc_list, rrset_couple_freev);
	return NULL;
}



syntax highlighted by Code2HTML, v. 0.9.1