/*
 *	cfg.c -- config file handling for openupsd
 *	Copyright (C) 2003 Fred Barnes <frmb2@ukc.ac.uk>
 *
 *	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., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

/*{{{  includes, defines, etc.*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <termios.h>
#include <errno.h>
#include <netdb.h>

#include "support.h"
#include "openupsd.h"

/*}}}*/
/*{{{  strings table*/
static char *trig_strings[] = UPSD_DS_STRINGS;

/*}}}*/
/*{{{  static openupsd_netsvr_t *makelinktcpnetsvr (openupsd_netsvr_t **svrlist, char *dnames, char *hostport, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
/*
 *	this processes a NETSERVER TCP directive, linking the server info into
 *	the `sdevs' and `netclis' as appropriate.  note, might trash contents of "dnames"..
 *	returns the new/existing entry on success, NULL on failure
 */
static openupsd_netsvr_t *makelinktcpnetsvr (openupsd_netsvr_t **svrlist, char *dnames, char *hostport, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
	openupsd_netsvr_t *tmp;
	int gotdev = 0;
	int port;
	char *ch = strchr (hostport, ':');
	struct hostent *hp;
	struct sockaddr_in sin;

	if (!ch) {
		if (errstream) {
			fprintf (errstream, "%s: malformed host:port: %s\n", progname, hostport);
		}
		return NULL;
	}
	if (sscanf (ch + 1, "%d", &port) != 1) {
		if (errstream) {
			fprintf (errstream, "%s: malformed port: %s\n", progname, ch + 1);
		}
		return NULL;
	}
	*ch = '\0';
	if (strcmp (hostport, "*")) {
		hp = gethostbyname (hostport);
		if (!hp) {
			if (errstream) {
				fprintf (errstream, "%s: failed to resolve %s\n", progname, hostport);
			}
			return NULL;
		}
		if (hp->h_addrtype != AF_INET) {
			if (errstream) {
				fprintf (errstream, "%s: only INET address type supported (for %s)\n", progname, hostport);
			}
			return NULL;
		}
		memcpy (&(sin.sin_addr), hp->h_addr, sizeof (struct in_addr));
	} else {
		sin.sin_addr.s_addr = INADDR_ANY;
	}
	sin.sin_family = AF_INET;
	sin.sin_port = htons (port);

	/* see if we're listening here already */
	for (tmp = *svrlist; tmp; tmp = tmp->next) {
		if ((tmp->listen_host.sin_addr.s_addr == sin.sin_addr.s_addr) && (tmp->listen_host.sin_port == sin.sin_port)) {
			break;
		}
	}
	if (!tmp) {
		/* create one and add to the list */
		tmp = (openupsd_netsvr_t *)smalloc (sizeof (openupsd_netsvr_t));

		tmp->next = tmp->prev = NULL;
		tmp->listen_host.sin_addr.s_addr = sin.sin_addr.s_addr;
		tmp->listen_host.sin_port = sin.sin_port;
		tmp->listen_host.sin_family = sin.sin_family;
		tmp->fd = -1;
		dynarray_init (tmp->allow);
		dynarray_init (tmp->disallow);
		dynarray_init (tmp->clients);

		if (*svrlist) {
			tmp->next = *svrlist;
			(*svrlist)->prev = tmp;
		}
		*svrlist = tmp;
	}
	/* then deal with dnames and make the links */
	if (!strcmp (dnames, "*")) {
		/* link to all things */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; ts; ts = ts->next) {
			dynarray_maybeadd (ts->outtcpsvrs, tmp);
			gotdev++;
		}
		for (ns = netclis; ns; ns = ns->next) {
			dynarray_maybeadd (ns->outtcpsvrs, tmp);
			gotdev++;
		}
	} else if (!strchr (dnames, ',')) {
		/* just one thing, go looking for it */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; !gotdev && ts; ts = ts->next) {
			if (!strcmp (dnames, ts->name)) {
				dynarray_maybeadd (ts->outtcpsvrs, tmp);
				gotdev++;
			}
		}
		for (ns = netclis; !gotdev && ns; ns = ns->next) {
			if (!strcmp (dnames, ns->name)) {
				dynarray_maybeadd (ns->outtcpsvrs, tmp);
				gotdev++;
			}
		}
	} else {
		/* got comma, so a list of things */
		char *ch, *dh;
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ch = dnames; *ch != '\0'; ch++) {
			char *name = ch;
			int lgotdev = 0;

			for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
			if (*dh == ',') {
				*dh = '\0';
				ch = dh;
			} else {
				ch = dh - 1;
			}
			/* name valid here */
			for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
				if (!strcmp (name, ts->name)) {
					dynarray_maybeadd (ts->outtcpsvrs, tmp);
					lgotdev++;
				}
			}
			for (ns = netclis; !lgotdev && ns; ns = ns->next) {
				if (!strcmp (name, ns->name)) {
					dynarray_maybeadd (ns->outtcpsvrs, tmp);
					lgotdev++;
				}
			}
			if (lgotdev) {
				gotdev++;
			}
		}
	}
	if (!gotdev) {
		/* no devices */
		if (errstream) {
			fprintf (stderr, "%s: no devices found for OUTPUT\n", progname);
		}
		return NULL;
	}
	return tmp;
}
/*}}}*/
/*{{{  static int linkalarm (openupsd_alarm_t *alrm, char *dnames, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
static int linkalarm (openupsd_alarm_t *alrm, char *dnames, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
	int gotdev = 0;

	if (!strcmp (dnames, "*")) {
		/* link to all things */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; ts; ts = ts->next) {
			if (dynarray_maybeadd (ts->alarms, alrm)) {
				dynarray_add (ts->triggered, -1);
			}
			gotdev++;
		}
		for (ns = netclis; ns; ns = ns->next) {
			if (dynarray_maybeadd (ns->alarms, alrm)) {
				dynarray_add (ns->triggered, -1);
			}
			gotdev++;
		}
	} else if (!strchr (dnames, ',')) {
		/* just one thing, go looking for it */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; !gotdev && ts; ts = ts->next) {
			if (!strcmp (dnames, ts->name)) {
				if (dynarray_maybeadd (ts->alarms, alrm)) {
					dynarray_add (ts->triggered, -1);
				}
				gotdev++;
			}
		}
		for (ns = netclis; !gotdev && ns; ns = ns->next) {
			if (!strcmp (dnames, ns->name)) {
				if (dynarray_maybeadd (ns->alarms, alrm)) {
					dynarray_add (ns->triggered, -1);
				}
				gotdev++;
			}
		}
	} else {
		/* got comma, so a list of things */
		char *ch, *dh;
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ch = dnames; *ch != '\0'; ch++) {
			char *name = ch;
			int lgotdev = 0;

			for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
			if (*dh == ',') {
				*dh = '\0';
				ch = dh;
			} else {
				ch = dh - 1;
			}
			/* name valid here */
			for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
				if (!strcmp (name, ts->name)) {
					if (dynarray_maybeadd (ts->alarms, alrm)) {
						dynarray_add (ts->triggered, -1);
					}
					lgotdev++;
				}
			}
			for (ns = netclis; !lgotdev && ns; ns = ns->next) {
				if (!strcmp (name, ns->name)) {
					if (dynarray_maybeadd (ns->alarms, alrm)) {
						dynarray_add (ns->triggered, -1);
					}
					lgotdev++;
				}
			}
			if (lgotdev) {
				gotdev++;
			}
		}
	}
	if (!gotdev) {
		/* no devices */
		if (errstream) {
			fprintf (stderr, "%s: no devices found for ALARM\n", progname);
		}
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static openupsd_netcli_t *maketcpnetclidev (char *name, char *hostport)*/
static openupsd_netcli_t *maketcpnetclidev (char *name, char *hostport)
{
	openupsd_netcli_t *tmp;
	char *ch;
	int port;
	struct hostent *hp;

	ch = strchr (hostport, ':');
	if (!ch) {
		return NULL;
	}
	if (sscanf (ch+1, "%d", &port) != 1) {
		/* make sure we haven't trashed the string by this point ;) */
		return NULL;
	}
	*ch = '\0';
	hp = gethostbyname (hostport);
	if (!hp) {
		return NULL;
	}
	if (hp->h_addrtype != AF_INET) {
		return NULL;
	}
	tmp = (openupsd_netcli_t *)smalloc (sizeof (openupsd_netcli_t));
	tmp->next = tmp->prev = NULL;
	tmp->name = string_dup (name);
	tmp->fd = -1;
	tmp->retry = 60;
	tmp->state = UPSD_NETCLI_INACTIVE;
	memcpy (&(tmp->ups_host.sin_addr), hp->h_addr, sizeof (struct in_addr));
	tmp->ups_host.sin_family = AF_INET;
	tmp->ups_host.sin_port = htons (port);
	tmp->inbuf = NULL;
	tmp->bufsize = 0;
	tmp->inbytes = 0;
	dynarray_init (tmp->outfiles);
	dynarray_init (tmp->outtcpsvrs);
	dynarray_init (tmp->alarms);
	dynarray_init (tmp->triggered);
	return tmp;
}
/*}}}*/
/*{{{  static int decode_alarm_trigger (char *trigger)*/
static int decode_alarm_trigger (char *trigger)
{
	int i;

	for (i=0; i<UPSD_DS_NUMFIELDS; i++) {
		if (!strcasecmp (trigger, trig_strings[i])) {
			return i;
		}
	}
	return -1;
}
/*}}}*/
/*{{{  static int decode_alarm_cmp (char *cmp)*/
static int decode_alarm_cmp (char *cmp)
{
	int i = -1;

	switch (*cmp) {
	case '!':
		cmp++;
		if (*cmp == '=') {
			i = UPSD_CMP_NE;
			cmp++;
		}
		break;
	case '=':
		cmp++;
		i = UPSD_CMP_EQ;
		if (*cmp == '=') {
			cmp++;
		}
		break;
	case '<':
		cmp++;
		if (*cmp == '=') {
			cmp++;
			i = UPSD_CMP_LE;
		} else {
			i = UPSD_CMP_LT;
		}
		break;
	case '>':
		cmp++;
		if (*cmp == '=') {
			cmp++;
			i = UPSD_CMP_GE;
		} else {
			i = UPSD_CMP_GT;
		}
		break;
	}
	if (*cmp != '\0') {
		return -1;
	}
	return i;
}
/*}}}*/
/*{{{  static openupsd_alarm_t *makelogalarm (openupsd_trigger_t *trigger, char *delay)*/
static openupsd_alarm_t *makelogalarm (openupsd_trigger_t *trigger, char *delay)
{
	openupsd_alarm_t *tmp;
	int td;

	if (delay) {
		if (sscanf (delay, "%d", &td) != 1) {
			return NULL;
		}
	} else {
		td = 0;
	}
	tmp = (openupsd_alarm_t *)smalloc (sizeof (openupsd_alarm_t));
	tmp->next = tmp->prev = NULL;
	tmp->alarm = trigger;
	tmp->time = (time_t)td;
	tmp->action = UPSD_ACTION_LOG;
	tmp->devname = NULL;
	tmp->initial = 0;
	tmp->xdata = NULL;
	return tmp;
}
/*}}}*/
/*{{{  static openupsd_alarm_t *makeexecalarm (openupsd_trigger_t *trigger, char *delay)*/
static openupsd_alarm_t *makeexecalarm (openupsd_trigger_t *trigger, char *delay)
{
	openupsd_alarm_t *tmp;
	openupsd_exec_t *etmp;
	int td;

	if (delay) {
		if (sscanf (delay, "%d", &td) != 1) {
			return NULL;
		}
	} else {
		td = 0;
	}
	etmp = (openupsd_exec_t *)smalloc (sizeof (openupsd_exec_t));
	etmp->prev = etmp->next = NULL;
	etmp->path_to_run = NULL;
	etmp->alarm = NULL;
	etmp->env = NULL;
	dynarray_init (etmp->args);

	tmp = (openupsd_alarm_t *)smalloc (sizeof (openupsd_alarm_t));
	tmp->next = tmp->prev = NULL;
	tmp->alarm = trigger;
	tmp->time = (time_t)td;
	tmp->action = UPSD_ACTION_EXEC;
	tmp->devname = NULL;
	tmp->initial = 0;
	tmp->xdata = (void *)etmp;

	return tmp;
}
/*}}}*/
/*{{{  static openupsd_trigger_t *openupsd_find_trigger (char *str, openupsd_trigger_t *tlist)*/
static openupsd_trigger_t *openupsd_find_trigger (char *str, openupsd_trigger_t *tlist)
{
	while (tlist) {
		if (!strcasecmp (tlist->name, str)) {
			return tlist;
		}
		tlist = tlist->next;
	}
	return NULL;
}
/*}}}*/
/*{{{  static openupsd_sdev_t *makeserialdev (char *name, char *device, char *params, char *polltime)*/
static openupsd_sdev_t *makeserialdev (char *name, char *device, char *params, char *polltime)
{
	openupsd_sdev_t *tmp;
	int sbaud, sdata, sstop;
	char sparity;
	int pt;

	if (sscanf (polltime, "%d", &pt) != 1) {
		return NULL;
	}
	if ((pt < 5) || (pt > 86400)) {
		/* min 5 secs, max 1 day */
		return NULL;
	}
	if (sscanf (params, "%d-%d-%c-%d", &sbaud, &sdata, &sparity, &sstop) != 4) {
		return NULL;
	}
	/* check sanity of data/parity/stop -- baud checked when the device is opened */
	switch (sdata) {
	case 6:
	case 7:
	case 8:
		break;
	default:
		return NULL;
	}
	switch (sparity) {
	case 'N':
	case 'n':
	case 'O':
	case 'o':
	case 'E':
	case 'e':
	case 'M':
	case 'm':
	case 'S':
	case 's':
		break;
	default:
		return NULL;
	}
	switch (sstop) {
	case 1:
	case 2:
		break;
	default:
		return NULL;
	}

	tmp = (openupsd_sdev_t *)smalloc (sizeof (openupsd_sdev_t));
	tmp->next = tmp->prev = NULL;
	tmp->name = string_dup (name);
	tmp->device = string_dup (device);
	tmp->fd = -1;
	tmp->inter_poll_sec = pt;
	tmp->s_baud = sbaud;
	tmp->s_data = sdata;
	tmp->s_parity = sparity;
	tmp->s_stop = sstop;
	dynarray_init (tmp->outfiles);
	dynarray_init (tmp->outtcpsvrs);
	dynarray_init (tmp->alarms);
	dynarray_init (tmp->triggered);
	return tmp;
}
/*}}}*/
/*{{{  static int makelinkofile (openupsd_ofile_t **oflist, char *dnames, char *fname, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)*/
/*
 *	this processes an OUTPUT directive, linking the various output files
 *	into the `sdevs' and `netclis' as appropriate.  note, might trash contents of "dnames"..
 *	returns 0 on success, anything else on failure.
 */
static int makelinkofile (openupsd_ofile_t **oflist, char *dnames, char *fname, openupsd_sdev_t *sdevs, openupsd_netcli_t *netclis, FILE *errstream)
{
	openupsd_ofile_t *tmp;
	int gotdev = 0;

	/* see if we've already got a file of this name */
	for (tmp=*oflist; tmp; tmp = tmp->next) {
		if (!strcmp (tmp->fname, fname)) {
			break;
		}
	}
	if (!tmp) {
		/* create one and add to the list */
		tmp = (openupsd_ofile_t *)smalloc (sizeof (openupsd_ofile_t));

		tmp->next = tmp->prev = NULL;
		tmp->fname = (char *)string_dup (fname);

		if (*oflist) {
			tmp->next = *oflist;
			(*oflist)->prev = tmp;
		}
		*oflist = tmp;
	}
	/* then pick apart dnames */
	if (!strcmp (dnames, "*")) {
		/* link to all things */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; ts; ts = ts->next) {
			dynarray_maybeadd (ts->outfiles, tmp);
			gotdev++;
		}
		for (ns = netclis; ns; ns = ns->next) {
			dynarray_maybeadd (ns->outfiles, tmp);
			gotdev++;
		}
	} else if (!strchr (dnames, ',')) {
		/* just one thing, go looking for it */
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ts = sdevs; !gotdev && ts; ts = ts->next) {
			if (!strcmp (dnames, ts->name)) {
				dynarray_maybeadd (ts->outfiles, tmp);
				gotdev++;
			}
		}
		for (ns = netclis; !gotdev && ns; ns = ns->next) {
			if (!strcmp (dnames, ns->name)) {
				dynarray_maybeadd (ns->outfiles, tmp);
				gotdev++;
			}
		}
	} else {
		/* got a comma, so a list of things */
		char *ch, *dh;
		openupsd_sdev_t *ts;
		openupsd_netcli_t *ns;

		for (ch = dnames; *ch != '\0'; ch++) {
			char *name = ch;
			int lgotdev = 0;

			for (dh = ch; (*dh != ',') && (*dh != '\0'); dh++);
			if (*dh == ',') {
				*dh = '\0';
				ch = dh;
			} else {
				ch = dh - 1;
			}
			/* name valid here */
			for (ts = sdevs; !lgotdev && ts; ts = ts->next) {
				if (!strcmp (name, ts->name)) {
					dynarray_maybeadd (ts->outfiles, tmp);
					lgotdev++;
				}
			}
			for (ns = netclis; !lgotdev && ns; ns = ns->next) {
				if (!strcmp (name, ns->name)) {
					dynarray_maybeadd (ns->outfiles, tmp);
					lgotdev++;
				}
			}
			if (lgotdev) {
				gotdev++;
			}
		}
	}
	if (!gotdev) {
		/* no devices */
		if (errstream) {
			fprintf (stderr, "%s: no devices found for OUTPUT\n", progname);
		}
		return -1;
	}
	return 0;
}
/*}}}*/
/*{{{  static openupsd_trigger_t *makeaddtrigger (openupsd_trigger_t **tlist, char *name, int field, int cmp, char *rhs)*/
static openupsd_trigger_t *makeaddtrigger (openupsd_trigger_t **tlist, char *name, int field, int cmp, char *rhs)
{
	openupsd_trigger_t *tmp;
	void *nrhs;
	char *ch;
	int x, y;

	/* make sure we don't have one already */
	for (tmp = *tlist; tmp; tmp = tmp->next) {
		if (!strcmp (tmp->name, name)) {
			return NULL;
		}
	}
	/* look at field, check cmp and rhs */
	switch (field) {
	case UPSD_DS_MODEL:		/* model */
		if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
			return NULL;
		}
		/* RHS must be a _quoted_ string */
		if (*rhs != '\"') {
			return NULL;
		}
		ch = rhs + 1;
		if ((ch = strchr (ch, '\"')) == NULL) {
			return NULL;
		}
		*ch = '\0';
		nrhs = (void *)string_dup (rhs + 1);
		break;
	case UPSD_DS_BAT_COND:		/* battery-condition (weak/normal) */
		if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
			return NULL;
		}
		/* RHS must be a _quoted_ string */
		if (*rhs != '\"') {
			return NULL;
		}
		ch = rhs + 1;
		if ((ch = strchr (ch, '\"')) == NULL) {
			return NULL;
		}
		*ch = '\0';
		nrhs = (void *)0;
		for (ch = rhs + 1; *ch != '\0'; ch++) {
			char *dh = strchr (ch, ' ');

			if (dh) {
				*dh = '\0';
			}
			/* pick field from ch */
			if (!strcmp (ch, "weak")) {
				nrhs = (void *)1;
			} else if (!strcmp (ch, "normal")) {
				nrhs = (void *)0;
			} else {
				return NULL;
			}
			if (dh) {
				ch = dh;
			} else {
				ch += (strlen (ch) - 1);
			}
		}
		break;
	case UPSD_DS_BAT_IS:		/* battery-is (charging/discharging) */
		if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
			return NULL;
		}
		/* RHS must be a _quoted_ string */
		if (*rhs != '\"') {
			return NULL;
		}
		ch = rhs + 1;
		if ((ch = strchr (ch, '\"')) == NULL) {
			return NULL;
		}
		*ch = '\0';
		nrhs = (void *)0;
		for (ch = rhs + 1; *ch != '\0'; ch++) {
			char *dh = strchr (ch, ' ');

			if (dh) {
				*dh = '\0';
			}
			/* pick field from ch */
			if (!strcmp (ch, "discharging")) {
				nrhs = (void *)1;
			} else if (!strcmp (ch, "charging")) {
				nrhs = (void *)0;
			} else {
				return NULL;
			}
			if (dh) {
				ch = dh;
			} else {
				ch += (strlen (ch) - 1);
			}
		}
		break;
	case UPSD_DS_BAT_VOLTS:		/* battery-volts (.1f) */
	case UPSD_DS_IN_FREQ:		/* input-frequency (.1f) */
	case UPSD_DS_IN_VOLTS:		/* input-voltage (.1f) */
	case UPSD_DS_OUT_FREQ:		/* output-frequency (.1f) */
	case UPSD_DS_OUT_VOLTS:		/* output-voltage (.1f) */
		/* RHS is a number, factor of 10 out */
		ch = strchr (rhs, '.');
		if (ch) {
			if (sscanf (rhs, "%d.%d", &x, &y) != 2) {
				return NULL;
			} else if ((y < 0) || (y > 9)) {
				return NULL;
			}
		} else {
			if (sscanf (rhs, "%d", &x) != 1) {
				return NULL;
			}
			y = 0;
		}
		nrhs = (void *)((x * 10) + y);
		break;
	case UPSD_DS_BAT_CHARGE:	/* battery-charge (d) */
	case UPSD_DS_BAT_TEMP:		/* battery-temperature (d) */
	case UPSD_DS_OUT_LOAD:		/* output-load (d) */
		/* RHS is a simple INT */
		if (sscanf (rhs, "%d", &x) != 1) {
			return NULL;
		}
		nrhs = (void *)x;
		break;
	case UPSD_DS_STATUS:		/* status-field */
		if ((cmp != UPSD_CMP_EQ) && (cmp != UPSD_CMP_NE)) {
			return NULL;
		}
		/* RHS must be a _quoted_ string */
		if (*rhs != '\"') {
			return NULL;
		}
		ch = rhs + 1;
		if ((ch = strchr (ch, '\"')) == NULL) {
			return NULL;
		}
		*ch = '\0';
		nrhs = (void *)0;		/* add flags to this */
		for (ch = rhs + 1; *ch != '\0'; ch++) {
			char *dh = strchr (ch, ' ');

			if (dh) {
				*dh = '\0';
			}
			/* pick field from ch */
			if (!strcmp (ch, "overheat")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_OVERHEAT);
			} else if (!strcmp (ch, "on-battery")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_ONBATTERY);
			} else if (!strcmp (ch, "output-bad")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_OUTPUTBAD);
			} else if (!strcmp (ch, "overload")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_OVERLOAD);
			} else if (!strcmp (ch, "bypass-bad")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_BYPASSBAD);
			} else if (!strcmp (ch, "output-off")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_OUTPUTOFF);
			} else if (!strcmp (ch, "charger-bad")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_CHARGERBAD);
			} else if (!strcmp (ch, "ups-off")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_UPSOFF);
			} else if (!strcmp (ch, "fan-failure")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_FANFAIL);
			} else if (!strcmp (ch, "fuse-break")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_FUSEBREAK);
			} else if (!strcmp (ch, "ups-fault")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_FAULT);
			} else if (!strcmp (ch, "awaiting-power")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_AWAITPOWER);
			} else if (!strcmp (ch, "buzzer-alarm-on")) {
				nrhs = (void *)((int)nrhs | UPSD_ST_BUZZERON);
			} else {
				return NULL;
			}
			if (dh) {
				ch = dh;
			} else {
				ch += (strlen (ch) - 1);
			}
		}
		break;
	default:
		return NULL;
	}
	/* right, build new one and add to the list */
	tmp = (openupsd_trigger_t *)smalloc (sizeof (openupsd_trigger_t));
	tmp->prev = tmp->next = NULL;
	tmp->name = string_dup (name);
	tmp->trig = field;
	tmp->comp = cmp;
	tmp->rhs = nrhs;
	dynarray_init (tmp->inv);

	if (*tlist) {
		tmp->next = *tlist;
		(*tlist)->prev = tmp;
	}
	*tlist = tmp;

	return tmp;
}
/*}}}*/
/*{{{  static int add_network_netmask (int allow, openupsd_netsvr_t *svr, char *netspec)*/
/*
 *	adds allow/deny stuff to network servers
 *	returns 0 on success, -1 on error
 */
