/*****************************************************************
**
**	@(#) zfparse.c -- A zone file parser
**
**	Copyright (c) Jan 2010 - Jan 2010, Holger Zuleger HZnet. All rights reserved.
**
**	This software is open source.
**
**	Redistribution and use in source and binary forms, with or without
**	modification, are permitted provided that the following conditions
**	are met:
**
**	Redistributions of source code must retain the above copyright notice,
**	this list of conditions and the following disclaimer.
**
**	Redistributions in binary form must reproduce the above copyright notice,
**	this list of conditions and the following disclaimer in the documentation
**	and/or other materials provided with the distribution.
**
**	Neither the name of Holger Zuleger HZnet nor the names of its contributors may
**	be used to endorse or promote products derived from this software without
**	specific prior written permission.
**
**	THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
**	"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
**	TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
**	PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE
**	LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
**	CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
**	SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
**	INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
**	CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
**	ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
**	POSSIBILITY OF SUCH DAMAGE.
**
*****************************************************************/
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <unistd.h>	/* for link(), unlink() */
# include <ctype.h>
# include <assert.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
# include "config_zkt.h"
# include "zconf.h"
# include "misc.h"
# include "log.h"
# include "debug.h"
#define extern
# include "zfparse.h"
#undef extern


extern	const	char	*progname;

/*****************************************************************
**	is_multiline_rr (const char *s)
*****************************************************************/
static	const	char	*is_multiline_rr (int *multi_line_rr, const char *p)
{
	while ( *p && *p != ';' )
	{
		if ( *p == '\"' )
			do
				p++;
			while ( *p && *p != '\"' );

		if ( *p == '(' )
			*multi_line_rr = 1;
		if ( *p == ')' )
			*multi_line_rr = 0;
		p++;
	}
	return p;
}

/*****************************************************************
**	skipws (const char *s)
*****************************************************************/
static	const	char	*skipws (const char *s)
{
	while ( *s && (*s == ' ' || *s == '\t' || *s == '\n') )
		s++;
	return s;
}

/*****************************************************************
**	skiplabel (const char *s)
*****************************************************************/
static	const	char	*skiplabel (const char *s)
{
	while ( *s && *s != ';' && *s != ' ' && *s != '\t' && *s != '\n' )
		s++;
	return s;
}

/*****************************************************************
**	setminmax ()
*****************************************************************/
static	void	setminmax (long *pmin, long val, long *pmax)
{
	if ( val < *pmin )
		*pmin = val;
	if ( val > *pmax )
		*pmax = val;
}

/*****************************************************************
**	get_ttl ()
*****************************************************************/
static	long	get_ttl (const char *s)
{
	char	quantity;
	long	lval;

	quantity = 'd';
	sscanf (s, "%ld%c", &lval, &quantity);
	quantity = tolower (quantity);
	if  ( quantity == 'm' )
		lval *= MINSEC;
	else if  ( quantity == 'h' )
		lval *= HOURSEC;
	else if  ( quantity == 'd' )
		lval *= DAYSEC;
	else if  ( quantity == 'w' )
		lval *= WEEKSEC;
	else if  ( quantity == 'y' )
		lval *= YEARSEC;

	return lval;
}

/*****************************************************************
**	addkeydb ()
*****************************************************************/
int	addkeydb (const char *file, const char *keydbfile)
{
	FILE	*fp;

	if ( (fp = fopen (file, "a")) == NULL )
		return -1;

	fprintf (fp, "\n");	
	fprintf (fp, "$INCLUDE %s\t; this is the database of public DNSKEY RR\n", keydbfile);	

	fclose (fp);

	return 0;
}

