/* * cfg.c -- config file handling for openupsd * Copyright (C) 2003 Fred Barnes * * 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 #include #include #include #include #include #include #include #include #include #include #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': 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 */ /* 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 */ /* 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 : [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 [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 "" */ 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 WHEN */ 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 [INITIAL] [AFTER delay] */ 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 . (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; } /*}}}*/