/*
 * server.c: Things dealing with server connections, etc. 
 *
 * Written By Michael Sandrof
 *
 * Copyright(c) 1990 
 *
 * See the COPYRIGHT file, or do a HELP IRCII COPYRIGHT 
 */

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include "irc.h"
#include "parse.h"
#include "build.h"

#include <stdarg.h>
#include <assert.h>

#include "server.h"
#include "ircaux.h"
#include "input.h"
#include "whois.h"
#include "lastlog.h"
#include "exec.h"
#include "window.h"
#include "output.h"
#include "names.h"
#include "hook.h"
#include "struct.h"
#include "vars.h"
#include "screen.h"
#include "notify.h"
#include "misc.h"
#include "status.h"
#include "fset.h"

/* for debug */
#define MODULE_ID XD_COMM

static char *set_umode(int du_index);
static void login_to_server(int, int);

const char *umodes = "abcdefghijklmnopqrstuvwxyz";

/* server_list: the list of servers that the user can connect to,etc */
struct server *server_list = NULL;

/* number_of_servers: in the server list */
int number_of_servers = 0;

extern WhoisQueue *WQ_head;
extern WhoisQueue *WQ_tail;

int primary_server = -1;
int from_server = -1;
int never_connected = 1;	/* true until first connection is made */
int connected_to_server = 0;	/* true when connection is confirmed */
char *connect_next_nick;
int parsing_server_index = -1;
int last_server = -1;

extern int doing_who;
extern int in_on_who;

#ifdef HAVE_SSL
extern int do_use_ssl;
#else
static const int do_use_ssl = 0;
#endif

/* What to use as a local address */
sa_addr_t *local_host_addr;

/* given a host and a port, make a sa_addr_t */
static sa_rc_t make_address(const char *host, int port, sa_addr_t **addr)
{
    char buffer[BIG_BUFFER_SIZE];
    sa_addr_t *naddr;
    sa_rc_t ret;

    assert(addr);
    assert(host);

    snprintf(buffer, BIG_BUFFER_SIZE, "inet://%s:%d", host, port);
    buffer[BIG_BUFFER_SIZE - 1] = '\0';

    sa_addr_create(&naddr);

    if ((ret = sa_addr_u2a(naddr, buffer)) != SA_OK) {
	say("Couldn't figure out address %s: %s", buffer, sa_error(ret));
	sa_addr_destroy(naddr);
    } else {
	*addr = naddr;
    }
    return ret;
}

/*
 * close_server: Given an index into the server list, this closes the
 * connection to the corresponding server.  It does no checking on the
 * validity of the index.  It also first sends a "QUIT" to the server being
 * closed 
 */
void close_server(int cs_index, char *message)
{
    int i, min, max;

    if (cs_index == -1) {
	min = 0;
	max = number_of_servers;
    } else {
	min = cs_index;
	max = cs_index + 1;
    }
    for (i = min; i < max; i++) {
	if (i == primary_server) {
	    int old_server = from_server;

	    /* make sure no leftover whois's are out there */
	    from_server = i;
	    clean_whois_queue();
	    from_server = old_server;
	    if (waiting_out > waiting_in)
		waiting_out = waiting_in = 0;
	    set_waiting_channel(i);
	} else {
	    int old_server = from_server;

	    from_server = -1;
	    clear_channel_list(i);
	    from_server = old_server;
	}
	if (split_watch) {
	    clear_link(&server_last);
	    clear_link(&tmplink);
	}
	server_list[i].operator = 0;
	server_list[i].connected = 0;
	server_list[i].pos = 0;
	server_list[i].close_serv = -1;
	new_free(&server_list[i].away);

	if (server_list[i].sock) {
	    if (message && *message) {
		sa_writef(server_list[i].sock, "QUIT :%s\n", message);
	    }
	    sa_shutdown(server_list[i].sock, "rw");
	    sa_destroy(server_list[i].sock);
	    server_list[i].sock = NULL;
	}
    }
}

/*
 * set_server_bits: Sets the proper bits in the fd_set structure according to
 * which servers in the server list have currently active read descriptors.  
 */
void set_server_bits(fd_set * rd, fd_set * wr)
{
    int i;

    for (i = 0; i < number_of_servers; i++) {
	if (server_list[i].sock) {
	    int fd;

	    sa_getfd(server_list[i].sock, &fd);
	    FD_SET(fd, rd);
	    if (!(server_list[i].flags & (LOGGED_IN | CLOSE_PENDING)))
		FD_SET(fd, wr);
	}
    }
}

int in_timed_server = 0;

int timed_server(void *args)
{
    char *p = (char *) args;
    static int retry = 0;

    if (!connected_to_server) {
	get_connected(atol(p));
	bitchsay("Servers exhausted. Restarting. [%d]", ++retry);
    }
    new_free(&p);
    in_timed_server = 0;
    return 0;
}

static int is_connected_p(int i)
{
    if (server_list[i].sock) {
	int des;
	struct sockaddr_in sa;
	socklen_t sa_len = sizeof(struct sockaddr_in);

	sa_getfd(server_list[i].sock, &des);
	if (getpeername(des, (struct sockaddr *) &sa, &sa_len) != -1)
	    return 1;
    }
    return 0;
}

