/*
 * gizconf - generate configuration info for gizmo binaries
 */
#include <stdio.h>
#include <strings.h>
#include <ctype.h>
#include "gizmo_syslog.h"
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/in.h>
#include <errno.h>

#include "ntp_fp.h"
#include "ntp.h"
#include "ntp_refclock.h"
#include "gizmo.h"

#define	STREQ(a, b)	(*(a) == *(b) && strcmp((a), (b)) == 0)

char *progname;
int debug;

int doexecutable = 0;
char *exename;

/*
 * Buffers to hold configuration info in binary order
 */
#define	FIRST	0
#define	LAST	1

u_char firstbuf[GIZMO_CONFIG_SIZE];
u_char lastbuf[GIZMO_CONFIG_SIZE];

u_char *bufpt[2];

int blength;
u_long config_csum;

/*
 * Number of keys to collect at a time
 */
#define	NUMKEYS	32

/*
 * main - parse arguments and handle options
 */
main(argc, argv)
int argc;
char *argv[];
{
	int c;
	int errflg = 0;
	FILE *in;
	extern int optind;
	extern char *optarg;
	void outputc();
	void writeexec();
	char *file;

	progname = argv[0];
	while ((c = getopt(argc, argv, "de:")) != EOF)
		switch (c) {
		case 'd':
			++debug;
			break;
		case 'e':
			exename = optarg;
			doexecutable++;
			break;
		default:
			errflg++;
			break;
		}
	if (errflg || optind < argc-1) {
		(void) fprintf(stderr,
		    "usage: %s [-e executable] [config_file]\n", progname);
		exit(2);
	}
	if (optind == argc) {
		file = "stdin";
		c = getconfig(stdin, file);
	} else {
		file = argv[optind];
		if ((in = fopen(file, "r")) == NULL) {
			(void) fprintf(stderr, "%s: can't open %s: ",
			    progname, file);
			perror("");
			exit(1);
		}
		c = getconfig(in, file);
		(void) fclose(in);
	}

	if (c != 0)
		exit(1);

	if (doexecutable)
		writeexec(exename);
	else
		outputc(file);
	exit(0);
}



/*
 * Translation table - keywords to function index
 */
struct keyword {
	char *text;
	int keytype;
};

struct keyword keywords[] = {
	{ "peer",		CONFIG_PEER },
	{ "server",		CONFIG_SERVER },
	{ "precision",		CONFIG_PRECISION },
	{ "drift",		CONFIG_DRIFT },
	{ "broadcast",		CONFIG_BROADCAST },
	{ "broadcastclient",	CONFIG_BROADCASTCLIENT },
	{ "authenticate",	CONFIG_AUTHENTICATE },
	{ "keys",		CONFIG_KEYS },
	{ "monitor",		CONFIG_MONITOR },
	{ "authdelay",		CONFIG_AUTHDELAY },
	{ "restrict",		CONFIG_RESTRICT },
	{ "broadcastdelay",	CONFIG_BDELAY },
	{ "trustedkey",		CONFIG_TRUSTEDKEY },
	{ "requestkey",		CONFIG_REQUESTKEY },
	{ "controlkey",		CONFIG_CONTROLKEY },
	{ "trap",		CONFIG_TRAP },
	{ "fudge",		CONFIG_FUDGE },
	{ "maxskew",		CONFIG_MAXSKEW },
	{ "sysloghost",		CONFIG_SYSLOGHOST },
	{ "debuglevel",		CONFIG_DEBUGLEVEL },
	{ "",			CONFIG_UNKNOWN }
};

/*
 * Modifier keywords
 */
struct keyword mod_keywords[] = {
	{ "version",	CONF_MOD_VERSION },
	{ "key",	CONF_MOD_KEY },
	{ "minpoll",	CONF_MOD_MINPOLL },
	{ "",		CONFIG_UNKNOWN }
};


/*
 * Special restrict keywords
 */
struct keyword res_keywords[] = {
	{ "mask",	CONF_RES_MASK },
	{ "ignore",	CONF_RES_IGNORE },
	{ "noserve",	CONF_RES_NOSERVE },
	{ "notrust",	CONF_RES_NOTRUST },
	{ "noquery",	CONF_RES_NOQUERY },
	{ "nomodify",	CONF_RES_NOMODIFY },
	{ "nopeer",	CONF_RES_NOPEER },
	{ "notrap",	CONF_RES_NOTRAP },
	{ "lowpriotrap",	CONF_RES_LPTRAP },
	{ "ntpport",	CONF_RES_NTPPORT },
	{ "",		CONFIG_UNKNOWN }
};


/*
 * Keywords for the trap command
 */
struct keyword trap_keywords[] = {
	{ "port",	CONF_TRAP_PORT },
	{ "interface",	CONF_TRAP_INTERFACE },
	{ "",		CONFIG_UNKNOWN }
};


