/* This file is part of the Project Athena Zephyr Notification System.
 * It is part of the Zephyr Host Manager.
 * It contains code to do remote realm hosts.
 *
 *      Created by:     Derek Atkins <warlord@MIT.EDU>
 *
 *	$Source: /afs/media-lab.mit.edu/user/warlord/C/zephyr/zhm/RCS/servhost.c,v $
 *      $Author: warlord $
 *
 */

#ifndef lint
#ifndef SABER
static char rcsid_servhost_c[] =
  "$Id: servhost.c,v 1.16 93/08/23 01:12:16 warlord Exp $";
#endif
#endif

#include "zhm.h"
#include "newqueue.h"
#include "ZRemoteRealms.h"
#include <sys/types.h>
#include <sys/param.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/file.h>
#include <string.h>
#include <stdio.h>

#ifdef lint
#include <sys/uio.h>                    /* make lint shut up */
#endif /* lint */

#ifdef DEBUG
#define DPR(a) fprintf(stderr, a); fflush(stderr)
#define DPR2(a,b) fprintf(stderr, a, b); fflush(stderr)
#define Zperr(e) fprintf(stderr, "Error = %d\n", e)
#else
#define DPR(a)
#define DPR2(a,b)
#define Zperr(e)
#endif

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

#ifndef SERVER_LIST
#define SERVER_LIST "/etc/zephyr.conf"
#endif

extern int timeout_type;
extern u_short cli_port, serv_port;
extern RealmEntry *PrimRealm;
extern char **zhm_ext_argv;

static RealmEntry *RealmList = NULL;
static char line[BUFSIZ];

static void add_host_to_hostlist();
static RealmEntry *create_new_realm();
char *list_remote_servers();
extern void resend_notices();

RealmEntry *
ZGetRealmFromRealm(realm)
char *realm;
{
    RealmEntry *Realm;
    FILE *fd;
    char *ret, *host, *where, *old_where;
    char **serv_list;
    
    if (realm == NULL) {
	return(PrimRealm);
    }

    /* This assumes that we don't yet have this realm cached,
     * but lets check just in case someone is REALLY confused,
     * and just return the realm if it is there.
     */

    DPR2("Trying to find out realm %s\n", realm);

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	DPR2("Is it equal to %s?\n", Realm->realm);
	if (!strcasecmp(realm, Realm->realm)) {
	    DPR("\tGot it.\n");
	    if (Realm->deactivated) {
		DPR("\tand I have to activate it.\n");
		send_boot_notice(Realm, HM_BOOT);
		Realm->deactivated = 0;
	    }
	    return(Realm);
	}
    }

    Realm = NULL;		/* Clear this as a flag */

    DPR2("New Realm: %s\n", realm);

    if (PrimRealm == NULL)	/* Only do this for the primary realm */
	if (zhm_ext_argv != NULL) {	/* We got some line-args */

	    if (Realm == NULL) {
		Realm = create_new_realm();
		strcpy(Realm->realm, realm);
	    }

	    do {
		add_host_to_hostlist(Realm, *zhm_ext_argv);
		zhm_ext_argv++;
	    } while (*zhm_ext_argv != NULL);
	}

#ifdef Z_HaveHesiod
    sprintf(line, "zephyr@%s", realm);
    if (((serv_list = hes_resolve(line, "sloc")) != (char **) NULL) &&
	*serv_list != NULL) {
	/* We got positive Hesiod feedback! */

	if (Realm == NULL) {
	    Realm = create_new_realm();
	    strcpy(Realm->realm, realm);
	}

	/* This is just to make a new copy of everything locally, sort of.
	 * Hesiod doesnt free up the list, I dont think, so I can use it
	 * myself!  I just want to copy the string locally into the
	 * structure...  There probably a better way to do this!
	 */

	do {
	    add_host_to_hostlist(Realm, *serv_list);
	    serv_list++;
	} while (*serv_list != NULL);
    }
