/**********************************************************************
 * powstatd: a configurable UPS monitor.
 * Copyright (C) 1999-2001 The University of Iowa
 * Author: Alberto Maria Segre
 *         segre@cs.uiowa.edu
 *         S378 Pappajohn Building
 *         The University of Iowa
 *         Iowa City, IA  52242-1000
 *
 * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 **********************************************************************/

#include <stdio.h>		/* Needed for fprintf */
#include <stdlib.h>
#include <stdarg.h>		/* Needed for va_list */
#include <fcntl.h>		/* Needed for open */
#include <unistd.h>		/* Needed for unbuffered write */
#include <string.h>
#include <time.h>		/* Needed for time(). */
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#ifdef DEBUG
#include <arpa/inet.h>		/* Needed for inet_ntoa when debugging. */
#endif
#include <netdb.h>
#include <signal.h>
#include <syslog.h>
#include <errno.h>

#ifndef NULL
#define NULL 0
#endif

#ifndef FALSE
#define FALSE 0
#define TRUE !FALSE
#endif

#ifndef ERROR
#define ERROR -1
#endif

#ifndef MAX
#define MAX(m,n) ((m)>=(n)?(m):(n))
#endif

#ifdef SECURE
/* Time stamp (as "seconds after 1970"). */
#ifdef ALPHA
#define TSTAMP() ((unsigned int) time (NULL))
#else
#define TSTAMP() ((unsigned long int) time (NULL))
#endif
/* Maximum acceptable difference in master/slave clocks, in seconds. */
#define MAXCLOCKDRIFT 5
#endif

/* Some defaults. */
#define TESTINT 1		/* Test UPS polling interval, in seconds. */
#define POLLINT 10		/* UPS polling interval, in seconds. */
#define POLLCNT 5		/* State change debouncing counter. */
#define KILLINT 10		/* UPS kill interval, in seconds. */
#define KILLWAIT 30		/* UPS master delay at kill. */
#define PPORT 1111		/* Port to watch for UPS information. */
#define MAXLINELEN 128		/* Configuration file maximum line length. */
#ifdef SECURE
#define MAXSTATLEN 56		/* Max length at least 2xULONG_MAX+1 digits. */
#else
#define MAXSTATLEN 8		/* Max chars in {POK,PLOW,PFAIL}. */
#endif

/* Defines for UPS status. */
#define POK 0
#define PFAIL 1
#define PLOW 2
#define PSHOW(s) ((s==POK)?"OK":(s==PFAIL)?"FAIL":(s==PLOW)?"LOW":"ERROR")

/* Defines for UPS mode. */
#define MASTER 0
#define SLAVE 1
#define SOLO 2
#define MSHOW(s) ((s==MASTER)?"master":(s==SLAVE)?"slave":(s==SOLO)?"standalone":"ERROR")

/* Slaves. */
#define MAXSLAVES 2		/* No more than 2 machines on one UPS. */
int nslaves = 0;		/* Number of "extra" machines on this UPS. */
char slaves[MAXSLAVES][MAXLINELEN + 1];		/* Slave names. */

/* Master/slave Internet host data. Let hosts[MASTER] represent the
 * master, if any, and hosts[1] through hosts[MAXSLAVES+1] represent
 * the slaves, if any. */
#define IPADDRLEN 4	/* IPv4 requires 32 bits. */
typedef struct host
  {
    char address[IPADDRLEN];
    int length;		/* Length of the address. */
  }
Host;
Host hosts[MAXSLAVES + 1];

/* Global configuration variables. */
int testMode = FALSE;		/* Test mode. */
int killMode = FALSE;		/* Kill mode. */
uint failmask = 0, failtest = 0;
uint battmask = 0, batttest = 0;
uint setmask = ~0, setbits = 0;
uint killmask = ~0, killbits = 0;

/* Some prototypes. */
#ifdef SECURE
#ifdef ALPHA
void readConfigFile (char *monitor, int *mode, unsigned int *key);
#else
void readConfigFile (char *monitor, int *mode, unsigned long int *key);
#endif
#else
void readConfigFile (char *monitor, int *mode);
#endif
void daemonize ();
int openSock (unsigned short port);
int initLine (char *device);
int readLine (int line);
#ifdef SECURE
#ifdef ALPHA
int readSock (int socket, unsigned int *key);
void tellSlaves (int socket, unsigned short int port,
		 int status, unsigned int *key);
#else
int readSock (int socket, unsigned long int *key);
void tellSlaves (int socket, unsigned short int port,
		 int status, unsigned long int *key);
#endif
#else
int readSock (int socket);
void tellSlaves (int socket, unsigned short port, int status);
#endif
#ifdef SECURE
#ifdef ALPHA
void encipher(unsigned *const v,
	      unsigned *const w,
	      const unsigned *const k);
void decipher(unsigned *const v,
	      unsigned *const w,
	      const unsigned *const k);
#else
void encipher(unsigned long *const v,
	      unsigned long *const w,
	      const unsigned long *const k);
void decipher(unsigned long *const v,
	      unsigned long *const w,
	      const unsigned long *const k);
#endif
#endif

/**********************************************************************
 * UPS monitor.
 *
 * Configured mostly from the configuration file, which is usually
 * found as /etc/powstatd.conf.
 *
 * Only two command line options are supported:
 *    -t: test mode; don't turn into daemon, don't actually signal init.
 *    -k: kill mode; send kill signal to turn off UPS.
 **********************************************************************/
