/*    bcnud.c,v 1.21 2000/04/10 10:38:42 jap Exp

 *    Copyright (c) 1998 John Phillips
 *
 *    You may distribute under the terms of the Artistic License
 *
 */


#include "bcnu.h"

char *id = "bcnud";
char *subver = "1.21";

int bcnu_parse (char *buf, int len); 
int bcnu_process (char *hostname); 
int bcnu_write_file (char *hostname); 

int s;				/* connected socket descriptor */
int ls;				/* listen socket descriptor */

struct bcnu_agent_def bcnud_agent[BCNU_MAX_AGENTS];
struct bcnu_msg_def bcnud_in;


char buf[BCNU_MESS + 1], *bptr;
char response[BCNU_MESS + 1];

int dosyslog = 1;		/* log to syslog */

int port = 0;
char progname[80];		/* our name */
char master[80];		/* hostname of master */
int masterok = 0;		/* It is my master */
int agentlog = 0;		/* log agent startup */
int gap = BCNU_GAP;
int fqdn = 0;
int errorlevel = BCNU_ALERT;
char *hostname;			/* hostname of connection */

struct hostent *hp;		/* pointer to host info for remote host */
struct servent *sp;		/* pointer to service information */

long timevar;			/* contains time returned by time() */
char *ctime ();			/* declare time formatting routine */

long startup;
char rettime[80], starttime[80];

struct linger linger;		/* allow a lingering, graceful close; */
				/* used when setting SO_LINGER */

char usage[] = "\n\
        -s port		the tcp service port no to use\n\
        -e error level	log alerts for faults >= than this\n\
        -d		witch on debug flag\n\
        -q		show FQDN if supplied\n\
	-v		show version info\n\
	-a		log agent info\n\
	-g		gap between scheduling agents at startup\n\
        master	 	the name of the master";

struct sockaddr_in myaddr_in;	/* for local socket address */
struct sockaddr_in peeraddr_in;	/* for peer socket address */

/*
 *                    M A I N
 *
 *      This routine starts the server.  It forks, leaving the child
 *      to do all the work, so it does not have to be run in the
 *      background.  It sets up the listen socket, and for each incoming
 *      connection, it forks a child process to process the data.  It
 *      will loop forever, until killed by a signal.
 *
 */
main (argc, argv)
     int argc;
     char *argv[];
{
  int addrlen;

  int c;
  pid_t pid;
  int one = 1;
  extern char *optarg;
  extern int optind;
  char aport[8];
  int errflg = 0;
  int returned_from_longjump;
  int savemask = 1;

  time (&startup);
  bcnu_time (startup, starttime);
  bcnu_hour (hour, mins);

  debug = 0;
  reqcnt = 0;

  strcpy (progname, argv[0]);

  strcpy (master, argv[argc - 1]);

  while ((c = getopt (argc, argv, "avqds:g:e:")) != EOF)
    switch (c)
      {
      case 's':
	port++;
	strcpy (aport, optarg);
	break;
      case 'q':
	fqdn++;
	break;
      case 'd':
	debug++;
	break;
      case 'v':
	verbose++;
	printf ("%s version %s.%s\n", id, BCNU_VER, subver);
	break;
      case 'a':
	agentlog++;
	break;
      case 'g':
	gap = (atoi (optarg));
	break;
      case 'e':
	errorlevel = (atoi (optarg));
	break;
      case '?':
	errflg++;
      }
  if (errflg)
    {
      fprintf (stderr, "usage: %s -avd -s port -g gap master %s\n", argv[0], usage);
      exit (2);
    }

  hp = gethostbyname (master);
  if (hp == NULL)
    {
      sprintf (buf, "%s not found by nameservice\n", master);
      bcnu_error (buf);
    }


  bcnu_init ();
  sprintf (buf, "System Startup - master (%s)", master);
  bcnu_log (buf);
  bcnu_pipe_init ();
  bcnu_load_agents ();

  /* clear out address structures */
  memset ((char *) &myaddr_in, 0, sizeof (struct sockaddr_in));
  memset ((char *) &peeraddr_in, 0, sizeof (struct sockaddr_in));

  /* Set up address structure for the listen socket. */
  myaddr_in.sin_family = AF_INET;
  myaddr_in.sin_addr.s_addr = INADDR_ANY;
  /* Find the information for the "bcnu" server
   * in order to get the needed port number.
   */

  myaddr_in.sin_port = htons (BCNU_PORT);
  if (port)
    {
      if (isdigit (*aport))
	myaddr_in.sin_port = htons (atoi (aport));
      else
	{
	  if ((myaddr_in.sin_port = htons (bcnu_port (aport))) == 0)
	    {
	      sp = getservbyname (aport, "tcp");
	      if (sp == NULL)
		{
		  bcnu_error ("Invalid service port");
		}
	      else
		myaddr_in.sin_port = sp->s_port;
	    }
	}
    }

  if (verbose)
    printf ("startup at %s\nmaster is %s\nport is %d\n", starttime, master, port);

  /* Create the listen socket. */

  ls = socket (AF_INET, SOCK_STREAM, 0);
  if (ls == -1)
    {
      bcnu_error ("Unable to create socket");
    }

/* set up the socket to be reusable right away */

  if (setsockopt (ls, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof (one)) == -1)
    {
      sprintf (workbuf, "Unable to set socket SO_REUSEADDR\n");
      bcnu_error (workbuf);
    }

  /* Bind the listen address to the socket. */
  if (bind (ls, (struct sockaddr *) &myaddr_in, sizeof (struct sockaddr_in)) == -1)
    {
      bcnu_error ("Unable to bind address");
    }
  /* Initiate the listen on the socket so remote users
   * can connect.  The listen backlog is set to BCNU_LISTEN
   */
  if (listen (ls, BCNU_LISTEN) == -1)
    {
      bcnu_error ("Unable to listen on socket");
    }

  setsid ();

  if (gethostname (myhostname, 80) != 0)
    bcnu_error ("Couldn't get my hostname");

  switch (pid = fork ())
    {
    case -1:			/* Unable to fork, for some reason. */
      bcnu_error ("Unable to fork daemon");

    case 0:			/* The child process (daemon) comes here. */
      close (stdin);
      agent_run (master);
      if ((returned_from_longjump = sigsetjmp (env, savemask)) != 0)
	switch (returned_from_longjump)
	  {
	  case SIGALRM:
	    if (bcnu_pipe_read (buf) > 0)
	      bcnu_process_cmd (buf);
	    agent_run (master);
	    break;
	  case SIGHUP:
	    bcnu_load_agents ();
	    break;
	  }
      signal (SIGUSR1, ag_handler);
      signal (SIGALRM, signal_handler);
      signal (SIGHUP, signal_handler);
#ifdef NOZOMBIE
      signal (SIGCHLD, chld_handler);
#else
      signal (SIGCHLD, SIG_IGN);
#endif
      alarm (BCNU_INTERVAL);
      if (mins == 0)
	{
	  bcnu_log ("heartbeat");
	}
      for (;;)
	{
	  addrlen = sizeof (struct sockaddr_in);
	  s = accept (ls, (struct sockaddr *) &peeraddr_in, &addrlen);

	  reqcnt++;		/* increment no of requests */

	  if (s == -1)
	    exit (1);
	  switch (fork ())
	    {
	    case -1:		/* Can't fork, just exit. */
	      exit (1);
	    case 0:		/* Child process comes here. */
	      signal (SIGALRM, SIG_IGN);
	      signal (SIGHUP, SIG_IGN);
	      bcnu_server ();
	      exit (0);
	    default:		/* Daemon process comes here. */
	      close (s);
	    }

	}

    default:			/* Parent process comes here. */
      write_pid (pid);
      exit (0);
    }
}

