/**************************************************************************************************
	$Id: rr.c,v 1.65 2005/04/29 16:10:27 bboy Exp $

	Copyright (C) 2002-2005  Don Moore <bboy@bboy.net>

	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
**************************************************************************************************/

#include "mydns.h"

char mydns_rr_table_name[PATH_MAX] = MYDNS_RR_TABLE;
char *mydns_rr_where_clause = NULL;

/* Optional columns */
int mydns_rr_use_active = 0;
int mydns_rr_use_stamp = 0;

/* Make this nonzero to enable debugging within this source file */
#define	DEBUG_LIB_RR	0


/**************************************************************************************************
	MYDNS_RR_COUNT
	Returns the number of records in the rr table.
**************************************************************************************************/
long
mydns_rr_count(SQL *sqlConn)
{
	return sql_count(sqlConn, "SELECT COUNT(*) FROM %s", mydns_rr_table_name);
}
/*--- mydns_rr_count() --------------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_SET_RR_TABLE_NAME
**************************************************************************************************/
void
mydns_set_rr_table_name(char *name)
{
	if (!name)
		strncpy(mydns_rr_table_name, MYDNS_RR_TABLE, sizeof(mydns_rr_table_name)-1);
	else
		strncpy(mydns_rr_table_name, name, sizeof(mydns_rr_table_name)-1);
}
/*--- mydns_set_rr_table_name() -----------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_SET_RR_WHERE_CLAUSE
**************************************************************************************************/
void
mydns_set_rr_where_clause(char *where)
{
	if (where && strlen(where))
	{
		if (!(mydns_rr_where_clause = strdup(where)))
			Errx("out of memory");
	}
}
/*--- mydns_set_rr_where_clause() ---------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_GET_TYPE
**************************************************************************************************/
inline dns_qtype_t
mydns_rr_get_type(char *type)
{
	register char *c;

	for (c = type; *c; c++)
		*c = toupper(*c);

	switch (type[0])
	{
		case 'A':
			if (!type[1])
				return DNS_QTYPE_A;

			if (type[1] == 'A' && type[2] == 'A' && type[3] == 'A' && !type[4])
				return DNS_QTYPE_AAAA;

#if ALIAS_ENABLED
			if (type[1] == 'L' && type[2] == 'I' && type[3] == 'A' && type[4] == 'S' && !type[5])
				return DNS_QTYPE_ALIAS;
#endif
			break;

		case 'C':
			if (type[1] == 'N' && type[2] == 'A' && type[3] == 'M' && type[4] == 'E' && !type[5])
				return DNS_QTYPE_CNAME;
			break;

		case 'H':
			if (type[1] == 'I' && type[2] == 'N' && type[3] == 'F' && type[4] == 'O' && !type[5])
				return DNS_QTYPE_HINFO;
			break;

		case 'M':
			if (type[1] == 'X' && !type[2])
				return DNS_QTYPE_MX;
			break;

		case 'N':
			if (type[1] == 'S' && !type[2])
				return DNS_QTYPE_NS;
			if (type[1] == 'A' && type[2] == 'P' && type[3] == 'T' && type[4] == 'R' && !type[5])
				return DNS_QTYPE_NAPTR;
			break;

		case 'T':
			if (type[1] == 'X' && type[2] == 'T' && !type[3])
				return DNS_QTYPE_TXT;
			break;

		case 'P':
			if (type[1] == 'T' && type[2] == 'R' && !type[3])
				return DNS_QTYPE_PTR;
			break;

		case 'R':
			if (type[1] == 'P' && !type[2])
				return DNS_QTYPE_RP;
			break;

		case 'S':
			if (type[1] == 'R' && type[2] == 'V' && !type[3])
				return DNS_QTYPE_SRV;
			break;
	}
	return 0;
}
/*--- mydns_rr_get_type() -----------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_PARSE_RP
	RP contains two names in 'data' -- the mbox and the txt.
	NUL-terminate mbox and fill 'rp_txt' with the txt part of the record.
**************************************************************************************************/
static inline void
mydns_rr_parse_rp(SQL_ROW row, const char *origin, MYDNS_RR *rr)
{
	char *c;

	/* If no space, set txt to '.' */
	if (!(c = strchr(rr->data, ' ')))
	{
		rr->rp_txt[0] = '.';
		rr->rp_txt[1] = '\0';
	}
	else
	{
		strncpy(rr->rp_txt, c+1, sizeof(rr->rp_txt)-1);
		*c = '\0';

		/* Append origin to rp_txt if necessary */
		{
			int namelen = strlen(rr->rp_txt);
			if (namelen && rr->rp_txt[namelen-1] != '.')
			{
				strncat(rr->rp_txt, ".", sizeof(rr->rp_txt) - namelen - 1);
				strncat(rr->rp_txt, origin, sizeof(rr->rp_txt) - namelen - 2);
			}
		}
	}
}
/*--- mydns_rr_parse_rp() -----------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_PARSE_SRV
	SRV records contain two unsigned 16-bit integers in the "data" field before the target,
	'srv_weight' and 'srv_port' - parse them and make "data" contain only the target.  Also, make
	sure 'aux' fits into 16 bits, clamping values above 65535.
**************************************************************************************************/
static inline void
mydns_rr_parse_srv(SQL_ROW row, const char *origin, MYDNS_RR *rr)
{
	char *weight, *port, *target;

	/* Clamp 'aux' if necessary */
	if (rr->aux > 65535)
		rr->aux = 65535;

	/* Parse weight (into srv_weight), port (into srv_port), and target */
	target = rr->data;
	if ((weight = strsep(&target, " \t")))
	{
		rr->srv_weight = atoi(weight);
		if ((port = strsep(&target, " \t")))
			rr->srv_port = atoi(port);
		memmove(rr->data, target, strlen(target)+1);
	}
}
/*--- mydns_rr_parse_srv() ----------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_PARSE_NAPTR
	Returns 0 on success, -1 on error.
**************************************************************************************************/
static inline int
mydns_rr_parse_naptr(SQL_ROW row, const char *origin, MYDNS_RR *rr)
{
	char int_tmp[12], data_copy[DNS_MAXNAMELEN * 2 + 2], *p;

	strncpy(data_copy, rr->data, sizeof(data_copy) - 1);
	p = data_copy;

	if (!strsep_quotes(&p, int_tmp, sizeof(int_tmp)))
		return (-1);
	rr->naptr_order = atoi(int_tmp);

	if (!strsep_quotes(&p, int_tmp, sizeof(int_tmp)))
		return (-1);
	rr->naptr_pref = atoi(int_tmp);

	if (!strsep_quotes(&p, rr->naptr_flags, sizeof(rr->naptr_flags)))
		return (-1);

	if (!strsep_quotes(&p, rr->naptr_service, sizeof(rr->naptr_service)))
		return (-1);

	if (!strsep_quotes(&p, rr->naptr_regex, sizeof(rr->naptr_regex)))
		return (-1);

	if (!strsep_quotes(&p, rr->naptr_replacement, sizeof(rr->naptr_replacement)))
		return (-1);

	return 0;
}
/*--- mydns_rr_parse_naptr() --------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_PARSE
	Given the SQL results with RR data, populates and returns a matching MYDNS_RR structure.
	Returns NULL on error.
**************************************************************************************************/
inline MYDNS_RR *
mydns_rr_parse(SQL_ROW row, const char *origin)
{
	MYDNS_RR *rr;

	if ((rr = (MYDNS_RR *)calloc(1, sizeof(MYDNS_RR))))
	{
		rr->next = NULL;

		rr->id = atou(row[0]);
		rr->zone = atou(row[1]);
		strncpy(rr->name, row[2], sizeof(rr->name)-1);
		strncpy(rr->data, row[3], sizeof(rr->data)-1);
		rr->class = DNS_CLASS_IN;
		rr->aux = atou(row[4]);
		rr->ttl = atou(row[5]);
		if (!(rr->type = mydns_rr_get_type(row[6])))
		{
			/* Ignore unknown RR type(s) */
			free(rr);
			return (NULL);
		}
#if ALIAS_ENABLED
		if (rr->type == DNS_QTYPE_ALIAS)
		{
			rr->type = DNS_QTYPE_A;
			rr->alias = 1;
		}
		else
			rr->alias = 0;
#endif

		/* Populate special fields for RP records */
		if (rr->type == DNS_QTYPE_RP)
			mydns_rr_parse_rp(row, origin, rr);

		/* Populate special fields for NAPTR records */
		if (rr->type == DNS_QTYPE_NAPTR)
		{
			if (mydns_rr_parse_naptr(row, origin, rr) < 0)
			{
				free(rr);
				return (NULL);
			}
		}

		/* Append origin to data if it's not there for these types: */
		if (origin)
			switch (rr->type)
			{
				case DNS_QTYPE_CNAME:
				case DNS_QTYPE_MX:
				case DNS_QTYPE_NS:
				case DNS_QTYPE_RP:
				case DNS_QTYPE_SRV:
#ifdef DN_COLUMN_NAMES
					/* Just append dot for DN */
					strncat(rr->data, ".", sizeof(rr->data) - strlen(rr->data) - 1);
#else
					{
						int namelen = strlen(rr->data);
						if (namelen && rr->data[namelen-1] != '.')
						{
							strncat(rr->data, ".", sizeof(rr->data) - namelen - 1);
							strncat(rr->data, origin, sizeof(rr->data) - namelen - 2);
						}
					}
#endif
					break;
				default: break;
			}

		if (rr->type == DNS_QTYPE_SRV)
			mydns_rr_parse_srv(row, origin, rr);
	}
	return (rr);
}
/*--- mydns_rr_parse() --------------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_DUP
	Make and return a copy of a MYDNS_RR record.  If 'recurse' is specified, copies all records
	in the RRset.
**************************************************************************************************/
MYDNS_RR *
mydns_rr_dup(MYDNS_RR *start, int recurse)
{
	register MYDNS_RR *first = NULL, *last = NULL, *rr, *s, *tmp;

	for (s = start; s; s = tmp)
	{
		tmp = s->next;

		if (!(rr = (MYDNS_RR *)calloc(1, sizeof(MYDNS_RR))))
			Err(_("out of memory"));

		rr->id = s->id;
		rr->zone = s->zone;
		strncpy(rr->name, s->name, sizeof(rr->name)-1);
		rr->type = s->type;
		rr->class = s->class;
		strncpy(rr->data, s->data, sizeof(rr->data)-1);
		rr->aux = s->aux;
		rr->ttl = s->ttl;
#if ALIAS_ENABLED
		rr->alias = s->alias;
#endif

		rr->srv_weight = s->srv_weight;
		rr->srv_port = s->srv_port;

		/* Copy rp_txt only for RP records */
		if (rr->type == DNS_QTYPE_RP)
			strncpy(rr->rp_txt, s->rp_txt, sizeof(rr->rp_txt) - 1);

		/* Copy naptr fields only for NAPTR records */
		if (rr->type == DNS_QTYPE_NAPTR)
		{
			rr->naptr_order = s->naptr_order;
			rr->naptr_pref = s->naptr_pref;
			strncpy(rr->naptr_flags, s->naptr_flags, sizeof(rr->naptr_flags) - 1);
			strncpy(rr->naptr_service, s->naptr_service, sizeof(rr->naptr_service) - 1);
			strncpy(rr->naptr_regex, s->naptr_regex, sizeof(rr->naptr_regex) - 1);
			strncpy(rr->naptr_replacement, s->naptr_replacement, sizeof(rr->naptr_replacement) - 1);
		}

		rr->next = NULL;
		if (recurse)
		{
			if (!first) first = rr;
			if (last) last->next = rr;
			last = rr;
		}
		else
			return (rr);
	}
	return (first);
}
/*--- mydns_rr_dup() ----------------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_SIZE
**************************************************************************************************/
inline size_t
mydns_rr_size(MYDNS_RR *first)
{
	register MYDNS_RR *p;
	register size_t size = 0;

	for (p = first; p; p = p->next)
		size += sizeof(MYDNS_RR);

	return (size);
}
/*--- mydns_rr_size() ---------------------------------------------------------------------------*/