/*
 * Keywords for the fudge command
 */
struct keyword fudge_keywords[] = {
	{ "time1",	CONF_FDG_TIME1 },
	{ "time2",	CONF_FDG_TIME2 },
	{ "value1",	CONF_FDG_VALUE1 },
	{ "value2",	CONF_FDG_VALUE2 },
	{ "flag1",	CONF_FDG_FLAG1 },
	{ "flag2",	CONF_FDG_FLAG2 },
	{ "flag3",	CONF_FDG_FLAG3 },
	{ "flag4",	CONF_FDG_FLAG4 },
	{ "",		CONFIG_UNKNOWN }
};


/*
 * Limits on things
 */
#define	MAXTOKENS	20	/* 20 tokens on line */
#define	MAXLINE		1024	/* maximum length of line */


/*
 * Miscellaneous macros
 */
#define	STRSAME(s1, s2)		(*(s1) == *(s2) && strcmp((s1), (s2)) == 0)
#define	ISEOL(c)		((c) == '#' || (c) == '\n' || (c) == '\0')
#define	ISSPACE(c)		((c) == ' ' || (c) == '\t')

/*
 * Error status.  Set when an error occurs
 */
int errstat;

/*
 * getconfig - read the configuration file
 */
int
getconfig(fp, inname)
	FILE *fp;
	char *inname;
{
	register int i;
	int c;
	int errflg;
	int peerversion;
	u_long peerkey;
	l_fp ts;
	char keysfile[256];
	struct sockaddr_in peeraddr;
	struct sockaddr_in maskaddr;
	char line[MAXLINE];
	char *tokens[MAXTOKENS];
	int ntkeys;
	u_long tkey[MAXTOKENS];
	int ntokens;
	int tok;
	int gettokens();
	void cputtoken(), cputuchar();
	void cputchar(), cputushort();
	void cputulong(), cputnetnum();
	void cputlong(), cputts();
	extern char *ntoa();

	errstat = 0;
	blength = 0;
	config_csum = 0;
	keysfile[0] = '\0';
	bufpt[FIRST] = firstbuf;
	bufpt[LAST] = lastbuf;

	while ((tok = gettokens(fp, line, tokens, &ntokens))
	    != CONFIG_UNKNOWN) {
		switch(tok) {
		case CONFIG_PEER:
		case CONFIG_SERVER:
		case CONFIG_BROADCAST:
			if (ntokens < 2) {
				log(LOG_ERR,
				    "No address for %s, line ignored",
				    tokens[0]);
				break;
			}

			if (!getnetnum(tokens[1], &peeraddr))
				break;
			
			if (!ISREFCLOCKADR(&peeraddr) && ISBADADR(&peeraddr)) {
				log(LOG_ERR,
				    "attempt to configure invalid address %s",
				    ntoa(&peeraddr));
				break;
			}
			cputtoken(tok, LAST);
			cputnetnum(&peeraddr, LAST);
			
			for (i = 2; i < ntokens; i++)
				switch (matchkey(tokens[i], mod_keywords)) {
				case CONF_MOD_VERSION:
					if (i >= ntokens-1) {
						log(LOG_ERR,
				"peer/server version requires an argument");
						break;
					}
					peerversion = atoi(tokens[++i]);
					if (peerversion != NTP_VERSION
					    && peerversion != NTP_OLDVERSION) {
						log(LOG_ERR,
				"inappropriate version number %s, line ignored",
						    tokens[i]);
						break;
					}
					cputtoken(CONF_MOD_VERSION, LAST);
					cputuchar(peerversion, LAST);
					break;

				case CONF_MOD_KEY:
					/*
					 * XXX
					 * This is bad because atoi
					 * returns 0 on errors.  Do
					 * something later.
					 */
					if (i >= ntokens-1) {
						log(LOG_ERR,
					"peer/server key requires an argument");
						errflg = 1;
						break;
					}
					peerkey = (u_long)atoi(tokens[++i]);
					cputtoken(CONF_MOD_KEY, LAST);
					cputulong(peerkey, LAST);
					break;

				case CONF_MOD_MINPOLL:
					cputtoken(CONF_MOD_MINPOLL, LAST);
					break;

				case CONFIG_UNKNOWN:
					log(LOG_ERR,
					    "unknown keyword %s encountered",
					    tokens[i]);
					break;
				}
			cputtoken(CONFIG_END, LAST);
			break;

		case CONFIG_PRECISION:
			if (ntokens >= 2) {
				i = atoi(tokens[1]);
				if (i >= 0 || i < -25) {
					log(LOG_ERR,
				"unlikely precision %s, line ignored",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_PRECISION, LAST);
					cputchar(i, LAST);
				}
			}
			break;

		case CONFIG_DRIFT:
			if (ntokens >= 2) {
				l_fp tmp;

				if (!atolfp(tokens[1], &tmp)) {
					log(LOG_ERR,
					 "drift value %s undecodable",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_DRIFT, LAST);
					cputts(&tmp, LAST);
				}
			}
			break;

		case CONFIG_BROADCASTCLIENT:
			errflg = 0;
			if (ntokens >= 2) {
				if (STREQ(tokens[1], "yes")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_YES, LAST);
				} else if (STREQ(tokens[1], "no")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_NO, LAST);
				} else
					errflg++;
			} else {
				errflg++;
			}

			if (errflg)
				log(LOG_ERR,
				    "should be `broadcastclient yes|no'");
			break;
		
		case CONFIG_AUTHENTICATE:
			errflg = 0;
			if (ntokens >= 2) {
				if (STREQ(tokens[1], "yes")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_YES, LAST);
				} else if (STREQ(tokens[1], "no")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_NO, LAST);
				} else
					errflg++;
			} else {
				errflg++;
			}

			if (errflg)
				log(LOG_ERR,
				    "should be `authenticate yes|no'");
			break;

		case CONFIG_KEYS:
			if (ntokens >= 2)
				strcpy(keysfile, tokens[1]);
			break;

		case CONFIG_MONITOR:
			errflg = 0;
			if (ntokens >= 2) {
				if (STREQ(tokens[1], "yes")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_YES, LAST);
				} else if (STREQ(tokens[1], "no")) {
					cputtoken(tok, LAST);
					cputuchar(CONF_YORN_NO, LAST);
				} else
					errflg++;
			} else {
				errflg++;
			}

			if (errflg)
				log(LOG_ERR,
				    "should be `monitor yes|no'");
			break;
		
		case CONFIG_AUTHDELAY:
			if (ntokens >= 2) {
				l_fp tmp;

				if (!atolfp(tokens[1], &tmp)) {
					log(LOG_ERR,
					    "authdelay value %s undecodable",
					    tokens[1]);
				} else if (tmp.l_ui != 0) {
					log(LOG_ERR,
					    "authdelay value %s is unlikely",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_AUTHDELAY, LAST);
					cputulong(tmp.l_uf, LAST);
				}
			}
			break;
		
		case CONFIG_RESTRICT:
			if (ntokens < 2) {
				log(LOG_ERR, "restrict requires an address");
				break;
			}
			if (STREQ(tokens[1], "default"))
				peeraddr.sin_addr.s_addr = INADDR_ANY;
			else if (!getnetnum(tokens[1], &peeraddr))
				break;
			
			/*
			 * Use peerversion as flags, peerkey as mflags.  Ick.
			 */
			peerversion = 0;
			errflg = 0;
			maskaddr.sin_addr.s_addr = ~0;
			for (i = 2; i < ntokens; i++) {
				switch (tok
				    = matchkey(tokens[i], res_keywords)) {
				case CONF_RES_MASK:
					if (i >= ntokens-1) {
						log(LOG_ERR,
						"mask keyword needs argument");
						errflg++;
						break;
					}
					i++;
					if (!getnetnum(tokens[i], &maskaddr))
						errflg++;
					break;

				case CONF_RES_IGNORE:
				case CONF_RES_NOSERVE:
				case CONF_RES_NOTRUST:
				case CONF_RES_NOQUERY:
				case CONF_RES_NOMODIFY:
				case CONF_RES_NOPEER:
				case CONF_RES_NOTRAP:
				case CONF_RES_LPTRAP:
				case CONF_RES_NTPPORT:
					peerversion |= tok;
					break;

				case CONFIG_UNKNOWN:
					log(LOG_ERR,
				"unknown restrict keyword %s", tokens[i]);
					errflg++;
					break;
				}
			}
			if (SRCADR(&peeraddr) == INADDR_ANY)
				maskaddr.sin_addr.s_addr = 0;
			if (!errflg) {
				cputtoken(CONFIG_RESTRICT, LAST);
				cputnetnum(&peeraddr, LAST);
				cputnetnum(&maskaddr, LAST);
				cputushort(peerversion, LAST);
			}
			break;

		case CONFIG_BDELAY:
			if (ntokens >= 2) {
				l_fp tmp;

				if (!atolfp(tokens[1], &tmp)) {
					log(LOG_ERR,
					 "broadcastdelay value %s undecodable",
					    tokens[1]);
				} else if (tmp.l_ui != 0) {
					log(LOG_ERR,
					  "broadcastdelay value %s is unlikely",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_BDELAY, LAST);
					cputts(&tmp, LAST);
				}
			}
			break;
		
		case CONFIG_TRUSTEDKEY:
			ntkeys = 0;
			for (i = 1; i < ntokens; i++) {
				tkey[ntkeys] = (u_long) atoi(tokens[i]);
				if (tkey[ntkeys] == 0) {
					log(LOG_ERR,
					    "trusted key %s unlikely",
					    tokens[i]);
				} else {
					ntkeys++;
				}
			}
			if (ntkeys > 0) {
				cputtoken(CONFIG_TRUSTEDKEY, LAST);
				cputuchar(ntkeys, LAST);
				for (i = 0; i < ntkeys; i++)
					cputulong(tkey[i], LAST);
			}
			break;

		case CONFIG_REQUESTKEY:
			if (ntokens >= 2) {
				u_long rkey;

				if (!atouint(tokens[1], &rkey)) {
					log(LOG_ERR,
					    "%s is undecodeable as request key",
					    tokens[1]);
				} else if (rkey == 0) {
					log(LOG_ERR,
					    "%s makes a poor request keyid",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_REQUESTKEY, LAST);
					cputulong(rkey, LAST);
				}
			}
			break;
		
		case CONFIG_CONTROLKEY:
			if (ntokens >= 2) {
				u_long ckey;

				ckey = (u_long)atoi(tokens[1]);
				if (ckey == 0) {
					log(LOG_ERR,
					    "%s makes a poor control keyid",
					    tokens[1]);
				} else {
					cputtoken(CONFIG_REQUESTKEY, LAST);
					cputulong(ckey, LAST);
				}
			}
			break;
		
		case CONFIG_TRAP:
			if (ntokens < 2) {
				log(LOG_ERR,
				  "no address for trap command, line ignored");
				  break;
			}
			if (!getnetnum(tokens[1], &peeraddr))
				break;
			
			cputtoken(CONFIG_TRAP, LAST);
			cputnetnum(&peeraddr, LAST);

			/*
			 * Use peerversion for port number.  Barf.
			 */
			for (i = 2; i < ntokens-1; i++) {
				switch (matchkey(tokens[i], trap_keywords)) {
				case CONF_TRAP_PORT:
					if (i >= ntokens-1) {
						log(LOG_ERR,
					    "trap port requires an argument");
						break;
					}
					peerversion = atoi(tokens[++i]);
					if (peerversion <= 0
					    || peerversion > 32767) {
						log(LOG_ERR,
					"invalid port number %s, trap ignored",
						    tokens[i]);
						break;
					}
					cputtoken(CONF_TRAP_PORT, LAST);
					cputushort(peerversion, LAST);
					break;

				case CONF_TRAP_INTERFACE:
					if (i >= ntokens-1) {
						log(LOG_ERR,
					 "trap interface requires an argument");
						break;
					}

					if (!getnetnum(tokens[++i],
					    &maskaddr)) {
						break;
					}
					cputtoken(CONF_TRAP_INTERFACE, LAST);
					cputnetnum(&maskaddr, LAST);
					break;

				case CONFIG_UNKNOWN:
					log(LOG_ERR,
					    "unknown trap keyword %s",
					    tokens[i]);
					break;
				}
			}
			cputtoken(CONFIG_END, LAST);
			break;
		
		case CONFIG_FUDGE:
			if (ntokens < 2) {
				log(LOG_ERR,
				  "no address for fudge command, line ignored");
				  break;
			}
			if (!getnetnum(tokens[1], &peeraddr))
				break;

			if (!ISREFCLOCKADR(&peeraddr)) {
				log(LOG_ERR,
	"%s is inappropriate address for the fudge command, line ignored",
				    ntoa(&peeraddr));
				break;
			}

			cputtoken(CONFIG_FUDGE, LAST);
			cputnetnum(&peeraddr);
			for (i = 2; i < ntokens-1; i++) {
				long val;

				switch (c = matchkey(tokens[i],
				    fudge_keywords)) {
				case CONF_FDG_TIME1:
				case CONF_FDG_TIME2:
					if (!atolfp(tokens[++i], &ts)) {
						log(LOG_ERR,
					"fudge %s time value in error",
						    ntoa(&peeraddr));
						break;
					}
					cputtoken(c, LAST);
					cputts(&ts, LAST);
					break;

				case CONF_FDG_VALUE1:
				case CONF_FDG_VALUE2:
					if (!atoint(tokens[++i], &val)) {
						log(LOG_ERR,
					"fudge %s value in error",
						    ntoa(&peeraddr));
						break;
					}
					cputtoken(c, LAST);
					cputlong(val, LAST);
					break;

				case CONF_FDG_FLAG1:
				case CONF_FDG_FLAG2:
				case CONF_FDG_FLAG3:
				case CONF_FDG_FLAG4:
					if (!atoint(tokens[++i], &peerkey)
					    || peerkey > 1) {
						log(LOG_ERR,
					"fudge %s flag value in error",
						    ntoa(&peeraddr));
						break;
					}
					cputtoken(c, LAST);
					if (peerkey == 0)
						cputuchar(CONF_YORN_NO, LAST);
					else
						cputuchar(CONF_YORN_YES, LAST);
					break;

				case CONFIG_UNKNOWN:
					log(LOG_ERR,
					    "unknown fudge keyword %s",
					    tokens[i]);
					break;
				}
			}
			cputtoken(CONFIG_END, LAST);
			break;

		case CONFIG_MAXSKEW:
			if (ntokens >= 2) {
				l_fp tmp;
				u_fp utmp;

				if (!atolfp(tokens[1], &tmp)) {
					log(LOG_ERR,
					 "maxskew value %s undecodable",
					    tokens[1]);
				} else if (tmp.l_ui != 0) {
					log(LOG_ERR,
					  "maxskew value %s is unlikely",
					    tokens[1]);
				} else {
					utmp = LFPTOFP(&tmp);
					cputtoken(CONFIG_MAXSKEW, LAST);
					cputulong(&utmp, LAST);
				}
			}
			break;
		
		case CONFIG_SYSLOGHOST:
			if (ntokens < 2) {
				log(LOG_ERR,
				    "no syslog host specified");
				break;
			}
			if (!getnetnum(tokens[1], &peeraddr))
				break;
			cputtoken(CONFIG_SYSLOGHOST, FIRST);
			cputnetnum(&peeraddr, FIRST);
			break;
		
		case CONFIG_DEBUGLEVEL:
			if (ntokens > 2) {
				log(LOG_ERR,
				    "no debug level specified");
				break;
			}
			if (!atouint(tokens[1], &peerkey)) {
				log(LOG_ERR,
				    "undecodeable debug level %s specified",
				    tokens[1]);
				break;
			}
			cputtoken(CONFIG_DEBUGLEVEL, FIRST);
			cputuchar((int)peerkey, FIRST);
			break;
		}
	}

	/*
	 * decode the keys file, if we got one
	 */
	if (keysfile[0] != 0)
		readkeys(keysfile, line);

	/*
	 * Put the final end marker
	 */
	cputtoken(CONFIG_END, LAST);

	/*
	 * Copy everything in the second buffer to the end of the first
	 */
	bcopy(lastbuf, bufpt[FIRST], bufpt[LAST] - lastbuf);

	/*
	 * Negate the checksum and stick it in the buffer
	 */
	config_csum = -config_csum;
	firstbuf[blength++] = (u_char)((config_csum >> 24) & 0xff);
	firstbuf[blength++] = (u_char)((config_csum >> 16) & 0xff);
	firstbuf[blength++] = (u_char)((config_csum >>  8) & 0xff);
	firstbuf[blength++] = (u_char)(config_csum & 0xff);

	/*
	 * Zero out the end
	 */
	if (blength < GIZMO_CONFIG_SIZE)
		bzero(&firstbuf[blength], GIZMO_CONFIG_SIZE-blength);
	
	/*
	 * Done!
	 */
	return errstat;
}