static int read_data(int i, time_t * last_timeout)
{
    static int times = 0;
    char *buf = server_list[i].line;
    int pos = server_list[i].pos;
    size_t done = 0;
    sa_rc_t ret = sa_readln(server_list[i].sock, buf + pos, SERVER_BUF_LEN - pos, &done);

    last_server = from_server = i;
    switch (ret) {
    case SA_OK:
	*last_timeout = 0;
	parsing_server_index = i;
	server_list[i].last_msg = time(NULL);
	parse_server(buf);
	server_list[i].pos = 0;
	parsing_server_index = -1;
	message_from(NULL, LOG_CRAP);
	return 1;

    case SA_ERR_TMT:
	server_list[from_server].pos = pos+done;
	return 0;

    default:
	{
	    int old_serv = server_list[i].close_serv;

	    close_server(i, empty_str);
	    *last_timeout = 0;
	    say("Connection closed from %s: %s", server_list[i].name, sa_error(ret));
	    server_list[i].connected = 0;
	    sa_destroy(server_list[i].sock);
	    server_list[i].sock = NULL;

	    if (!(server_list[i].flags & LOGGED_IN)) {
		if (old_serv == i)	/* a hack? you bet */
		    goto a_hack;
		if (old_serv != -1 && (server_list[old_serv].flags & CLOSE_PENDING)) {
		    say("Connection to server %s resumed...", server_list[old_serv].name);
		    server_list[i].close_serv = -1;
		    server_list[old_serv].flags &= ~CLOSE_PENDING;
		    server_list[old_serv].flags |= LOGGED_IN;
		    server_list[old_serv].connected = 1;
		    get_connected(old_serv);
		    return 0;
		}
	    }
	  a_hack:

	    if (i == primary_server) {
		if (server_list[i].eof) {
		    put_it("%s",
			   convert_output_format(get_fset_var(FORMAT_DISCONNECT_FSET), "%s %s", update_clock(GET_TIME),
						 "Unable to connect to a server"));
		    if (++i >= number_of_servers) {
			clean_whois_queue();
			times = 0;
			i = 0;
		    }
		    window_check_servers();
		    check_server_connect(-1);
		    return 0;
		} else {
		    if (times++ > 3) {
			clean_whois_queue();
			if (do_hook(DISCONNECT_LIST, "No Connection"))
			    put_it("%s",
				   convert_output_format(get_fset_var(FORMAT_DISCONNECT_FSET), "%s %s", update_clock(GET_TIME),
							 "No connection"));
			times = 0;
		    }
		    window_check_servers();
		    check_server_connect(-1);
		    return 0;
		}
	    } else if (server_list[i].eof) {
		say("Connection to server %s lost.", server_list[i].name);
		close_server(i, empty_str);
		clean_whois_queue();
		window_check_servers();
	    } else if (connect_to_server_by_refnum(i, -1)) {
		say("Connection to server %s lost.", server_list[i].name);
		close_server(i, empty_str);
		clean_whois_queue();
		window_check_servers();
	    }
	    server_list[i].eof = 1;
	    return 0;
	}
    }
    from_server = primary_server;
    return 0;
}

/*
 * do_server: check the given fd_set against the currently open servers in
 * the server list.  If one have information available to be read, it is read
 * and and parsed appropriately.  If an EOF is detected from an open server,
 * one of two things occurs. 1) If the server was the primary server,
 * get_connected() is called to maintain the connection status of the user.
 * 2) If the server wasn't a primary server, connect_to_server() is called to
 * try to keep that connection alive. 
 */
void do_server(fd_set * rd, fd_set * wr)
{
    int des, i;
    static time_t last_timeout = 0;

    for (i = 0; i < number_of_servers; i++) {
	if (server_list[i].sock == NULL)
	    continue;
	sa_getfd(server_list[i].sock, &des);
	if (FD_ISSET(des, wr) && !(server_list[i].flags & LOGGED_IN)) {
	    if (is_connected_p(i))
		login_to_server(i, -1);
	}
	if (FD_ISSET(des, rd)) {
	    while (read_data(i, &last_timeout)) ;
	}
	if (errno == ENETUNREACH || errno == EHOSTUNREACH) {
	    if (last_timeout == 0)
		last_timeout = time(NULL);
	    else if (time(NULL) - last_timeout > 600) {
		close_server(i, empty_str);
		get_connected(i);
	    }
	}

    }
}

/*
 * find_in_server_list: given a server name, this tries to match it against
 * names in the server list, returning the index into the list if found, or
 * -1 if not found 
 */
extern int find_in_server_list(char *server, int port)
{
    int i, len, len2;

    len = strlen(server);
    for (i = 0; i < number_of_servers; i++) {
	if (port && server_list[i].port && port != server_list[i].port && port != -1)
	    continue;

	len2 = strlen(server_list[i].name);
	if (len2 > len) {
	    if (!my_strnicmp(server, server_list[i].name, len))
		return (i);
	} else {
	    if (!my_strnicmp(server, server_list[i].name, len2))
		return (i);
	}

	if (server_list[i].itsname) {
	    len2 = strlen(server_list[i].itsname);
	    if (len2 > len) {
		if (!my_strnicmp(server, server_list[i].itsname, len))
		    return (i);
	    } else {
		if (!my_strnicmp(server, server_list[i].name, len2))
		    return (i);
	    }
	}
    }
    return (-1);
}

/*
 * parse_server_index:  given a string, this checks if it's a number, and if
 * so checks it validity as a server index.  Otherwise -1 is returned 
 */
int parse_server_index(char *str)
{
    int i;

    if (is_number(str)) {
	i = my_atol(str);
	if ((i >= 0) && (i < number_of_servers))
	    return (i);
    }
    return (-1);
}

/*
 * This replaces ``get_server_index''.
 */
int find_server_refnum(char *server, char **rest)
{
    int refnum;
    int port = irc_port;
    char *cport = NULL, *password = NULL, *nick = NULL;

    /* 
     * First of all, check for an existing server refnum
     */
    if ((refnum = parse_server_index(server)) != -1)
	return refnum;

    /* 
     * Next check to see if its a "server:port:password:nick"
     */
    else if (index(server, ':'))
	parse_server_info(server, &cport, &password, &nick);

    /* 
     * Next check to see if its "server port password nick"
     */
    else if (rest && *rest) {
	cport = next_arg(*rest, rest);
	password = next_arg(*rest, rest);
	nick = next_arg(*rest, rest);
    }

    if (cport && *cport)
	port = my_atol(cport);

    /* 
     * Add to the server list (this will update the port
     * and password fields).
     */
    add_to_server_list(server, port, password, nick, 1);
    return from_server;
}