/**************************************************************************************************
	_MYDNS_RR_FREE
	Frees the pointed-to structure.	Don't call this function directly, call the macro.
**************************************************************************************************/
inline void
_mydns_rr_free(MYDNS_RR *first)
{
	register MYDNS_RR *p, *tmp;

	for (p = first; p; p = tmp)
	{
		tmp = p->next;
		Free(p);
	}
}
/*--- _mydns_rr_free() --------------------------------------------------------------------------*/


/**************************************************************************************************
	MYDNS_RR_LOAD
	Returns 0 on success or nonzero if an error occurred.
	If "name" is NULL, all resource records for the zone will be loaded.
**************************************************************************************************/
int
mydns_rr_load(SQL *sqlConn, MYDNS_RR **rptr, uint32_t zone,
				  dns_qtype_t type, char *name, char *origin)
{
	MYDNS_RR *first = NULL, *last = NULL;
	size_t	querylen;
	uchar		query[DNS_QUERYBUFSIZ],
				namequery[DNS_MAXNAMELEN + DNS_MAXNAMELEN + DNS_MAXNAMELEN + 25] = "";
	uchar		*wheretype;
	register char *c, *cp;
	SQL_RES	*res;
	SQL_ROW	row;
#ifdef DN_COLUMN_NAMES
	int		originlen = origin ? strlen(origin) : 0;
	int		namelen = name ? strlen(name) : 0;
#endif

#if DEBUG_ENABLED && DEBUG_LIB_RR
	Debug("mydns_rr_load(zone=%u, type='%s', name='%s', origin='%s')",
			zone, mydns_qtype_str(type), name ?: "NULL", origin ?: "NULL");
#endif

	if (rptr) *rptr = NULL;

	/* Verify args */
	if (!sqlConn || !rptr)
	{
		errno = EINVAL;
		return (-1);
	}

	/* Get the type='XX' part of the WHERE clause */
	switch (type)
	{
#if ALIAS_ENABLED
		case DNS_QTYPE_A:			wheretype = " AND (type='A' OR type='ALIAS')"; break;
#else
		case DNS_QTYPE_A:			wheretype = " AND type='A'"; break;
#endif
		case DNS_QTYPE_AAAA:		wheretype = " AND type='AAAA'"; break;
		case DNS_QTYPE_CNAME:	wheretype = " AND type='CNAME'"; break;
		case DNS_QTYPE_HINFO:	wheretype = " AND type='HINFO'"; break;
		case DNS_QTYPE_MX:		wheretype = " AND type='MX'"; break;
		case DNS_QTYPE_NAPTR:	wheretype = " AND type='NAPTR'"; break;
		case DNS_QTYPE_NS:		wheretype = " AND type='NS'"; break;
		case DNS_QTYPE_PTR:		wheretype = " AND type='PTR'"; break;
		case DNS_QTYPE_SOA:		wheretype = " AND type='SOA'"; break;
		case DNS_QTYPE_SRV:		wheretype = " AND type='SRV'"; break;
		case DNS_QTYPE_TXT:		wheretype = " AND type='TXT'"; break;
		case DNS_QTYPE_ANY:		wheretype = ""; break;
		default:
			errno = EINVAL;
			return (-1);
	}

	/* Make sure 'name' and 'origin' (if present) are valid */
	if (name)
	{
		for (c = name; *c; c++)
			if (SQL_BADCHAR(*c))
				return (0);
	}
	if (origin)
	{
		for (c = origin; *c; c++)
			if (SQL_BADCHAR(*c))
				return (0);
	}

#ifdef DN_COLUMN_NAMES
	/* Remove dot from origin and name for DN */
	if (originlen && origin[originlen - 1] == '.')
		origin[originlen-1] = '\0';
	else
		originlen = 0;

	if (name)
	{
		if (namelen && name[namelen - 1] == '.')
			name[namelen-1] = '\0';
		else
			namelen = 0;
	}
#endif

	/* Construct query */
	if (name)
	{
		if (origin)
		{
			if (!name[0])
				snprintf(namequery, sizeof(namequery), "(name='' OR name='%s')", origin);
			else
			{
#ifdef DN_COLUMN_NAMES
				snprintf(namequery, sizeof(namequery), "name='%s'", name);
#else
				snprintf(namequery, sizeof(namequery), "(name='%s' OR name='%s.%s')", name, name, origin);
#endif
			}
		}
		else
			snprintf(namequery, sizeof(namequery), "name='%s'", name);
	}

#ifdef DN_COLUMN_NAMES
	if (originlen)
		origin[originlen - 1] = '.';							/* Readd dot to origin for DN */

	if (name)
	{
		if (namelen)
			name[namelen - 1] = '.';
	}
#endif

	querylen = snprintf(query, sizeof(query),
		"SELECT "MYDNS_RR_FIELDS"%s FROM %s WHERE "
#ifdef DN_COLUMN_NAMES
			"zone_id=%u%s"
#else
			"zone=%u%s"
#endif
			"%s%s%s%s",
			(mydns_rr_use_active ? ",active" : ""),
			mydns_rr_table_name,
			zone, wheretype,
			(namequery[0]) ? " AND " : "",
			namequery,
			(mydns_rr_where_clause) ? " AND " : "",
			(mydns_rr_where_clause) ? mydns_rr_where_clause : "");

	/* Submit query */
	if (!(res = sql_query(sqlConn, query, querylen)))
		return (-1);

#if DEBUG_ENABLED && DEBUG_LIB_RR
	{
		int numresults = sql_num_rows(res);

		Debug("RR query: %d row%s: %s", numresults, S(numresults), query);
	}
#endif

	/* Add results to list */
	while ((row = sql_getrow(res)))
	{
		MYDNS_RR *new;

		/* Obey "active" column */
		if (mydns_rr_use_active && row[MYDNS_RR_NUMFIELDS] && !GETBOOL(row[MYDNS_RR_NUMFIELDS]))
			continue;

		if (!(new = mydns_rr_parse(row, origin)))
			continue;

		/* Always trim origin from name (XXX: Why? When did I add this?) */
		/* Apparently removing this code breaks RRs where the name IS the origin */
		/* But trim only where the name is exactly the origin */
		if (origin && (cp = strstr(new->name, origin)) && !(cp - new->name))
			*cp = '\0';

		if (!first) first = new;
		if (last) last->next = new;
		last = new;
	}

	*rptr = first;
	sql_free(res);
	return (0);
}
/*--- mydns_rr_load() ---------------------------------------------------------------------------*/

/* vi:set ts=3: */


syntax highlighted by Code2HTML, v. 0.9.1