/*
 * gizmo_config.c - read and apply configuration information
 */
#include <stdio.h>
#include "gizmo_syslog.h"
#include <strings.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>

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

/*
 * These routines are used decode and apply configuration information
 * at run time.  The configuration is compiled in.
 */

/*
 * Definitions of things either imported from or exported to outside
 */
#ifdef DEBUG
extern int debug;
#endif
extern u_long ctl_auth_keyid;
extern u_long info_auth_keyid;

/*
 * Configuration array.  This is initialized at compile time.
 */
extern u_char gizmo_config_data[];

/*
 * Used by the configuration data reading routines
 */
static u_char *config_pt;
static u_long config_csum;

/*
 * getstartup - search through the options looking for a debug level
 *		and host to log to.
 */
void
getstartup(argc, argv)
	int argc;
	char *argv[];
{
	void doconfig();

	doconfig(0);
}

/*
 * getconfig - get command line options and read the configuration file
 */
void
getconfig(argc, argv)
	int argc;
	char *argv[];
{
	void doconfig();

	doconfig(1);
}


/*
 * doconfig - do the gross work of configuration
 */
void
doconfig(applyit)
	int applyit;
{
	register int i;
	int errflg;
	int peerversion;
	u_long peerkey;
	int peerflags;
	int hmode;
	struct sockaddr_in peeraddr;
	struct sockaddr_in maskaddr;
	l_fp ts;
	int tok;
	struct interface *localaddr;
	struct refclockstat clock;
	u_long data_csum;
	int cgettoken();
	int cgetuchar();
	int cgetchar();
	int cgetushort();
	u_long cgetulong();
	void cgetts();
	void cgetnetnum();
	void check_size();
	extern void proto_config();
	extern void authtrust();
	extern struct peer *peer_config();
	extern void stats_config();
	extern void mon_start(), mon_stop();
	extern void loop_config();
	extern int atolfp();
	extern int authreadkeys();
	extern struct interface *findinterface();
	extern char *ntoa();
	extern void setauthkey();

	/*
	 * Initialize, initialize
	 */
#ifdef DEBUG
	debug = 0;
#endif	/* DEBUG */
	config_pt = gizmo_config_data;
	config_csum = 0;

	while ((tok = cgettoken())
	    != CONFIG_UNKNOWN) {
		switch(tok) {
		case CONFIG_PEER:
		case CONFIG_SERVER:
		case CONFIG_BROADCAST:
			if (tok == CONFIG_PEER)
				hmode = MODE_ACTIVE;
			else if (tok == CONFIG_SERVER)
				hmode = MODE_CLIENT;
			else
				hmode = MODE_BROADCAST;

			cgetnetnum(&peeraddr);

#ifdef REFCLOCK
			if (!ISREFCLOCKADR(&peeraddr) && ISBADADR(&peeraddr)) {
#else
			if (ISBADADR(&peeraddr)) {
#endif
				syslog(LOG_ERR,
				    "attempt to configure invalid address %s",
				    ntoa(&peeraddr));
				break;
			}
			
			errflg = 0;
			peerversion = NTP_VERSION;
			peerkey = 0;
			peerflags = 0;
			while ((tok = cgettoken()) != CONFIG_END) {
				switch (tok) {
				case CONF_MOD_VERSION:
					peerversion = cgetuchar();
					if (peerversion != NTP_VERSION
					    && peerversion != NTP_OLDVERSION) {
						syslog(LOG_ERR,
				"inappropriate version number %d, %s ignored",
					peerversion, ntoa(&peeraddr));
						errflg = 1;
					}
					break;

				case CONF_MOD_KEY:
					peerkey = cgetulong();
					peerflags |= FLAG_AUTHENABLE;
					break;

				case CONF_MOD_MINPOLL:
					peerflags |= FLAG_MINPOLL;
					break;

				default:
					syslog(LOG_ERR,
		"inappropriate code for peer configuration <%d>, %s ignored\n",
					    tok, ntoa(&peeraddr));
					errflg++;
					break;
				}
			}
			if (!errflg && applyit) {
				if (peer_config(&peeraddr,
				    (struct interface *)0,
				    hmode, peerversion, peerkey,
				    peerflags) == 0) {
					syslog(LOG_ERR,
					    "configuration of %s failed",
					    ntoa(&peeraddr));
				}
			}
			break;

		case CONFIG_PRECISION:
			i = cgetchar();
			if (i >= 0 || i < -25)
				syslog(LOG_ERR,
				    "unlikely precision %d, line ignored", i);
			else if (applyit)
				proto_config(PROTO_PRECISION, i);
			break;

		case CONFIG_DRIFT:
			cgetts(&ts);
			if (applyit)
				loop_config(LOOP_DRIFTCOMP, &ts);
			break;
		
		case CONFIG_BROADCASTCLIENT:
			i = cgetuchar();
			if (i == CONF_YORN_YES) {
				if (applyit)
					proto_config(PROTO_BROADCLIENT, 1);
			} else if (i = CONF_YORN_NO) {
				if (applyit)
					proto_config(PROTO_BROADCLIENT, 0);
			} else {
				syslog(LOG_ERR,
			"Invalid argument for broadcastclient <%d>", i);
			}
			break;
		
		case CONFIG_AUTHENTICATE:
			i = cgetuchar();
			if (i == CONF_YORN_YES) {
				if (applyit)
					proto_config(PROTO_AUTHENTICATE, 1);
			} else if (i = CONF_YORN_NO) {
				if (applyit)
					proto_config(PROTO_AUTHENTICATE, 0);
			} else {
				syslog(LOG_ERR,
			"Invalid argument for authenticate <%d>", i);
			}
			break;
		
		case CONFIG_KEYS:
			i = cgetuchar();
			while (i-- > 0) {
				u_long keys[2];

				peerkey = cgetulong();
				keys[0] = cgetulong();
				keys[1] = cgetulong();
				if (applyit)
					setauthkey(peerkey, keys);
			}
			break;

		case CONFIG_MONITOR:
			i = cgetuchar();
			if (i == CONF_YORN_YES) {
				if (applyit)
					mon_start();
			} else if (i = CONF_YORN_NO) {
				if (applyit)
					mon_stop();
			} else {
				syslog(LOG_ERR,
			"Invalid argument for monitor <%d>", i);
			}
			break;
		
		case CONFIG_AUTHDELAY:
			peerkey = cgetulong();
			if (applyit)
				proto_config(PROTO_AUTHDELAY, (long)peerkey);
			break;
		
		case CONFIG_RESTRICT:
			cgetnetnum(&peeraddr);
			cgetnetnum(&maskaddr);
			i = cgetushort();

			/*
			 * Use peerversion as flags, peerkey as mflags.  Ick.
			 */
			peerversion = 0;
			peerkey = 0;
			if (i & CONF_RES_IGNORE)
				peerversion |= RES_IGNORE;
			if (i & CONF_RES_NOSERVE)
				peerversion |= RES_DONTSERVE;
			if (i & CONF_RES_NOTRUST)
				peerversion |= RES_DONTTRUST;
			if (i & CONF_RES_NOQUERY)
				peerversion |= RES_NOQUERY;
			if (i & CONF_RES_NOMODIFY)
				peerversion |= RES_NOMODIFY;
			if (i & CONF_RES_NOPEER)
				peerversion |= RES_NOPEER;
			if (i & CONF_RES_NOTRAP)
				peerversion |= RES_NOTRAP;
			if (i & CONF_RES_LPTRAP)
				peerversion |= RES_LPTRAP;
			if (i & CONF_RES_NTPPORT)
				peerkey |= RESM_NTPONLY;
			if (SRCADR(&peeraddr) == INADDR_ANY)
				maskaddr.sin_addr.s_addr = 0;
			if (applyit)
				restrict(RESTRICT_FLAGS, &peeraddr, &maskaddr,
				    (int)peerkey, peerversion);
			break;

		case CONFIG_BDELAY:
			cgetts(&ts);
			peerkey = LFPTOFP(&ts);
			if (applyit)
				proto_config(PROTO_BROADDELAY, peerkey);
			break;

		case CONFIG_TRUSTEDKEY:
			i = cgetuchar();
			while (i-- > 0) {
				peerkey = cgetulong();
				if (applyit)
					authtrust(peerkey, 1);
			}
			break;

		case CONFIG_REQUESTKEY:
			peerkey = cgetulong();
			if (applyit)
				info_auth_keyid = peerkey;
			break;

		case CONFIG_CONTROLKEY:
			peerkey = cgetulong();
			if (applyit)
				ctl_auth_keyid = peerkey;
			break;
		
		case CONFIG_TRAP:
			cgetnetnum(&peeraddr);

			/*
			 * Use peerversion for port number.  Barf.
			 */
			errflg = 0;
			peerversion = 0;
			localaddr = 0;
			while ((tok = cgettoken()) != CONFIG_END) {
				switch (tok) {
				case CONF_TRAP_PORT:
					peerversion = cgetushort();
					if (peerversion == 0
					    || peerversion > 32767) {
						syslog(LOG_ERR,
				"invalid port number %d, trap %s ignored",
						  peerversion, ntoa(&peeraddr));
						errflg = 1;
					}
					break;

				case CONF_TRAP_INTERFACE:
					cgetnetnum(&maskaddr);
					if (applyit) {
						localaddr
						    = findinterface(&maskaddr);
						if (localaddr == NULL) {
							syslog(LOG_ERR,
					"can't find interface with address %s",
							    ntoa(&maskaddr));
							errflg = 1;
						}
					}
					break;

				default:
					errflg++;
					break;
				}
			}

			if (!errflg && applyit) {
				extern struct interface *any_interface;

				if (peerversion != 0)
					peeraddr.sin_port = htons(peerversion);
				else
					peeraddr.sin_port = htons(TRAPPORT);
				if (localaddr == NULL)
					localaddr = any_interface;
				if (!ctlsettrap(&peeraddr, localaddr, 0,
				    NTP_VERSION))
					syslog(LOG_ERR,
					"can't set trap for %s, no resources",
					    ntoa(&peeraddr));
			}
			break;

		case CONFIG_FUDGE:
			cgetnetnum(&peeraddr);

			errflg = 0;
			if (!ISREFCLOCKADR(&peeraddr)) {
				syslog(LOG_ERR,
			"%s is inappropriate address for the fudge command",
				    ntoa(&peeraddr));
				errflg++;
			}

			bzero((char *)&clock, sizeof clock);
			while ((tok = cgettoken()) != CONFIG_END) {
				switch (tok) {
				case CONF_FDG_TIME1:
					cgetts(&clock.fudgetime1);
					clock.haveflags |= CLK_HAVETIME1;
					break;

				case CONF_FDG_TIME2:
					cgetts(&clock.fudgetime2);
					clock.haveflags |= CLK_HAVETIME2;
					break;

				case CONF_FDG_VALUE1:
					clock.fudgeval1 =
					    (long) cgetulong();
					clock.haveflags |= CLK_HAVEVAL1;
					break;

				case CONF_FDG_VALUE2:
					clock.fudgeval2 =
					    (long) cgetulong();
					clock.haveflags |= CLK_HAVEVAL2;
					break;

				case CONF_FDG_FLAG1:
				case CONF_FDG_FLAG2:
				case CONF_FDG_FLAG3:
				case CONF_FDG_FLAG4:
					peerversion = cgetuchar();
					if (peerversion != CONF_YORN_NO
					    && peerversion != CONF_YORN_YES) {
						syslog(LOG_ERR,
				"incorrect flag value <%d> for fudge %s",
						  peerversion, ntoa(&peeraddr));
						errflg++;
					}
					switch(tok) {
					case CONF_FDG_FLAG1:
						hmode = CLK_FLAG1;
						clock.haveflags|=CLK_HAVEFLAG1;
						break;
					case CONF_FDG_FLAG2:
						hmode = CLK_FLAG2;
						clock.haveflags|=CLK_HAVEFLAG2;
						break;
					case CONF_FDG_FLAG3:
						hmode = CLK_FLAG3;
						clock.haveflags|=CLK_HAVEFLAG3;
						break;
					case CONF_FDG_FLAG4:
						hmode = CLK_FLAG4;
						clock.haveflags|=CLK_HAVEFLAG4;
						break;
					}
					if (peerkey == CONF_YORN_NO)
						clock.flags &= ~hmode;
					else
						clock.flags |= hmode;
					break;

				default:
					errflg++;
					break;
				}
			}

#ifdef REFCLOCK
			/*
			 * If reference clock support isn't defined the
			 * fudge line will still be accepted and syntax
			 * checked, but will essentially do nothing.
			 */
			if (!errflg & applyit) {
				extern void refclock_control();

				refclock_control(&peeraddr, &clock,
				    (struct refclockstat *)0);
			}
#endif
			break;

		case CONFIG_MAXSKEW:
			peerkey = cgetulong();
			if (applyit)
				proto_config(PROTO_MAXSKEW, peerkey);
			break;

		case CONFIG_SYSLOGHOST:
			cgetnetnum(&peeraddr);
			setloghost(&peeraddr);
			break;

		case CONFIG_DEBUGLEVEL:
			i = cgetuchar();
#ifdef DEBUG
			debug = i;
			if (debug)
				printf("debug level set to %d\n", i);
#endif
			break;
		}
	}

	/*
	 * If we got here, there should be a checksum in the next
	 * 4 bytes of the file.  Check it out.
	 */
	check_size(4);
	data_csum = 0;
	for (i = 0; i < 4; i++) {
		data_csum <<= 8;
		data_csum += ((u_long)(*config_pt++)) & 0xff;
	}
	if ((config_csum + data_csum) != 0) {
		syslog(LOG_ERR,
		    "configuration checksum error (0x%08x != 0x%08x)",
		    config_csum, -data_csum);
#ifdef notdef
		exit(1);
#endif
	}
}


/*
 * check_size - check to see if there are enough bytes in the buffer.
 */
static void
check_size(nbytes)
	int nbytes;
{
	if ((config_pt + nbytes) > &gizmo_config_data[GIZMO_CONFIG_SIZE]) {
		syslog(LOG_ERR, "whoops!  Slipped off end of config info");
		exit(1);
	}
}

/*
 * cgetuchar - return an unsigned char (as an int)
 */
int
cgetuchar()
{
	register u_char u;

	check_size(sizeof(u_char));
	u = *config_pt++;
	config_csum += (u_long)u;
	return (int)u;
}


/*
 * cgettoken - return a token value
 */
int
cgettoken()
{
	register u_char u;

	check_size(sizeof(u_char));
	u = *config_pt++;
	config_csum += (u_long)u;
	return (int)u;
}


/*
 * cgetushort - return an unsigned short value (as an int)
 */
int
cgetushort()
{
	register u_char u;
	register u_short s;

	check_size(sizeof(u_short));
	u = *config_pt++;
	config_csum += (u_long)u;
	s = ((u_short)u) << 8;
	u = *config_pt++;
	config_csum += (u_long)u;
	s += (u_short)u;

	return (int)s;
}


/*
 * cgetulong - return an unsigned long value
 */
u_long
cgetulong()
{
	register int i;
	register u_long v;
	register u_char u;

	check_size(sizeof(u_long));
	v = 0;
	for (i = 0; i < sizeof(u_long); i++) {
		u = *config_pt++;
		config_csum += (u_long)u;
		v <<= 8;
		v += (u_long)u;
	}

	return v;
}


/*
 * cgetchar - return a signed char value
 */
int
cgetchar()
{
	register int i;

	config_csum += (u_long)(*config_pt);
	i = (int)*((s_char *)config_pt);
	config_pt++;
	config_csum += (u_long)(i & 0xff);
	return i;
}


/*
 * cgetts - return an l_fp timestamp
 */
void
cgetts(ts)
	l_fp *ts;
{
	register int i;
	register u_long v;
	register u_char u;

	check_size(sizeof(l_fp));
	v = 0;
	for (i = 0; i < sizeof(l_fp); i++) {
		if (i == (sizeof(l_fp)/2)) {
			ts->l_ui = v;
			v = 0;
		}
		u = *config_pt++;
		config_csum += (u_long)u;
		v <<= 8;
		v += (u_long)u;
	}
	ts->l_uf = v;
}



/*
 * cgetnetnum - return a net number
 */
void
cgetnetnum(addr)
	struct sockaddr_in *addr;
{
	register int i;
	register u_long uaddr;
	register u_char u;

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

	check_size(sizeof(u_long));
	for (i = 0, uaddr = 0; i < sizeof(u_long); i++) {
		u = *config_pt++;
		config_csum += (u_long)u;
		uaddr <<= 8;
		uaddr += (u_long)u;
	}
	addr->sin_addr.s_addr = htonl(uaddr);
}
