/**************************************************************************************************
	$Id: import-tinydns.c,v 1.14 2005/04/20 16:49:12 bboy Exp $

	import-tinydns.c: Import DNS data from a tinydns-data format data file.

	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 "util.h"

#ifdef TINYDNS_IMPORT

#define MAX_FIELDS	20											/* Max fields per line */
#define TINYDNS_DEF_REFRESH	16384
#define TINYDNS_DEF_RETRY		2048
#define TINYDNS_DEF_EXPIRE		1048576
#define TINYDNS_DEF_MINIMUM	2560
#define TINYDNS_DEF_TTL			2560

typedef struct _zone
{
	uint32_t id;
	char		*origin, *ns, *mbox;
	uint32_t	serial, refresh, retry, expire, minimum, ttl;
} ZONE;

static ZONE	**Zones = NULL;									/* List of zones found */
static int	numZones = 0;										/* Number of zones found */

static uint32_t		default_serial;						/* Default serial */
static unsigned int	lineno;									/* Current line number */
static char				*filename;								/* Input file name */
static char				*field[MAX_FIELDS];					/* Fields parsed from line */

static char				tinydns_zone[DNS_MAXNAMELEN+1];	/* Zone to import (only this zone) */

extern uint32_t		import_zone_id;						/* ID of current zone */

extern uint32_t import_soa(const char *origin, const char *ns, const char *mbox,
  unsigned serial, unsigned refresh, unsigned retry, unsigned expire,
  unsigned minimum, unsigned ttl);
extern void import_rr(char *name, char *type, char *data, unsigned aux, unsigned ttl);


/**************************************************************************************************
	TWARN
	Convenience function; outputs error message for current file/line.
**************************************************************************************************/
static void
twarn(const char *fmt, ...)
{
	va_list	ap;
	char		buf[1024];

	va_start(ap, fmt);
	vsnprintf(buf, sizeof(buf)-1, fmt, ap);
	va_end(ap);

	Warnx("%s: line %u: %s", filename, lineno, buf);
	return;
}
/*--- twarn() -----------------------------------------------------------------------------------*/


