/* 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.16 93/04/04 01:36:29 warlord Exp Locker: warlord $";

#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 <net/if.h>
#include <netinet/in.h>
#include <sys/time.h>
#if defined(sun) && defined(__svr4__)
#include <sys/fcntl.h>
#include <sys/termios.h>
#include <sys/sockio.h>
#endif
/* 
 * 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;
char **zhm_ext_argv;
RealmEntry *PrimRealm = NULL;

#ifdef SABER
static struct timeval timer_timeout, *timer_timeout_p;
#endif SABER

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

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

#if defined(ultrix) || defined(_POSIX_SOURCE)
void
#endif
  set_sig_type(sig)
     int sig;
{
#ifdef SA_INTERRUPT
     struct sigaction sa;
#endif

     sig_type = sig;

     /* Reset the signal handler */
#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   
}

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;
#ifdef SABER
     int retval;
     fd_set readmaster, read_fds;
#endif SABER

#ifdef PROFILE_MEMORY
     set_mprof_autosave(1000);
#endif

     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);
     }
     
     /* Override server argument? */
     /* unlike the original zhm, this will just add these hosts to the
      * server list, instead of overriding Hesiod or the server file.
      */
     if (optind < argc) 
	 zhm_ext_argv = &argv[optind];
     else
	 zhm_ext_argv = NULL;

     /* Initialize me, baby! */
     init_hm();

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

#ifdef SABER
     FD_ZERO(&readmaster);
     FD_SET(ZGetFD(), &readmaster);
#endif SABER
     /* Main loop */
     for ever {
	  DPR ("Waiting for a packet...");

#ifdef SABER
	  bcopy((char *)&readmaster, (char *)&read_fds, sizeof(fd_set));

	  if (!ZQLength())
	      retval = select(FD_SETSIZE, &read_fds, (fd_set *)0, (fd_set *)0, 
			      timer_timeout_p);
	  else
	      retval = 1;

	  switch(retval) {
	  case -1:		/* Interrupt/Error in select */
#endif SABER
	      switch(sig_type) {
	      case 0:
		  	/* Shouldnt EVER get here, but check just in case */
		  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;
	      }
#ifdef SABER
	      break;

	  case 0:		/* Timeout */
	      if (!all_queues_empty())
		  handle_timeout();
	      clear_alarm();
	      break;
	      
	  default:
#endif SABER
	      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))) {
			      if (!strcmp(notice.z_opcode, CLIENT_CANCELSUB)) 
				  send_to_all_realms(&notice, packet, pak_len);
			      else {
				  /* 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);
			      }
			  }
		      }
		  }
	      }
	  }
      }
#ifdef SABER
}
#endif

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_LOCAL1);
  
     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;
*/

     getlocalhost(loopback);

     /* 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 = ZSendRawList(&newnotice, list, nitems)) != ZERR_NONE) {
	 Zperr(ret);
	 com_err("hm", ret, "sending stat notice");
     }

/*     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()
{
#ifdef SABER
    if (timer_timeout.tv_sec == 0) {
	timer_timeout.tv_sec = NOTICE_TIMEOUT/2+1;
	DPR("to full time\n");
    } else
	DPR2("to left time of %d\n", timer_timeout.tv_sec);

    timer_timeout_p = &timer_timeout;
#else
    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/2+1);
        DPR("to full time\n");
    }
#endif SABER
    timeout_type = NOTICES;
}

clear_alarm()
{
#ifdef SABER
    if (all_queues_empty()) {
	DPR("Timer reset to Blocking Wait\n");
	timer_timeout.tv_sec = 0;
	timer_timeout.tv_usec = 0;
	timer_timeout_p = NULL;
    } else {
	timer_timeout_p = &timer_timeout;
	DPR2("Alarm tried to reset but left at time %d\n", 
	     timer_timeout.tv_sec);
    }
#else
    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");
    }
#endif SABER
    timeout_type = NOTICES;
}

void getlocalhost(addr)
u_long *addr;
{
    int s, i, len;
    struct ifreq ifs[100];
    struct ifconf ifc;
    struct sockaddr_in *sin;
    u_long sinaddr;

    *addr = 0;
    s = socket (AF_INET, SOCK_DGRAM, 0);
    if (s < 0) {
        syslog (LOG_ERR, "couldn't create socket to get loopback address\n");
        exit(1);
    }

    ifc.ifc_len = sizeof (ifs);
    ifc.ifc_req = ifs;
    if (ioctl (s, SIOCGIFCONF, (char *) &ifc) < 0) {
        syslog (LOG_ERR, "couldn't find loopback address (ioctl)\n");
        exit(1);
    }

    len = ifc.ifc_len / sizeof(struct ifreq);

    for (i = 0; i < len; ++i) {
        sin = (struct sockaddr_in *)&ifs[i].ifr_addr;
        sinaddr = sin->sin_addr.s_addr;

        if (sinaddr && sin->sin_family == AF_INET
            && ioctl(s, SIOCGIFFLAGS, (char *)&ifs[i]) == 0
            && ifs[i].ifr_flags & IFF_LOOPBACK)
        {
            *addr = sinaddr;
            break;
        }
    }

    (void) close (s);

    if (*addr == 0) {
        if (isatty(2))
            fprintf(stderr, "couldn't find loopback address\n");
        syslog (LOG_ERR, "couldn't find loopback address\n");
        exit(1);
    }
}
