/* Copyright (c) 1993 Stephen F. White */

#include <stdio.h>
#ifdef SYSV
#include <string.h>
#else
#include <strings.h>
#endif
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/resource.h>
#include <sys/wait.h>
#include <signal.h>
#include <netdb.h>

#include "config.h"
#include "cool.h"
#include "proto.h"
#include "sys_proto.h"
#include "socket_proto.h"
#include "buf.h"
#include "netio.h"
#include "servers.h"
#include "servers_private.h"

#ifdef PROTO
extern struct hostent	*gethostbyname(const char *name);
extern struct hostent	*gethostbyaddr(GENPTR addr, int len, int type);
extern time_t		 time(time_t *t);
extern char		*ctime(time_t *t);
#endif /* PROTO */

Server	*servers = 0;
List	*server_list;

static int	 serverconfig(const char *line, int lineno);
static void	 serv_addtolist(Serverid id);
static void	 serv_addtocfg(Server *new);
static char	 configfilename[MAX_PATH_LEN];

static void
serv_addtocfg(Server *new)
{
    FILE	*f;

    if (!(f = fopen(configfilename, "a"))) {
	writelog();
	perror(configfilename);
    } else {
	fprintf(f, "server %s %s %d\n", new->name, new->hostname, new->port);
	fclose(f);
    }
}

#define MATCHES(A, B) (!cool_strncasecmp(A, B, strlen(B)))

int
read_config(const char *filename)
{
    char		 line[512];
    int			 lineno = 0;
    FILE		*configfile;
    int			 ok;

    strcpy(configfilename, filename);
    if (!(configfile = fopen(filename, "r"))) {
	writelog();
	perror(filename);
	return -1;
    }
    servers = 0;
    server_list = list_new(0);
    while (!feof(configfile)) {			/* while there's more input */
	if (!fgets(line, sizeof(line), configfile)) {	/* get a line */
	    break;
	}
	lineno++;	ok = 1;
	switch (line[0]) {
	  case '#':  case '\n':  case '\0':
	  /* if it starts with '#', or is blank, skip it */
	    break;
	  case 'c':	 /* cache, corefile */
	    if (MATCHES(line, "cache ")) {
		if (cacheconfig(line + 6, lineno)) {
		    fclose(configfile);
		    return -3;
		}
	    } else if (MATCHES(line, "corefile")) {
		corefile = 1;
	    } else {
		ok = 0;
	    }
	    break;
	  case 's':	 /* server */
	    if (MATCHES(line, "server ")) {
	        if (serverconfig(line + 7, lineno)) {
		    fclose(configfile);
		    return -2;
		}
	    } else {
		ok = 0;
	    }
	    break;
	  case 'm':	/* max_age, max_ticks */
	    if (MATCHES(line, "max_age ")) {
		max_age = atoi(line + 8);
	    } else if (MATCHES(line, "max_ticks ")) {
		max_ticks = atoi(line + 10);
	    } else {
		ok = 0;
	    }
	  case 'p':	/* player_port, promiscuous */
	    if (MATCHES(line, "player_port ")) {
		player_port = atoi(line + 12);
	    } else if (MATCHES(line, "promiscuous")) {
		promiscuous = 1;
	    } else {
		ok = 0;
	    }
	    break;
	  case 'r':	/* registration */
	    if (MATCHES(line, "registration")) {
		registration = 1;
	    } else {
		ok = 0;
	    }
	    break;
	  case 'v':	/* verify_servers */
	    if (MATCHES(line, "verify_servers")) {
		verify_servers = 1;
		continue;
	    } else {
		ok = 0;
	    }
	    break;
	  default:
	    ok = 0;
	}
	  /* unknown config command */
	if (!ok) {
	    writelog();
	    fprintf(stderr, "Couldn't read config file, line %d\n", lineno);
	    fclose(configfile);
	    return -4;
	}
    }
    if (!servers) {
	writelog();
	fprintf(stderr, "Must be at least one servers entry in config file\n");
	fclose(configfile);
	return -5;
    }
    yo_port = servers->port;
    fclose(configfile);
    return 0;
}

static int
serverconfig(const char *line, int lineno)
{
    Server		*s;
    struct hostent	*h;
    static Serverid	 sid = 0;
    static Server	*prev = 0;
    char		 name[21], hostname[41];
    short		 port;

    if (sscanf(line, "%20s %40s %hd", name, hostname, &port) != 3) {
	writelog();
	fprintf(stderr, "Bad server entry in config file, line %d\n", lineno);
	return -1;
    } else if (!(h = gethostbyname(hostname))) {
	writelog();
	fprintf(stderr, "Host '%s' not found, line %d\n", hostname, lineno);
	return -2;
    } else if (h->h_addrtype != AF_INET || h->h_length != 4) {
	writelog();
	fprintf(stderr, "Host '%s' not an Internet host, line %d\n",
		hostname, lineno);
	return -3;
    } else {
	s = MALLOC(Server, 1);
	strcpy(s->name, name);
	strcpy(s->hostname, hostname);
	s->port = port;
	s->addr = ntohl(*( (unsigned long *) h->h_addr_list[0]));
	s->id = sid++;
	s->last_msgid = -1;
	s->connected = 0;
	s->next = 0;
	if (prev) {
	    prev->next = s;
	} else {
	    servers = s;
	}
	prev = s;
	serv_addtolist(s->id);
    }
    return 0;
}

