/*****************************************************************
**
**	@(#) misc.c -- helper functions for the dnssec zone key tools
**
**	Copyright (c) Jan 2005, 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 <sys/types.h>
# include <sys/stat.h>
# include <time.h>
# include <utime.h>
# include <assert.h>
# include <errno.h>
# include <fcntl.h>
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
# include "config_zkt.h"
# include "zconf.h"
# include "log.h"
# include "debug.h"
#define extern
# include "misc.h"
#undef extern

# define	TAINTEDCHARS	"`$@;&<>|"

extern	const	char	*progname;

/*****************************************************************
**	getnameappendix (progname, basename)
**	return a pointer to the substring in progname subsequent
**	following "<basename>-".
*****************************************************************/
const	char	*getnameappendix (const char *progname, const char *basename)
{
	const	char	*p;
	int	baselen;

	assert (progname != NULL);
	assert (basename != NULL);

	if ( (p = strrchr (progname, '/')) != NULL )
		p++;
	else
		p = progname;

	baselen = strlen (basename);
	if ( strncmp (p, basename, baselen-1) == 0 && *(p+baselen) == '-' )
	{
		p += baselen + 1;
		if ( *p )
			return p;
	}

	return NULL;
}

/*****************************************************************
**	getdefconfname (view)
**	returns a pointer to a dynamic string containing the
**	default configuration file name
*****************************************************************/
const	char	*getdefconfname (const char *view)
{
	char	*p;
	char	*file;
	char	*buf;
	int	size;
	
	if ( (file = getenv ("ZKT_CONFFILE")) == NULL )
		file = CONFIG_FILE;
	dbg_val2 ("getdefconfname (%s) file = %s\n", view ? view : "NULL", file);

	if ( view == NULL || *view == '\0' || (p = strrchr (file, '.')) == NULL )
		return strdup (file);

	size = strlen (file) + strlen (view) + 1 + 1;
	if ( (buf = malloc (size)) == NULL )
		return strdup (file);

	dbg_val1 ("0123456789o123456789o123456789\tsize=%d\n", size);
	dbg_val4 ("%.*s-%s%s\n", p - file, file, view, p);

	snprintf (buf, size, "%.*s-%s%s", (int)(p - file), file, view, p);
	return buf;	
}

/*****************************************************************
**	domain_canonicdup (s)
**	returns NULL or a pointer to a dynamic string containing the
**	canonic (all lower case letters and ending with a '.')
**	domain name
*****************************************************************/
char	*domain_canonicdup (const char *s)
{
	char	*new;
	char	*p;
	int	len;
	int	add_dot;

	if ( s == NULL )
		return NULL;

	add_dot = 0;
	len = strlen (s);
	if ( len > 0 && s[len-1] != '.' )
		add_dot = len++;

	if ( (new = p = malloc (len + 1)) == NULL )
		return NULL;

	while ( *s )
		*p++ = tolower (*s++);
	if ( add_dot )
		*p++ = '.';
	*p = '\0';

	return new;
}
#if 0		/* replaced by domain_canonicdup */
/*****************************************************************
**	str_tolowerdup (s)
*****************************************************************/
char	*str_tolowerdup (const char *s)
{
	char	*new;
	char	*p;

	if ( s == NULL || (new = p = malloc (strlen (s) + 1)) == NULL )
		return NULL;

	while ( *s )
		*p++ = tolower (*s++);
	*p = '\0';

	return new;
}
#endif

/*****************************************************************
**	str_delspace (s)
**	Remove in string 's' all white space char 
*****************************************************************/
char	*str_delspace (char *s)
{
	char	*start;
	char	*p;

	if ( !s )	/* no string present ? */
		return NULL;

	start = s;
	for ( p = s; *p; p++ )
		if ( !isspace (*p) )
			*s++ = *p;	/* copy each nonspace */

	*s = '\0';	/* terminate string */

	return start;
}