static int add_network_netmask (int allow, openupsd_netsvr_t *svr, char *netspec)
{
	char *ch;
	unsigned long netaddr = 0;
	unsigned long netbits = 0xffffffff;
	int a, b, c, d, i;
	openupsd_netnet_t *tmp;

	ch = strchr (netspec, '/');
	if (ch) {
		int l;

		*ch = '\0';
		ch++;
		if ((sscanf (ch, "%d", &l) != 1) || (l < 0) || (l > 32)) {
			return -1;
		}
		for (l = (31 - l); l >= 0; netbits <<= 1, l--);
	}
	if (sscanf (netspec, "%d.%d.%d.%d", &a, &b, &c, &d) != 4) {
		return -1;
	}
	netaddr = ((unsigned long)(a & 0xff) << 24) | ((unsigned long)(b & 0xff) << 16) | ((unsigned long)(c & 0xff) << 8) | (unsigned long)(d & 0xff);

	/* network byte-order them */
	netaddr = htonl (netaddr);
	netbits = htonl (netbits);

	/* see if already here.. */
	for (i = 0; i < (allow ? DA_CUR(svr->allow) : DA_CUR(svr->disallow)); i++) {
		openupsd_netnet_t *item = (allow ? DA_NTHITEM(svr->allow, i) : DA_NTHITEM(svr->disallow, i));

		if ((item->allow_mask == netbits) && ((item->allow_net & item->allow_mask) == (netaddr & netbits))) {
			/* already here */
			return 0;
		}
	}

	/* not here, create a new one and add it */
	tmp = (openupsd_netnet_t *)smalloc (sizeof (openupsd_netnet_t));
	tmp->allow_net = netaddr;
	tmp->allow_mask = netbits;

	if (allow) {
		dynarray_add(svr->allow, tmp);
	} else {
		dynarray_add(svr->disallow, tmp);
	}
	return 0;
}
/*}}}*/
/*{{{  static char *getnextarg (char **buf, int *nextarg)*/
static char *getnextarg (char **buf, int *nextarg)
{
	char *ch, *arg;
	
	arg = *buf;
	for (ch=*buf; (*ch != '\0') && (*ch != ' ') && (*ch != '\t'); ch++);
	if (*ch == '\0') {
		*nextarg = 0;
		*buf  = ch;
	} else {
		*ch = '\0';
		for (ch++; (*ch == ' ') || (*ch == '\t'); ch++);
		*buf = ch;
		*nextarg = *nextarg + 1;
	}
	return arg;
}
/*}}}*/
/*{{{  int parse_config (openupsd_t *upsinfo)*/
/*
 *	reads the configuration file and populates parts of upsinfo
 *	this is relaxed -- spouts configuration errors to `errstream' if not null
 *	returns 0 on success, number of errors encountered on failure
 */