#endif Z_HaveHesiod 

    /* Lets check the file SERVER_LIST */

    if ((fd = fopen(SERVER_LIST, "r")) != NULL) {
	DPR("Looking in File...\n");
	for (ret = fgets(line, BUFSIZ, fd); ret != NULL; 
	     ret = fgets(line, BUFSIZ, fd)) {
	    if (!strncmp(realm, line, strlen(realm))) {
		/* this is it! */
		DPR2("Found Info for realm %s\n", realm);

		where = index(line, ' ');
		*where++ = '\0';

		if (Realm == NULL) {
		    Realm = create_new_realm();
		    strcpy(Realm->realm, realm);
		}

		/* Now lets parse our input line */
		do {		/* Get a hostname.  */
		    old_where = where;
		    if ((where = index(old_where, ' ')) != NULL) {
			/* Its completely in this 'line' */
			*where++ = '\0';
			host = (char *) malloc (strlen(old_where) +1);
			sprintf(host,"%s",old_where);
		    } else if ((where = index(old_where, '\n')) != NULL) {
			/* We got a return -- end of input */
			*where++ = '\0';
			host = (char *) malloc (strlen(old_where) +1);
			sprintf(host,"%s",old_where);
		    } else {	    
			/* This is continued.  Lets read in some more */
			host = (char *) malloc (line+BUFSIZ-old_where+1);
			strncpy(host, old_where, line+BUFSIZ-old_where);
			fgets(line, BUFSIZ, fd);
			if ((where = index(old_where, ' ')) != NULL) {
			    *where++ = '\0';
			    host = (char *) realloc (strlen(host) + 
						     strlen(old_where) +1);
			    strcat(host,line);
			} else if ((where = index(old_where, '\n')) != NULL) {
			    *where++ = '\0';
			    host = (char *) malloc (strlen(host) + 
						    strlen(old_where) +1);
			    strcat(host,line);
			}
		    }
		    add_host_to_hostlist(Realm, host);
		} while(*where != '\0');
	    }		/* if */

	}	/* for */
	
	fclose(fd);
    } /* if */

    if (Realm != NULL) {
	Realm->next = RealmList;
	RealmList = Realm;

	if (*Realm->hostlist == NULL) {	/* No Reasolved hosts! */
	    Realm->prim_serv = *(Realm->badhostlist);

	} else {
	    Realm->no_servers = 0;
	    Realm->prim_serv = *(Realm->hostlist);
	    Realm->cur_serv = Realm->prim_serv;
	
	    find_next_server(Realm, Realm->prim_serv);
	    send_boot_notice(Realm, HM_BOOT);
	    Realm->deactivated = 0;
	}

	/* Default to local realm if I cant do anything else */
    } else if (strcasecmp(realm, ZGetRealm())) 
	   Realm = ZGetRealmFromRealm(NULL);

    return(Realm);
}

static void
add_host_to_hostlist(Realm, host)
RealmEntry *Realm;
char *host;
{
    struct hostent *hp;

    if ((hp = gethostbyname(host)) == NULL) {
	/* Just log the errors. */
	syslog(LOG_ERR, "Unknown server name '%s'.", host);
	add_name_to_list(Realm->badhostlist, host);
    } else {
	add_name_to_list(Realm->hostlist, host);
    }

    DPR2("\t%s\n", host);
    return;
}

add_name_to_list(list, name)
char **list;
char *name;
{
    while (*list != NULL) {
	if (!strcasecmp(*list, name))
	    return 0;		/* its already in the list */
	list++;
    }
    *list = name;
    *++list = NULL;
    return 1;
}

remove_name_from_list(list, name)
char **list;
char *name;
{
    char **listp = list;
    int done = 0;

    while (*listp != NULL) {
	if (!strcasecmp(*listp,name)) {
	    done = 1;
	    listp++;	    
	}
	if (done)
	    *list = *listp;

	listp++;
	list++;
    }
    *list = NULL;
    return(done);
}

recheck_badhosts(Realm)
RealmEntry *Realm;
{
    struct hostent *hp;
    char **hostlist = Realm->badhostlist;
    char *host;

    for (host = *hostlist; host != NULL; host = *(++hostlist)) 
	if ((hp = gethostbyname(host)) != NULL) {
	    remove_name_from_list(Realm->badhostlist, host);
	    add_name_to_list(Realm->hostlist, host);
	}

    if (*Realm->hostlist != NULL) 
	Realm->no_servers = 0;

    Realm->lasthostcheck = time((time_t *)0);

    return;
}


static RealmEntry *
create_new_realm()
{
    RealmEntry *Realm;

    if ((Realm = (RealmEntry *) malloc(sizeof(RealmEntry))) == NULL)
	return (NULL);

    bzero(Realm->realm, sizeof(Realm->realm));
    bzero(Realm->hostlist, sizeof(Realm->hostlist));
    bzero(Realm->badhostlist, sizeof(Realm->badhostlist));
    bzero((char *)&Realm->host_sin, sizeof(struct sockaddr_in));
    Realm->cur_serv_list = Realm->hostlist;
    Realm->cur_serv = NULL;
    Realm->hp = &(Realm->host_sin);
    Realm->num_timeouts = 0;
    Realm->booting = 1;
    Realm->no_servers = 1;
    Realm->no_server = 1;
    Realm->deactivated = 1;
    Realm->nservchang = 0;
    Realm->nserv = 0;
    Realm->host_sin.sin_port = serv_port;
    Realm->host_sin.sin_family = AF_INET;

    /* Initialize Queue for this realm */
    if ((Realm->queue = (Qelem *) malloc (sizeof(Qelem))) == NULL) {
	free(Realm);
	return (NULL);
    }    
    Realm->queue->q_forw = Realm->queue;
    Realm->queue->q_back = Realm->queue;
    Realm->queue->q_data = NULL;
    init_queue(Realm->queue);

    Realm->lasthostcheck = time((time_t *)0);

    return(Realm);
}