/*****************************************************************
**	in_strarr (str, arr, cnt)
**	check if string array 'arr' contains the string 'str'
**	return 1 if true or 'arr' or 'str' is empty, otherwise 0
*****************************************************************/
int	in_strarr (const char *str, char *const arr[], int cnt)
{
	if ( arr == NULL || cnt <= 0 )
		return 1;

	if ( str == NULL || *str == '\0' )
		return 0;

	while ( --cnt >= 0 )
		if ( strcmp (str, arr[cnt]) == 0 )
			return 1;

	return 0;
}

/*****************************************************************
**	str_untaint (s)
**	Remove in string 's' all TAINTED chars
*****************************************************************/
char	*str_untaint (char *str)
{
	char	*p;

	assert (str != NULL);

	for ( p = str; *p; p++ )
		if ( strchr (TAINTEDCHARS, *p) )
			*p = ' ';
	return str;
}

/*****************************************************************
**	str_chop (str, c)
**	delete all occurrences of char 'c' at the end of string 's'
*****************************************************************/
char	*str_chop (char *str, char c)
{
	int	len;

	assert (str != NULL);

	len = strlen (str) - 1;
	while ( len >= 0 && str[len] == c )
		str[len--] = '\0';

	return str;
}

/*****************************************************************
**	parseurl (url, &proto, &host, &port, &para )
**	parses the given url (e.g. "proto://host.with.domain:port/para")
**	and set the pointer variables to the corresponding part of the string.
*****************************************************************/
void	parseurl (char *url, char **proto, char **host, char **port, char **para)
{
	char	*start;
	char	*p;

	assert ( url != NULL );

	/* parse protocol */
	if ( (p = strchr (url, ':')) == NULL )	/* no protocol string given ? */
		p = url;
	else					/* looks like a protocol string */
		if ( p[1] == '/' && p[2] == '/' )	/* protocol string ? */
		{
			*p = '\0';
			p += 3;
			if ( proto )
				*proto = url;
		}
		else				/* no protocol string found ! */
			p = url;

	/* parse host */
	if ( *p == '[' )	/* ipv6 address as hostname ? */
	{
		for ( start = ++p; *p && *p != ']'; p++ )
			;
		if ( *p )
			*p++ = '\0';
	}
	else
		for ( start = p; *p && *p != ':' && *p != '/'; p++ )
			;
	if ( host )
		*host = start;

	/* parse port */
	if ( *p == ':' )
	{
		*p++ = '\0';
		for ( start = p; *p && isdigit (*p); p++ )
			;
		if ( *p )
			*p++ = '\0';
		if ( port )
			*port = start;
	}

	if ( *p == '/' )
		*p++ = '\0';

	if ( *p && para )
		*para = p;
}

/*****************************************************************
**	splitpath (path, pathsize, filename)
**	if filename is build of "path/file" then copy filename to path
**	and split of the filename part.
**	return pointer to filename part in path or NULL if path is too
**	small to hold "path+filename"
*****************************************************************/
const	char	*splitpath (char *path, size_t psize, const char *filename)
{
	char 	*p;

	if ( !path )
		return NULL;

	*path = '\0';
	if ( !filename )
		return filename;

	if ( (p = strrchr (filename, '/')) )	/* file arg contains path ? */
	{
		if ( strlen (filename) + 1 > psize )
			return filename;

		strcpy (path, filename);	/* copy whole filename to path */
		path[p-filename] = '\0';	/* split of the file part */
		filename = ++p;
	}
	return filename;
}

/*****************************************************************
**	pathname (path, size, dir, file, ext)
**	Concatenate 'dir', 'file' and 'ext' (if not null) to build
**	a pathname, and store the result in the character array
**	with length 'size' pointed to by 'path'.
*****************************************************************/
char	*pathname (char *path, size_t size, const char *dir, const char *file, const char *ext)
{
	int	len;

	if ( path == NULL || file == NULL )
		return path;

	len = strlen (file) + 1;
	if ( dir )
		len += strlen (dir);
	if ( ext )
		len += strlen (ext);
	if ( len > size )
		return path;

	*path = '\0';
	if ( dir && *dir )
	{
		len = sprintf (path, "%s", dir);
		if ( path[len-1] != '/' )
		{
			path[len++] = '/';
			path[len] = '\0';
		}
	}
	strcat (path, file);
	if ( ext )
		strcat (path, ext);
	return path;
}