/*
 * cputbytes - put 1 - 4 bytes of data
 */
void
cputbytes(where, num, b1, b2, b3, b4)
	int where;
	int num;
	int b1, b2, b3, b4;
{
	if (blength + num > GIZMO_CONFIG_SIZE - 4) {
		(void) fprintf(stderr,
		    "%s: configuration exceeds buffer size\n", progname);
		exit(1);
	}
	blength += num;

	config_csum += (u_long)(b1 & 0xff);
	*(bufpt[where])++ = (u_char)b1;
	if (num > 1) {
		config_csum += (u_long)(b2 & 0xff);
		*(bufpt[where])++ = (u_char)b2;
		if (num > 2) {
			config_csum += (u_long)(b3 & 0xff);
			*(bufpt[where])++ = (u_char)b3;
			if (num > 3) {
				config_csum += (u_long)(b4 & 0xff);
				*(bufpt[where])++ = (u_char)b4;
			}
		}
	}
}


/*
 * cputtoken - put a token
 */
void
cputtoken(tok, where)
	int tok;
	int where;
{
	cputbytes(where, 1, tok, 0, 0, 0);
}


/*
 * cputuchar - put an unsigned char
 */
void
cputuchar(u, where)
	int u;
	int where;
{
	cputbytes(where, 1, u, 0, 0, 0);
}


