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

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

#ifndef lint
#ifndef SABER
static char rcsid_hm_server_c[] = "$Header: /afs/media-lab.mit.edu/user/w/warlord/C/zephyr/zhm/RCS/zhm_server.c,v 1.5 92/03/19 19:25:14 warlord Exp Locker: warlord $";
#endif /* SABER */
#endif /* lint */

int serv_loop = 0;
extern u_short cli_port, serv_port;
extern struct sockaddr_in from;
extern int timeout_type, hmdebug;
extern int rebootflag;
extern void die_gracefully();
extern char *strcpy();
extern unsigned long inet_addr();

/* Argument is whether we are actually booting, or just attaching
 * after a server switch */
send_boot_notice(Realm, op)
RealmEntry *Realm;
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(Realm->hp)) != 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");
     }
     set_alarm();
}

/* Argument is whether we are detaching or really going down */
send_flush_notice(Realm, op)
RealmEntry *Realm;
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(Realm->hp)) != 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(Realm, sugg_serv)
RealmEntry *Realm;
char *sugg_serv;
{
     struct hostent *hp;
     int done = 0;
     int roundtrip = 0;
     char **parse = Realm->hostlist;
  
     if (Realm->no_servers) {
	 return;
     }

     /* If we were told a server, make sure we know about it! */
     if (sugg_serv) {
	  do {
	       if (!strcasecmp(*parse, sugg_serv))
		    done = 1;
	  } while ((done == 0) && (*++parse != NULL));
     }

     /* If we know about, try to find out its host information */
     if (done) {
	  if ((hp = gethostbyname(sugg_serv)) != NULL) {
	       DPR2 ("Server = %s\n", sugg_serv);	
	       Realm->cur_serv = *parse;
	       if (hmdebug)
		    syslog(LOG_DEBUG, "Suggested server: %s\n", sugg_serv);
	  } else {
	       done = 0; 
	       sleep(1);
	       remove_name_from_list(Realm->hostlist, sugg_serv);
	       add_name_to_list(Realm->badhostlist, sugg_serv);
	       if (*Realm->hostlist == NULL) {
		   Realm->no_servers = 1;
		   done = 1;
	       }
	  }
     }

     /* Well, we don't have a valid host, either the suggested server
      * was bad, or we didn't get a suggested server at all.  Lets just
      * try to find the next available server.
      */
     while (!done) {

         Realm->cur_serv_list++; /* Next host */

	 if (*(Realm->cur_serv_list) == NULL) {
	     /* Exit if we are rebooting, */
	     /* and cannot find a server. */
	     if (rebootflag)
		 die_gracefully();
	     
	     /* Reset list to beginning */
	     Realm->cur_serv_list = Realm->hostlist;

	     if (roundtrip == 1) 
		 done = 1;
	     else
		 roundtrip = 1;
	 }

	 /* Try to find a valid hostname */
	 if ((hp = gethostbyname(*(Realm->cur_serv_list))) != NULL){
	     DPR2 ("Server = %s\n", *(Realm->cur_serv_list));
	     Realm->cur_serv =  *(Realm->cur_serv_list);
	     done = 1;
	 } else {
	     add_name_to_list(Realm->badhostlist, *(Realm->cur_serv_list));
	     remove_name_from_list(Realm->hostlist, *(Realm->cur_serv_list));
	     if (*Realm->hostlist == NULL) {
		 Realm->no_servers = 1;
		 done = 1;
	     }
	     sleep(1);
	 }
     }	   

     if (Realm->no_servers || !hp) {
	 DPR2("Cannot resolv any servers for realm %s\n", Realm->realm);
     } else {
	 bcopy((char *)hp->h_addr, (char *)&(Realm->host_sin.sin_addr), 
	       hp->h_length);
	 Realm->nservchang++;
	 DPR2("New server for realm %s", Realm->realm);
	 DPR2(": %s\n", Realm->cur_serv);
     }
     return;
}

server_manager(notice)
ZNotice_t *notice;
{
     RealmEntry *Realm;

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

hm_control(Realm, notice)
RealmEntry *Realm;
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)) {
	  syslog(LOG_INFO, "A Server is shutting down in realm %s.", 
		 Realm->realm);
	  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(Realm, suggested_server);
	       } else
		    new_server(Realm, (char *)NULL);
	  }
	  else
	       new_server(Realm, (char *)NULL);
     } else if (!strcmp(notice->z_opcode, SERVER_PING)) {
	  clear_alarm();
	  notice->z_kind = HMACK;
	  if ((ret = ZSetDestAddr(Realm->hp)) != 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 (Realm->no_server) {
	       Realm->no_server = 0;
	       retransmit_queue(Realm->queue, Realm->hp);
	  }
     } else
	  syslog (LOG_INFO, "Bad control message.");
}

send_back(Realm, notice)
RealmEntry *Realm;
ZNotice_t *notice;
{
     ZNotice_Kind_t kind;
     struct sockaddr_in repl;
     Code_t ret;
  
     clear_alarm();
     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(Realm->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 (Realm->no_server) {
	  Realm->no_server = 0;
	  Realm->num_timeouts = 0;
	  retransmit_queue(Realm->queue, Realm->hp);
     }
}

new_server(Realm, sugg_serv)
RealmEntry *Realm;
char *sugg_serv;
{
     Realm->no_server = 1;
     syslog (LOG_INFO, "Finding new server for realm %s.", Realm->realm);
     send_flush_notice(Realm, HM_DETACH);
     find_next_server(Realm, sugg_serv);
     if (Realm->booting) {
	  send_boot_notice(Realm, HM_BOOT);
	  Realm->deactivated = 0;
     } else
	  send_boot_notice(Realm, HM_ATTACH);
}