/*****************************************************************
**	is_directory (name)
**	Check if the given pathname 'name' exists and is a directory.
**	returns 0 | 1
*****************************************************************/
int	is_directory (const char *name)
{
	struct	stat	st;

	if ( !name || !*name )	
		return 0;
	
	return ( stat (name, &st) == 0 && S_ISDIR (st.st_mode) );
}

/*****************************************************************
**	fileexist (name)
**	Check if a file with the given pathname 'name' exists.
**	returns 0 | 1
*****************************************************************/
int	fileexist (const char *name)
{
	struct	stat	st;
	return ( stat (name, &st) == 0 && S_ISREG (st.st_mode) );
}

/*****************************************************************
**	filesize (name)
**	return the size of the file with the given pathname 'name'.
**	returns -1 if the file not exist 
*****************************************************************/
size_t	filesize (const char *name)
{
	struct	stat	st;
	if  ( stat (name, &st) == -1 )
		return -1L;
	return ( st.st_size );
}

/*****************************************************************
**	is_keyfilename (name)
**	Check if the given name looks like a dnssec (public)
**	keyfile name. Returns 0 | 1
*****************************************************************/
int	is_keyfilename (const char *name)
{
	int	len;

	if ( name == NULL || *name != 'K' )
		return 0;

	len = strlen (name);
	if ( len > 4 && strcmp (&name[len - 4], ".key") == 0 ) 
		return 1;

	return 0;
}

/*****************************************************************
**	is_dotfilename (name)
**	Check if the given pathname 'name' looks like "." or "..".
**	Returns 0 | 1
*****************************************************************/
int	is_dotfilename (const char *name)
{
	if ( name && (
	     (name[0] == '.' && name[1] == '\0') || 
	     (name[0] == '.' && name[1] == '.' && name[2] == '\0')) )
		return 1;

	return 0;
}

/*****************************************************************
**	touch (name, sec)
**	Set the modification time of the given pathname 'fname' to
**	'sec'.	Returns 0 on success.
*****************************************************************/
int	touch (const char *fname, time_t sec)
{
	struct	utimbuf	utb;

	if ( !sec )
		time (&sec);

	utb.actime = utb.modtime = sec;
	return utime (fname, &utb);
}

/*****************************************************************
**	linkfile (fromfile, tofile)
*****************************************************************/
int	linkfile (const char *fromfile, const char *tofile)
{
	int	ret;

	/* fprintf (stderr, "linkfile (%s, %s)\n", fromfile, tofile); */
	if ( (ret = link (fromfile, tofile)) == -1 && errno == EEXIST )
		if ( unlink (tofile) == 0 )
			ret = link (fromfile, tofile);

	return ret;
}

/*****************************************************************
**	copyfile (fromfile, tofile, dnskeyfile)
**	copy fromfile into tofile.
**	Add (optional) the content of dnskeyfile to tofile.
*****************************************************************/
int	copyfile (const char *fromfile, const char *tofile, const char *dnskeyfile)
{
	FILE	*infp;
	FILE	*outfp;
	int	c;

	/* fprintf (stderr, "copyfile (%s, %s)\n", fromfile, tofile); */
	if ( (infp = fopen (fromfile, "r")) == NULL )
		return -1;
	if ( (outfp = fopen (tofile, "w")) == NULL )
	{
		fclose (infp);
		return -2;
	}
	while ( (c = getc (infp)) != EOF ) 
		putc (c, outfp);

	fclose (infp);
	if ( dnskeyfile && *dnskeyfile && (infp = fopen (dnskeyfile, "r")) != NULL )
	{
		while ( (c = getc (infp)) != EOF ) 
			putc (c, outfp);
		fclose (infp);
	}
	fclose (outfp);

	return 0;
}