/*
 * cputchar - put a signed char
 */
void
cputchar(s, where)
	int s;
	int where;
{
	cputbytes(where, 1, s, 0, 0, 0);
}


/*
 * cputushort - put an unsigned short
 */
void
cputushort(u, where)
	int u;
	int where;
{
	cputbytes(where, 2, ((u >> 8) & 0xff), (u & 0xff), 0, 0);
}


/*
 * cputulong - put an unsigned long
 */
void
cputulong(u, where)
	u_long u;
	int where;
{
	cputbytes(where, 4, (int)((u >> 24) & 0xff), (int)((u >> 16) & 0xff),
	    (int)((u >> 8) & 0xff), (int)(u & 0xff));
}


/*
 * cputlong - put a long value
 */
void
cputlong(s, where)
	long s;
	int where;
{
	cputbytes(where, 4, (int)((s >> 24) & 0xff), (int)((s >> 16) & 0xff),
	    (int)((s >> 8) & 0xff), (int)(s & 0xff));
}


/*
 * cputts - put a value in long timestamp format
 */
void
cputts(ts, where)
	l_fp *ts;
	int where;
{
	cputulong(ts->l_ui, where);
	cputulong(ts->l_uf, where);
}


/*
 * cputnetnum - put a network number
 */
void
cputnetnum(addr, where)
	struct sockaddr_in *addr;
	int where;
{
	cputulong(ntohl(addr->sin_addr.s_addr), where);
}



