/* This file is part of the Project Athena Zephyr Notification System.
 * It contains the hostmanager client program.
 *
 *      Created by:     David C. Jedlinsky
 *
 *      $Source: /afs/media-lab.mit.edu/user/warlord/C/zephyr/zhm/RCS/zhm.c,v $
 *      $Author: warlord $
 *
 *      Copyright (c) 1987,1991 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h". 
 */

#include "zhm.h"
#include "ZRemoteRealms.h"

#ifndef PIDFILE
#define PIDFILE "/etc/athena/zhm.pid"
#endif

static char rcsid_hm_c[] = "$Id: zhm.c,v 1.3 92/03/12 20:38:42 warlord Exp $";

#include <ctype.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <sys/file.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/time.h>
/* 
 * warning: sys/param.h may include sys/types.h which may not be protected from
 * multiple inclusions on your system
 */
#include <sys/param.h>

#ifdef Z_HaveHesiod
#include <hesiod.h>
#endif

#ifdef macII
#define srandom srand48
#endif

int hmdebug, rebootflag, errflg, dieflag, inetd, oldpid;
int nclt;
int timeout_type;
long starttime;
u_short cli_port, serv_port;
struct sockaddr_in cli_sin, from;
int sig_type;
char hostname[MAXHOSTNAMELEN], loopback[4];
char *PidFile = PIDFILE;
RealmEntry *PrimRealm = NULL;

extern char *index(), *sbrk();
extern long time();

void init_hm(), detach(), handle_timeout(), resend_notices(), die_gracefully();

#if defined(ultrix) || defined(_POSIX_SOURCE)
void
#endif
  set_sig_type(sig)
     int sig;
{
     sig_type = sig;
}

char *strsave();

main(argc, argv)
char *argv[];
{
     ZNotice_t notice;
     ZPacket_t packet;
     Code_t ret;
     int opt, pak_len;
     RealmEntry *Realm;
     extern int optind;

     if (gethostname(hostname, MAXHOSTNAMELEN) < 0) {
	  printf("Can't find my hostname?!\n");
	  exit(-1);
     }
     while ((opt = getopt(argc, argv, "drhi")) != EOF)
	  switch(opt) {
	  case 'd':
	       hmdebug++;
	       break;
	  case 'h':
	       /* Die on SIGHUP */
	       dieflag++;
	       break;
	  case 'r':
	       /* Reboot host -- send boot notice -- and exit */
	       rebootflag++;
	       break;
	  case 'i':
	       /* inetd operation: don't do bind ourselves, fd 0 is
		  already connected to a socket. Implies -h */
	       inetd++;
	       dieflag++;
	       break;
	  case '?':
	  default:
	       errflg++;
	       break;
	  }
     if (errflg) {
	  fprintf(stderr, "Usage: %s [-d] [-h] [-r] [server]\n", argv[0]);
	  exit(2);
     }
     
     init_hm();

     DPR2 ("zephyr server port: %u\n", ntohs(serv_port));
     DPR2 ("zephyr client port: %u\n", ntohs(cli_port));
  
     clear_alarm();

     /* Main loop */
     for ever {
	  DPR ("Waiting for a packet...");
	  switch(sig_type) {
	  case 0:
	      break;
	  case SIGHUP:
	      /* A SIGHUP means we are deactivating the ws. */
	      sig_type = 0;
	      if (dieflag) {
		  die_gracefully();
	      } else {
		  flush_all_servers();
	      }
	      break;
	  case SIGTERM:
	      sig_type = 0;
	      die_gracefully();
	      break;
	  case SIGALRM:
	      /* This shouldnt happen, but Ill leave it in
	       * just in case.
	       */
	      sig_type = 0;
	      handle_timeout();
	      break;
	  default:
	      sig_type = 0;
	      syslog (LOG_WARNING, "Unknown system interrupt.");
	      break;
	  }

	  ret = ZReceivePacket(packet, &pak_len, &from);
	  if ((ret != ZERR_NONE) && (ret != EINTR)){
	      Zperr(ret);
	      com_err("hm", ret, "receiving notice");
	  } else if (ret != EINTR) {
	      /* Where did it come from? */
	      if ((ret = ZParseNotice(packet, pak_len, &notice))
		  != ZERR_NONE) {
		  Zperr(ret);
		  com_err("hm", ret, "parsing notice");
	      } else {
		  DPR ("Got a packet.\n");
		  DPR ("notice:\n");
		  DPR2("\trealm: %s\n", ZGetDestRealm(&notice));
		  DPR2("\tz_kind: %d\n", notice.z_kind);
		  DPR2("\tz_port: %u\n", ntohs(notice.z_port));
		  DPR2("\tz_class: %s\n", notice.z_class);
		  DPR2("\tz_class_inst: %s\n", notice.z_class_inst);
		  DPR2("\tz_opcode: %s\n", notice.z_opcode);
		  DPR2("\tz_sender: %s\n", notice.z_sender);
		  DPR2("\tz_recip: %s\n", notice.z_recipient);
		  DPR2("\tz_def_format: %s\n", notice.z_default_format);
		  DPR2("\tz_message: %s\n", notice.z_message);
		  if ((bcmp(loopback, (char *)&from.sin_addr, 4) != 0) &&
		      ((notice.z_kind == SERVACK) ||
		       (notice.z_kind == SERVNAK) ||
		       (notice.z_kind == HMCTL))) {
		      server_manager(&notice);
		  } else {
		      Realm = ZGetRealmFromNotice(&notice);
		      if ((bcmp(loopback, (char *)&from.sin_addr, 4) == 0) &&
			  ((notice.z_kind == UNSAFE) ||
			   (notice.z_kind == UNACKED) ||
			   (notice.z_kind == ACKED) ||
			   (notice.z_kind == HMCTL))) {
			  /* Client program... */
			  if (Realm->deactivated) {
			      send_boot_notice(Realm, HM_BOOT);
			      Realm->deactivated = 0;
			  }
			  transmission_tower(Realm, &notice, packet, pak_len);
			  DPR2 ("Pending = %d\n", ZPending());
		      } else {
			  if (notice.z_kind == STAT) {
			      send_stats(Realm, &notice, &from);
			  } else {
			      syslog(LOG_INFO,
				     "Unknown notice type: %d",
				     notice.z_kind);
			  }
		      }
		  }
	      }
	  }
      }
}