ZStripDestRealm(notice)
ZNotice_t *notice;
{
  char *where;
  char *new_recip;

  new_recip = (char *) malloc (strlen(notice->z_recipient)+1);
  strcpy(new_recip, notice->z_recipient);
  
  if (*new_recip == '*') {
    new_recip[0] = '\0';
    
  } else if (((where = rindex(new_recip, '@')) != NULL) &&
	     (where > index(new_recip, '@'))) {
    *where = '\0';
  } 
  notice->z_recipient = new_recip;

  DPR2("Recipient is now == %s\n", notice->z_recipient);
  return(0);
}

RealmEntry *
ZGetRealmFromAddr(addr)
char *addr;
{
  RealmEntry *Realm;

  DPR2("Looking for Realm of %s\n", inet_ntoa(*(struct in_addr *)addr));

  for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
    DPR2("Is it equal to %s?\n", inet_ntoa(Realm->host_sin.sin_addr));
    if (!bcmp(addr, (char *)&Realm->host_sin.sin_addr, 4)) {
      DPR2("Got Realm:  %s\n", Realm->realm);
      return(Realm);
    }
  }
  return(NULL);
}

RealmEntry *
ZGetRealmFromNotice(notice)
ZNotice_t *notice;
{
    char *realm;

    realm = ZGetDestRealm(notice);
    return(ZGetRealmFromRealm(realm));
}


flush_all_servers()
{
    RealmEntry *Realm;

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	send_flush_notice(Realm, HM_FLUSH);
	Realm->deactivated = 1;
	init_queue(Realm->queue);
    }    
}

help_booting_servers()
{
    RealmEntry *Realm;

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	if (Realm->no_server)
	    if (Realm->num_timeouts++ > MAX_SERV_TIMEOUTS) {
		syslog(LOG_INFO, "Too long without a server in realm %s.",
		       Realm->realm);
		new_server(Realm, (char *)0);
		Realm->num_timeouts = 0;
	    }
    }    
}

help_resend_notices()
{
    RealmEntry *Realm;

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	DPR2("For realm %s, I am ", Realm->realm);
#ifdef DEBUG	
	dump_queue(Realm->queue);
#endif
	if (Realm->queue->q_forw != Realm->queue) {
	    DPR2("For realm %s, I am ", Realm->realm);
	    resend_notices(Realm->queue, Realm->hp);
	}
    }    
}

all_queues_empty()
{
    RealmEntry *Realm;

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	if (*Realm->badhostlist != NULL) 
	    if (Realm->lasthostcheck + RECHECKTIME <= time((time_t *)0))
		recheck_badhosts(Realm);

	if ((Realm->queue->q_forw != Realm->queue) || (Realm->booting)
	    || ((Realm->no_server) && (!Realm->no_servers)))
	    return(0);
    }
    return(1);
}

send_to_all_realms(notice, packet, pak_len)
ZNotice_t *notice;
ZPacket_t packet;
int pak_len;
{
    RealmEntry *Realm;

    for (Realm = RealmList; Realm != NULL; Realm = Realm->next) {
	transmission_tower(Realm, notice, packet, pak_len);
    }
}

/* List remote servers */
char *
list_remote_servers(type)
char* type;
{
  RealmEntry *ThisHost;
  char *host_list, *temp;
  char line[BUFSIZ];
  static char title[] = "\n\nRemote Realm               Server Name                Q   #pkts #chg\n------------               -----------                -   ----- ----";

  host_list = (char *) malloc (strlen(type) + strlen(title) +1);
  sprintf(host_list, "%s%s", type, title);

  for (ThisHost = RealmList; ThisHost != NULL; ThisHost = ThisHost->next) {
      sprintf(line, "\n%-27.26s%-27.26s%-4d%-6d%-d", 
	      ThisHost->realm, ThisHost->cur_serv,
	      queue_len(ThisHost->queue), ThisHost->nserv,
	      ThisHost->nservchang);

    if ((temp = (char *) realloc(host_list, strlen(host_list) + 
				 strlen(line) +3)) != NULL) {
				/* +3 is for \n, \t, pending and NULL!!
				 *  -- It's gotta be the [Null] shoes! --*/ 
	strcat (temp, line);
	host_list = temp;
    }
  }
  return(host_list);
}