/*
 * gettokens - read a line and return tokens
 */
int
gettokens(fp, line, tokenlist, ntokens)
	FILE *fp;
	char *line;
	char **tokenlist;
	int *ntokens;
{
	register char *cp;
	register int eol;
	register int ntok;
	int matchkey();

	/*
	 * Find start of first token
	 */
again:
	while ((cp = fgets(line, MAXLINE, fp)) != NULL) {
		cp = line;
		while (ISSPACE(*cp))
			cp++;
		if (!ISEOL(*cp))
			break;
	}
	if (cp == NULL) {
		*ntokens = 0;
		return CONFIG_UNKNOWN;	/* hack.  Is recognized as EOF */
	}

	/*
	 * Now separate out the tokens
	 */
	eol = 0;
	ntok = 0;
	while (!eol) {
		tokenlist[ntok++] = cp;
		while (!ISEOL(*cp) && !ISSPACE(*cp))
			cp++;
		if (ISEOL(*cp)) {
			*cp = '\0';
			eol = 1;
		} else {		/* must be space */
			*cp++ = '\0';
			while (ISSPACE(*cp))
				cp++;
			if (ISEOL(*cp))
				eol = 1;
		}
		if (ntok == MAXTOKENS)
			eol = 1;
	}

	/*
	 * Return the match
	 */
	*ntokens = ntok;
	ntok = matchkey(tokenlist[0], keywords);
	if (ntok == CONFIG_UNKNOWN)
		goto again;
	return ntok;
}