int
main (int argc, char *argv[])
{
  int i;
  int mode = ERROR;		/* UPS mode. */

  char monitor[MAXLINELEN + 1];	/* Device to monitor. */
  int line;			/* Line to monitor. */
  int socket;			/* Socket to monitor (slave), send (master). */

  unsigned int bits;		/* For serial communication. */

  int lastStatus = POK;		/* UPS status. */
  int newStatus;
  int bounceCnt;		/* Debouncing counter. */

  char sfile[MAXLINELEN + 1] = STATFILE;	/* Usually /etc/powerstatus. */
  FILE *sfp;

#ifdef SECURE
#ifdef ALPHA
  unsigned int key[5];		/* 128 bits of password; key[4] for \0. */
#else
  unsigned long int key[5];	/* 128 bits of password; key[4] for \0. */
#endif
#endif

  /* Open system logfile for output. Tag output as coming from current
   * program and indicate the PID of the process as well. */
  if (!testMode)
    openlog (argv[0], LOG_PID, LOG_DAEMON);

  /* Parse input arguments. */
  for (i = 1; i < argc; i++)
    {
      if (strncmp (argv[i], "-h", 2) == 0)
	{
	  printf ("Usage: %s [ -t | -k ]\n", argv[0]);
	  printf ("where -t : test mode\n");
	  printf ("      -k : kill mode\n");
	  printf ("Unless -t or -k are specified, %s runs as daemon.\n", argv[0]);
	  exit (ERROR);
	}
      else if (strncmp (argv[i], "-t", 2) == 0)
	/* test mode. */
	testMode = TRUE;
      else if (strncmp (argv[i], "-k", 2) == 0)
	/* kill mode. */
	killMode = TRUE;
      else
	{
	  if (!testMode)
	    syslog (LOG_ERR, "unrecognized argument %s; quitting.\n",
		    argv[i]);
	  else
	    printf ("powstatd: unrecognized argument %s; quitting.\n",
		    argv[i]);
	  exit (ERROR);
	}
    }

#ifdef SECURE
  /* If you're running a secure system, make sure the key is initially
   * zero filled. */
#ifdef ALPHA
  memset (key, 0, (5 * sizeof (unsigned int)));
#else
  memset (key, 0, (5 * sizeof (unsigned long int)));
#endif
#endif

  /* Read configuration file. Testing of the serial line is actually
   * carried out at the bit level. The configuration file is used to
   * set up the appropriate bit conditions which can then be used to
   * detect the power supply's condition.  */
#ifdef SECURE
  readConfigFile (monitor, &mode, key);
#else
  readConfigFile (monitor, &mode);
#endif
  if (mode == ERROR)
    {
      if (!testMode)
	syslog (LOG_ERR, "no UPS to watch; quitting.\n");
      else
	printf ("powstatd: no UPS to watch; quitting.\n");
      exit (ERROR);
    }
#ifdef SECURE
  else if ((mode != SOLO) &&
	   (key[0] == 0 && key[1] == 0 && key[2] == 0 && key[3] == 0))
    {
      if (!testMode)
	syslog (LOG_ERR, "missing password; quitting.\n");
      else
	printf ("powstatd: missing password; quitting.\n");
      exit (ERROR);
    }
#endif

  /* Slaves are not responsible for killing their power supplies, so
   * just exit. */
  if (mode == SLAVE && killMode)
    exit (EXIT_FAILURE);

  /* Turn yourself into a daemon. */
  if (!(testMode || killMode))
    daemonize ();

  /* Establish port slaves watch for incoming reports, and the master
   * uses to broadcast status information. If you can't get the port,
   * abort (we're assuming another powstatd process is running and has
   * already grabbed the port; only one powstatd process is
   * allowed). Grabbing the port is always attempted, as this is a
   * convenient mechanism to enforce the one CPU/one powstatd
   * constraint. If we are slaves, then this will actually be the
   * device we monitor for status information broadcast via UDP from
   * our master (through this socket). Solo machines simply ignore the
   * socket. */
  if ((socket = openSock (PPORT)) == ERROR)
    {
      if (!testMode)
	syslog (LOG_ERR, "socket %d unavailable; quitting.\n", PPORT);
      else
	printf ("powstatd: socket %d unavailable; quitting.\n", PPORT);
      exit (ERROR);
    }

  /* Get rid of any stray status file that may confuse init. */
  unlink (sfile);

  /* Set up your connection to your UPS via serial line (if standalone
   * or master). If you are a slave, then the socket you will be
   * watching has already been opened. */
  if (mode != SLAVE)
    line = initLine (monitor);

  /* All systems go. */
  if (!killMode)
    {
      if (!testMode)
	syslog (LOG_NOTICE, "online (%s), watching %s.\n", MSHOW (mode), monitor);
      else
	printf ("powstatd: online (%s), watching %s.\n", MSHOW (mode), monitor);
    }

  /* Start monitoring loop; but first print out headers if you are in
   * testMode. */
  if (testMode && mode != SLAVE)
    printf ("CTS DSR DCD RNG   DTR RTS   STATUS\n");
  else if (testMode && mode == SLAVE)
    printf ("STATUS\n");
  while (TRUE)
    {
      /* Get new status. If you're a slave reading from a socket and
       * you get nonsense back, try again (don't worry, it blocks if
       * there's nothing to process). */
      if (mode == SLAVE)
	{
#ifdef SECURE
	  while ((newStatus = readSock (socket, key)) == ERROR);
#else
	  while ((newStatus = readSock (socket)) == ERROR);
#endif
	}
      else
	newStatus = readLine (line);

      /* To insulate ourselves from power glitches, we require that a
       * new status persist POLLCNT samples (using shortened sampling
       * interval TESTINT) before certifying the state change. Slave
       * machines needn't debounce, of course, since their master will
       * have debounced before issuing a change in status. */
      if (mode != SLAVE)
	{
	  bounceCnt = POLLCNT;
	  while ((lastStatus != newStatus) && bounceCnt-- > 0)
	    {
	      newStatus = readLine (line);
	      /* Sleep for reduced polling interval. */
	      sleep (TESTINT);
	    }
	}

      /* Service kill mode, then exit. Need to do this after
       * debouncing, because sometimes startup effects can cause a
       * perfectly good UPS to appear to be failing. */
      if (killMode)
	{
	  if (newStatus != POK && mode != SLAVE)
	    {
	      if (!testMode)
		{
		  syslog (LOG_NOTICE, "killing UPS.\n");
		  closelog ();
		}
	      else
		printf ("powstatd: killing UPS.\n");

	      /* If you have slave machines, wait a prespecified
	       * interval to ensure they catch up before yanking the
	       * UPS out from under them. */
	      if (nslaves > 0)
		sleep (KILLWAIT);

	      if (killmask == ~0)
		{
		  /* This is the default case when killmask is all
		   * 1's, indicating the UPS is expecting you to send
		   * a break signal (for KILLINT/10 seconds) along on
		   * TxD instead of signaling on another
		   * line. Wouldn't normally be my choice for default
		   * behavior, as its use seems confined to some older
		   * APC units with homebrew cabling, but it should be
		   * harmless enough.*/
		  ioctl (line, TCSBRKP, KILLINT);
		  if (testMode)
		    printf ("[sending BREAK signal]\n");
		}
	      else
		{
		  /* Some killbits have been specified in the
		   * configuration file: drop them momentarily. Make
		   * sure you hold them down long enough for the UPS
		   * to notice. */
		  ioctl (line, TIOCMGET, &bits);
		  bits = (bits & killmask) | (~killbits & ~killmask);
		  if (testMode)
		    printf (" %i   %i   %i   %i     %i   %i    DROP\n",
			    (bits & TIOCM_CTS) != 0,
			    (bits & TIOCM_DSR) != 0,
			    (bits & TIOCM_CD) != 0,
			    (bits & TIOCM_RNG) != 0,
			    (bits & TIOCM_DTR) != 0,
			    (bits & TIOCM_RTS) != 0);
		  ioctl (line, TIOCMSET, &bits);
		  sleep (TESTINT);
		  
		  /* Now set the killbits. */
		  ioctl (line, TIOCMGET, &bits);
		  bits = (bits & killmask) | killbits;
		  if (testMode)
		    printf (" %i   %i   %i   %i     %i   %i    KILL\n",
			    (bits & TIOCM_CTS) != 0,
			    (bits & TIOCM_DSR) != 0,
			    (bits & TIOCM_CD) != 0,
			    (bits & TIOCM_RNG) != 0,
			    (bits & TIOCM_DTR) != 0,
			    (bits & TIOCM_RTS) != 0);
		  ioctl (line, TIOCMSET, &bits);
		  sleep (KILLINT);
#if FALSE
		  /* Finally, clear the killbits again. Probably isn't 
		   * needed, as the UPS should have already understood. */
		  ioctl (line, TIOCMGET, &bits);
		  bits = (bits & killmask) | (~killbits & ~killmask);
		  if (testMode)
		    printf (" %i   %i   %i   %i     %i   %i    DEAD\n",
			    (bits & TIOCM_CTS) != 0,
			    (bits & TIOCM_DSR) != 0,
			    (bits & TIOCM_CD) != 0,
			    (bits & TIOCM_RNG) != 0,
			    (bits & TIOCM_DTR) != 0,
			    (bits & TIOCM_RTS) != 0);
		  ioctl (line, TIOCMSET, &bits);
		  sleep (TESTINT);
#endif
		}

	      /* If you're in test mode, watch the output forever. */
	      while (testMode)
		{
		  ioctl (line, TIOCMGET, &bits);
		  printf (" %i   %i   %i   %i     %i   %i    DEAD\n",
			  (bits & TIOCM_CTS) != 0,
			  (bits & TIOCM_DSR) != 0,
			  (bits & TIOCM_CD) != 0,
			  (bits & TIOCM_RNG) != 0,
			  (bits & TIOCM_DTR) != 0,
			  (bits & TIOCM_RTS) != 0);
		  sleep (TESTINT);
		}
	      exit (EXIT_SUCCESS);
	    }
	  else if (!testMode)
	    {
	      syslog (LOG_NOTICE, "ignoring kill signal.\n");
	      closelog ();
	      exit (EXIT_FAILURE);
	    }
	  else
	    {
	      printf ("powstatd: ignoring kill signal.\n");
	      exit (EXIT_FAILURE);
	    }
	  /* Should never reach here... */
	  exit (ERROR);
	}

      /* OK, status has in fact changed (and survived debouncing). */
      if (lastStatus != newStatus)
	{
	  /* Accept state change and enter change into logfile. */
	  if (newStatus == POK)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "power restored.\n");
	      else
		printf ("powstatd: power restored.\n");
	    }
	  else if (newStatus == PFAIL)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "power is failing.\n");
	      else
		printf ("powstatd: power is failing.\n");
	    }
	  else if (newStatus == PLOW)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "low battery.\n");
	      else
		printf ("powstatd: low battery.\n");
	    }

	  /* Next write appropriate status indicator to status file
	   * (usually /etc/powerstatus) and then signal init. Note
	   * that init must already know where to look! Also, if you
	   * are running in test mode, don't actually signal init. */
	  if (!testMode)
	    {
	      sfp = fopen (sfile, "w");
	      fprintf (sfp, "%s", PSHOW (newStatus));
	      fclose (sfp);
	      kill (1, SIGPWR);
	    }

	  /* Retain new status as current status. */
	  lastStatus = newStatus;
	}

      /* Broadcast your status, then sleep for the polling
       * interval. */