/*
 *                            S E R V E R
 */
bcnu_server ()
{
  char *inet_ntoa ();
  int len;

  /* Close the listen socket inherited from the daemon. */
  close (ls);
  close (bcnu_pipein);		/* close the input pipe */


  /* Look up the host information for the remote host
   * that we have connected with.  Its internet address
   * was returned by the accept call, in the main
   * daemon loop above.
   */
  hp = gethostbyaddr ((char *) &peeraddr_in.sin_addr,
		      sizeof (struct in_addr),
		      peeraddr_in.sin_family);

  if (hp == NULL)
    {
      /* The information is unavailable for the remote
       * host.  Just format its internet address to be
       * printed out in the logging information.  The
       * address will be shown in "internet dot format".
       */
      hostname = inet_ntoa (peeraddr_in.sin_addr);
    }
  else
    {
      hostname = hp->h_name;	/* point to host's name */
    }

  if (strcasecmp (master, hostname) == 0)
    masterok++;
  time (&timevar);
  /* The port number must be converted first to host byte
   * order before printing.  On most hosts, this is not
   * necessary, but the ntohs() call is included here so
   * that this program could easily be ported to a host
   * that does require it.
   */
  if (debug)
    printf ("Startup from %s port %u at %s",
	    hostname, ntohs (peeraddr_in.sin_port), ctime (&timevar));

  /* Set the socket for a lingering, graceful close.
   * This will cause a final close of this socket to wait until all of the
   * data sent on it has been received by the remote host.
   */
  linger.l_onoff = 0;
  linger.l_linger = 1;
  if (setsockopt (s, SOL_SOCKET, SO_LINGER, (char *) &linger,
		  sizeof (linger)) == -1)
    {
      sprintf (workbuf, "Connection with %s aborted on error\n", hostname);
      bcnu_error (workbuf);
    }

  len = recv (s, buf, BCNU_MESS, 0);
  bptr = buf;
  if (len == -1)
    {
      bcnu_error ("error from recv(main loop)");	/* error from recv */
    }
  if (!bcnu_parse (bptr, len))
    bcnu_error ("error parsing received data");
  bcnu_process (hostname);
  /* Send a response back to the client. */
  if (send (s, response, strlen (response), 0) != strlen (response))
    bcnu_error ("error from send");
  shutdown (s, 2);

  time (&timevar);
  if (debug)
    printf ("Completed %s port %u, %d requests, at %s\n",
	  hostname, ntohs (peeraddr_in.sin_port), reqcnt, ctime (&timevar));
}


void
ag_handler (sig)
     int sig;
{
  switch (sig)
    {
    case SIGUSR1:
      if (agentlog)
	agentlog = 0;
      else
	agentlog++;
      siglongjmp (env, sig);
    }
}