/*****************************************************************
**	copyzonefile (fromfile, tofile, dnskeyfile)
**	copy a already signed zonefile and replace all zone DNSKEY
**	resource records by one "$INCLUDE dnskey.db" line
*****************************************************************/
int	copyzonefile (const char *fromfile, const char *tofile, const char *dnskeyfile)
{
	FILE	*infp;
	FILE	*outfp;
	int	len;
	int	dnskeys;
	int	multi_line_dnskey;
	int	bufoverflow;
	char	buf[1024];
	char	*p;

	if ( fromfile == NULL )
		infp = stdin;
	else
		if ( (infp = fopen (fromfile, "r")) == NULL )
			return -1;
	if ( tofile == NULL )
		outfp = stdout;
	else
		if ( (outfp = fopen (tofile, "w")) == NULL )
		{
			if ( fromfile )
				fclose (infp);
			return -2;
		}

	multi_line_dnskey = 0;
	dnskeys = 0;
	bufoverflow = 0;
	while ( fgets (buf, sizeof buf, infp) != NULL ) 
	{
		p = buf;
		if ( !bufoverflow && !multi_line_dnskey && (*p == '@' || isspace (*p)) )	/* check if DNSKEY RR */
		{
			do
				p++;
			while ( isspace (*p) ) ;

			/* skip TTL */
			while ( isdigit (*p) )
				p++;

			while ( isspace (*p) )
				p++;

			/* skip Class */
			if ( strncasecmp (p, "IN", 2) == 0 )
			{
				p += 2;
				while ( isspace (*p) )
					p++;
			}

			if ( strncasecmp (p, "DNSKEY", 6) == 0 )	/* bingo! */
			{
				dnskeys++;
				p += 6;
				while ( *p )
				{
					if ( *p == '(' )
						multi_line_dnskey = 1;
					if ( *p == ')' )
						multi_line_dnskey = 0;
					p++;
				}
				if ( dnskeys == 1 )
					fprintf (outfp, "$INCLUDE %s\n", dnskeyfile);	
			}
			else 
				fputs (buf, outfp);	
		}
		else
		{
			if ( bufoverflow )
				fprintf (stderr, "!! buffer overflow in copyzonefile() !!\n");
			if ( !multi_line_dnskey )
				fputs (buf, outfp);	
			else
			{
				while ( *p && *p != ')' )
					p++;
				if ( *p == ')' )
					multi_line_dnskey = 0;
			}
		}
		
		len = strlen (buf);
		bufoverflow = buf[len-1] != '\n';	/* line too long ? */
	}

	if ( fromfile )
		fclose (infp);
	if ( tofile )
		fclose (outfp);

	return 0;
}

/*****************************************************************
**	cmpfile (file1, file2)
**	returns -1 on error, 1 if the files differ and 0 if they
**	are identical.
*****************************************************************/
int	cmpfile (const char *file1, const char *file2)
{
	FILE	*fp1;
	FILE	*fp2;
	int	c1;
	int	c2;

	/* fprintf (stderr, "cmpfile (%s, %s)\n", file1, file2); */
	if ( (fp1 = fopen (file1, "r")) == NULL )
		return -1;
	if ( (fp2 = fopen (file2, "r")) == NULL )
	{
		fclose (fp1);
		return -1;
	}

	do {
		c1 = getc (fp1);
		c2 = getc (fp2);
	}  while ( c1 != EOF && c2 != EOF && c1 == c2 );

	fclose (fp1);
	fclose (fp2);

	if ( c1 == c2 )
		return 0;
	return 1;
}

/*****************************************************************
**	file_age (fname)
*****************************************************************/
int	file_age (const char *fname)
{
	time_t	curr = time (NULL);
	time_t	mtime = file_mtime (fname);

	return curr - mtime;
}

/*****************************************************************
**	file_mtime (fname)
*****************************************************************/
time_t	file_mtime (const char *fname)
{
	struct	stat	st;

	if ( stat (fname, &st) < 0 )
		return 0;
	return st.st_mtime;
}