#ifdef SECURE
      if (mode == MASTER)
	tellSlaves (socket, PPORT, lastStatus, key);
#else
      if (mode == MASTER)
	tellSlaves (socket, PPORT, lastStatus);
#endif
      sleep (testMode ? TESTINT : POLLINT);
    }

  /* Should never execute. */
  return (ERROR);
}

/**********************************************************************
 * Read in configuration file. We have two basic tasks to
 * accomplish. First, determine our mode (solo, master, or slave),
 * that is, if we are to watch a serial line (and which serial line)
 * or instead prod another machine for status. Second, if we are
 * monitoring a serial line, what bits correspond to OK, FAIL, LOW,
 * and KILL. We return the mode of the daemon as UNKNOWN, SOLO,
 * MASTER, or SLAVE.
 **********************************************************************/
#ifdef SECURE
#ifdef ALPHA
void
readConfigFile (char *monitor, int *mode, unsigned int *key)
#else
void
readConfigFile (char *monitor, int *mode, unsigned long int *key)
#endif
#else
void
readConfigFile (char *monitor, int *mode)
#endif
{
  char filename[MAXLINELEN + 1] = CONFIG;
  char line[MAXLINELEN + 1];
  char temp[MAXLINELEN + 1];
  struct hostent *temphost;
  int n;
  FILE *cfp;
#ifdef SECURE
  struct stat fstatus;
  char *kbuffer;
#endif

#ifdef SECURE
  /* Make sure the configuration file is available, else punt. Note
   * that the secure version requires we first stat the file, so the
   * error given when configuration file is not found will differ from
   * the non-secure version, which won't fail until it attempts to
   * open the configuration file for reading. */
  if (stat (filename, &fstatus) != 0)
    {
      if (!testMode)
	syslog (LOG_ERR, "can't stat configuration file (%d); quitting.\n",
		errno);
      else
	printf ("powstatd: can't stat configuration file (%d); quitting.\n",
		errno);
      exit (ERROR);
    }
  else if ((fstatus.st_mode & S_IRGRP) ||
	   (fstatus.st_mode & S_IWGRP) ||
	   (fstatus.st_mode & S_IXGRP) ||
	   (fstatus.st_mode & S_IROTH) ||
	   (fstatus.st_mode & S_IWOTH) ||
	   (fstatus.st_mode & S_IXOTH))
    {
      /* As a security precaution, ensure that the configuration file
       * has appropriate permissions set. If not, issue a message and
       * punt, as the password could be compromised. */
      if (!testMode)
	syslog (LOG_ERR, "configuration file permission error (must be go-rwx); quitting.\n");
      else
	printf ("powstatd: configuration file permission error (must be go-rwx); quitting.\n");
      exit (ERROR);
    }
#endif

  /* Open the configuration file. If you can't find it, punt. */
  if (!(cfp = fopen (filename, "r")))
    {
      if (!testMode)
	syslog (LOG_ERR, "can't open %s for read; quitting.\n",
		filename);
      else
	printf ("powstatd: can't open %s for read; quitting.\n",
		filename);
      exit (ERROR);
    }

  /* While not EOF, read in the configuration file line by line. */
  while (fgets (line, MAXLINELEN, cfp))
    {
      if ((strlen (line) == strspn (line, " \t\n\r")) ||
	  (strncmp (&line[strspn (line, " \t")], "#", 1) == 0))
	{
	  /* Flush blank lines or lines starting with the comment
	   * character (ignore tabs and spaces before the #). */
	  continue;
	}
      else if (sscanf (line, "watch %s", temp) == 1)
	{
	  /* What to watch: serial line or master host? */
	  if (*mode == ERROR)
	    {
	      if ((snprintf (monitor, MAXLINELEN, "/dev/%s", temp) > 0) &&
		  (access (monitor, R_OK) != ERROR))
		{
		  /* OK, you're supposed to watch the serial line. We'll
		   * assume you're in stand alone mode unless and until we
		   * encounter any slave definitions. */
		  *mode = SOLO;
		}
	      else if (access (monitor, F_OK) != ERROR)
		{
		  /* Tried to specify a device to monitor for which
		   * you do not have access. Generate an error, but
		   * keep looking just in case there is some
		   * redundancy in the configuration file. */
		  if (!testMode)
		    syslog (LOG_NOTICE, "can't watch %s; access denied.\n",
			    monitor);
		  else
		    printf ("powstatd: can't watch %s; access denied.\n",
			    monitor);
		}
	      else if ((temphost = gethostbyname (temp)) != NULL)
		{
		  /* This is an internet host, so we must be a slave. */
		  *mode = SLAVE;
		  /* Copy master host name into monitor string. */
		  snprintf (monitor, MAXLINELEN, "%s", temp);
		  /* Set up the master host record. We can't rely on
		   * the hostent returned by gethostbyname, since its
		   * contents will change if gethostbyname is
		   * reinvoked. */
		  memcpy (&hosts[MASTER].address,			  
			  temphost->h_addr,
			  temphost->h_length);
		  hosts[MASTER].length = temphost->h_length;
#ifdef DEBUG
		  if (testMode)
		    printf ("powstatd: slave of %s\n",
			    inet_ntoa(*((struct in_addr *)temphost->h_addr)));
#endif
		}
	      else if (!testMode)
		syslog (LOG_NOTICE, "can't watch %s; no such host.\n",
			temp);
	      else
		printf ("powstatd: can't watch %s; no such host.\n",
			temp);
	    }
	  else
	    {
	      /* This is the second "watch" line encountered. Issue a
	       * warning and ignore it. */
	      if (!testMode)
		syslog (LOG_NOTICE, "multiple `watch' statments, ignoring %s.\n",
			temp);
	      else
		printf ("powstatd: multiple `watch' statements, ignoring %s.\n",
			temp);
	    }
	  continue;
	}
      else if (sscanf (line, "slave %s", temp) == 1)
	{
	  if (*mode == ERROR)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "no line to watch yet; ignoring %s.\n",
			temp);
	      else
		printf ("powstatd: no line to watch yet; ignoring %s.\n",
			temp);
	    }
	  else if (*mode == SLAVE)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "slave can't watch serial line; ignoring %s.\n",
			temp);
	      else
		printf ("powstatd: slave can't watch serial line; ignoring %s.\n",
			temp);
	    }
	  else if (nslaves >= MAXSLAVES)
	    {
	      if (!testMode)
		syslog (LOG_NOTICE, "too many slaves; ignoring %s.\n",
			temp);
	      else
		printf ("powstatd: too many slaves; ignoring %s.\n",
			temp);
	    }
	  else if ((temphost = gethostbyname (temp)) != NULL)
	    {
	      /* Specifying a legal slave. There can be at most MAXSLAVES
	       * of these. */
	      *mode = MASTER;
	      /* Copy slave host name into appropriate slave string. */
	      snprintf (slaves[nslaves], MAXLINELEN, "%s", temp);
	      /* Increment slave count; note slaves[] is 0-based while
	       * hosts[] is 1-based as far as slaves are concerned. */
	      nslaves++;
	      /* Set up the slave host record. We can't rely on the
	       * hostent returned by gethostbyname, since its contents
	       * will change if gethostbyname is reinvoked. */
	      memcpy (&hosts[nslaves].address,
		      temphost->h_addr,
		      temphost->h_length);
	      hosts[nslaves].length = temphost->h_length;
#ifdef DEBUG
	      if (testMode)
		printf ("powstatd: master of %s\n",
			inet_ntoa(*((struct in_addr *)temphost->h_addr)));
#endif
	    }
	  else if (!testMode)
	    syslog (LOG_ERR, "can't find slave %s; no such host.\n",
		    temp);
	  else
	    printf ("powstatd: can't find slave %s; no such host.\n",
		    temp);
	  continue;
	}