/*
 * add_to_server_list: adds the given server to the server_list.  If the
 * server is already in the server list it is not re-added... however, if the
 * overwrite flag is true, the port and passwords are updated to the values
 * passes.  If the server is not on the list, it is added to the end. In
 * either case, the server is made the current server. 
 */
void add_to_server_list(char *server, int port, char *password, char *nick, int overwrite)
{
    if ((from_server = find_in_server_list(server, port)) == -1) {
	from_server = number_of_servers++;
	server_list = new_realloc(server_list, struct server, number_of_servers - 1, number_of_servers + 1);

	server_list[from_server].name = m_strdup(server);
	server_list[from_server].sock = NULL;
	server_list[from_server].lag = -1;
	server_list[from_server].motd = 1;
	server_list[from_server].port = port;
	server_list[from_server].pos = 0;
	server_list[from_server].index = from_server;
#ifdef HAVE_SSL
	server_list[from_server].enable_ssl = do_use_ssl;
#endif         


	if (password && *password)
	    malloc_strcpy(&(server_list[from_server].password), password);

	if (nick && *nick)
	    malloc_strcpy(&(server_list[from_server].d_nickname), nick);
	else if (!server_list[from_server].d_nickname)
	    malloc_strcpy(&(server_list[from_server].d_nickname), nickname);

	make_notify_list(from_server);
	set_umode(from_server);
    } else {
	if (overwrite) {
	    server_list[from_server].port = port;
	    if (password || !server_list[from_server].password) {
		if (password && *password)
		    malloc_strcpy(&(server_list[from_server].password), password);
		else
		    new_free(&(server_list[from_server].password));
	    }
	    if (nick || !server_list[from_server].d_nickname) {
		if (nick && *nick)
		    malloc_strcpy(&(server_list[from_server].d_nickname), nick);
		else
		    new_free(&(server_list[from_server].d_nickname));
	    }
	}
	if (strlen(server) > strlen(server_list[from_server].name))
	    malloc_strcpy(&(server_list[from_server].name), server);
    }
}

void remove_from_server_list(int i)
{
    int old_server = from_server;
    Window *tmp;

    if (i < 0 || i >= number_of_servers)
	return;

    say("Deleting server [%d]", i);

    from_server = i;
    clean_whois_queue();
    from_server = old_server;

    new_free(&server_list[i].name);
    new_free(&server_list[i].itsname);
    new_free(&server_list[i].password);
    new_free(&server_list[i].away);
    new_free(&server_list[i].version_string);
    new_free(&server_list[i].nickname);
    new_free(&server_list[i].s_nickname);
    new_free(&server_list[i].d_nickname);

    new_free(&server_list[i].whois_stuff.nick);
    new_free(&server_list[i].whois_stuff.user);
    new_free(&server_list[i].whois_stuff.host);
    new_free(&server_list[i].whois_stuff.channel);
    new_free(&server_list[i].whois_stuff.channels);
    new_free(&server_list[i].whois_stuff.name);
    new_free(&server_list[i].whois_stuff.server);
    new_free(&server_list[i].whois_stuff.server_stuff);

    /* 
     * this should save a coredump.  If number_of_servers drops
     * down to zero, then trying to do a realloc ends up being
     * a free, and accessing that is a no-no.
     */
    if (number_of_servers == 1) {
	say("Sorry, the server list is empty and I just dont know what to do.");
	irc_exit("Sorry, the server list is empty and I just dont know what to do.", NULL);
    }

    memmove(&server_list[i], &server_list[i + 1], (number_of_servers - i) * sizeof(struct server));
    number_of_servers--;
    server_list = new_realloc(server_list, struct server, number_of_servers + 1, number_of_servers);

    /* update all he structs with server in them */
    channel_server_delete(i);
    exec_server_delete(i);
    if (i < primary_server)
	--primary_server;
    if (i < from_server)
	--from_server;
    tmp = NULL;
    while (traverse_all_windows(&tmp))
	if (tmp->server > i)
	    tmp->server--;
}

/*
 * parse_server_info:  This parses a single string of the form
 * "server:portnum:password:nickname".  It the points port to the portnum
 * portion and password to the password portion.  This chews up the original
 * string, so * upon return, name will only point the the name.  If portnum
 * or password are missing or empty,  their respective returned value will
 * point to null. 
 */
void parse_server_info(char *name, char **port, char **password, char **nick)
{
    char *ptr;

    *port = *password = *nick = NULL;
    if ((ptr = (char *) index(name, ':')) != NULL) {
	*(ptr++) = (char) 0;
	if (*ptr) {
	    *port = ptr;
	    if ((ptr = (char *) index(ptr, ':')) != NULL) {
		*(ptr++) = (char) 0;
		if (*ptr) {
		    *password = ptr;
		    if ((ptr = (char *) index(ptr, ':')) != NULL) {
			*(ptr++) = '\0';
			if (*ptr)
			    *nick = ptr;
		    }
		}
	    }
	}
    }
}

/*
 * build_server_list: given a whitespace separated list of server names this
 * builds a list of those servers using add_to_server_list().  Since
 * add_to_server_list() is used to added each server specification, this can
 * be called many many times to add more servers to the server list.  Each
 * element in the server list case have one of the following forms: 
 *
 * servername 
 *
 * servername:port 
 *
 * servername:port:password 
 *
 * servername::password 
 *
 * Note also that this routine mucks around with the server string passed to it,
 * so make sure this is ok 
 */
void build_server_list(char *servers)
{
    char *host, *rest, *password = NULL, *port = NULL, *nick = NULL;
    int port_num;

    if (servers == NULL)
	return;

    while (servers) {
	if ((rest = (char *) index(servers, '\n')) != NULL)
	    *rest++ = '\0';
	while ((host = next_arg(servers, &servers)) != NULL) {
	    parse_server_info(host, &port, &password, &nick);
	    if (port && *port) {
		port_num = my_atol(port);
		if (!port_num)
		    port_num = irc_port;
	    } else
		port_num = irc_port;

	    add_to_server_list(host, port_num, password, nick, 0);
	}
	servers = rest;
    }
}