/**************************************************************************************************
	ZONE_OK
	Given an fqdn, is this a zone we want to process?
**************************************************************************************************/
static inline int
zone_ok(const char *fqdn)
{
	if (!tinydns_zone[0])										/* Processing all zones */
		return 1;
	if (!strcasecmp(fqdn, tinydns_zone))					/* Exact match on zone */
		return 1;

	/* Does FQDN at least end with the zone? */
	if (strlen(fqdn) > strlen(tinydns_zone)
		 && !strcasecmp(fqdn + strlen(fqdn) - strlen(tinydns_zone), tinydns_zone))
		return 1;

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


/**************************************************************************************************
	ZONECMP
	Comparison function for sorting zones.
**************************************************************************************************/
static int
zonecmp(const void *p1, const void *p2)
{
	ZONE *z1 = *(ZONE **)p1, *z2 = *(ZONE **)p2;
	return strcasecmp(z1->origin, z2->origin);
}
/*--- zonecmp() ---------------------------------------------------------------------------------*/


/**************************************************************************************************
	FIND_ZONE
	Looks for the specified zone.  Returns it if found, else NULL.
**************************************************************************************************/
static inline ZONE *
find_zone(const char *origin)
{
	register int n;

	for (n = 0; n < numZones; n++)
		if (!strcasecmp(Zones[n]->origin, origin))
			return Zones[n];
	return NULL;
}
/*--- find_zone() -------------------------------------------------------------------------------*/


/**************************************************************************************************
	FIND_HOST_ZONE
	Looks for the zone matching the provided FQDN.  Returns it if found, else NULL.  Also sets
	the hostname part of 'fqdn' in 'hostname'.
**************************************************************************************************/
static inline ZONE *
find_host_zone(char *fqdn, char *hostname)
{
	register char *c;
	ZONE *z;

	hostname[0] = '\0';

	if ((z = find_zone(fqdn)))									/* See if 'fqdn' is a plain zone origin */
		return (z);

	for (c = fqdn; *c; c++)
		if (*c == '.')
			if ((z = find_zone(c + 1)))
			{
				memcpy(hostname, fqdn, c - fqdn);
				hostname[c - fqdn] = '\0';
				return (z);
			}

	return NULL;
}
/*--- find_host_zone() --------------------------------------------------------------------------*/


/**************************************************************************************************
	FIND_ARPA_ZONE
	Given an IP address in numbers-and-dots format, returns the relevant in-addr.arpa zone, or
	NULL if not found.
**************************************************************************************************/
static inline ZONE *
find_arpa_zone(const char *ip, char *hostname)
{
	char zone[DNS_MAXNAMELEN + 1], buf[DNS_MAXNAMELEN + 1], *p, *a, *b, *c, *d;
	ZONE *z;

	hostname[0] = '\0';
	strncpy(buf, ip, sizeof(buf)-1);
	p = buf;
	if (!(a = strsep(&p, ".")) || !(b = strsep(&p, "."))
		 || !(c = strsep(&p, ".")) || !(d = strsep(&p, ".")))
		return NULL;

	snprintf(zone, sizeof(zone), "%s.in-addr.arpa", a);
	if ((z = find_zone(zone)))
	{
		snprintf(hostname, DNS_MAXNAMELEN, "%s.%s.%s", d, c, b);
		return z;
	}

	snprintf(zone, sizeof(zone), "%s.%s.in-addr.arpa", b, a);
	if ((z = find_zone(zone)))
	{
		snprintf(hostname, DNS_MAXNAMELEN, "%s.%s", d, c);
		return z;
	}

	snprintf(zone, sizeof(zone), "%s.%s.%s.in-addr.arpa", c, b, a);
	if ((z = find_zone(zone)))
	{
		strncpy(hostname, d, DNS_MAXNAMELEN);
		return z;
	}

	return NULL;
}
/*--- find_arpa_zone() --------------------------------------------------------------------------*/


/**************************************************************************************************
	NEW_ZONE
	Adds a new zone.  Returns the zone.
**************************************************************************************************/
static ZONE *
new_zone(const char *originp)
{
	ZONE *z;
	char origin[DNS_MAXNAMELEN+1];

	/* If this is an 'in-addr.arpa' zone, knock off the first quad if present */
	if (strlen(originp) > 12 && !strcasecmp(originp + strlen(originp) - 13, ".in-addr.arpa"))
	{
		char buf[DNS_MAXNAMELEN+1], *p, *a, *b, *c, *d;

		strncpy(buf, originp, sizeof(buf)-1);
		p = buf;
		if ((d = strsep(&p, ".")) && (c = strsep(&p, "."))
			 && (b = strsep(&p, ".")) && (a = strsep(&p, ".")))
			snprintf(origin, sizeof(origin), "%s.%s.%s.in-addr.arpa", c, b, a);
		else
			strncpy(origin, originp, sizeof(origin)-1);
	}
	else
		strncpy(origin, originp, sizeof(origin)-1);

	if ((z = find_zone(origin)))
		return z;

	if (!(z = malloc(sizeof(ZONE))))
		Err("malloc");

	z->id = 0;
	if (!(z->origin = strdup(origin)))
		Err("strdup");
	z->ns = NULL;
	z->mbox = NULL;
	z->serial = default_serial;
	z->refresh = TINYDNS_DEF_REFRESH;
	z->retry = TINYDNS_DEF_RETRY;
	z->expire = TINYDNS_DEF_EXPIRE;
	z->minimum = TINYDNS_DEF_MINIMUM;
	z->ttl = TINYDNS_DEF_TTL;

	if (!Zones)
	{
		if (!(Zones = malloc((numZones + 1) * sizeof(ZONE))))
			Err("malloc");
	}
	else
	{
		if (!(Zones = realloc(Zones, (numZones + 1) * sizeof(ZONE))))
			Err("realloc");
	}
	Zones[numZones++] = z;

	return z;
}
/*--- new_zone() --------------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_ADD_SOA
	Adds a zone to 'Zones' due to a '.' or 'Z' line.
**************************************************************************************************/
void
tinydns_add_soa(char *line)
{
	char	*l = line + 1;
	ZONE	*z;

	if (line[0] == '.')											/* '.' (NS) line */
	{
		char *fqdn = NULL, *ip = NULL, *x = NULL, *ttl = NULL, *timestamp = NULL, *lo = NULL;

		if (!(fqdn = strsep(&l, ":")) || !strlen(fqdn))
			return twarn("'.' line has no fqdn");

		if (!zone_ok(fqdn))
			return;

		if (!(ip = strsep(&l, ":")))
			return twarn("'.' line has no ip");

		if ((x = strsep(&l, ":")) && (ttl = strsep(&l, ":")) && (timestamp = strsep(&l, ":"))
			 && (lo = strsep(&l, ":")))
			/* DONOTHING */;

		z = new_zone(fqdn);

		/* Assign 'ns' */
		if (!z->ns)
		{
			if (!x || !strlen(x))
				sdprintf(&z->ns, "ns.%s", fqdn);
			else if (strchr(x, '.'))
			{
				if (!(z->ns = strdup(x)))
					Err("strdup");
			}
			else
				sdprintf(&z->ns, "%s.ns.%s", x, fqdn);
		}

		/* Assign 'mbox' */
		if (!z->mbox)
			sdprintf(&z->mbox, "hostmaster.%s", fqdn);
	}
	else if (line[0] == 'Z')									/* 'Z' (SOA) line */
	{
		char *fqdn = NULL, *ns = NULL, *mbox = NULL, *serial = NULL, *refresh = NULL,
			  *retry = NULL, *expire = NULL, *minimum = NULL, *ttl = NULL, *timestamp = NULL,
			  *lo = NULL;

		if (!(fqdn = strsep(&l, ":")) || !strlen(fqdn))
			return twarn("'Z' line has no fqdn");

		if (!zone_ok(fqdn))
			return;

		if (!(ns = strsep(&l, ":")) || !strlen(ns))
			return twarn("'Z' line has no ns");
		if (!(mbox = strsep(&l, ":")) || !strlen(mbox))
			return twarn("'Z' line has no mbox");

		if ((serial = strsep(&l, ":")) && (refresh = strsep(&l, ":")) && (retry = strsep(&l, ":"))
			 && (expire = strsep(&l, ":")) && (minimum = strsep(&l, ":")) && (ttl = strsep(&l, ":"))
			 && (timestamp = strsep(&l, ":")) && (lo = strsep(&l, ":")))
			/* DONOTHING */;

		z = new_zone(fqdn);
		if (!z->ns)
		{
			if (!(z->ns = strdup(ns)))
				Err("strdup");
		}
		if (!z->mbox)
		{
			if (!(z->mbox = strdup(mbox)))
				Err("strdup");
		}
		if (serial) z->serial = atol(serial);
		if (refresh) z->refresh = atol(refresh);
		if (retry) z->retry = atol(retry);
		if (expire) z->expire = atol(expire);
		if (minimum) z->minimum = atol(minimum);
		if (ttl) z->ttl = atol(ttl);
	}
}
/*--- tinydns_add_soa() -------------------------------------------------------------------------*/


/**************************************************************************************************
	CREATE_ZONE
	Loads or creates the actual SOA record and loads the ID.
**************************************************************************************************/
static void
create_zone(ZONE *z)
{
	char origin[DNS_MAXNAMELEN + 3], ns[DNS_MAXNAMELEN + 3], mbox[DNS_MAXNAMELEN + 3];

	if (!z->ns)
		sdprintf(&z->ns, "ns.%s", z->origin);
	if (!z->mbox)
		sdprintf(&z->mbox, "hostmaster.%s", z->origin);

	snprintf(origin, sizeof(origin), "%s.", z->origin);
	snprintf(ns, sizeof(ns), "%s.", z->ns);
	snprintf(mbox, sizeof(mbox), "%s.", z->mbox);

	z->id = import_soa(origin, ns, mbox,
							 z->serial, z->refresh, z->retry, z->expire, z->minimum, z->ttl);
}
/*--- create_zone() -----------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_PLUS

	+fqdn:ip:ttl:timestamp:lo

	Alias fqdn with IP address ip. This is just like =fqdn:ip:ttl except that tinydns-data
	does not create the PTR record.
**************************************************************************************************/
static void
tinydns_plus(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *ip = field[1], *ttl = field[2];
	char	hostname[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;
	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));

	/* Set import_zone_id and import the record */
	if (ip && strlen(ip))
	{
		import_zone_id = z->id;
		import_rr(hostname, "A", ip, 0, ttl ? atol(ttl) : z->ttl);
	}
}
/*--- tinydns_plus() ----------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_DOT

	.fqdn:ip:x:ttl:timestamp:lo

	1. Create an NS record showing x.ns.fqdn as a name server for fqdn
	2. Create an A record showing ip as the IP address of x.ns.fqdn
**************************************************************************************************/
static void
tinydns_dot(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *ip = field[1], *x = field[2], *ttl = field[3];
	char	hostname[DNS_MAXNAMELEN + 1] = "", buf[DNS_MAXNAMELEN + 1];

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;

	/* Create NS record */
	if (x && strlen(x))
	{
		if (strchr(x, '.'))
			strncpy(buf, x, sizeof(buf)-1);
		else
			snprintf(buf, sizeof(buf), "%s.ns", x);
		if (LASTCHAR(buf) != '.')
			strcat(buf, ".");
		import_rr(hostname, "NS", buf, 0, ttl ? atol(ttl) : z->ttl);
	}
	else
	{
		strncpy(buf, "ns", sizeof(buf)-1);
		import_rr(hostname, "NS", buf, 0, ttl ? atol(ttl) : z->ttl);
	}

	/* Create A record if 'ip' is present */
	if (ip && strlen(ip))
		import_rr(buf, "A", ip, 0, ttl ? atol(ttl) : z->ttl);
}
/*--- tinydns_dot() -----------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_AMP

	&fqdn:ip:x:ttl:timestamp:lo

	Name server for domain fqdn. tinydns-data creates

	1. An NS record showing x.ns.fqdn as a name server for fqdn.
	2. An A record showing ip as the IP address of x.ns.fqdn.
**************************************************************************************************/
static void
tinydns_amp(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *ip = field[1], *x = field[2], *ttl = field[3];
	char	hostname[DNS_MAXNAMELEN + 1] = "", buf[DNS_MAXNAMELEN + 1];

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;

	/* Create NS record */
	if (x && strlen(x))
	{
		if (strchr(x, '.'))
			strncpy(buf, x, sizeof(buf)-1);
		else
			snprintf(buf, sizeof(buf), "%s.ns", x);
		if (LASTCHAR(buf) != '.')
			strcat(buf, ".");
		import_rr(hostname, "NS", buf, 0, ttl ? atol(ttl) : z->ttl);
	}
	else
	{
		strncpy(buf, "ns", sizeof(buf)-1);
		import_rr(hostname, "NS", buf, 0, ttl ? atol(ttl) : z->ttl);
	}

	/* Create A record if 'ip' is present */
	if (ip && strlen(ip))
		import_rr(buf, "A", ip, 0, ttl ? atol(ttl) : z->ttl);
}
/*--- tinydns_amp() -----------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_EQUAL

	=fqdn:ip:ttl:timestamp:lo

	Host 'fqdn' with IP address 'ip'.  Creates:

	1. An A record showing 'ip' as the IP address of 'fqdn'
	2. A PTR record showing 'fqdn' as the name of d.c.b.a.in-addr.arpa if 'ip' is a.b.c.d. 

	Remember to specify name servers for some suffix of 'fqdn'; otherwise tinydns will not respond
	to queries about 'fqdn'.  The same comment applies to other records described below. 
	Similarly, remember to specify name servers for some suffix of d.c.b.a.in-addr.arpa, if that
	domain has been delegated to you.

	Example:

		=button.panic.mil:1.8.7.108

	creates an A record showing 1.8.7.108 as the IP address of button.panic.mil, and a PTR record
	showing button.panic.mil as the name of 108.7.8.1.in-addr.arpa.
**************************************************************************************************/
static void
tinydns_equal(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *ip = field[1], *ttl = field[2];
	char	hostname[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;

	/* Create A record showing 'ip' as the IP address of 'fqdn' */
	import_rr(fqdn, "A", ip, 0, ttl ? atol(ttl) : z->ttl);

	/* Create PTR record if we can find an appropriate in-addr.arpa zone */
	if ((z = find_arpa_zone(ip, hostname)))
	{
		import_zone_id = z->id;
		import_rr(hostname, "PTR", fqdn, 0, ttl ? atol(ttl) : z->ttl);
	}
	else
	{
		char buf[DNS_MAXNAMELEN + 1], *p, *a, *b, *c, *d;

		strncpy(buf, ip, sizeof(buf)-1);
		p = buf;
		if (!(a = strsep(&p, ".")) || !(b = strsep(&p, "."))
			 || !(c = strsep(&p, ".")) || !(d = strsep(&p, ".")))
			return;
		Warnx("%s:%u: %s.%s.%s.%s.in-addr.arpa: %s", filename, lineno,
			d, c, b, a, _("no matching zone for PTR record"));
	}
}
/*--- tinydns_equal() ---------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_AT

	@fqdn:ip:x:dist:ttl:timestamp:lo

	Mail exchanger for 'fqdn'.  Creates:

	1. An MX record showing x.mx.fqdn as a mail exchanger for 'fqdn' at distance 'dist' and
	2. An A record showing 'ip' as the IP address of x.mx.fqdn. 

	You may omit dist; the default distance is 0.

	If 'x' contains a dot then it is treated specially; see above.

	You may create several MX records for 'fqdn', with a different 'x' for each server.
	Make sure to arrange for the SMTP server on each IP address to accept mail for 'fqdn'.

	Example:

		@panic.mil:1.8.7.88:mail.panic.mil

	creates an MX record showing mail.panic.mil as a mail exchanger for panic.mil at distance 0,
	and an A record showing 1.8.7.88 as the IP address of mail.panic.mil.
**************************************************************************************************/
static void
tinydns_at(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *ip = field[1], *x = field[2], *dist = field[3], *ttl = field[4];
	char	hostname[DNS_MAXNAMELEN + 1] = "", mx[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!x || !strlen(x))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("no mail exchanger specified"));

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;

	/* Create MX record showing 'x'.mx.fqdn as MX for 'fqdn' */
	if (strchr(x, '.'))
		strncpy(mx, x, sizeof(mx)-1);
	else
		snprintf(mx, sizeof(mx), "%s.mx.%s", x, z->origin);
	if (LASTCHAR(mx) != '.')
		strcat(mx, ".");
	import_rr(hostname, "MX", mx, dist ? atol(dist) : 0, ttl ? atol(ttl) : z->ttl);

	/* Create A record for 'mx' */
	if (ip && strlen(ip))
	{
		if (!(z = find_host_zone(mx, hostname)))
			return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
		import_zone_id = z->id;
		import_rr(mx, "A", ip, 0, ttl ? atol(ttl) : z->ttl);
	}
}
/*--- tinydns_at() ------------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_APOS

	'fqdn:str:ttl:timestamp:lo

	Creates a TXT record for 'fqdn' containing the string 'str'.  You may use octal \nnn codes to
	include arbitrary bytes inside 'str'; for example, \072 is a colon.
**************************************************************************************************/
static void
tinydns_apos(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *str = field[1], *ttl = field[2], *txt;
	char	hostname[DNS_MAXNAMELEN + 1] = "";
	register char *s, *d;

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!str || !strlen(str))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("no text data specified"));

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;

	/* Unescape octal chars */
	if (!(txt = malloc(strlen(str) + 1)))
		Err("malloc");
	for (s = str, d = txt; *s; s++)
	{
		if (s[0] == '\\' && (s[1] >= '0' && s[1] <= '7')
			 && (s[2] >= '0' && s[2] <= '7') && (s[3] >= '0' && s[3] <= '7'))
		{
			*d++ = strtol(s + 1, NULL, 8);
			s += 3;
		}
		else
			*d++ = *s;
	}
	*d = '\0';
	import_rr(hostname, "TXT", txt, 0, ttl ? atol(ttl) : z->ttl);
}
/*--- tinydns_apos() ----------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_CARET

	^fqdn:p:ttl:timestamp:lo

	PTR record for fqdn. tinydns-data creates a PTR record for fqdn pointing to the domain name p.
**************************************************************************************************/
static void
tinydns_caret(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *p = field[1], *ttl = field[2];
	char	hostname[DNS_MAXNAMELEN + 1] = "", ptr[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!p || !strlen(p))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("no PTR data specified"));
	strncpy(ptr, p, sizeof(ptr)-1);
	if (strchr(ptr, '.') && LASTCHAR(ptr) != '.')
		strcat(ptr, ".");

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;
	import_rr(hostname, "PTR", ptr, 0, ttl ? atol(ttl) : z->ttl);
}
/*--- tinydns_caret() ---------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_C

	Cfqdn:p:ttl:timestamp:lo

	Creates a CNAME record for 'fqdn' pointing to the domain name 'p'.
**************************************************************************************************/
static void
tinydns_C(void)
{
	ZONE	*z;
	char	*fqdn = field[0], *p = field[1], *ttl = field[2];
	char	hostname[DNS_MAXNAMELEN + 1] = "", cname[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!p || !strlen(p))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("no PTR data specified"));
	strncpy(cname, p, sizeof(cname)-1);
	if (strchr(cname, '.') && LASTCHAR(cname) != '.')
		strcat(cname, ".");

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));
	import_zone_id = z->id;
	import_rr(hostname, "CNAME", cname, 0, ttl ? atol(ttl) : z->ttl);
}
/*--- tinydns_C() -------------------------------------------------------------------------------*/