#ifdef SECURE
      else if (sscanf (line, "password %s", temp) == 1)
	{
	  /* Copy the first 128 bits of the password into the global
	   * key variable, key[0] through key[4]. Note that key[5]
	   * exists only so that the trailing '\0' has someplace to
	   * go; we never really treat the password as a string after
	   * this point. */
	  kbuffer = (char *) &key[0];
	  snprintf (kbuffer, 17, "%s", temp);
	}
#endif
      else if (sscanf (line, "fail %s %d", temp, &n) == 2)
	{
	  /* Power failure line and condition. */
	  if (strncmp (temp, "cts", 3) == 0)
	    {
	      failtest = ((n == 0) ? TIOCM_CTS : 0);
	      failmask = TIOCM_CTS;
	    }
	  else if (strncmp (temp, "dsr", 3) == 0)
	    {
	      failtest = ((n == 0) ? TIOCM_DSR : 0);
	      failmask = TIOCM_DSR;
	    }
	  else if (strncmp (temp, "dcd", 3) == 0)
	    {
	      failtest = ((n == 0) ? TIOCM_CD : 0);
	      failmask = TIOCM_CD;
	    }
	  else if (strncmp (temp, "rng", 3) == 0)
	    {
	      failtest = ((n == 0) ? TIOCM_RNG : 0);
	      failmask = TIOCM_RNG;
	    }
	  else if (!testMode)
	    syslog (LOG_ERR, "ignoring bad `fail' specification %s.\n",
		    temp);
	  else
	    printf ("powstatd: ignoring bad `fail' specification %s.\n",
		    temp);
	  continue;
	}
      else if (sscanf (line, "low %s %d", temp, &n) == 2)
	{
	  /* Low battery line and condition. */
	  if (strncmp (temp, "cts", 3) == 0)
	    {
	      batttest = ((n == 0) ? TIOCM_CTS : 0);
	      battmask = TIOCM_CTS;
	    }
	  else if (strncmp (temp, "dsr", 3) == 0)
	    {
	      batttest = ((n == 0) ? TIOCM_DSR : 0);
	      battmask = TIOCM_DSR;
	    }
	  else if (strncmp (temp, "dcd", 3) == 0)
	    {
	      batttest = ((n == 0) ? TIOCM_CD : 0);
	      battmask = TIOCM_CD;
	    }
	  else if (strncmp (temp, "rng", 3) == 0)
	    {
	      batttest = ((n == 0) ? TIOCM_RNG : 0);
	      battmask = TIOCM_RNG;
	    }
	  else if (!testMode)
	    syslog (LOG_ERR, "ignoring bad `low' specification %s.\n",
		    temp);
	  else
	    printf ("powstatd: ignoring bad `low' specification %s.\n",
		    temp);
	  continue;
	}
      else if (sscanf (line, "kill %s %d", temp, &n) == 2)
	{
	  /* UPS kill line and condition. */
	  if (strncmp (temp, "rts", 3) == 0)
	    {
	      killmask &= ~TIOCM_RTS;
	      killbits |= ((n == 1) ? TIOCM_RTS : 0);
	    }
	  else if (strncmp (temp, "dtr", 3) == 0)
	    {
	      killmask &= ~TIOCM_DTR;
	      killbits |= ((n == 1) ? TIOCM_DTR : 0);
	    }
	  else if (!testMode)
	    syslog (LOG_ERR, "ignoring bad `kill' specification %s.\n",
		    temp);
	  else
	    printf ("powstatd: ignoring bad `kill' specification %s.\n",
		    temp);
	  continue;
	}
      else if (strncmp (line, "kill break", 10) == 0)
	{
	  /* UPS kill line special case: send break on TxD. We'll
	   * indicate the special case by setting killmask to all 1's
	   * and killbits to 0, as if there were no kill bits in use
	   * at all. In effect, this makes sending the break signal
	   * the default behavior if no kill specification is given in
	   * the configuration file (should be harmless enough). */
	  killmask = ~0;
	  killbits = 0;
	  continue;
	}
      else if (sscanf (line, "init %s %d", temp, &n) == 2)
	{
	  /* Initialize line and condition. */
	  if (strncmp (temp, "rts", 3) == 0)
	    {
	      setmask &= ~TIOCM_RTS;
	      setbits |= ((n == 1) ? TIOCM_RTS : 0);
	    }
	  else if (strncmp (temp, "dtr", 3) == 0)
	    {
	      setmask &= ~TIOCM_DTR;
	      setbits |= ((n == 1) ? TIOCM_DTR : 0);
	    }
	  else if (!testMode)
	    syslog (LOG_ERR, "ignoring bad `init' specification %s.\n",
		    temp);
	  else
	    printf ("powstatd: ignoring bad `init' specification %s.\n",
		    temp);
	  continue;
	}
      else if (!testMode)
	syslog (LOG_ERR, "ignoring unrecognized configuration line `%s'.\n",
		line);
      else
	printf ("powstatd: ignoring unrecognized configuration line `%s'.\n",
		line);
    }
  /* Close config file and return. */
  fclose (cfp);
}