#ifdef HAVE_SSL
static void SSL_show_errors(void)
{       
    char buf[1000];
    int sslerr;

    while((sslerr = ERR_get_error()))
    {       
	ERR_error_string(sslerr, buf);
	say("%s", buf);
    }
}

static ssize_t ssl_read_fn(void *ctx, int fd, void *data, size_t len)
{
    return SSL_read(ctx, data, len);
}

static ssize_t ssl_write_fn(void *ctx, int fd, const void *data, size_t len)
{
    return SSL_write(ctx, data, len);
}
#endif


static void login_to_server(int refnum, int c_server)
{
    sa_buffer(server_list[refnum].sock, SA_BUFFER_READ, 1024);
    sa_timeout(server_list[refnum].sock, SA_TIMEOUT_READ, 0, 400);
    sa_addr_create(&server_list[refnum].lcl_addr);
    if (sa_getlocal(server_list[refnum].sock, &server_list[refnum].lcl_addr) != SA_OK) {
	sa_addr_destroy(server_list[refnum].lcl_addr);
	server_list[refnum].lcl_addr = NULL;
    }

    if ((c_server != -1) && (c_server != refnum)) {
	server_list[c_server].flags &= ~CLOSE_PENDING;
	close_server(c_server, "changing servers");
    }

#ifdef HAVE_SSL
    if(get_server_ssl(refnum)) {       
	int err = 0;

	/* XXX TODO error handling here sucks */
	if(!server_list[refnum].ctx) {       
	    int fd;
	    sa_getfd(server_list[refnum].sock, &fd);
	    set_blocking(fd);
	    server_list[refnum].ctx = SSL_CTX_new (SSLv23_client_method());
	    if (server_list[refnum].ctx == NULL) {
		say("ssl init failed");
		close_server(refnum, empty_str);
		return;
	    }
	    server_list[refnum].ssl_fd = SSL_new (server_list[refnum].ctx);
	    if (server_list[refnum].ssl_fd == NULL) {
		say("ssl new failed");
		close_server(refnum, empty_str);
		return; 
	    }
	    SSL_set_fd (server_list[refnum].ssl_fd, fd);

	    sa_syscall(server_list[refnum].sock, SA_SYSCALL_READ, (void *)&ssl_read_fn, server_list[refnum].ssl_fd);
	    sa_syscall(server_list[refnum].sock, SA_SYSCALL_WRITE, (void *)&ssl_write_fn, server_list[refnum].ssl_fd);
	}
	err = SSL_connect (server_list[refnum].ssl_fd);
	if(err == -1) {       
	    server_list[refnum].ssl_error = SSL_get_error((SSL *)server_list[refnum].ssl_fd, err);
	    if(server_list[refnum].ssl_error == SSL_ERROR_WANT_READ || server_list[refnum].ssl_error == SSL_ERROR_WANT_WRITE) {
		return;
	    }
	}
	SSL_show_errors();
	say("SSL server connected");
    }
#endif                                                                                                                                    

    if (!server_list[refnum].d_nickname)
	malloc_strcpy(&(server_list[refnum].d_nickname), nickname);

    server_list[refnum].flags |= LOGGED_IN;
    if (server_list[refnum].password)
	send_to_server(SERVER(refnum), "PASS %s", server_list[refnum].password);
    register_server(refnum, server_list[refnum].d_nickname);

    server_list[refnum].last_msg = time(NULL);
    in_on_who = 0;
    doing_who = 0;
    server_list[refnum].connected = 1;
    *server_list[refnum].umode = 0;
    server_list[refnum].operator = 0;
    set_umode(refnum);
}

int connect_to_server_by_refnum(int refnum, int c_server)
{
    char *sname;
    int sport;

    if (refnum == -1) {
	say("Connecting to refnum -1.  That makes no sense.");
	return -1;
    }

    sname = server_list[refnum].name;
    if ((sport = server_list[refnum].port) == -1)
	sport = irc_port;

    if (server_list[refnum].sock == NULL) {
	sa_rc_t ret;

	from_server = refnum;
	if (local_host_name) {
	    char *uri;
	    sa_addr_a2u(local_host_addr, &uri);
	    say("Using local host name %s (%s)", local_host_name, uri);
	    free(uri);
	}
	say("Connecting to port %d of server %s", sport, sname);

	if (!server_list[refnum].rem_addr) {
	    sa_addr_destroy(server_list[refnum].rem_addr);
	}
	if (!server_list[refnum].lcl_addr) {
	    sa_addr_destroy(server_list[refnum].lcl_addr);
	    server_list[refnum].lcl_addr = NULL;
	}

	if ((ret = make_address(sname, sport, &server_list[refnum].rem_addr)) != SA_OK) {
	    say("Couldn't figure out address %s:%d: %s", sname, sport, sa_error(ret));
	    return -1;
	}
	sa_create(&server_list[refnum].sock);
	if (local_host_name) {
	    if ((ret = sa_bind(server_list[refnum].sock, local_host_addr))) {
		yell("can't bind %s: %s", local_host_name, sa_error(ret));
		sa_destroy(server_list[refnum].sock);
		server_list[refnum].sock = NULL;
		return -1;
	    }
	}
	if ((ret = sa_connect(server_list[refnum].sock, server_list[refnum].rem_addr)) != SA_OK) {
	    say("Couldn't connect to %s:%d: %s", sname, sport, sa_error(ret));
	    sa_destroy(server_list[refnum].sock);
	    server_list[refnum].sock = NULL;
	    return -1;
	}
	server_list[refnum].flags &= ~LOGGED_IN;
	if (is_connected_p(refnum))
	    login_to_server(refnum, c_server);
    } else {
	say("Connected to port %d of server %s", sport, sname);
	from_server = refnum;
    }
    message_from(NULL, LOG_CRAP);
    update_all_status(curr_scr_win, NULL, 0);
    return 0;
}