/*
 * matchkey - match a keyword to a list
 */
int
matchkey(word, keys)
	register char *word;
	register struct keyword *keys;
{
	for (;;) {
		if (keys->keytype == CONFIG_UNKNOWN) {
			log(LOG_ERR,
			    "configure: keyword \"%s\" unknown, line ignored",
			    word);
			return CONFIG_UNKNOWN;
		}
		if (STRSAME(word, keys->text))
			return keys->keytype;
		keys++;
	}
}


/*
 * getnetnum - return a net number (this is crude, but careful)
 */
int
getnetnum(num, addr)
	char *num;
	struct sockaddr_in *addr;
{
	register char *cp;
	register char *bp;
	register int i;
	register int temp;
	char buf[80];		/* will core dump on really stupid stuff */
	u_long netnum;
#ifdef DEBUG
	extern char *ntoa();
#endif

	cp = num;
	netnum = 0;
	for (i = 0; i < 4; i++) {
		bp = buf;
		while (isdigit(*cp))
			*bp++ = *cp++;
		if (bp == buf)
			break;

		if (i < 3) {
			if (*cp++ != '.')
				break;
		} else if (*cp != '\0')
			break;

		*bp = '\0';
		temp = atoi(buf);
		if (temp > 255)
			break;
		netnum <<= 8;
		netnum += temp;
#ifdef DEBUG
	if (debug > 3)
		printf("getnetnum %s step %d buf %s temp %d netnum %d\n",
		    num, i, buf, temp, netnum);
#endif
	}

	if (i < 4) {
		log(LOG_ERR,
		    "configure: \"%s\" not valid host number, line ignored",
		    num);
		return 0;
	}

	/*
	 * make up socket address.  Clear it out for neatness.
	 */
	bzero((char *)addr, sizeof(struct sockaddr_in));
	addr->sin_family = AF_INET;
	addr->sin_port = htons(NTP_PORT);
	addr->sin_addr.s_addr = htonl(netnum);
#ifdef DEBUG
	if (debug > 1)
		printf("getnetnum given %s, got %s (%x)\n",
		    num, ntoa(addr), netnum);
#endif
	return 1;
}