/**********************************************************************
 * Call this procedure to turn yourself into a daemon. See p418 of
 * Steven's book "Advanced Programming in the UNIX Environment" for
 * more details.
 **********************************************************************/
void
daemonize ()
{
  int pid;

  /* Fork a process and have parent exit. Returns control to the shell
   * if this was called from the shell, and also makes sure the daemon
   * is not a process group leader. */
  if ((pid = fork ()) < 0)
    {
      /* Oops! Can't fork. Abort. */
      syslog (LOG_ERR, "can't fork daemon process; quitting.\n");
      exit (ERROR);
    }
  else if (pid != 0)
    {
      /* Let the parent die. */
      exit (EXIT_SUCCESS);
    }
  else
    {
      /* Create a new session and become the session leader. */
      setsid ();

      /* Clear file mode creation mask. This allows daemon to open
       * files without any a priori constraints on file access. */
      umask (0);

      /* OK, we're done. */
      return;
    }
}

/**********************************************************************
 * Initialize serial line. Returns a file descriptor, suitable for ioctl.
 **********************************************************************/
int
initLine (char *device)
{
  int line;
  unsigned int bits;

  /* Open serial line. If unsuccessful, exit after logging the
   * error. */
  if ((line = open (device, O_RDWR | O_NDELAY)) == ERROR)
    {
      if (!testMode)
	syslog (LOG_ERR, "can't open %s; quitting.\n",
		device);
      else
	printf ("powstatd: can't open %s; quitting.\n",
		device);
      exit (ERROR);
    }

  /* Set initial conditions if any were specified. */
  if (setmask != ~0)
    {
      ioctl (line, TIOCMGET, &bits);
      bits = (bits & setmask) | setbits;
      ioctl (line, TIOCMSET, &bits);
    }

  /* Return file descriptor for open serial line. */
  return (line);
}