/*
 * get_connected: This function connects the primary server for IRCII.  It
 * attempts to connect to the given server.  If this isn't possible, it
 * traverses the server list trying to keep the user connected at all cost.  
 */
void get_connected(int server)
{
    int s, ret = -1;

    if (server_list) {
	if (server == number_of_servers)
	    server = 0;
	else if (server < 0)
	    server = number_of_servers - 1;

	s = server;
	if (connect_to_server_by_refnum(server, primary_server)) {
	    while (server_list[server].sock == NULL) {
		server++;
		if (server == number_of_servers) {
		    server = 0;
		    break;
		}
		if (server == s) {
		    clean_whois_queue();
		    break;
		}
		from_server = server;
		ret = connect_to_server_by_refnum(server, primary_server);
	    }
	    if (!ret)
		from_server = server;
	    else
		from_server = -1;
	}
	change_server_channels(primary_server, from_server);
	set_window_server(-1, from_server, 1);
    } else {
	clean_whois_queue();
	if (do_hook(DISCONNECT_LIST, "No Server List"))
	    put_it("%s",
		   convert_output_format(get_fset_var(FORMAT_DISCONNECT_FSET), "%s %s", update_clock(GET_TIME),
					 "You are not connected to a server. Use /SERVER to connect."));
    }
}

/*
 * read_server_file: reads hostname:portnum:password server information from
 * a file and adds this stuff to the server list.  See build_server_list()/ 
 */
int read_server_file(char *servers_file)
{
    FILE *fp;
    int some = 0;
    char *file_path = NULL;
    char buffer[BIG_BUFFER_SIZE + 1];
    int old_window_display = window_display;

    window_display = 0;
#ifdef SERVERS_FILE
    malloc_sprintf(&file_path, "%s/%s", XARIC_DATA_PATH, SERVERS_FILE);
    if ((fp = fopen(file_path, "r"))) {
	while (fgets(buffer, BIG_BUFFER_SIZE, fp)) {
	    chop(buffer, 1);
	    build_server_list(buffer);
	}
	fclose(fp);
    } else
	some = 1;
    new_free(&file_path);
#endif
    malloc_sprintf(&file_path, "~/%s", servers_file);
    if ((fp = uzfopen(&file_path, "."))) {
	while (fgets(buffer, BIG_BUFFER_SIZE, fp)) {
	    chop(buffer, 1);
	    build_server_list(buffer);
	}
	fclose(fp);
	some = 0;
    }
    new_free(&file_path);
    window_display = old_window_display;
    return (some ? 0 : 1);
}

/* display_server_list: just guess what this does */
void display_server_list(void)
{
    int i;

    if (server_list) {
	if (from_server != -1)
	    say("Current server: %s %d", server_list[from_server].name, server_list[from_server].port);
	else
	    say("Current server: <None>");

	if (primary_server != -1)
	    say("Primary server: %s %d", server_list[primary_server].name, server_list[primary_server].port);
	else
	    say("Primary server: <None>");

	say("Server list:");
	for (i = 0; i < number_of_servers; i++) {
	    if (!server_list[i].nickname) {
		if (server_list[i].sock == NULL)
		    say("\t%d) %s %d", i, server_list[i].name, server_list[i].port);
		else
		    say("\t%d) %s %d", i, server_list[i].name, server_list[i].port);
	    } else {
		if (server_list[i].sock == NULL)
		    say("\t%d) %s %d (was %s)", i, server_list[i].name, server_list[i].port, server_list[i].nickname);
		else
		    say("\t%d) %s %d (%s)", i, server_list[i].name, server_list[i].port, server_list[i].nickname);
	    }
	}
    } else
	say("The server list is empty");
}

/*
 * set_server_password: this sets the password for the server with the given
 * index.  If password is null, the password for the given server is returned 
 */
char *set_server_password(int ssp_index, char *password)
{

    if (server_list) {
	if (password)
	    malloc_strcpy(&(server_list[ssp_index].password), password);
	return (server_list[ssp_index].password);
    } else
	return (NULL);
}

/* server_list_size: returns the number of servers in the server list */
int server_list_size(void)
{
    return (number_of_servers);
}

/*
 * flush_server: eats all output from server, until there is at least a
 * second delay between bits of servers crap... useful to abort a /links. 
 */
void flush_server(void)
{
    fd_set rd;
    struct timeval timeout;
    int flushing = 1;
    int des;
    int old_timeout;
    char buffer[BIG_BUFFER_SIZE + 1];

    if (server_list[from_server].sock == NULL)
	return;

    sa_getfd(server_list[from_server].sock, &des);
    timeout.tv_usec = 0;
    timeout.tv_sec = 1;
    old_timeout = dgets_timeout(1);
    while (flushing) {
	FD_ZERO(&rd);
	FD_SET(des, &rd);
	switch (new_select(&rd, NULL, &timeout)) {
	case -1:
	case 0:
	    flushing = 0;
	    break;
	default:
	    if (FD_ISSET(des, &rd)) {
		if (!dgets(buffer, BIG_BUFFER_SIZE, des, NULL))
		    flushing = 0;
	    }
	    break;
	}
    }
    /* make sure we've read a full line from server */
    FD_ZERO(&rd);
    FD_SET(des, &rd);
    if (new_select(&rd, NULL, &timeout) > 0)
	dgets(buffer, BIG_BUFFER_SIZE, des, NULL);
    (void) dgets_timeout(old_timeout);
}

static char *set_umode(int du_index)
{
    char *c = server_list[du_index].umode;
    int flags = server_list[du_index].flags;
    int i;

    for (i = 0; umodes[i]; i++) {
	if (umodes[i] == 'o')
	    continue;
	if (flags & USER_MODE << i)
	    *c++ = umodes[i];
    }
    if (get_server_operator(du_index))
	*c++ = 'O';
    *c = 0;
    return server_list[du_index].umode;
}