/**************************************************************************************************
	TINYDNS_COLON

	:fqdn:n:rdata:ttl:timestamp:lo

	Generic record for 'fqdn'.  Creates a record of type 'n' for 'fqdn' showing 'rdata'.
	'n' must be an integer between 1 and 65535; it must not be 2 (NS), 5 (CNAME), 6 (SOA),
	12 (PTR), 15 (MX), or 252 (AXFR).  The proper format of 'rdata' depends on 'n'.  You may use
	octal \nnn codes to include arbitrary bytes inside 'rdata'.
**************************************************************************************************/
static void
tinydns_colon(void)
{
	ZONE	*z;
	char	*fqdn = field[0]; /* *n = field[1], *rdata = field[2], *ttl = field[3]; */
	char	hostname[DNS_MAXNAMELEN + 1] = "";

	if (!fqdn || !strlen(fqdn))
		return (void)Warnx("%s:%u: %s", filename, lineno, _("fqdn field empty"));
	if (!zone_ok(fqdn))
		return;

	if (!(z = find_host_zone(fqdn, hostname)))
		return (void)Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("fqdn does not match any zones"));

	Warnx("%s:%u: %s: %s", filename, lineno, fqdn, _("generic record type not supported"));
}
/*--- tinydns_colon() ---------------------------------------------------------------------------*/