/**********************************************************************
 * Read UPS status. Return one of POK, PFAIL, or PLOW.
 **********************************************************************/
int
readLine (int line)
{
  unsigned int bits;
  int status = POK;

  /* Obtain bits from serial line. */
  ioctl (line, TIOCMGET, &bits);

  /* Interpret bits according to configuration. Note that the low
   * battery signal is orthogonal to power failure. If the power is
   * off, we want to signal PLOW, but if the power is on we want to
   * signal POK. */
  if ((bits & failmask) ^ failtest)
    {
      if ((bits & battmask) ^ batttest)
	status = PLOW;
      else
	status = PFAIL;
    }

  if (testMode)
    printf (" %i   %i   %i   %i     %i   %i    %s\n",
	    (bits & TIOCM_CTS) != 0,
	    (bits & TIOCM_DSR) != 0,
	    (bits & TIOCM_CD) != 0,
	    (bits & TIOCM_RNG) != 0,
	    (bits & TIOCM_DTR) != 0,
	    (bits & TIOCM_RTS) != 0,
	    PSHOW (status));

  /* Return status. */
  return (status);
}

/**********************************************************************
 * Open socket at a specified port address. Every daemon does this, if
 * only to ensure that only one daemon is running per machine. Solo
 * daemons then simply ignore the socket; slave daemons monitor the
 * socket for incoming UDP packets from their master, who uses the
 * socket to send its status to its slaves.
 **********************************************************************/