/*****************************************************************
**	is_exec_ok (prog)
**	Check if we are running as root or if the file owner of
**	"prog" do not match the current user or the file permissions
**	allows file modification for others then the owner.
**	The same condition will be checked for the group ownership.
**	return 1 if the execution of the command "prog" will not
**	open a big security whole, 0 otherwise
*****************************************************************/
int	is_exec_ok (const char *prog)
{
	uid_t	curr_uid;
	struct	stat	st;

	if ( stat (prog, &st) < 0 )
		return 0;

	curr_uid = getuid ();
	if ( curr_uid == 0 )			/* don't run the cmd if we are root */
		return 0;

	/* if the file owner and the current user matches and */
	/* the file mode is not writable except for the owner, we are save */
	if ( curr_uid == st.st_uid && (st.st_mode & (S_IWGRP | S_IWOTH)) == 0 )
		return 1;

	/* if the file group and the current group matches and */
	/* the file mode is not writable except for the group, we are also save */
	if ( getgid() != st.st_gid && (st.st_mode & (S_IWUSR | S_IWOTH)) == 0 )
		return 1;

	return 0;
}

/*****************************************************************
**	fatal (fmt, ...)
*****************************************************************/
void fatal (char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        if ( progname )
		fprintf (stderr, "%s: ", progname);
        vfprintf (stderr, fmt, ap);
        va_end(ap);
        exit (127);
}

/*****************************************************************
**	error (fmt, ...)
*****************************************************************/
void error (char *fmt, ...)
{
        va_list ap;

        va_start(ap, fmt);
        vfprintf (stderr, fmt, ap);
        va_end(ap);
}

/*****************************************************************
**	logmesg (fmt, ...)
*****************************************************************/
void logmesg (char *fmt, ...)
{
        va_list ap;

#if defined (LOG_WITH_PROGNAME) && LOG_WITH_PROGNAME
        fprintf (stdout, "%s: ", progname);
#endif
        va_start(ap, fmt);
        vfprintf (stdout, fmt, ap);
        va_end(ap);
}

/*****************************************************************
**	verbmesg (verblvl, conf, fmt, ...)
*****************************************************************/
void	verbmesg (int verblvl, const zconf_t *conf, char *fmt, ...)
{
	char	str[511+1];
        va_list ap;

	str[0] = '\0';
	va_start(ap, fmt);
	vsnprintf (str, sizeof (str), fmt, ap);
	va_end(ap);

	//fprintf (stderr, "verbmesg (%d stdout=%d filelog=%d str = :%s:\n", verblvl, conf->verbosity, conf->verboselog, str);
	if ( verblvl <= conf->verbosity )	/* check if we have to print this to stdout */
		logmesg (str);

	str_chop (str, '\n');
	if ( verblvl <= conf->verboselog )	/* check logging to syslog and/or file */
		lg_mesg (LG_DEBUG, str);
}


/*****************************************************************
**	logflush ()
*****************************************************************/
void logflush ()
{
        fflush (stdout);
}

/*****************************************************************
**	timestr2time (timestr)
**	timestr should look like "20071211223901" for 12 dec 2007 22:39:01
*****************************************************************/
time_t	timestr2time (const char *timestr)
{
	struct	tm	t;
	time_t	sec;

	// fprintf (stderr, "timestr = \"%s\"\n", timestr);
	if ( sscanf (timestr, "%4d%2d%2d%2d%2d%2d", 
			&t.tm_year, &t.tm_mon, &t.tm_mday, 
			&t.tm_hour, &t.tm_min, &t.tm_sec) != 6 )
		return 0L;
	t.tm_year -= 1900;
	t.tm_mon -= 1;
	t.tm_isdst = 0;

#if defined(HAVE_TIMEGM) && HAVE_TIMEGM
	sec = timegm (&t);
#else
	{
	char	tzstr[31+1];
	char	*tz;

	tz = getenv("TZ");
	snprintf (tzstr, sizeof (tzstr), "TZ=%s", "UTC");
	putenv (tzstr);
	tzset();
	sec = mktime(&t);
	if (tz)
		snprintf (tzstr, sizeof (tzstr), "TZ=%s", tz);
	else
		snprintf (tzstr, sizeof (tzstr), "TZ=%s", "");
	putenv (tzstr);
	tzset();
	}
#endif
	
	return sec < 0L ? 0L : sec;
}