/**************************************************************************************************
	IMPORT_TINYDNS
	Imports a tinydns-data format file.  Works in two passes: The first pass creates all SOA
	records, the second pass creates all RR records.
**************************************************************************************************/
void
import_tinydns(char *datafile, char *import_zone)
{
	struct stat st;
	FILE	*fp;
	char	buf[4096], *b;
	int	n;

	/* Clear zone list */
	for (n = 0; n < numZones; n++)
	{
		Free(Zones[n]->origin);
		if (Zones[n]->ns)
			Free(Zones[n]->ns);
		if (Zones[n]->mbox)
			Free(Zones[n]->mbox);
		Free(Zones[n]);
	}
	if (Zones)
		Free(Zones);
	Zones = NULL;
	numZones = 0;

	/* Reset globals and open input file */
	lineno = 0;
	filename = datafile;
	if (import_zone)
		strncpy(tinydns_zone, import_zone, sizeof(tinydns_zone)-1);
	else
		tinydns_zone[0] = '\0';
	if (stat(filename, &st))
		return (void)Warn("%s", filename);
	if (!(fp = fopen(filename, "r")))
		return (void)Warn("%s", filename);
	strftime(buf, sizeof(buf)-1, "%Y%m%d", localtime(&st.st_mtime));
	default_serial = atoi(buf);

	/* Pass 1: Search for '.' and 'Z' lines; create SOA records */
	while (fgets(buf, sizeof(buf), fp))
	{
		lineno++;
		strtrim(buf);
		if (strlen(buf) < 2)
			continue;
		buf[0] = toupper(buf[0]);
		if (buf[0] == '.' || buf[0] == 'Z')
			tinydns_add_soa(buf);
	}
	if (!numZones)
	{
		if (import_zone)
			Warnx("%s: %s: %s", filename, import_zone, _("no zone data found"));
		else
			Warnx("%s: %s", filename, _("no zones found"));
		return;
	}
	qsort(Zones, numZones, sizeof(ZONE *), zonecmp);

	/* Load and/or create zones */
	for (n = 0; n < numZones; n++)
		create_zone(Zones[n]);

	if (!import_zone)
		Notice("%s: %u %s", filename, numZones, _("zones"));

	/* Pass 2: Insert resource records */
	rewind(fp);
	lineno = 0;
	while (fgets(buf, sizeof(buf), fp))
	{
		lineno++;
		strtrim(buf);
		if (strlen(buf) < 2)
			continue;
		buf[0] = toupper(buf[0]);
		for (n = 0; n < MAX_FIELDS; n++)						/* Reset fields */
			field[n] = (char *)NULL;
		for (n = 0, b = buf + 1; n < MAX_FIELDS; n++)	/* Load fields */
			if (!(field[n] = strsep(&b, ":")))
				break;
		switch (buf[0])
		{
			case '.':
				tinydns_dot();
				break;

			case '&':
				tinydns_amp();
				break;

			case '=':
				tinydns_equal();
				break;

			case '+':
				tinydns_plus();
				break;

			case '@':
				tinydns_at();
				break;

			case '#':		/* Comment line */
				break;

			case '-':		/* Ignored (by tinydns-data) */
				break;

			case '\'':
				tinydns_apos();
				break;

			case '^':
				tinydns_caret();
				break;

			case 'C':
				tinydns_C();
				break;

			case 'Z':		/* Ignored (SOA record; already processed on first pass) */
				break;

			case ':':
				tinydns_colon();
				break;

			default:
				Warnx("%s:%u: %s (0x%X)", filename, lineno, _("unknown line type"), buf[0]);
				break;
		}
	}
	fclose(fp);
	if (import_zone)
		Notice("%s: %s: %s", filename, import_zone, _("zone imported"));
}
/*--- import_tinydns() --------------------------------------------------------------------------*/

#endif /* TINYDNS */

/* vi:set ts=3: */
/* NEED_PO */


syntax highlighted by Code2HTML, v. 0.9.1