/*
 * Types of ascii representations for keys.  "Standard" means a 64 bit
 * hex number in NBS format, i.e. with the low order bit of each byte
 * a parity bit.  "NTP" means a 64 bit key in NTP format, with the
 * high order bit of each byte a parity bit.  "Ascii" means a 1-to-8
 * character string whose ascii representation is used as the key.
 */
#define	KEY_TYPE_STD	1
#define	KEY_TYPE_NTP	2
#define	KEY_TYPE_ASCII	3

#define	STD_PARITY_BITS	0x01010101


/*
 * nexttok - basic internal tokenizing routine
 */
char *
nexttok(str)
	char **str;
{
	register char *cp;
	char *starttok;

	cp = *str;

	/*
	 * Space past white space
	 */
	while (*cp == ' ' || *cp == '\t')
		cp++;
	
	/*
	 * Save this and space to end of token
	 */
	starttok = cp;
	while (*cp != '\0' && *cp != '\n' && *cp != ' '
	    && *cp != '\t' && *cp != '#')
		cp++;
	
	/*
	 * If token length is zero return an error, else set end of
	 * token to zero and return start.
	 */
	if (starttok == cp)
		return 0;
	
	if (*cp == ' ' || *cp == '\t')
		*cp++ = '\0';
	else
		*cp = '\0';
	
	*str = cp;
	return starttok;
}



/*
 * readkeys - read keys from a file.
 */
int
readkeys(file, buf)
	char *file;
	char *buf;
{
	FILE *fp;
	char *line;
	char *token;
	u_long keyno[NUMKEYS];
	u_long keys[NUMKEYS][2];
	int keytype;
	int i, n;

	/*
	 * Open file.  Complain and return if it can't be opened.
	 */
	fp = fopen(file, "r");
	if (fp == NULL) {
		log(LOG_ERR, "can't open key file %s: %m", file);
		return 0;
	}

	/*
	 * Now read lines from the file, looking for key entries
	 */
	n = 0;
	while ((line = fgets(buf, MAXLINE, fp)) != NULL) {
		token = nexttok(&line);
		if (token == 0)
			continue;
		
		/*
		 * First is key number.  See if it is okay.
		 */
		if (!atouint(token, &keyno[n])) {
			log(LOG_ERR, "invalid key number `%s'", token);
			continue;
		}
		if (keyno[n] == 0) {
			log(LOG_ERR,
			    "cannot change keyid 0, key entry `%s'ignored",
			    token);
			continue;
		}

		/*
		 * Next is keytype.  See if that is all right.
		 */
		token = nexttok(&line);
		if (token == 0) {
			log(LOG_ERR,
			    "no key type for key number %lu, entry ignored",
			    keyno[n]);
			continue;
		}
		if (*token == 'S' || *token == 's')
			keytype = KEY_TYPE_STD;
		else if (*token == 'N' || *token == 'n')
			keytype = KEY_TYPE_NTP;
		else if (*token == 'A' || *token == 'a')
			keytype = KEY_TYPE_ASCII;
		else {
			log(LOG_ERR,
			    "invalid key type for key number %d, entry ignored",
			    keyno[n]);
			continue;
		}

		/*
		 * Finally, get key and insert it
		 */
		token = nexttok(&line);
		if (token == 0) {
			log(LOG_ERR,
			    "no key for number %d entry, entry ignored",
			    keyno[n]);
			continue;
		} else if (!decodekey(keyno[n], keytype, token, &keys[n][0])) {
			log(LOG_ERR,
			    "format/parity error for key %d, not used",
			    keyno[n]);
			continue;
		}
		if (++n >= NUMKEYS) {
			cputtoken(CONFIG_KEYS, LAST);
			cputuchar(n, LAST);
			for (i = 0; i < n; i++) {
				cputulong(keyno[i], LAST);
				cputulong(keys[i][0], LAST);
				cputulong(keys[i][1], LAST);
			}
		}
	}

	if (n > 0) {
		cputtoken(CONFIG_KEYS, LAST);
		cputuchar(n, LAST);
		for (i = 0; i < n; i++) {
			cputulong(keyno[i], LAST);
			cputulong(keys[i][0], LAST);
			cputulong(keys[i][1], LAST);
		}
	}

	(void) fclose(fp);
	return 1;
}


/*
 * decodekey - decode an ASCII key
 */