int
openSock (unsigned short port)
{
  int sock;
  struct sockaddr_in server;

  /* First make sure that the address structure is clear. */
  memset (&server, 0, sizeof (server));

  /* Create the socket. Note that we don't bother fetching the
   * protocol index number for UDP; instead, we use 0 as the third
   * argument on the assumption that UDP is indeed the only DGRAM
   * protocol available. */
  if ((sock = socket (AF_INET, SOCK_DGRAM, 0)) < 0)
    {
      if (!testMode)
	syslog (LOG_ERR, "can't create socket; quitting.\n");
      else
	printf ("powstatd: can't create socket; quitting.\n");
      exit (ERROR);
    }

  /* Set up the address structure and bind to the socket. */
  server.sin_family = AF_INET;
  /* Input address. */
  server.sin_addr.s_addr = INADDR_ANY;
  /* Port number. */
  server.sin_port = htons (port);

  /* We need to cast the second argument to bind to avoid a
   * warning. Note that we are relying on the fact that sockaddr and
   * sockaddr_in have the same size, which is enforced in the
   * appropriate system include files. */
  if (bind (sock, (struct sockaddr *) &server, sizeof (server)) == ERROR)
    {
      if (!testMode)
	syslog (LOG_ERR, "socket bind error; quitting.\n");
      else
	printf ("powstatd: socket bind error; quitting.\n");
      exit (ERROR);
    }

  /* Success. No need to put the socket in listen mode, since we'll
   * just be watching for UDP packets. */
  return (sock);
}

/**********************************************************************
 * Wait for a report from your master.
 **********************************************************************/