const char *
serv_id2name(Serverid  id)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (s->id >= 0 && s->id == id) {
	    return s->name;
	}
    }
    return "";
}

Serverid
serv_name2id(const char *name)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (!cool_strcasecmp(s->name, name)) {
	    return s->id;
	}
    }
    return -1;
}

void
serv_id2entry(Serverid server, char *buf)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (s->id == server) {
	    sprintf(buf, "%s %s %d\n", s->name, s->hostname, s->port);
	    return;
	}
    }
    buf[0] = '\0';
}

extern int	h_errno;

Server *
serv_add(struct sockaddr_in *from, const char *name)
{
    Server	*s, *prev, *new;
    struct hostent	*h;
    int		 newid = 0;

    if (serv_addr2server(from) || serv_name2id(name) >= 0) {
	return 0;
    }
    for (s = servers; s; s = s->next) {
	prev = s;
	if (s->id >= newid) {
	    newid = s->id + 1;
	}
    }

    new = MALLOC(Server, 1);

    new->addr = ntohl(from->sin_addr.s_addr);
    new->port = ntohs(from->sin_port);
    strncpy(new->name, name, 30);
    if (!(h = gethostbyaddr(&(from->sin_addr.s_addr), 
			    sizeof(from->sin_addr.s_addr), AF_INET))) {
	writelog();
	fprintf(stderr, "%s", addr_htoa(new->addr));
#ifdef HERROR
	herror( (char *) 0 );
#else
	fprintf(stderr, ":  gethostbyaddr() failed\n");
#endif
	FREE(new);
	return 0;
    }
    strncpy(new->hostname, h->h_name, 40);
    new->last_msgid = -1;
    writelog();

    fprintf(stderr, "SERVER %s (%s %d) added successfully.\n",
	    new->name, new->hostname, new->port);
    new->id = newid;
    serv_addtolist(newid);
    serv_addtocfg(new);

    if (prev) {
	prev->next = new;
    } else {
	servers = new;
    }

    return new;
}

static void
serv_addtolist(Serverid id)
{
    Var		 el;

    el.type = OBJ;
    el.v.obj.id = 0;	/* add #0@remoteserver */
    el.v.obj.server = id;
    server_list = list_setadd(server_list, el);
}

void
writelog(void)
{
    long	t = time ((time_t *) 0);
    char	*s = ctime((time_t *) &t);
    s[19] = '\0';		/* remove newline */

    fprintf(stderr, "%s:  ", s + 4);
}

Server
*serv_addr2server(struct sockaddr_in *sock)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (s->addr == ntohl(sock->sin_addr.s_addr)
	 && s->port == ntohs(sock->sin_port)) {
	    return s;
	}
    }
    return 0;
} 

Server
*serv_id2server(Serverid server)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (s->id == server) {
	    return s;
	}
    }
    return 0;
}

Server *
serv_name2server(const char *name)
{
    Server	*s;

    for (s = servers; s; s = s->next) {
	if (!cool_strcasecmp(s->name, name)) {
	    return s;
	}
    }
    return 0;
}

int
verify_server(Server *s, struct sockaddr_in *from)
{
    struct hostent	*h;

    if (!(h = gethostbyaddr(&from->sin_addr.s_addr,
			    sizeof(from->sin_addr.s_addr), AF_INET))) {
	writelog();
	fprintf(stderr, "Failed verify host %s as %s (%s %d)",
	    addr_htoa(ntohl(from->sin_addr.s_addr)), s->name, s->hostname,
	    s->port);
#ifdef HERROR
	herror( (char *) 0 );
#else
	fprintf(stderr, ":  gethostbyaddr() failed\n");
#endif
	return 0;
    } else if (cool_strcasecmp(h->h_name, s->hostname)
	    || ntohs(from->sin_port) != s->port) {
	writelog();
	fprintf(stderr, "Host %s %d tried to connect as server %s (%s %d)\n",
	  h->h_name, ntohs(from->sin_port), s->name, s->hostname, s->port);
	return 0;
    } else {
	return 1;
    }
}

/*
 * serv_discardmsg()
 *
 * Returns non-zero if the system should discard a received message.
 */

int
serv_discardmsg(Serverid server, int msgid)
{
    Server	*s = serv_id2server(server);

    if (!s) {
	return -1;
    }
    if (msgid > s->last_msgid) {
	s->last_msgid = msgid;
	return 0;
    } else {
	return 1;
    }
}