void init_hm()
{
     struct servent *sp;
     Code_t ret;
     FILE *fp;
     RealmEntry *Realm;
#ifdef SA_INTERRUPT
     struct sigaction sa;
#endif

     starttime = time((time_t *)0);
     OPENLOG("hm", LOG_PID, LOG_DAEMON);
  
     if ((ret = ZInitialize()) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "initializing");
	  closelog();
	  exit(-1);
     }
     (void)ZSetServerState(1);	/* Aargh!!! */

     loopback[0] = 127;
     loopback[1] = 0;
     loopback[2] = 0;
     loopback[3] = 1;
      
     /* kill old hm if it exists */
     fp = fopen(PidFile, "r");
     if (fp != NULL) {
	  (void)fscanf(fp, "%d\n", &oldpid);
	  while (!kill(oldpid, SIGTERM))
	       sleep(1);
	  syslog(LOG_INFO, "Killed old image.");
	  (void) fclose(fp);
     }

     if (inetd) {
	     (void) ZSetFD(0);		/* fd 0 is on the socket,
					   thanks to inetd */
     } else {
	     /* Open client socket, for receiving client and server notices */
	     if ((sp = getservbyname(HM_SVCNAME, "udp")) == NULL) {
		     printf("No %s entry in /etc/services.\n", HM_SVCNAME);
		     exit(1);
	     }
	     cli_port = sp->s_port;
      
	     if ((ret = ZOpenPort(&cli_port)) != ZERR_NONE) {
		     Zperr(ret);
		     com_err("hm", ret, "opening port");
		     exit(ret);
	     }
     }
     cli_sin = ZGetDestAddr();
  
     /* Open the server socket */
  
     if ((sp = getservbyname(SERVER_SVCNAME, "udp")) == NULL) {
	  printf("No %s entry in /etc/services.\n", SERVER_SVCNAME);
	  exit(1);
     }
     serv_port = sp->s_port;

     /* Set up communications with server */
     /* target is SERVER_SVCNAME port on server machine */

     if ((Realm = ZGetRealmFromRealm(ZGetRealm())) == NULL) {
	 printf("No Local server hosts\n");
	 exit (1);
     }

     PrimRealm = Realm;

#ifndef DEBUG
     if (!inetd)
	     detach();
  
     /* Write pid to file */
     fp = fopen(PidFile, "w");
     if (fp != NULL) {
	     fprintf(fp, "%d\n", getpid());
	     (void) fclose(fp);
     }
#endif /* DEBUG */

     if (hmdebug) {
	  syslog(LOG_INFO, "Debugging on.");
     }

#ifdef SA_INTERRUPT
     /* POSIX semantics */
     sa.sa_handler = (void (*)())set_sig_type;
     sa.sa_mask = 0;
     sa.sa_flags = SA_INTERRUPT;

     sigaction(SIGHUP, &sa, 0);
     sigaction(SIGALRM, &sa, 0);
     sigaction(SIGTERM, &sa, 0);
#else
     /* BSD semantics */
     (void)signal (SIGHUP,  set_sig_type);
     (void)signal (SIGALRM, set_sig_type);
     (void)signal (SIGTERM, set_sig_type);
