/* This file is part of the Project Athena Zephyr Notification System.
 * It contains the hostmanager <--> server interaction routines.
 *
 *      Created by:     David C. Jedlinsky
 *
 *      $Source: /mit/zephyr/repository/zephyr/zhm/zhm_server.c,v $
 *      $Author: probe $
 *
 *      Copyright (c) 1987 by the Massachusetts Institute of Technology.
 *      For copying and distribution information, see the file
 *      "mit-copyright.h". 
 */

#include "zhm.h"

#ifndef lint
#ifndef SABER
static char rcsid_hm_server_c[] = "$Header: /mit/zephyr/repository/zephyr/zhm/zhm_server.c,v 1.11 1993/11/19 15:12:21 probe Exp $";
#endif /* SABER */
#endif /* lint */

int serv_loop = 0;
extern u_short cli_port;
extern struct sockaddr_in serv_sin, from;
extern int timeout_type, hmdebug, nservchang, booting, nserv, no_server;
extern int deactivated, rebootflag;
extern int numserv;
extern char **serv_list;
extern char cur_serv[], prim_serv[];
extern void die_gracefully();
extern unsigned long inet_addr();

/* Argument is whether we are actually booting, or just attaching
 * after a server switch */
send_boot_notice(op)
char *op;
{
     ZNotice_t notice;
     Code_t ret;
  
     /* Set up server notice */
     notice.z_kind = HMCTL;
     notice.z_port = cli_port;
     notice.z_class = ZEPHYR_CTL_CLASS;
     notice.z_class_inst = ZEPHYR_CTL_HM;
     notice.z_opcode = op;
     notice.z_sender = "HM";
     notice.z_recipient = "";
     notice.z_default_format = "";
     notice.z_num_other_fields = 0;
     notice.z_message_len = 0;
  
     /* Notify server that this host is here */
     if ((ret = ZSetDestAddr(&serv_sin)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "setting destination");
     }
     if ((ret = ZSendNotice(&notice, ZNOAUTH)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "sending startup notice");
     }
     timeout_type = BOOTING;
     (void)alarm(SERV_TIMEOUT);
}

/* Argument is whether we are detaching or really going down */
send_flush_notice(op)
char *op;
{
     ZNotice_t notice;
     Code_t ret;
     
     /* Set up server notice */
     notice.z_kind = HMCTL;
     notice.z_port = cli_port;
     notice.z_class = ZEPHYR_CTL_CLASS;
     notice.z_class_inst = ZEPHYR_CTL_HM;
     notice.z_opcode = op;
     notice.z_sender = "HM";
     notice.z_recipient = "";
     notice.z_default_format = "";
     notice.z_num_other_fields = 0;
     notice.z_message_len = 0;

     /* Tell server to lose us */
     if ((ret = ZSetDestAddr(&serv_sin)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "setting destination");
     }
     if ((ret = ZSendNotice(&notice, ZNOAUTH)) != ZERR_NONE) {
	  Zperr(ret);
	  com_err("hm", ret, "sending flush notice");
     }
}

find_next_server(sugg_serv)
char *sugg_serv;
{
     struct hostent *hp;
     int done = 0;
     char **parse = serv_list;
     char *new_serv;
  
     if (sugg_serv) {
	  do {
	       if (!strcmp(*parse, sugg_serv))
		    done = 1;
	  } while ((done == 0) && (*++parse != NULL));
     }
     if (done) {
	  if ((hp = gethostbyname(sugg_serv)) != NULL) {
	       DPR2 ("Server = %s\n", sugg_serv);	
	       (void)strcpy(cur_serv, sugg_serv);
	       if (hmdebug)
		    syslog(LOG_DEBUG, "Suggested server: %s\n", sugg_serv);
	  } else {
	       done = 0; 
	  }
     }
     while (!done) {
	 if ((++serv_loop > 3) && (strcmp(cur_serv, prim_serv))) {
	     serv_loop = 0;
	     if ((hp = gethostbyname(prim_serv)) != NULL) {
		 DPR2 ("Server = %s\n", prim_serv);
		 (void)strcpy(cur_serv, prim_serv);
		 done = 1;
		 break;
	     }
	 }

	 switch (numserv) {
	 case 1:
	     if ((hp = gethostbyname(*serv_list)) != NULL) {
		 DPR2 ("Server = %s\n", *serv_list);
		 (void)strcpy(cur_serv, *serv_list);
		 done = 1;
		 break;
	     }
	     /* fall through */
	 case 0:
	     if (rebootflag)
		 die_gracefully();
	     else
		 sleep(1);
	     break;
	 default:
	     do {
		 new_serv = serv_list[random() % numserv];
	     } while (!strcmp(new_serv, cur_serv));

	     if ((hp = gethostbyname(new_serv)) != NULL) {
		 DPR2 ("Server = %s\n", new_serv);
		 (void)strcpy(cur_serv, new_serv);
		 done = 1;
	     } else
		 sleep(1);

	     break;
	 }
     }
     (void) memcpy((char *)&serv_sin.sin_addr, hp->h_addr, hp->h_length);
     nservchang++;
}