/*****************************************************************
**	time2str (sec, precison)
**	sec is seconds since 1.1.1970
**	precison is currently either 's' (for seconds) or 'm' (minutes)
*****************************************************************/
char	*time2str (time_t sec, int precision)
{
	struct	tm	*t;
	static	char	timestr[31+1];	/* 27+1 should be enough */
#if defined(HAVE_STRFTIME) && HAVE_STRFTIME
	char	tformat[127+1];

	timestr[0] = '\0';
	if ( sec <= 0L )
		return timestr;
	t = localtime (&sec);
	if ( precision == 's' )
		strcpy (tformat, "%b %d %Y %T");
	else
		strcpy (tformat, "%b %d %Y %R");
# if PRINT_TIMEZONE
	strcat (tformat, " %z");
# endif
	strftime (timestr, sizeof (timestr), tformat, t);

#else	/* no strftime available */
	static	char	*mstr[] = {
			"Jan", "Feb", "Mar", "Apr", "May", "Jun",
			"Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
	};

	timestr[0] = '\0';
	if ( sec <= 0L )
		return timestr;
	t = localtime (&sec);
# if PRINT_TIMEZONE
	{
	int	h,	s;

	s = abs (t->tm_gmtoff);
	h = t->tm_gmtoff / 3600;
	s = t->tm_gmtoff % 3600;
	if ( precision == 's' )
		snprintf (timestr, sizeof (timestr), "%s %2d %4d %02d:%02d:%02d %c%02d%02d",
			mstr[t->tm_mon], t->tm_mday, t->tm_year + 1900, 
			t->tm_hour, t->tm_min, t->tm_sec,
			t->tm_gmtoff < 0 ? '-': '+',
			h, s);
	else
		snprintf (timestr, sizeof (timestr), "%s %2d %4d %02d:%02d %c%02d%02d",
			mstr[t->tm_mon], t->tm_mday, t->tm_year + 1900, 
			t->tm_hour, t->tm_min, 
			t->tm_gmtoff < 0 ? '-': '+',
			h, s);
	}
# else
	if ( precision == 's' )
		snprintf (timestr, sizeof (timestr), "%s %2d %4d %02d:%02d:%02d",
			mstr[t->tm_mon], t->tm_mday, t->tm_year + 1900, 
			t->tm_hour, t->tm_min, t->tm_sec);
	else
		snprintf (timestr, sizeof (timestr), "%s %2d %4d %02d:%02d",
			mstr[t->tm_mon], t->tm_mday, t->tm_year + 1900, 
			t->tm_hour, t->tm_min);
# endif
#endif

	return timestr;
}

/*****************************************************************
**	time2isostr (sec, precison)
**	sec is seconds since 1.1.1970
**	precison is currently either 's' (for seconds) or 'm' (minutes)
*****************************************************************/
char	*time2isostr (time_t sec, int precision)
{
	struct	tm	*t;
	static	char	timestr[31+1];	/* 27+1 should be enough */

	timestr[0] = '\0';
	if ( sec <= 0L )
		return timestr;

	t = gmtime (&sec);
	if ( precision == 's' )
		snprintf (timestr, sizeof (timestr), "%4d%02d%02d%02d%02d%02d",
			t->tm_year + 1900, t->tm_mon+1, t->tm_mday,
			t->tm_hour, t->tm_min, t->tm_sec);
	else
		snprintf (timestr, sizeof (timestr), "%4d%02d%02d%02d%02d",
			t->tm_year + 1900, t->tm_mon+1, t->tm_mday,
			t->tm_hour, t->tm_min);

	return timestr;
}