#endif   
}

void detach()
{
     /* detach from terminal and fork. */
     register int i, x = ZGetFD(), size = getdtablesize();
  
     if ((i = fork()) != 0) {
	  if (i < 0)
	       perror("fork");
	  exit(0);
     }

     for (i = 0; i < size; i++)
	  if (i != x)
	       (void) close(i);
  
     if ((i = open("/dev/tty", O_RDWR, 666)) < 0)
	  syslog(LOG_DEBUG, "No tty");/* Can't open tty, but don't flame about it. */
     else {
	  (void) ioctl(i, TIOCNOTTY, (caddr_t) 0);
	  (void) close(i);
     }
}

static char version[BUFSIZ];

send_stats(Realm, notice, sin)
     RealmEntry *Realm;
     ZNotice_t *notice;
     struct sockaddr_in *sin;
{
     ZNotice_t newnotice;
     Code_t ret;
     char *bfr;
     char *list[20];
     int len, i, nitems = 10;
     unsigned int size;

     newnotice = *notice;
     
     if ((ret = ZSetDestAddr(sin)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "setting destination");
     }
     newnotice.z_kind = HMACK;

     list[0] = (char *)malloc(MAXHOSTNAMELEN);
     (void)strcpy(list[0], Realm->cur_serv);
     list[1] = (char *)malloc(64);
     (void)sprintf(list[1], "%d", queue_len(Realm->queue));
     list[2] = (char *)malloc(64);
     (void)sprintf(list[2], "%d", nclt);
     list[3] = (char *)malloc(64);
     (void)sprintf(list[3], "%d", Realm->nserv);
     list[4] = (char *)malloc(64);
     (void)sprintf(list[4], "%d", Realm->nservchang);
     list[5] = (char *)malloc(strlen(rcsid_hm_c)+1);
     (void)strcpy(list[5], rcsid_hm_c);
     list[6] = (char *)malloc(64);
     if (Realm->no_server)
	  (void)sprintf(list[6], "yes");
     else
	  (void)sprintf(list[6], "no");
     list[7] = (char *)malloc(64);
     (void)sprintf(list[7], "%ld", time((time_t *)0) - starttime);
#ifdef adjust_size
     size = (unsigned int)sbrk(0);
     adjust_size (size);
#else
     size = -1;
#endif
     list[8] = (char *)malloc(64);
     (void)sprintf(list[8], "%ld", size);
     list[9] = (char *)list_remote_servers(MACHINE);
/*     list[9] = (char *)malloc(32);
**     (void)strcpy(list[9], MACHINE);
*/
     /* Since ZFormatRaw* won't change the version number on notices,
	we need to set the version number explicitly.  This code is taken
	from Zinternal.c, function Z_FormatHeader */
     if (!*version)
	     (void) sprintf(version, "%s%d.%d", ZVERSIONHDR, ZVERSIONMAJOR,
			    ZVERSIONMINOR);
     newnotice.z_version = version;

     if ((ret = ZFormatRawNoticeList(&newnotice, list, nitems, &bfr,
				     &len)) != ZERR_NONE) {
	  syslog(LOG_INFO, "Couldn't format stats packet");
     } else
	  if ((ret = ZSendPacket(bfr, len, 0)) != ZERR_NONE) {
	       Zperr(ret);
	       com_err("hm", ret, "sending stats");
	  }
     free(bfr);
     for(i=0;i<nitems;i++)
	  free(list[i]);
}

void handle_timeout()
{
     switch(timeout_type) {
     case NOTICES:
	  DPR ("Notice timeout\n");
	  help_booting_servers();
	  help_resend_notices();
	  break;
     default:
	  syslog (LOG_ERR, "Unknown timeout type: %d\n", timeout_type);
	  break;
     }
}

void die_gracefully()
{
     syslog(LOG_INFO, "Terminate signal caught...");
     flush_all_servers();
     (void)unlink(PidFile);
     closelog();
     exit(0);
}

char *
strsave(sp)
char *sp;
{
    register char *ret;

    if((ret = malloc((unsigned) strlen(sp)+1)) == NULL) {
	    abort();
    }
    (void) strcpy(ret,sp);
    return(ret);
}

set_alarm()
{
    time_t tleft;

    DPR("Timer is being set ");
    if ((tleft = alarm((time_t)0)) != 0) {
	alarm(tleft);
	DPR2("to old time of %d\n", tleft);
    } else {
	alarm(NOTICE_TIMEOUT);
	DPR("to full time\n");
    }
    timeout_type = NOTICES;
}

clear_alarm()
{
    if (all_queues_empty()) {
	DPR("Timer reset to Zero\n");
	alarm((time_t)0);
    } else {
	DPR("Alarm tried to reset but left at current time\n");
    }
    timeout_type = NOTICES;
}