/*****************************************************************
**	parsezonefile ()
**	parse the BIND zone file 'file' and store the minimum and
**	maximum ttl value in the corresponding parameter.
**	if keydbfile is set, check if this file is already include.
**	if inclfiles is not NULL store a list of included files names
**	in it.
**	return 0 if keydbfile is not included
**	return 1 if keydbfile is included
**	return -1 on error
*****************************************************************/
int	parsezonefile (const char *file, long *pminttl, long *pmaxttl, const char *keydbfile, char *inclfiles, size_t *plen)
{
	FILE	*infp;
	int	len;
	int	lnr;
	long	ttl;
	int	multi_line_rr;
	int	keydbfilefound;
	char	buf[1024];
	const	char	*p;

	assert (file != NULL);
	assert (pminttl != NULL);
	assert (pmaxttl != NULL);

	dbg_val4 ("parsezonefile (\"%s\", %ld, %ld, \"%s\")\n", file, *pminttl, *pmaxttl, keydbfile);

	if ( (infp = fopen (file, "r")) == NULL )
	{
		error ("parsezonefile: couldn't open file \"%s\" for input\n", file); 
		return -1;
	}

	lnr = 0;
	keydbfilefound = 0;
	multi_line_rr = 0;
	while ( fgets (buf, sizeof buf, infp) != NULL ) 
	{
		len = strlen (buf);
		if ( buf[len-1] != '\n' )	/* line too long ? */
			fprintf (stderr, "line too long\n");
		lnr++;

		p = buf;
		if ( multi_line_rr )	/* skip line if it's part of a multiline rr */
		{
			is_multiline_rr (&multi_line_rr, p);
			continue;
		}

		if ( *p == '$' )	/* special directive ? */
		{
			if ( strncmp (p+1, "TTL", 3) == 0 )	/* $TTL ? */
			{
				ttl = get_ttl (p+4);
				dbg_val3 ("%s:%d:ttl %ld\n", file, lnr, ttl);
				setminmax (pminttl, ttl, pmaxttl);
			}
			else if ( strncmp (p+1, "INCLUDE", 7) == 0 )	/* $INCLUDE ? */
			{
				char	fname[30+1];

				sscanf (p+9, "%30s", fname);
				dbg_val ("$INCLUDE directive for file \"%s\" found\n", fname);
				if ( strcmp (fname, keydbfile) == 0 )
					keydbfilefound = 1;
				else
				{
					if ( inclfiles && plen )
					{
						len = snprintf (inclfiles, *plen, ",%s", fname);
						if ( *plen <= len )	/* no space left in include file string */
						{
							fclose (infp);
							return keydbfilefound;
						}
						inclfiles += len;
						*plen -= len;
					}
					int	ret = parsezonefile (fname, pminttl, pmaxttl, keydbfile, inclfiles, plen);
					if ( ret )	/* keydb found or read error ? */
						keydbfilefound = ret;
				}
			}
		}
		else if ( !isspace (*p) )	/* label ? */
			p = skiplabel (p);

		p = skipws (p);
		if ( *p == ';' )	/* skip line if it's  a comment line */
			continue;

			/* skip class (hesiod is not supported now) */
		if ( (toupper (*p) == 'I' && toupper (p[1]) == 'N') ||
		     (toupper (*p) == 'C' && toupper (p[1]) == 'H') )
			p += 2;
		p = skipws (p);

		if ( isdigit (*p) )	/* ttl ? */
		{
			ttl = get_ttl (p);
			dbg_val3 ("%s:%d:ttl %ld\n", file, lnr, ttl);
			setminmax (pminttl, ttl, pmaxttl);
		}

		/* check the rest of the line if it's the beginning of a multi_line_rr */
		is_multiline_rr (&multi_line_rr, p);
	}

	fclose (infp);

	dbg_val5 ("parsezonefile (\"%s\", %ld, %ld, \"%s\") ==> %d\n",
			file, *pminttl, *pmaxttl, keydbfile, keydbfilefound);
	return keydbfilefound;
}


#ifdef TEST
const char *progname;
int	main (int argc, char *argv[])
{
	long	minttl;
	long	maxttl;
	int	keydbfound;
	char	*dnskeydb;

	progname = *argv;
	dnskeydb = NULL;
	dnskeydb = "dnskey.db";

	minttl = 0x7FFFFFFF;
	maxttl = 0;
	keydbfound = parsezonefile (argv[1], &minttl, &maxttl, dnskeydb);
	if ( keydbfound < 0 )
		error ("can't parse zone file %s\n", argv[1]);

	if ( dnskeydb && !keydbfound )
	{
		printf ("$INCLUDE %s directive added \n", dnskeydb);
		addkeydb (argv[1], dnskeydb);
	}

	printf ("minttl = %ld\n", minttl);
	printf ("maxttl = %ld\n", maxttl);

	return 0;
}
#endif