#ifdef SECURE
#ifdef ALPHA
int
readSock (int socket, unsigned int *key)
#else
int
readSock (int socket, unsigned long int *key)
#endif
#else
int
readSock (int socket)
#endif
{
  char message[MAXSTATLEN + 1];
  struct sockaddr_in masteraddr;
  socklen_t masterlen = sizeof (masteraddr);

#ifdef SECURE
#ifdef ALPHA
  /* If you're running on a DEC Alpha, unsigned longs will be 64
   * bits instead of 32 bits; use unsigned int instead. */
  unsigned int timenow;
  unsigned int plain[2], cipher[2];
#else
  /* Use 32 bit words for XTEA. */
  unsigned long int timenow;
  unsigned long int plain[2], cipher[2];
#endif
#endif

  /* First make sure that the address structure is clear. */
  memset (&masteraddr, 0, masterlen);

  /* Wait for a UDP packet (this blocks until you get one). For some
   * reason, MSG_WAITALL is not defined on linux systems; use 0
   * instead. The cast to (struct sockaddr *) is just to keep the
   * compiler from complaining. */
  if (recvfrom (socket, message, MAXSTATLEN, 0, 
		(struct sockaddr *) &masteraddr, &masterlen) >= 0)
    {
#ifndef SECURE
      /* OK, got a message. Make sure the address of the packet
       * received matches the address of your master. Note that this
       * may not work properly if your master has multiple NICs. */
#ifdef DEBUG
      if (testMode)
	printf ("Packet from %s", inet_ntoa(masteraddr.sin_addr));
#endif
      if (memcmp (&masteraddr.sin_addr,
		  &hosts[MASTER].address,
		  hosts[MASTER].length) != 0)
	{
#ifdef DEBUG
	  if (testMode)
	    printf (" -> rejected, %s != %s (%s)\n", 
		    inet_ntoa(masteraddr.sin_addr),
		    inet_ntoa(*((struct in_addr *) hosts[MASTER].address)),
		    message);
#endif
	  return (ERROR);
	}
#ifdef DEBUG
      /* Echo status to screen. */
      if (testMode)
	printf (" -> %s\n", message);
#else
      /* Echo status to screen. */
      if (testMode)
	printf ("%s\n", message);
#endif

      /* Parse the message string received and return appropriate
       * status. */
      if (strncmp (message, "OK", 2) == 0)
	return (POK);
      else if (strncmp (message, "FAIL", 4) == 0)
	return (PFAIL);
      else if (strncmp (message, "LOW", 3) == 0)
	return (PLOW);
#else
      /* OK, got a message. Don't worry too much about packet source
       * address since we'll rely on encryption for
       * authentication. Start by fetching current time for comparison. */
      timenow = TSTAMP();

      /* Now decrypt the message you received. */
#ifdef ALPHA
      sscanf (message, "%u:%u", &cipher[0], &cipher[1]);
#else
      sscanf (message, "%lu:%lu", &cipher[0], &cipher[1]);
#endif
      decipher (cipher, plain, key);

#ifdef DEBUG
      if (testMode)
	printf ("Packet from %s [abs(%lu-%lu)=%lu]",
		inet_ntoa(masteraddr.sin_addr),
		plain[1], timenow, 
		((plain[1]>timenow)?plain[1]-timenow:timenow-plain[1]));
#endif
      /* Compare timestamps. If you are within allowable clock drift,
       * accept the message, else exit and return an ERROR. Careful;
       * these are unsigned numbers, so taking abs of the difference
       * will not work as you might first think! */
      if (((plain[1]>timenow)?plain[1]-timenow:timenow-plain[1]) > 
	  MAXCLOCKDRIFT)
	{
	  if (!testMode)
	    syslog (LOG_NOTICE, "rejecting expired packet.\n");
	  else
#ifdef DEBUG
	    printf (" -> rejected\n");
#else
	    printf ("powstatd: rejecting expired packet.\n");
#endif
	  return (ERROR);
	}

      /* Make sure the given status is legal (i.e., one of POK, PLOW,
       * or PFAIL). Otherwise, exit and return ERROR. */
      if ((plain[0] != POK) && (plain[0] != PLOW) && (plain[0] != PFAIL))
	{
	  if (!testMode)
	    syslog (LOG_NOTICE, "ignoring illformed packet.\n");
	  else
#ifdef DEBUG
	    printf (" -> ignored\n");
#else
	    printf ("powstatd: ignoring illformed packet.\n");
#endif
	  return (ERROR);
	}

#ifdef DEBUG
      /* Echo status to screen. */
      if (testMode)
	printf (" -> %s\n", PSHOW(plain[0]));
#else
      /* Echo status to screen. */
      if (testMode)
	printf ("%s\n", PSHOW(plain[0]));
#endif

      /* Return the status; make sure you cast plain[0] to int when
       * returning to avoid compiler complaints. */
      return ((int) plain[0]);
#endif
    }
  return (ERROR);
}

/**********************************************************************
 * Send your status to your slaves.
 **********************************************************************/
#ifdef SECURE
#ifdef ALPHA
void
tellSlaves (int socket, unsigned short port, int status, unsigned int *key)
#else
void
tellSlaves (int socket, unsigned short port, int status, unsigned long int *key)
#endif
#else
void
tellSlaves (int socket, unsigned short port, int status)
#endif
{
  int i;
  char message[MAXSTATLEN + 1];
  struct sockaddr_in slaveaddr;

#ifdef SECURE
#ifdef ALPHA
  /* If you're running on a DEC Alpha, unsigned longs will be 64
   * bits instead of 32 bits; use unsigned int instead. */
  unsigned int plain[2], cipher[2];
#else
  /* Use 32 bit words for XTEA. */
  unsigned long int plain[2], cipher[2];
#endif
#endif

  /* Make sure that the address structure is initially clear. */
  memset (&slaveaddr, 0, sizeof (slaveaddr));
  /* Set the unchanging parts of the address structure. */
  slaveaddr.sin_family = AF_INET;
  slaveaddr.sin_port = htons (port);

#ifdef SECURE
  /* Write your status and the current timestamp to plain text
   * variables. */
  plain[0] = status;
  plain[1] = TSTAMP ();

  /* Encrypt the message. */
  encipher (plain, cipher, key);

  /* Write your encrypted status and timestamp to the message
   * buffer. */
#ifdef ALPHA
  snprintf (message, MAXSTATLEN, "%u:%u", cipher[0], cipher[1]);
#else
  snprintf (message, MAXSTATLEN, "%lu:%lu", cipher[0], cipher[1]);
#endif
#else
  /* Write your status to the message buffer. */
  snprintf (message, MAXSTATLEN, "%s", PSHOW (status));
#endif

  /* Loop through the slaves and send one packet to each. */
  for (i = 0; i < nslaves; i++)
    {
      /* Set up the host part of the address structure. */
      memcpy (&slaveaddr.sin_addr,
	      &hosts[i + 1].address,
	      hosts[i + 1].length);

      /* Send the message. Only local errors will be caught; there is
       * no confirmation of actual receipt from slave. The cast to
       * (struct sockaddr *) is just to keep the compiler from
       * complaining. */
      if (sendto (socket, message, sizeof (message), 0,
		  (struct sockaddr *) &slaveaddr, 
		  sizeof (slaveaddr)) == ERROR)
	{
	  if (!testMode)
	    syslog (LOG_NOTICE, "can't inform %s [%d].\n", slaves[i], errno);
	  else
	    printf ("powstatd: can't inform %s [%d].\n", slaves[i], errno);
	}
    }
}