server_manager(notice)
ZNotice_t *notice;
{
     if (memcmp((char *)&serv_sin.sin_addr, (char *)&from.sin_addr, 4) ||
	 (serv_sin.sin_port != from.sin_port)) {
	  syslog (LOG_INFO, "Bad notice from port %u.", notice->z_port);
     } else {
	  /* This is our server, handle the notice */
	  booting = 0;
	  DPR ("A notice came in from the server.\n");
	  nserv++;
	  switch(notice->z_kind) {
	  case HMCTL:
	       hm_control(notice);
	       break;
	  case SERVNAK:
	  case SERVACK:
	       send_back(notice);
	       break;
	  default:
	       syslog (LOG_INFO, "Bad notice kind!?");
	       break;
	  }
     }
}

hm_control(notice)
ZNotice_t *notice;
{
     Code_t ret;
     struct hostent *hp;
     char suggested_server[64];
     unsigned long addr;
     
     DPR("Control message!\n");
     if (!strcmp(notice->z_opcode, SERVER_SHUTDOWN)) {
	  if (notice->z_message_len) {
	       addr = inet_addr(notice->z_message);
	       if ((hp = gethostbyaddr((char *)&addr, sizeof(addr),
				       AF_INET)) != NULL) {
		    (void)strcpy(suggested_server, hp->h_name);
		    new_server(suggested_server);
	       } else
		    new_server((char *)NULL);
	  }
	  else
	       new_server((char *)NULL);
     } else if (!strcmp(notice->z_opcode, SERVER_PING)) {
	  if (no_server)
	       (void)alarm(0);
	  notice->z_kind = HMACK;
	  if ((ret = ZSetDestAddr(&serv_sin)) != ZERR_NONE) {
	       Zperr(ret);
	       com_err("hm", ret, "setting destination");
	  }
	  if ((ret = send_outgoing(notice)) != ZERR_NONE) {
	       Zperr(ret);
	       com_err("hm", ret, "sending ACK");
	  }
	  if (no_server) {
	       no_server = 0;
	       retransmit_queue(&serv_sin);
	  }
     } else
	  syslog (LOG_INFO, "Bad control message.");
}

send_back(notice)
ZNotice_t *notice;
{
     ZNotice_Kind_t kind;
     struct sockaddr_in repl;
     Code_t ret;
  
     if (no_server)
	  (void)alarm(0);
     if (!strcmp(notice->z_opcode, HM_BOOT) ||
	 !strcmp(notice->z_opcode, HM_ATTACH)) {
	  /* ignore message, just an ack from boot, but exit if we
	   * are rebooting.
	   */
	  if (rebootflag)
	       die_gracefully();
     } else {
	  if (remove_notice_from_queue(notice, &kind,
				       &repl) != ZERR_NONE) {
	       syslog (LOG_INFO, "Hey! This packet isn't in my queue!");
	  } else {
	       /* check if client wants an ACK, and send it */
	       if (kind == ACKED) {
		    DPR2 ("Client ACK port: %u\n", ntohs(repl.sin_port));
		    if ((ret = ZSetDestAddr(&repl)) != ZERR_NONE) {
			 Zperr(ret);
			 com_err("hm", ret, "setting destination");
		    }
		    if ((ret = send_outgoing(notice)) != ZERR_NONE) {
			 Zperr(ret);
			 com_err("hm", ret, "sending ACK");
		    }
	       }
	  }
     }
     if (no_server) {
	  no_server = 0;
	  retransmit_queue(&serv_sin);
     }
}

new_server(sugg_serv)
char *sugg_serv;
{
     no_server = 1;
     syslog (LOG_INFO, "Server went down, finding new server.");
     send_flush_notice(HM_DETACH);
     find_next_server(sugg_serv);
     if (booting) {
	  send_boot_notice(HM_BOOT);
	  deactivated = 0;
     } else
	  send_boot_notice(HM_ATTACH);
}