int
decodekey(keyno, keytype, str, outkey)
	u_long keyno;
	int keytype;
	char *str;
	u_long *outkey;
{
	u_long key[2];
	u_char keybytes[8];
	u_long masterkey[2];
	char *cp;
	char *xdigit;
	int len;
	int i;
	u_char ekeys[128], dkeys[128];
	static char *hex = "0123456789abcdef";
	extern int auth_parity();
	extern void auth_subkeys();
	extern void auth_des();

	cp = str;
	len = strlen(cp);
	if (len == 0)
		return 0;

	switch(keytype) {
	case KEY_TYPE_STD:
	case KEY_TYPE_NTP:
		if (len != 16)		/* Lazy.  Should define constant */
			return 0;
		/*
		 * Decode hex key.
		 */
		key[0] = 0;
		key[1] = 0;
		for (i = 0; i < 16; i++) {
			if (!isascii(*cp))
				return 0;
			xdigit = index(hex, isupper(*cp) ? tolower(*cp) : *cp);
			cp++;
			if (xdigit == 0)
				return 0;
			key[i>>3] <<= 4;
			key[i>>3] |= (u_long)(xdigit - hex) & 0xf;
		}

		/*
		 * If this is an NTP format key, put it into NBS format
		 */
		if (keytype == KEY_TYPE_NTP) {
			for (i = 0; i < 2; i++)
				key[i] = ((key[i] << 1) & ~STD_PARITY_BITS)
				    | ((key[i] >> 7) & STD_PARITY_BITS);
		}

		/*
		 * Check the parity, reject the key if the check fails
		 */
		if (!auth_parity(key)) {
			return 0;
		}
		break;
	
	case KEY_TYPE_ASCII:
		/*
		 * Make up key from ascii representation
		 */
		bzero(keybytes, sizeof(keybytes));
		for (i = 0; i < 8 && i < len; i++)
			keybytes[i] = *cp++ << 1;
		key[0] = keybytes[0] << 24 | keybytes[1] << 16
		    | keybytes[2] << 8 | keybytes[3];
		key[1] = keybytes[4] << 24 | keybytes[5] << 16
		    | keybytes[6] << 8 | keybytes[7];
		
		/*
		 * Set parity on key
		 */
		(void)auth_parity(key);
		break;
	
	default:
		/* Oh, well */
		return 0;
	}

	masterkey[0] = (GIZMO_KEY_L ^ keyno) ^ (keyno << 1);
	masterkey[1] = (GIZMO_KEY_R ^ keyno) ^ (keyno << 1);
	auth_subkeys(masterkey, ekeys, dkeys);
	auth_des(key, ekeys);
	outkey[0] = key[0];
	outkey[1] = key[1];
	return 1;
}


/*
 * outputc - output a C language configuration file
 */
void
outputc(file)
	char *file;
{
	register int i;
	register int dolen;
	struct timeval tv;
	extern char *ctime();

	(void) printf("/*\n * Gizmo configuration data generated from %s\n",
	    file);
	(void) gettimeofday(&tv, (struct timezone *)0);
	(void) printf(" * on %s */\n", ctime(&tv.tv_sec));

	(void) printf("#include <sys/types.h>\n#include \"gizmo.h\"\n\n");
	(void) printf("u_char gizmo_config_data[GIZMO_CONFIG_SIZE] = {");

	dolen = ((blength + 11) / 12) * 12;
	if (dolen > GIZMO_CONFIG_SIZE)
		dolen = GIZMO_CONFIG_SIZE;

	for (i = 0; i < dolen; i++) {
		if (i != 0)
			(void) printf(",");
		if ((i % 12) == 0)
			(void) printf("\n\t");
		else
			(void) printf(" ");
		(void) printf("0x%02x", firstbuf[i]);
	}

	(void) printf("\n};\n");
}


/*
 * writeexec - write the configuration into the executable
 */
void
writeexec(exefile)
	char *exefile;
{
}


/*
 * log - send a message to the standard output
 *
 * I am truly ashamed of this.
 */
log(level, fmt, p0, p1, p2, p3, p4)
	int level;
	char *fmt;
	char *p0, *p1, *p2, *p3, *p4;
{
	char buf[1025];
	register int c;
	register char *b, *f;
	extern int errno;
	extern int sys_nerr;
	extern char *sys_errlist[];
	int olderrno;
	FILE *outfp;

	olderrno = errno;
	if (level <= LOG_ERR) {
		errstat++;
		outfp = stderr;
	} else {
		outfp = stdout;
	}

	b = buf;
	f = fmt;
	while ((c = *f++) != '\0' && c != '\n' && b < &buf[1024]) {
		if (c != '%') {
			*b++ = c;
			continue;
		}
		if ((c = *f++) != 'm') {
			*b++ = '%';
			*b++ = c;
			continue;
		}
		if ((unsigned)olderrno > sys_nerr)
			sprintf(b, "error %d", olderrno);
		else
			strcpy(b, sys_errlist[olderrno]);
		b += strlen(b);
	}
	*b++ = '\n';
	*b = '\0';
	(void) fprintf(outfp, "%s: ", progname);
	(void) fprintf(outfp, buf, p0, p1, p2, p3, p4);
}