int parse_config (openupsd_t *upsinfo, FILE *errstream)
{
	FILE *fp;
	char pbuf[1024];
	openupsd_sdev_t *sdevlist = NULL;
	openupsd_alarm_t *alarms = NULL;
	openupsd_netcli_t *netclis = NULL;
	openupsd_trigger_t *trigs = NULL;
	openupsd_inv_t *invds = NULL;
	int cline = 0;
	int errcount = 0;

	if (!upsinfo->conffile) {
		return -1;
	} else if (access (upsinfo->conffile, R_OK)) {
		return -1;
	}
	fp = fopen (upsinfo->conffile, "r");
	if (!fp) {
		return -1;
	}
	/* oki, go parse! */
	while (!feof (fp)) {
		char *ch, *dh;
		char odh;
		int nextarg = 0;

		fgets (pbuf, sizeof (pbuf) - 1, fp);
		cline++;
		/*{{{  tidy up line*/
		for (ch = pbuf; (*ch == ' ') || (*ch == '\t'); ch++);
		if (*ch == '#') {
			continue;
		}
		for (dh = ch; (*dh != '\0') && (*dh != '#'); dh++);
		for (dh--; (dh >= ch) && (*dh <= ' '); dh--) {
			*dh = '\0';
		}
		if ((*ch == '\0') || (ch == dh)) {
			continue;
		}
		/*}}}*/
		/* process line */
		for (dh=ch; (*dh != ' ') && (*dh != '\t') && (*dh != '\0'); dh++);
		odh = *dh;
		*dh = '\0';
		if (odh != '\0') {
			for (dh++; (*dh == ' ') || (*dh == '\t'); dh++);
			nextarg = 1;
		}
		/* ch has this arg, dh has the next if nextarg > 0 */
		if (!strcasecmp (ch, "log")) {
			/*{{{  LOG [SYSLOG|filename] */
			if (nextarg) {
				char *arg = getnextarg (&dh, &nextarg);

				if (!strcasecmp (arg, "syslog")) {
					upsinfo->use_syslog = 1;
				} else {
					upsinfo->use_syslog = 0;
					upsinfo->logfilename = string_dup (arg);
				}
			} else {
				/* turn logging off */
				if (upsinfo->logfilename) {
					sfree (upsinfo->logfilename);
					upsinfo->logfilename = NULL;
				}
				upsinfo->use_syslog = 0;
			}
			if (nextarg) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  ignoring trailing rubbish on line\n",
							progname, upsinfo->conffile, cline);
				}
				errcount++;
			}
			continue;
			/*}}}*/
		} else if (!strcasecmp (ch, "pidfile")) {
			/*{{{  PIDFILE filename*/
			char *fname = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (nextarg) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  ignoring trailing rubbish on line\n",
							progname, upsinfo->conffile, cline);
				}
				errcount++;
			}
			if (!fname) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires a filename argument\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else {
				if (upsinfo->pidfilename) {
					sfree (upsinfo->pidfilename);
				}
				upsinfo->pidfilename = string_dup (fname);
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "device")) {
			/*{{{  DEVICE <name> <sdev> <baud-data-parity-stop> <interpoll>*/
			/* next arguments should be a device name, serial device, parameters and inter-poll time */
			char *dname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *sdev = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *sparams = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *polltime = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (nextarg) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  ignoring trailing rubbish on line\n",
							progname, upsinfo->conffile, cline);
				}
				errcount++;
			}
			if (!polltime) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else if (access (sdev, R_OK | W_OK)) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  cannot access serial device %s\n",
							progname, upsinfo->conffile, cline, sdev);
				}
				errcount++;
			} else {
				openupsd_sdev_t *tsdev = makeserialdev (dname, sdev, sparams, polltime);

				if (tsdev) {
					if (!sdevlist) {
						sdevlist = tsdev;
					} else {
						tsdev->next = sdevlist;
						sdevlist->prev = tsdev;
						sdevlist = tsdev;
					}
				} else {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  failed to parse %s parameters\n",
								progname, upsinfo->conffile, cline, ch);
					}
					errcount++;
				}
			}
			continue;
			/*}}}*/
		} else if (!strcasecmp (ch, "output")) {
			/*{{{  OUTPUT <name> <filename>*/
			/* next arguments will be a name (possibly * or a list) and a filename */
			char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *fname = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (nextarg) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  ignoring trailing rubbish on line\n",
							progname, upsinfo->conffile, cline);
				}
				errcount++;
			}
			if (!fname) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else if (makelinkofile (&(upsinfo->outfiles), dnames, fname, sdevlist, netclis, errstream)) {
				errcount++;
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "netclient")) {
			/*{{{  NETCLIENT <mode> <name> <host>:<port> [RETRY time]*/
			char *mode = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (!mode) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else if (!strcasecmp (mode, "tcp")) {
				char *name = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				char *hostport = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				time_t retry_time = (time_t)60;

				if (nextarg) {
					/* probably a RETRY spec */
					char *targ = getnextarg (&dh, &nextarg);
					char *value = nextarg ? getnextarg (&dh, &nextarg) : NULL;

					if (value && !strcasecmp (targ, "retry")) {
						int tme = 0;

						if (sscanf (value, "%d", &tme) != 1) {
							if (errstream) {
								fprintf (errstream, "%s: error in config file %s:%d:  mangled %s value %s\n",
										progname, upsinfo->conffile, cline, targ, value);
							}
							errcount++;
						}
						retry_time = (time_t)tme;
					} else {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  ignoring trailing rubbish on line\n",
									progname, upsinfo->conffile, cline);
						}
						errcount++;
					}
				}
				if (!hostport) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  %s %s requires more arguments\n",
								progname, upsinfo->conffile, cline, ch, mode);
					}
					errcount++;
				} else {
					openupsd_netcli_t *nsdev = maketcpnetclidev (name, hostport);

					nsdev->retry = retry_time;
					if (nsdev) {
						if (!netclis) {
							netclis = nsdev;
						} else {
							nsdev->next = netclis;
							netclis->prev = nsdev;
							netclis = nsdev;
						}
					} else {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  failed to resolve %s\n",
									progname, upsinfo->conffile, cline, hostport);
						}
						errcount++;
					}
				}
			} else {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  unknown %s mode %s\n",
							progname, upsinfo->conffile, cline, ch, mode);
				}
				errcount++;
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "netserver")) {
			/*{{{  NETSERVER <mode> <names> <host:port> [ALLOW network/netmask [ALLOW ...]] [DENY network/netmask [DENY ...]]*/
			char *mode = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (!mode) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else if (!strcasecmp (mode, "tcp")) {
				char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				char *hostport = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				openupsd_netsvr_t *tns;

				if (!hostport) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
								progname, upsinfo->conffile, cline, ch);
					}
					errcount++;
				} else {
					tns = makelinktcpnetsvr (&(upsinfo->netsvr), dnames, hostport, sdevlist, netclis, errstream);
					if (!tns) {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  %s %s error\n",
									progname, upsinfo->conffile, cline, ch, mode);
						}
						errcount++;
					} else {
						int lerr = 0;

						/* process ALLOW/DENY stuff */
						while (nextarg) {
							char *option = getnextarg (&dh, &nextarg);
							char *value = nextarg ? getnextarg (&dh, &nextarg) : NULL;

							if (!value) {
								if (errstream) {
									fprintf (errstream, "%s: error in config file %s:%d:  expecting ALLOW/DENY pairs\n",
											progname, upsinfo->conffile, cline);
								}
								errcount++;
							} else if (!strcasecmp (option, "allow")) {
								if (add_network_netmask (1, tns, value)) {
									lerr++;
								}
							} else if (!strcasecmp (option, "deny")) {
								if (add_network_netmask (0, tns, value)) {
									lerr++;
								}
							} else {
								if (errstream) {
									fprintf (errstream, "%s: error in config file %s:%d:  unknown option %s\n",
											progname, upsinfo->conffile, cline, option);
								}
								errcount++;
							}
						}
						if (lerr) {
							if (errstream) {
								fprintf (errstream, "%s: error in config file %s:%d:  error processing network/netmask\n",
										progname, upsinfo->conffile, cline);
							}
							errcount++;
						}
					}
				}
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "trigger")) {
			/*{{{  TRIGGER <name> "<field>" <comparison> <rhs>*/
			char *tname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *tfield = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *tcmp = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *trhs = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			openupsd_trigger_t *tmp = NULL;

			if (!trhs) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else {
				int ifield = -1;
				int icmp = -1;

				if (*tfield == '\"') {
					char *eh = (tfield + 1);

					if ((eh = strchr (eh, '\"')) != NULL) {
						*eh = '\0';
						ifield = decode_alarm_trigger (tfield + 1);
					}
				} else {
					ifield = decode_alarm_trigger (tfield);
				}
				icmp = decode_alarm_cmp (tcmp);
				if ((ifield >= 0) && (icmp >= 0)) {
					/* attempt to create a new trigger (handles the fudging of RHS) */
					tmp = makeaddtrigger (&trigs, tname, ifield, icmp, trhs);
				}
				if (!tmp) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  bad %s directive\n",
								progname, upsinfo->conffile, cline, ch);
					}
					errcount++;
				} else if (nextarg) {
					/* spurious rubbish */
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  ignoring rubbish at end of %s directive\n",
								progname, upsinfo->conffile, cline, ch);
					}
					errcount++;
				} /* else already in the linked list */
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "invalidate")) {
			/*{{{  INVALIDATE <this> WHEN <that>*/
			char *thisname = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *when = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *thatname = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (!thatname) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else {
				openupsd_trigger_t *thistrig, *thattrig;

				for (thistrig = trigs; thistrig; thistrig = thistrig->next) {
					if (!strcmp (thistrig->name, thisname)) {
						break;
					}
				}
				for (thattrig = trigs; thattrig; thattrig = thattrig->next) {
					if (!strcmp (thattrig->name, thatname)) {
						break;
					}
				}
				if (strcasecmp (when, "WHEN")) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  mangled trigger definition\n",
								progname, upsinfo->conffile, cline);
					}
					errcount++;
				} else if (!thistrig || !thattrig) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  trigger %s not found\n",
								progname, upsinfo->conffile, cline, thattrig ? thisname : thatname);
					}
					errcount++;
				} else {
					/* add "thattrig" to invalidators for "thistrig", and pop in linked list (invds) */
					openupsd_inv_t *tmp;

					tmp = (openupsd_inv_t *)smalloc (sizeof (openupsd_inv_t));
					tmp->next = tmp->prev = NULL;
					tmp->when = thattrig;
					tmp->trash = thistrig;
					dynarray_maybeadd (thattrig->inv, tmp);

					if (invds) {
						invds->prev = tmp;
						tmp->next = invds;
					}
					invds = tmp;
				}
			}
			/*}}}*/
		} else if (!strcasecmp (ch, "alarm")) {
			/*{{{  ALARM <names> <trigger> [INITIAL] [AFTER delay] <action> */
			char *dnames = nextarg ? getnextarg (&dh, &nextarg) : NULL;
			char *trigger = nextarg ? getnextarg (&dh, &nextarg) : NULL;

			if (!trigger) {
				if (errstream) {
					fprintf (errstream, "%s: error in config file %s:%d:  %s requires more arguments\n",
							progname, upsinfo->conffile, cline, ch);
				}
				errcount++;
			} else {
				char *aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				char *delay = NULL;
				openupsd_alarm_t *tmp = NULL;
				int ialrm = 0;
				openupsd_trigger_t *trig = NULL;

				/* locate trigger */
				trig = openupsd_find_trigger (trigger, trigs);

				/* aa is either INITIAL, AFTER or <action>.  (in that order) */
				if (aa && !strcasecmp (aa, "INITIAL")) {
					aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
					ialrm = 1;
				}
				if (aa && nextarg && !strcasecmp (aa, "AFTER")) {
					delay = getnextarg (&dh, &nextarg);
					aa = nextarg ? getnextarg (&dh, &nextarg) : NULL;
				}
				/* aa is action, or NULL */
				if (!aa) {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  error processing %s directive\n",
								progname, upsinfo->conffile, cline, ch);
					}
					errcount++;
				} else if (!strcasecmp (aa, "LOG")) {
					tmp = makelogalarm (trig, delay);

					if (!tmp) {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  %s %s error\n",
									progname, upsinfo->conffile, cline, ch, aa);
						}
						errcount++;
					}
				} else if (!strcasecmp (aa, "EXEC")) {
					tmp = makeexecalarm (trig, delay);

					if (!nextarg) {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  %s %s requires more arguments\n",
									progname, upsinfo->conffile, cline, ch, aa);
						}
						errcount++;
					} else if (!tmp) {
						if (errstream) {
							fprintf (errstream, "%s: error in config file %s:%d:  %s %s error\n",
									progname, upsinfo->conffile, cline, ch, aa);
						}
						errcount++;
					} else {
						char *arg = getnextarg (&dh, &nextarg);
						char *copy;
						openupsd_exec_t *texec = (openupsd_exec_t *)(tmp->xdata);

						texec->prev = texec->next = NULL;
						texec->alarm = tmp;
						texec->path_to_run = string_dup (arg);
						copy = string_dup (arg);
						dynarray_add (texec->args, copy);

						while (nextarg) {
							arg = getnextarg (&dh, &nextarg);
							copy = string_dup (arg);
							dynarray_add (texec->args, copy);
						}
					}
				} else {
					if (errstream) {
						fprintf (errstream, "%s: error in config file %s:%d:  unknown %s %s\n",
								progname, upsinfo->conffile, cline, ch, aa);
					}
					errcount++;
				}

				if (tmp) {
					/* add it to the list of alarms and link into named devices */
					tmp->initial = ialrm;
					if (!alarms) {
						alarms = tmp;
					} else {
						alarms->prev = tmp;
						tmp->next = alarms;
						alarms = tmp;
					}
					linkalarm (tmp, dnames, sdevlist, netclis, errstream);
				}
			}
			/*}}}*/
		} else {
			/*{{{  anything else*/
			if (errstream) {
				fprintf (errstream, "%s: error in config file %s:%d:  unknown directive: %s\n",
						progname, upsinfo->conffile, cline, ch);
			}
			errcount++;
			/*}}}*/
		}


	}
	fclose (fp);
	upsinfo->sdev = sdevlist;
	upsinfo->salarms = alarms;
	upsinfo->netcli = netclis;
	upsinfo->trigs = trigs;
	upsinfo->invds = invds;
	return errcount;
}
/*}}}*/




syntax highlighted by Code2HTML, v. 0.9.1