char *get_umode(int gu_index)
{
    if (gu_index == -1)
	gu_index = primary_server;
    else if (gu_index >= number_of_servers)
	return empty_str;

    return server_list[gu_index].umode;
}

/*
 * Encapsulates everything we need to change our AWAY status.
 * This improves greatly on having everyone peek into that member.
 * Also, we can deal centrally with someone changing their AWAY
 * message for a server when we're not connected to that server
 * (when we do connect, then we send out the AWAY command.)
 * All this saves a lot of headaches and crashes.
 */
void set_server_away(int ssa_index, char *message)
{
    int old_from_server = from_server;

    from_server = ssa_index;
    if (ssa_index == -1)
	say("You are not connected to a server.");

    else if (message && *message) {
	if (!server_list[ssa_index].away)
	    away_set++;
	if (server_list[ssa_index].away != message)
	    malloc_strcpy(&server_list[ssa_index].away, message);

	if (!server_list[ssa_index].awaytime)
	    server_list[ssa_index].awaytime = time(NULL);

	if (get_int_var(MSGLOG_VAR))
	    log_toggle(1, NULL);
	if (!server_list[ssa_index].connected) {
	    from_server = old_from_server;
	    return;
	}
	if (get_fset_var(FORMAT_AWAY_FSET) && get_int_var(SEND_AWAY_MSG_VAR)) {
	    struct channel *chan;

	    for (chan = server_list[ssa_index].chan_list; chan; chan = chan->next) {
		send_to_server(SERVER(from_server), "PRIVMSG %s :ACTION %s", chan->channel,
			       stripansicodes(convert_output_format
					      (get_fset_var(FORMAT_AWAY_FSET), "%s %s", update_clock(GET_TIME), message)));
	    }
	}
	if (get_fset_var(FORMAT_AWAY_FSET)) {
	    char buffer[BIG_BUFFER_SIZE + 1];

	    send_to_server(SERVER(from_server), "%s :%s", "AWAY",
			   stripansicodes(convert_output_format
					  (get_fset_var(FORMAT_AWAY_FSET), "%s %s", update_clock(GET_TIME), message)));
	    strncpy(buffer, convert_output_format("%K*** %W$N%n ", NULL, NULL), BIG_BUFFER_SIZE);
	    strncat(buffer,
		    convert_output_format(get_fset_var(FORMAT_AWAY_FSET), "%s %s", update_clock(GET_TIME), message), BIG_BUFFER_SIZE);
	    put_it("%s", buffer);
	} else
	    send_to_server(SERVER(from_server), "%s :%s", "AWAY", stripansicodes(convert_output_format(message, NULL)));
    } else {
	if (server_list[ssa_index].away)
	    away_set--;
	server_list[ssa_index].awaytime = 0;
	new_free(&server_list[ssa_index].away);
	if (server_list[ssa_index].connected)
	    send_to_server(SERVER(from_server), "AWAY :");
    }
    from_server = old_from_server;
}

char *get_server_away(int gsa_index)
{
    if (gsa_index == -1)
	return NULL;
    return server_list[gsa_index].away;
}

void set_server_flag(int ssf_index, int flag, int value)
{
    if (ssf_index == -1)
	ssf_index = primary_server;
    else if (ssf_index >= number_of_servers)
	return;

    if (value)
	server_list[ssf_index].flags |= flag;
    else
	server_list[ssf_index].flags &= ~flag;

    set_umode(ssf_index);
}

int get_server_flag(int gsf_index, int value)
{
    if (gsf_index == -1)
	gsf_index = primary_server;
    else if (gsf_index >= number_of_servers)
	return 0;

    return server_list[gsf_index].flags & value;
}

/* get_server_name: returns the name for the given server index */
char *get_server_name(int gsn_index)
{
    if (gsn_index == -1)
	gsn_index = primary_server;
    if (gsn_index == -1 || gsn_index >= number_of_servers)
	return empty_str;

    return (server_list[gsn_index].name);
}

/* get_server_itsname: returns the server's idea of its name */
char *get_server_itsname(int gsi_index)
{
    if (gsi_index == -1)
	gsi_index = primary_server;
    else if (gsi_index >= number_of_servers)
	return empty_str;

    /* better check gsi_index for -1 here CDE */
    if (gsi_index == -1)
	return empty_str;

    if (server_list[gsi_index].itsname)
	return server_list[gsi_index].itsname;
    else
	return server_list[gsi_index].name;
}

void set_server_itsname(int ssi_index, char *name)
{
    if (ssi_index == -1)
	ssi_index = primary_server;
    else if (ssi_index >= number_of_servers)
	return;

    malloc_strcpy(&server_list[ssi_index].itsname, name);
}

/*
 * is_server_open: Returns true if the given server index represents a server
 * with a live connection, returns false otherwise 
 */
int is_server_open(int iso_index)
{
    if (iso_index < 0 || iso_index >= number_of_servers)
	return (0);
    return !!server_list[iso_index].sock;
}

/*
 * is_server_connected: returns true if the given server is connected.  This
 * means that both the tcp connection is open and the user is properly
 * registered 
 */
int is_server_connected(int isc_index)
{
    if (isc_index >= 0 && isc_index < number_of_servers)
	return (server_list[isc_index].connected);
    return 0;
}

/* get_server_port: Returns the connection port for the given server index */
int get_server_port(int gsp_index)
{
    if (gsp_index == -1)
	gsp_index = primary_server;
    else if (gsp_index >= number_of_servers)
	return 0;

    return (server_list[gsp_index].port);
}

/*
 * get_server_nickname: returns the current nickname for the given server
 * index 
 */