/*****************************************************************
**	age2str (sec)
**	!!Attention: This function is not reentrant 
*****************************************************************/
char	*age2str (time_t sec)
{
	static	char	str[20+1];	/* "2y51w6d23h50m55s" == 16+1 chars */
	int	len;
	int	strsize = sizeof (str);

	len = 0;
# if PRINT_AGE_WITH_YEAR
	if ( sec / (YEARSEC) > 0 )
	{
		len += snprintf (str+len, strsize - len, "%1luy", sec / YEARSEC );
		sec %= (YEARSEC);
	}
	else
		len += snprintf (str+len, strsize - len, "  ");
# endif
	if ( sec / WEEKSEC > 0 )
	{
		len += snprintf (str+len, strsize - len, "%2luw", (ulong) sec / WEEKSEC );
		sec %= WEEKSEC;
	}
	else
		len += snprintf (str+len, strsize - len, "   ");
	if ( sec / DAYSEC > 0 )
	{
		len += snprintf (str+len, strsize - len, "%2lud", sec / (ulong)DAYSEC);
		sec %= DAYSEC;
	}
	else
		len += snprintf (str+len, strsize - len, "   ");
	if ( sec / HOURSEC > 0 )
	{
		len += snprintf (str+len, strsize - len, "%2luh", sec / (ulong)HOURSEC);
		sec %= HOURSEC;
	}
	else
		len += snprintf (str+len, strsize - len, "   ");
	if ( sec / MINSEC > 0 )
	{
		len += snprintf (str+len, strsize - len, "%2lum", sec / (ulong)MINSEC);
		sec %= MINSEC;
	}
	else
		len += snprintf (str+len, strsize - len, "   ");
	if ( sec > 0 )
		snprintf (str+len, strsize - len, "%2lus", (ulong) sec);
	else
		len += snprintf (str+len, strsize - len, "   ");

	return str;
}

/*****************************************************************
**	start_timer ()
*****************************************************************/
time_t	start_timer ()
{
	return (time(NULL));
}

/*****************************************************************
**	stop_timer ()
*****************************************************************/
time_t	stop_timer (time_t start)
{
	time_t	stop = time (NULL);

	return stop - start;
}


/****************************************************************
**
**	int	gensalt (saltstr, sizeofsaltstr, bits)
**
**	generate a random hexstring of 'bits' salt and store it
**	in saltstr. return 1 on success, otherwise 0.
**
*****************************************************************/
int	gensalt (char *salt, size_t saltsize, int saltbits, unsigned int seed)
{
	static	char	hexstr[] = "0123456789ABCDEF";
	int	saltlen = 0;	/* current length of salt in hex nibbles */
	int	i;
	int	hex;

	if ( seed == 0 )
		srandom (seed = (unsigned int)time (NULL));

	saltlen = saltbits / 4;
	if ( saltlen+1 > saltsize )
		return 0;

	for ( i = 0; i < saltlen; i++ )
	{
		hex = random () % 16;
		assert ( hex >= 0 && hex < 16 );
		salt[i] = hexstr[hex];
	}
	salt[i] = '\0';

	return 1;
}


#ifdef COPYZONE_TEST
const char *progname;
main (int argc, char *argv[])
{
	progname = *argv;

	if ( copyzonefile (argv[1], NULL) < 0 )
		error ("can't copy zone file %s\n", argv[1]);
}
#endif

#ifdef URL_TEST
const char *progname;
main (int argc, char *argv[])
{
	char	*proto;
	char	*host;
	char	*port;
	char	*para;
	char	url[1024];

	progname = *argv;

	proto = host = port = para = NULL;

	if ( --argc <= 0 )
	{
		fprintf (stderr, "usage: url_test <url>\n");
		fprintf (stderr, "e.g.: url_test http://www.hznet.de:80/zkt\n");
		exit (1);
	}
	
	strcpy (url, argv[1]);
	parseurl (url, &proto, &host, &port, &para);

	if ( proto )
		printf ("proto: \"%s\"\n", proto);
	if ( host )
		printf ("host: \"%s\"\n", host);
	if ( port )
		printf ("port: \"%s\"\n", port);
	if ( para )
		printf ("para: \"%s\"\n", para);

}
#endif