char *get_server_nickname(int gsn_index)
{
    if (gsn_index >= number_of_servers)
	return empty_str;
    else if (gsn_index != -1 && server_list[gsn_index].nickname)
	return (server_list[gsn_index].nickname);
    else
	return "<Not-registered-yet>";
}

/* get_server_qhead - get the head of the whois queue */
WhoisQueue *get_server_qhead(int gsq_index)
{
    if (gsq_index == -1)
	return WQ_head;
    else if (gsq_index >= number_of_servers)
	return NULL;

    return server_list[gsq_index].WQ_head;
}

/* get_server_whois_stuff */
WhoisStuff *get_server_whois_stuff(int gsw_index)
{
    if (gsw_index == -1)
	gsw_index = primary_server;
    else if (gsw_index >= number_of_servers)
	return NULL;

    return &server_list[gsw_index].whois_stuff;
}

/* get_server_qtail - get the tail of the whois queue */
WhoisQueue *get_server_qtail(int gsq_index)
{
    if (gsq_index == -1)
	return WQ_tail;
    else if (gsq_index < number_of_servers)
	return server_list[gsq_index].WQ_tail;

    return NULL;
}

/* set_server_qhead - set the head of the whois queue */
void set_server_qhead(int ssq_index, WhoisQueue * value)
{
    if (ssq_index == -1)
	WQ_head = value;
    else if (ssq_index < number_of_servers)
	server_list[ssq_index].WQ_head = value;

    return;
}

/* set_server_qtail - set the tail of the whois queue */
void set_server_qtail(int ssq_index, WhoisQueue * value)
{
    if (ssq_index == -1)
	WQ_tail = value;
    else if (ssq_index < number_of_servers)
	server_list[ssq_index].WQ_tail = value;

    return;
}

/*
 * get_server_operator: returns true if the user has op privs on the server,
 * false otherwise 
 */
int get_server_operator(int gso_index)
{
    if ((gso_index < 0) || (gso_index >= number_of_servers))
	return 0;
    return (server_list[gso_index].operator);
}

/*
 * set_server_operator: If flag is non-zero, marks the user as having op
 * privs on the given server.  
 */
void set_server_operator(int sso_index, int flag)
{
    if (sso_index < 0 || sso_index >= number_of_servers)
	return;
    server_list[sso_index].operator = flag;
    oper_command = 0;
    set_umode(sso_index);
}

#ifdef HAVE_SSL
void set_server_ssl(int idx, int val)
{
    server_list[idx].enable_ssl = val;
}

int get_server_ssl(int idx)
{
    return server_list[idx].enable_ssl;
}
#endif /* HAVE_SSL */

/*
 * set_server_nickname: sets the nickname for the given server to nickname.
 * This nickname is then used for all future connections to that server
 * (unless changed with NICK while connected to the server 
 */
void set_server_nickname(int ssn_index, char *nick)
{
    if (ssn_index != -1 && ssn_index < number_of_servers) {
	malloc_strcpy(&(server_list[ssn_index].nickname), nick);
	if (ssn_index == primary_server)
	    strmcpy(nickname, nick, NICKNAME_LEN);
    }
    update_all_status(curr_scr_win, NULL, 0);
}

void register_server(int ssn_index, char *nick)
{
    int old_from_server = from_server;

    send_to_server(SERVER(from_server), "USER %s %s %s :%s", username,
		   (send_umode && *send_umode) ? send_umode :
		   local_host_name ? local_host_name : def_hostname, username, *realname ? realname : space_str);
    if (nick)
	change_server_nickname(SERVER(ssn_index), nick);
    from_server = old_from_server;
}

void set_server_motd(int ssm_index, int flag)
{
    if (ssm_index != -1 && ssm_index < number_of_servers)
	server_list[ssm_index].motd = flag;
}

int get_server_lag(int gso_index)
{
    if ((gso_index < 0 || gso_index >= number_of_servers))
	return 0;
    return (server_list[gso_index].lag);
}

int get_server_motd(int gsm_index)
{
    if (gsm_index != -1 && gsm_index < number_of_servers)
	return (server_list[gsm_index].motd);
    return (0);
}

void server_is_connected(int sic_index, int value)
{
    if (sic_index < 0 || sic_index >= number_of_servers)
	return;

    server_list[sic_index].connected = value;
    if (value)
	server_list[sic_index].eof = 0;
}

/* send_to_server: sends the given info the the server */
void send_to_server(struct server *s, const char *format, ...)
{
    assert(s);
    assert(format);

    if (s->sock && (s->flags & LOGGED_IN)) {
	char buffer[BIG_BUFFER_SIZE + 1];	/* make this buffer *much* bigger than needed */
	va_list args;

	va_start(args, format);
	vsnprintf(buffer, BIG_BUFFER_SIZE, format, args);
	va_end(args);

	buffer[IRCD_BUFFER_SIZE - 2] = (char) 0;
	strmcat(buffer, "\r\n", IRCD_BUFFER_SIZE);
	sa_write(s->sock, buffer, strlen(buffer), NULL);
	memset(buffer, 0, 80);
    } else {
	put_it("%s", convert_output_format(get_fset_var(FORMAT_DISCONNECT_FSET), "%s %s", update_clock(GET_TIME),
					   "You are not connected to a server. Use /SERVER to connect."));
    }
}

/*
 * close_all_server: Used when creating new screens to close all the open
 * server connections in the child process...
 */
extern void close_all_server(void)
{
    int i;

    for (i = 0; i < number_of_servers; i++) {
	if (server_list[i].sock)
	    sa_destroy(server_list[i].sock);
    }
}

extern char *create_server_list(void)
{
    int i;
    char *value = NULL;
    char buffer[BIG_BUFFER_SIZE / 4 + 1];

    *buffer = '\0';
    for (i = 0; i < number_of_servers; i++) {
	if (server_list[i].sock) {
	    if (server_list[i].itsname) {
		strcat(buffer, server_list[i].itsname);
		strcat(buffer, space_str);
	    } else
		yell("Warning: server_list[%d].itsname is null and it shouldnt be", i);
	}
    }
    malloc_strcpy(&value, buffer);

    return value;
}

/*
 * This is the function to attempt to make a nickname change.  You
 * cannot send the NICK command directly to the server: you must call
 * this function.  This function makes sure that the neccesary variables
 * are set so that if the NICK command fails, a sane action can be taken.
 *
 * If ``nick'' is NULL, then this function just tells the server what
 * we're trying to change our nickname to.  If we're not trying to change
 * our nickname, then this function does nothing.
 */
void change_server_nickname(struct server *s, char *nick)
{
    assert(nick);
    if ((nick = check_nickname(nick)) != NULL) {
	if (from_server != -1) {
	    malloc_strcpy(&s->d_nickname, nick);
	    malloc_strcpy(&s->s_nickname, nick);
	} else {
	    int i = 0;

	    strncpy(nickname, nick, NICKNAME_LEN);
	    for (i = 0; i < number_of_servers; i++) {
		malloc_strcpy(&s->nickname, nickname);
		new_free(&s->d_nickname);
		new_free(&s->s_nickname);
	    }
	    user_changing_nickname = 0;
	    return;
	}
    } else {
	reset_nickname(s);
    }
    if (s->s_nickname)
	send_to_server(s, "NICK %s", s->s_nickname);
}

void accept_server_nickname(int ssn_index, char *nick)
{
    malloc_strcpy(&server_list[ssn_index].nickname, nick);
    malloc_strcpy(&server_list[ssn_index].d_nickname, nick);
    new_free(&server_list[ssn_index].s_nickname);
    server_list[ssn_index].fudge_factor = 0;

    if (from_server == primary_server)
	strmcpy(nickname, nick, NICKNAME_LEN);

    update_all_status(curr_scr_win, NULL, 0);
    update_input(UPDATE_ALL);
}

/* 
 * This will generate up to 18 nicknames plus the 9-length(nickname)
 * that are unique but still have some semblance of the original.
 * This is intended to allow the user to get signed back on to
 * irc after a nick collision without their having to manually
 * type a new nick every time..
 * 
 * The func will try to make an intelligent guess as to when it is
 * out of guesses, and if it ever gets to that point, it will do the
 * manually-ask-you-for-a-new-nickname thing.
 */
void fudge_nickname(struct server *s)
{
    char l_nickname[NICKNAME_LEN + 1];

    /* 
     * If we got here because the user did a /NICK command, and
     * the nick they chose doesnt exist, then we just dont do anything,
     * we just cancel the pending action and give up.
     */
    if (user_changing_nickname) {
	new_free(&s->s_nickname);
	return;
    }

    /* 
     * Ok.  So we're not doing a /NICK command, so we need to see
     * if maybe we're doing some other type of NICK change.
     */
    if (s->s_nickname)
	strcpy(l_nickname, s->s_nickname);
    else if (s->nickname)
	strcpy(l_nickname, s->nickname);
    else
	strcpy(l_nickname, nickname);

    if (s->fudge_factor < strlen(l_nickname))
	s->fudge_factor = strlen(l_nickname);
    else {
	s->fudge_factor++;
	if (s->fudge_factor == 17) {
	    /* give up... */
	    reset_nickname(s);
	    s->fudge_factor = 0;
	    return;
	}
    }

    /* 
     * Process of fudging a nickname:
     * If the nickname length is less then 9, add an underscore.
     */
    if (strlen(l_nickname) < 9)
	strcat(l_nickname, "_");

    /* 
     * The nickname is 9 characters long. roll the nickname
     */
    else {
	char tmp = l_nickname[8];

	l_nickname[8] = l_nickname[7];
	l_nickname[7] = l_nickname[6];
	l_nickname[6] = l_nickname[5];
	l_nickname[5] = l_nickname[4];
	l_nickname[4] = l_nickname[3];
	l_nickname[3] = l_nickname[2];
	l_nickname[2] = l_nickname[1];
	l_nickname[1] = l_nickname[0];
	l_nickname[0] = tmp;
    }
    change_server_nickname(s, l_nickname);
}

/*
 * -- Callback function
 */
static void nickname_sendline(char *data, char *nick)
{
    int new_server;

    new_server = atoi(data);
    change_server_nickname(SERVER(new_server), nick);
}

/*
 * reset_nickname: when the server reports that the selected nickname is not
 * a good one, it gets reset here. 
 * -- Called by more than one place
 */
void reset_nickname(struct server *s)
{
    char server_num[10];

    kill(getpid(), SIGINT);
    say("You have specified an illegal nickname");
    say("Please enter your nickname");
    strcpy(server_num, ltoa(s->index));
    add_wait_prompt("Nickname: ", nickname_sendline, server_num, WAIT_PROMPT_LINE);
    update_all_status(curr_scr_win, NULL, 0);
}

/*
 * password_sendline: called by send_line() in get_password() to handle
 * hitting of the return key, etc 
 * -- Callback function
 */
void password_sendline(char *data, char *line)
{
    int new_server;

    new_server = atoi(line);
    set_server_password(new_server, line);
    connect_to_server_by_refnum(new_server, -1);
}

char *get_pending_nickname(int servnum)
{
    return server_list[servnum].s_nickname;
}


/* Figure out what local_host_name and local_host_addr should start off with */
void init_hostname(void)
{
    sa_rc_t ret;

    if (gethostname(def_hostname, sizeof(def_hostname))) {
	if (!local_host_name) {
	    fprintf(stderr, "%s: can't figure out hostname, pass -H please!\n", prog_name);
	    exit(1);
	}
    }
    if (local_host_name) {
	if ((ret = make_address(local_host_name, 0, &local_host_addr)) != SA_OK) {
	    fprintf(stderr, "%s: can't figure out address %s: %s", prog_name, local_host_name, sa_error(ret));
	    exit(1);
	}
    }
}
