/*
 * server.c: Things dealing with server connections, etc. 
 *
 * Written By Michael Sandrof
 *
 * Copyright (c) 1990 Michael Sandrof.
 * Copyright (c) 1991, 1992 Troy Rollo.
 * Copyright (c) 1992-2014 Matthew R. Green.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 */

#include "irc.h"
IRCII_RCSID("@(#)$eterna: server.c,v 1.231 2014/08/25 19:24:49 mrg Exp $");

#ifdef HAVE_SYS_UN_H
# include <sys/un.h>

int	connect_to_unix(int, u_char *);
#endif /* HAVE_SYS_UN_H */

#include "server.h"
#include "screen.h"
#include "ircaux.h"
#include "whois.h"
#include "lastlog.h"
#include "exec.h"
#include "window.h"
#include "output.h"
#include "names.h"
#include "parse.h"
#include "list.h"
#include "newio.h"
#include "vars.h"
#include "hook.h"
#include "icb.h"
#include "server.h"
#include "notice.h"
#include "ssl.h"

/* Server: a structure for the server_list */
typedef	struct
{
	u_char	*name;			/* the name of the server */
	u_char	*itsname;		/* the server's idea of its name */
	u_char	*password;		/* password for that server */
	int	port;			/* port number on that server */
	u_char	*proxy_name;		/* HTTP proxy server to use */
	int	proxy_port;		/* port number for proxy server */
	u_char	*nickname;		/* nickname for this server */
	u_char	*away;			/* away message for this server */
	int	operator;		/* true if operator */
	int	oper_command;		/* true just after an oper() command is
					   given.  Used to tell the difference
					   between an incorrect password
					   generated by an oper() command and
					   one generated when connecting to a
					   new server */
	int	version;		/* the version of the server -
					   defined above */
	u_char	*version_string;	/* what is says */
	int	whois;			/* true if server sends numeric 318 */
	int	flags;			/* Various flags */
	int	connected;		/* true if connection is assured */
	int	write;			/* write descriptor */
	int	read;			/* read descriptior */
	pid_t	pid;			/* process id of server */
	int	eof;			/* eof flag for server */
	int	motd;			/* motd flag (used in notice.c) */
	int	sent;			/* set if something has been sent,
					   used for redirect */
	u_char	*buffer;		/* buffer of what dgets() doesn't get */
	WhoisQueue *WQ_head;		/* WHOIS Queue head */
	WhoisQueue *WQ_tail;		/* WHOIS Queue tail */
	WhoisStuff whois_stuff;		/* Whois Queue current collection buf */
	WhoInfo *who_info;		/* /who command info */
	int	close_serv;		/* Server to close when LOGGED_IN */
        CtcpFlood *ctcp_flood;		/* flood info for CTCP */
	u_char	*group;			/* ICB group */
	u_char	*icbmode;		/* ICB initial mode */
	ChannelList *chan_list;		/* list of channels for this server */
	void	(*parse_server)(u_char *); /* pointer to parser for this
					      server */
	int	server_group;		/* group this server belongs to */
	int	attempting_to_connect;	/* are we trying to connect this
					   server? */
	struct	addrinfo *res, *res0;	/* current lookup; for non blocking
					   support */
	SOCKADDR_STORAGE *localaddr;	/* currently bound local port */
	int 	localaddrlen;		/* length of above */
	SslInfo	*ssl_info;		/* handle for ssl routines */
	server_ssl_level ssl_level;	/* what sort of SSL to do */
	void	*server_private;	/* private data per protocol */
	server_private_cb_type server_private_cb; /* callback to free
					   server_private */
}	Server;

/* default SSL for IRC connections */
server_ssl_level irc_ssl_level = SSL_OFF;	

/* SGroup: a structure for server groups. */
typedef struct ser_group_list SGroup;
struct ser_group_list
{
	SGroup	*next;
	u_char	*name;
	int	number;
};

static	void	add_to_server_buffer(int, u_char *);
static	void	login_to_server(int);
static	int	connect_to_server_direct(u_char *, int, u_char *, int);
static	int	connect_to_server_process(u_char *, int, u_char *, int);
static	void	irc2_login_to_server(int);
static	void	server_group_get_connected_next(int);
static	int	reconnect_to_server(int, int);
static	void	parse_server(u_char *);
static	int	ssl_level_to_sa_flags(server_ssl_level level);
static	ssl_init_status	server_check_ssl(int);
static	void	reestablish_close_server(int, int);

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

/* number_of_servers_count: in the server list */
static	int	number_of_servers_count = 0;

/* server_group_list:  list of server groups */
static	SGroup	*server_group_list = NULL;

	int	primary_server = -1;

/*
 * from_server: server we're currently interested in; set for window
 * operations or pretending to be on another server.
 */
static	int	from_server = -1;

static	int	never_connected_local = 1;	/* true until first connection
						 * is made */
static	int	connected_to_server_local = 0;	/* true when connection is
						 * confirmed */
static	int	parsing_server_index = -1;	/* set to the server we last
						   got a message from */

static	u_char	*default_proxy_name;
static	int	default_proxy_port;

#define DEFAULT_SERVER_VERSION Server2_8

#define DEFAULT_PROXY_PORT	3128

/*
 * 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 
 */
#include <assert.h>
void
close_server(int server_index, u_char *message)
{
	u_char	buffer[BIG_BUFFER_SIZE];
	int	i,
		min,
		max;

	Debug(DB_SERVER, "entered. server %d: '%s'", server_index, message);
	if (server_index == -1)
	{
		min = 0;
		max = number_of_servers();
	}
	else
	{
		min = server_index;
		max = server_index + 1;
	}
	for (i = min; i < max; i++)
	{
		int	old_server = from_server;

		Debug(DB_SERVER, "server %d: '%s'", i, message);
		if (server_list[i].flags & CLOSE_PENDING)
			continue;
			
		if (i == primary_server)
			clean_whois_queue();

		from_server = -1;
		mark_not_connected(i);
		from_server = old_server;

		if (server_list[i].server_private)
			server_list[i].server_private_cb(
				&server_list[i].server_private);

		server_list[i].operator = 0;
		server_list[i].connected = 0;
		server_list[i].buffer = NULL;
		server_list[i].flags = SERVER_2_6_2;
		if (-1 != server_list[i].write)
		{
			if (message && *message)
			{
				snprintf(CP(buffer), sizeof buffer, "QUIT :%s\n", message);
				/* XXX retval */
				ssl_write(server_list[i].ssl_info,
					  server_list[i].write, CP(buffer),
					  my_strlen(buffer));
			}
			new_close(server_list[i].write);
			if (server_list[i].write == server_list[i].read)
				server_list[i].read = -1;
			server_list[i].write = -1;
		}

		if (server_list[i].ssl_info)
		{
			dgets_clear_ssl_info(server_list[i].read);
			ssl_close_connection(&server_list[i].ssl_info);
		}

		if (-1 != server_list[i].read)
		{
			new_close(server_list[i].read);
			server_list[i].read = -1;
		}
		if (-1 != server_list[i].pid)
		{
			kill(server_list[i].pid, SIGKILL);
			server_list[i].pid = (pid_t) -1;
		}
	}
}

/*
 * server_set_bits: Sets the proper bits in the fd_set structure according to
 * which servers in the server list have currently active read descriptors.  
 */

void
server_set_bits(fd_set *rd, fd_set *wd)
{
	int	i;

	for (i = 0; i < number_of_servers(); i++)
	{
		if (server_list[i].read != -1)
			FD_SET(server_list[i].read, rd);
#ifdef NON_BLOCKING_CONNECTS
		if (server_list[i].write != -1 &&
		    !(server_list[i].flags & (LOGGED_IN|CLOSE_PENDING|CONNECTED)))
			FD_SET(server_list[i].write, wd);
#endif /* NON_BLOCKING_CONNECTS */
	}
}

static int
reconnect_to_server(int si, int fi)
{
	return connect_to_server(server_list[si].name, server_list[si].port, server_list[si].nickname, fi);
}

static void
reestablish_close_server(int server, int old_server)
{
	if (server_list[old_server].flags & CLOSE_PENDING)
	{
		Win_Trav wt;
		Window *tmp;

		say("Connection to server %s resumed...", server_list[old_server].name);
		server_list[server].close_serv = -1;
		server_list[old_server].flags &= ~(CLOSE_PENDING|CLEAR_PENDING);
		server_list[old_server].flags |= LOGGED_IN;
		server_list[old_server].connected = 1;
		wt.init = 1;
		while ((tmp = window_traverse(&wt)))
			if (window_get_server(tmp) == server)
			{
				window_set_server(window_get_refnum(tmp), old_server, WIN_ALL);
				break;
			}
	}
	window_check_servers();
}

/*
 * 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 *wd)
{
	u_char	lbuf[BIG_BUFFER_SIZE];
	int	des, j;
	static	int	times = 0;
	int	old_timeout;
	u_char	*close_msg;

	for (j = 0; j < number_of_servers(); j++)
	{
		close_msg = empty_string();
#ifdef NON_BLOCKING_CONNECTS
		/*
		 *	deraadt@theos.com suggests that every fd awaiting connection
		 *	should be run at this point.
		 */
		if ((des = server_list[j].write) != -1 && /*FD_ISSET(des, wd) &&*/
		    !(server_list[j].flags & (LOGGED_IN|CONNECTED))) {
			SOCKADDR_STORAGE sa;
			socklen_t salen = sizeof sa;

			if (getpeername(server_list[j].write, (struct sockaddr *) &sa, &salen) != -1)
				login_to_server((from_server = j));
		}
#endif /* NON_BLOCKING_CONNECTS */
		if ((des = server_list[j].read) != -1 && FD_ISSET(des, rd))
		{
			int	junk;
			u_char 	*bufptr;
			u_char	*s;
			int	i = j; /* i is always j? */
			int	old_sep = -1;
			int	is_icb;
			size_t	len;

			from_server = i;
			is_icb = server_get_version(from_server) == ServerICB;

			if (!(server_list[j].flags & LOGGED_IN))
			{
				/* If we're not logged in, try again, or keep going. */
				login_to_server((from_server = j));
				if (!(server_list[j].flags & LOGGED_IN))
					goto real_continue;
			}
#if 0
			Debug(DB_SERVER, "ssl_info[%d] = %p (des = %d)", j,
			      server_list[j].ssl_info, des);
#endif

			if (is_icb)
				old_sep = dgets_set_separator('\0');

			old_timeout = dgets_timeout(1);
			s = server_list[from_server].buffer;
			bufptr = lbuf;
			if (s && *s)
			{
				len = my_strlen(s);
				my_strncpy(lbuf, s, len);
				bufptr += len;
			}
			else
				len = 0;
			if (len >= sizeof(lbuf))
				goto buffer_is_full_hack;	/* XXX */
			junk = dgets(bufptr, (sizeof(lbuf) - len), des);
			(void) dgets_timeout(old_timeout);

			switch (junk)
			{
			case -2:
				/* SSL retry */
				goto real_continue;
			case -1:
				add_to_server_buffer(from_server, lbuf);
				goto real_continue;
			case 0:
			{
#ifdef NON_BLOCKING_CONNECTS
				int	old_serv = server_list[i].close_serv;
			/* Get this here before close_server() clears it -Sol */
				int	logged_in = server_list[i].flags & LOGGED_IN;
#endif /* NON_BLOCKING_CONNECTS */

				close_server(i, close_msg);
				say("Connection closed from %s: %s", server_list[i].name,
					dgets_errno() == -1 ? "Remote end closed connection" : strerror(dgets_errno()));
#ifdef NON_BLOCKING_CONNECTS
				if (!logged_in && server_list[i].res0)
				{
					say("Trying next IP address for %s...", server_list[i].name);
					if (reconnect_to_server(i, -1)) {
						say("Connection to server %s failed...", server_list[i].name);
						clean_whois_queue();
						window_check_servers();
					}
					goto real_continue;
				}

				if (!logged_in && old_serv != -1)
				{
					if (old_serv == i)	/* a hack?  you bet */
						goto a_hack;
					reestablish_close_server(i, old_serv);
					break;
				}
a_hack:
#endif /* NON_BLOCKING_CONNECTS */
				if (i == primary_server)
				{
					if (server_list[i].eof)
					{
						say("Unable to connect to server %s",
							server_list[i].name);
						if (i == number_of_servers() - 1)
  						{
							clean_whois_queue();
							window_check_servers();
							if (!connected_to_server())
								say("Use /SERVER to connect to a server");
							times = 0;
						}
						else
							server_group_get_connected_next(i);
					}
					else
					{
						if (times++ > 1)
						{
							clean_whois_queue();
							window_check_servers();
							if (!connected_to_server())
								say("Use /SERVER to connect to a server");
							times = 0;
  						}
						else
							get_connected(i);
					}
				}
				else if (server_list[i].eof)
				{
					say("Connection to server %s lost.", server_list[i].name);
					clean_whois_queue();
					window_check_servers();
				}
				else
				{
					if (reconnect_to_server(i, -1)) {
						say("Connection to server %s lost.", server_list[i].name);
						clean_whois_queue();
						window_check_servers();
					}
				}
				server_list[i].eof = 1;
				break;
			}
			default:
buffer_is_full_hack:
				{
					int	old_psi = parsing_server_index;

					parsing_server_index = i;
					parse_server(lbuf);
					new_free(&server_list[i].buffer);
					parsing_server_index = old_psi;
					break;
				}
			}
real_continue:
			from_server = primary_server;
			if (is_icb && old_sep != -1)
				(void)dgets_set_separator(old_sep);
		}
	}
}

/*
 * 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 
 */
int
find_in_server_list(u_char *server, int port, u_char *nick)
{
	int	i, maybe = -1;
	size_t	len;

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

		if (my_strnicmp(server, server_list[i].name, len) != 0)
			continue;

		if (nick)
		{
			if (server_list[i].nickname == NULL)
			{
				maybe = i;
				continue;
			}
			if (my_stricmp(server_list[i].nickname, nick))
				continue;
		}
		maybe = i;
		break;
	}
	return (maybe);
}

/*
 * 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(u_char *str)
{
	int	i;

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

/*
 * 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
 * SL_ADD_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(u_char *server, int port, u_char *proxy_name, int proxy_port,
		   u_char *password, u_char *nick,
		   int group, int type, int flags)
{
	Debug(DB_SERVER, "server '%s' port %d pass '%s' nick '%s' group %d "
			 "type %d flags %x proxy %s:%d", server, port, password,
			 nick, group, type, flags,
			 proxy_name ? proxy_name : UP("<>"), proxy_port);
	if (port == -1)
		port = CHOOSE_PORT(type);
	if ((from_server = find_in_server_list(server, port, nick)) == -1)
	{
		from_server = number_of_servers_count++;
		if (server_list)
			server_list = new_realloc(server_list, number_of_servers_count * sizeof(*server_list));
		else
			server_list = new_malloc(number_of_servers_count * sizeof(*server_list));
		server_list[from_server].name = NULL;
		server_list[from_server].proxy_name = NULL;
		server_list[from_server].itsname = NULL;
		server_list[from_server].password = NULL;
		server_list[from_server].away = NULL;
		server_list[from_server].version_string = NULL;
		server_list[from_server].operator = 0;
		server_list[from_server].read = -1;
		server_list[from_server].write = -1;
		server_list[from_server].pid = -1;
		server_list[from_server].whois = 0;
		server_list[from_server].flags = SERVER_2_6_2;
		server_list[from_server].nickname = NULL;
		server_list[from_server].connected = 0;
		server_list[from_server].eof = 0;
		server_list[from_server].motd = 1;
		server_list[from_server].group = NULL;
		server_list[from_server].icbmode = NULL;
		server_list[from_server].chan_list = NULL;
		malloc_strcpy(&server_list[from_server].name, server);
		if (password && *password)
			malloc_strcpy(&server_list[from_server].password, password);
		if (nick && *nick)
			malloc_strcpy(&server_list[from_server].nickname, nick);
		server_list[from_server].port = port;
		if (proxy_name)
			malloc_strcpy(&server_list[from_server].proxy_name, proxy_name);
		server_list[from_server].proxy_port = proxy_port;
		server_list[from_server].WQ_head = NULL;
		server_list[from_server].WQ_tail = NULL;
		server_list[from_server].whois_stuff.nick = NULL;
		server_list[from_server].whois_stuff.user = NULL;
		server_list[from_server].whois_stuff.host = NULL;
		server_list[from_server].whois_stuff.channel = NULL;
		server_list[from_server].whois_stuff.channels = NULL;
		server_list[from_server].whois_stuff.name = NULL;
		server_list[from_server].whois_stuff.server = NULL;
		server_list[from_server].whois_stuff.server_stuff = NULL;
		server_list[from_server].whois_stuff.away = NULL;
		server_list[from_server].whois_stuff.oper = 0;
		server_list[from_server].whois_stuff.chop = 0;
		server_list[from_server].whois_stuff.not_on = 0;
		server_list[from_server].who_info = alloc_who_info();
		server_list[from_server].buffer = NULL;
		server_list[from_server].close_serv = -1;
		server_list[from_server].localaddr = 0;
		server_list[from_server].localaddrlen = 0;
		server_list[from_server].ssl_info = NULL;
		server_list[from_server].server_private = NULL;
		if (flags & SL_ADD_DO_SSL_VERIFY)
			server_list[from_server].ssl_level = SSL_VERIFY;
		else if (flags & SL_ADD_DO_SSL)
			server_list[from_server].ssl_level = SSL_ON;
		else
			server_list[from_server].ssl_level = SSL_OFF;
		switch (type)
		{
		case ServerICB:
			server_list[from_server].parse_server = icb_parse_server;
			break;
		case -1:
			/* default */
			if (client_default_is_icb())
			{
				type = ServerICB;
				server_list[from_server].parse_server = icb_parse_server;
				break;
			}
			type = DEFAULT_SERVER_VERSION;
			/* FALLTHROUGH */
		default:
			server_list[from_server].parse_server = irc2_parse_server;
		}
		server_list[from_server].version = type;

		server_list[from_server].ctcp_flood = ctcp_new_flood();

		if (group == -1)
			server_list[from_server].server_group = 0;
		else
			server_list[from_server].server_group = group;
		server_list[from_server].res = 0;
		server_list[from_server].res0 = 0;
	}
	else
	{
		if (flags & SL_ADD_OVERWRITE)
		{
			server_list[from_server].port = port;
			if (password)
			{
				if (*password)
					malloc_strcpy(&(server_list[from_server].password), password);
				else
					new_free(&(server_list[from_server].password));
			}
			if (nick && *nick)
				malloc_strcpy(&(server_list[from_server].nickname), nick);
			if (group != -1)
				server_list[from_server].server_group = group;
		}
		if (server_list[from_server].res0)
		{
			freeaddrinfo(server_list[from_server].res0);
			server_list[from_server].res0 = 0;
		}
		server_list[from_server].res = 0;
		if ((int) my_strlen(server) > (int) my_strlen(server_list[from_server].name))
			malloc_strcpy(&(server_list[from_server].name), server);
	}
}

void
ctcp_reply_backlog_change(int size)
{
	int	i;

	if (size <= 0)
		size = 1;
	for (i = 0; i < number_of_servers(); i++)
		ctcp_refresh_flood(server_list[i].ctcp_flood, size);
}

void
remove_from_server_list(int i)
{
	int	old_server = from_server;

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

	close_server(i, NULL);

	if (server_list[i].name)
		new_free(&server_list[i].name);
	if (server_list[i].itsname)
		new_free(&server_list[i].itsname);
	if (server_list[i].password)
		new_free(&server_list[i].password);
	if (server_list[i].away)
		new_free(&server_list[i].away);
	if (server_list[i].version_string)
		new_free(&server_list[i].version_string);
	if (server_list[i].nickname)
		new_free(&server_list[i].nickname);
	if (server_list[i].group)
		new_free(&server_list[i].group);
	if (server_list[i].icbmode)
		new_free(&server_list[i].icbmode);
	if (server_list[i].whois_stuff.nick)
		new_free(&server_list[i].whois_stuff.nick);
	if (server_list[i].whois_stuff.user)
		new_free(&server_list[i].whois_stuff.user);
	if (server_list[i].whois_stuff.host)
		new_free(&server_list[i].whois_stuff.host);
	if (server_list[i].whois_stuff.channel)
		new_free(&server_list[i].whois_stuff.channel);
	if (server_list[i].whois_stuff.channels)
		new_free(&server_list[i].whois_stuff.channels);
	if (server_list[i].whois_stuff.name)
		new_free(&server_list[i].whois_stuff.name);
	if (server_list[i].whois_stuff.server)
		new_free(&server_list[i].whois_stuff.server);
	if (server_list[i].whois_stuff.server_stuff)
		new_free(&server_list[i].whois_stuff.server_stuff);
	if (server_list[i].ctcp_flood)
		ctcp_clear_flood(&server_list[i].ctcp_flood);
	if (server_list[i].res0)
		freeaddrinfo(server_list[i].res0);

	/* update all the structs with server in them */
	window_server_delete(i);
	channel_server_delete(i);	/* fix `higher' servers */
	clear_channel_list(i);
	exec_server_delete(i);
	if (i < primary_server)
		--primary_server;
	if (i < from_server)
		--from_server;

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

	if (from_server >= number_of_servers_count)
		from_server = -1;
}

static int
ssl_level_to_sa_flags(server_ssl_level level)
{
	if (level & SSL_VERIFY)
		return SL_ADD_DO_SSL_VERIFY;
	else if (level & SSL_ON)
		return SL_ADD_DO_SSL;
	return 0;
}

static void
server_split_proxy_port(u_char *proxy_and_port, u_char **name, int *port)
{
	u_char	*sport;

	if ((sport = my_index(proxy_and_port, ':')) == NULL)
	{
		*port = DEFAULT_PROXY_PORT;
	}
	else
	{
		*sport++ = '\0';
		*port = my_atoi(sport);
	}

	*name = proxy_and_port;
}

/*
 * parse_server_info:  This parses a single string of the form
 * "server:portnum:password:nickname[:icbgroup]".  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.  if extra is non NULL, it is set to anything after the
 * final : after the nickname..
 *
 * Note:  this will set *type if it sees the IRC/ or ICB/ at the start of
 * the "name".  The server group name will be set by prepending ":group:" to
 * the server, so any of these is valid:
 *
 *	:group:server:portnum:...
 *	ICB/:group:server:portnum:...
 *	server:portnum:...
 *	ICB/server:portnum:...
 *
 * SSLIRC/ and SSLIRCNOCHECK/ prefixes also enable using an SSL connection
 * to the server, the latter version does not perform any certificate
 * verification.
 *
 * In addition, the PROXY/proxy.host:port/ and NOPROXY/ prefixes may be
 * present (currently, only *before* any other prefix.)
 */
void
parse_server_info(u_char **name, u_char **port, u_char **password,
		  u_char **nick, u_char **group, u_char **extra,
		  int *type, server_ssl_level *level,
		  u_char **proxy_name, int *proxy_port)
{
	u_char *ptr, *ename, *savename = NULL;
	u_char *epname, *pname;

	Debug(DB_SERVER, "got %s", *name);
	Debug(DB_PROXY, "got %s port %d", *proxy_name ? *proxy_name : UP("<>"), *proxy_port);
	*port = *password = *nick = *extra = NULL;

	if (my_strncmp(*name, "PROXY/", 6) == 0)
	{
		pname = (*name) + 6;
		if ((epname = my_index(pname, '/')) == NULL)
		{
			yell("--- Unable to parse server info: %s", *name);
			return;
		}
		*epname++ = '\0';

		server_split_proxy_port(pname, proxy_name, proxy_port);
		Debug(DB_PROXY, "set proxy_name %s port %d", *proxy_name, *proxy_port);
		*name = epname;
	}
	else
	if (my_strncmp(*name, "NO_PROXY/", 9) == 0)
	{
		*proxy_port = -1;
		*name += 9;
	}

	if (my_strncmp(*name, "IRC/", 4) == 0)
	{
		*type = DEFAULT_SERVER_VERSION;
		*level = SSL_OFF;
		*name += 4;
	}
	else
	if (my_strncmp(*name, "SSLIRC/", 7) == 0)
	{
		*type = DEFAULT_SERVER_VERSION;
		*level = SSL_VERIFY;
		*name += 7;
	}
	else
	if (my_strncmp(*name, "SSLIRCNOCHECK/", 14) == 0)
	{
		*type = DEFAULT_SERVER_VERSION;
		*level = SSL_ON;
		*name += 14;
	}
	else
	if (my_strncmp(*name, "ICB/", 4) == 0)
	{
		*type = ServerICB;
		*level = SSL_OFF;
		*name += 4;
	}
	else
		*level = irc_ssl_level;

	/* check for :group: processing */
	if (**name == ':')
	{
		if ((ename = my_index((*name)+1, ':')))
		{
			*ename = '\0';
			if (group)
				*group = *name + 1;
			*name = ename + 1;	/* now points to empty or : we hope */
		}
	}

	/* check for [i:p:v:6]:port style */
	if (**name == '[')
	{
		if ((ename = my_index((*name)+1, ']')))
		{
			*ename = '\0';
			savename = *name + 1;
			*name = ename + 1;	/* now points to empty or : we hope */
		}
	}

	if ((ptr = my_index(*name, ':')) != NULL)
	{
		*(ptr++) = '\0';
		if (my_strlen(ptr) == 0)
			*port = NULL;
		else
		{
			*port = ptr;
			if ((ptr = my_index(ptr, ':')) != NULL)
			{
				*(ptr++) = '\0';
				if (my_strlen(ptr) == 0)
					*password = '\0';
				else
				{
					*password = ptr;
					if ((ptr = my_index(ptr, ':'))
							!= NULL)
					{
						*(ptr++) = '\0';
						if (!my_strlen(ptr))
							*nick = NULL;
						else
						{
							*nick = ptr;
							if (extra && (ptr = my_index(ptr, ':'))
									!= NULL)
							{
								*(ptr++) = '\0';
								if (!my_strlen(ptr))
									*extra = NULL;
								else
									*extra = ptr;
							}
						}
					}
				}
			}
		}
	}
	if (savename)
		*name = savename;
	Debug(DB_PROXY, "got %s port %d", *proxy_name ? *proxy_name : UP("<>"), *proxy_port);
}

/*
 * 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.
 *
 * A new format for ICB and more support is:
 *
 *	type/<type-specifc-format>
 *
 * eg:
 *	IRC/server:port:pass:nick:#foo:#bar:&baz
 * means connect to server on port port with pass and nick, and then to join
 * channels #foo, #bar and &baz.  this is not implemented beyond the nick...
 *
 * or
 *	ICB/[:group:]server:port:pass:nick:group:mode
 * which is all the things needed at connection startup.  this is done.
 */
void
build_server_list(u_char *servers)
{
	u_char	*host,
		*rest,
		*extra,
		*mode,
		*password = NULL,
		*port = NULL,
		*group = NULL,
		*nick = NULL,
		*proxy_name = NULL;
	int	port_num,
		proxy_port = 0,
		type = -1;

	if (servers == NULL)
		return;
	while (servers)
	{
		if ((rest = my_index(servers, '\n')) != NULL)
			*rest++ = '\0';
		while ((host = next_arg(servers, &servers)) != NULL)
		{
			server_ssl_level level = SSL_OFF;

			parse_server_info(&host, &port, &password, &nick,
					  &group, &extra, &type, &level,
					  &proxy_name, &proxy_port);
			if (port && *port)
			{
				port_num = my_atoi(port);
				if (!port_num)
					port_num = CHOOSE_PORT(type);
			}
			else
				port_num = CHOOSE_PORT(type);
			if (!nick)
				nick = my_nickname();

			add_to_server_list(host, port_num, proxy_name, proxy_port,
					   password, nick,
					   find_server_group(group, 1), type,
					   ssl_level_to_sa_flags(level));
			if (extra)
			{
				switch (type)
				{
				case ServerICB:
					if ((mode = my_index(extra, ':')) && mode[1])
						*mode++ = 0;
					else
						mode = NULL;
					server_set_icbgroup(from_server, extra);
					server_set_icbmode(from_server, mode);
					break;
				default:
					break;
					/* nothing yet */
				}
			}
		}
		servers = rest;
	}
}

/*
 * connect_to_server_direct: handles the tcp connection to a server.  If
 * successful, the user is disconnected from any previously connected server,
 * the new server is added to the server list, and the user is registered on
 * the new server.  If connection to the server is not successful,  the
 * reason for failure is displayed and the previous server connection is
 * resumed uniterrupted. 
 *
 * This version of connect_to_server() connects directly to a server 
 */
static	int
connect_to_server_direct(u_char *server_name, int port, u_char *nick, int server_index)
{
	int	new_des;
	struct	addrinfo *r = 0, *r0 = 0;
	u_char	*connect_name;
	int	connect_port;
	u_char	*proxy_name = NULL;
	int	proxy_port = 0;

	if (server_index >= 0)
		server_list[server_index].oper_command = 0;
	errno = 0;

	connect_name = server_name;
	connect_port = port;

	/* does this server use a proxy, or is there a non-ignored default? */
	if (server_index >= 0 &&
	    server_index < number_of_servers() &&
	    server_list[server_index].proxy_port != -1)
	{
		if (server_list[server_index].proxy_name)
		{
			connect_name = server_list[server_index].proxy_name;
			connect_port = server_list[server_index].proxy_port;
		}
		else if (default_proxy_name)
		{
			connect_name = default_proxy_name;
			connect_port = default_proxy_port;
		}
		proxy_name = connect_name;
		proxy_port = connect_port;
	}

#ifdef HAVE_SYS_UN_H
	if (*server_name == '/')
		new_des = connect_to_unix(port, server_name);
	else
#endif /* HAVE_SYS_UN_H */
	{
		if (server_index >= 0 &&
		    server_list[server_index].res &&
		    server_list[server_index].res0)
			new_des = connect_by_number(connect_port, connect_name,
					1, &server_list[server_index].res,
					&server_list[server_index].res0);
		else
		{

			new_des = connect_by_number(connect_port, connect_name,
					1, &r, &r0);
		}
	}
	if (new_des < 0)
	{
		int	error = errno;

		char *e = NULL;
		switch (new_des)
		{
		default:
		case -2:
			error = 0;
		case -1:
			e = "Unknown host";
			break;
		case -3:
			e = "socket";
			break;
		case -4:
			e = "connect";
			break;
		}
			
		if (proxy_name)
			say("Unable to connect to port %d of server %s "
			    "(proxy %s:%d): %s%s%s",
			    port, server_name,
			    proxy_name, proxy_port,
			    e, error ? ": " : "", error ? strerror(error) : "");
		else
			say("Unable to connect to port %d of server %s: %s%s%s",
			    port, server_name,
			    e, error ? ": " : "", error ? strerror(error) : "");
		if (is_server_open(from_server))
			say("Connection to server %s resumed...", server_list[from_server].name);
		return (-1);
	}

	update_all_status();
	add_to_server_list(server_name, port, proxy_name, proxy_port, NULL,
			   nick, -1, server_get_version(from_server),
			   SL_ADD_OVERWRITE);

	if (server_list[from_server].localaddr)
		new_free(&server_list[from_server].localaddr);
	server_list[from_server].localaddr = 0;

#ifdef HAVE_SYS_UN_H
	if (*server_name == '/')
	{
		server_list[from_server].localaddr = 0;
		server_list[from_server].localaddrlen = 0;
	}
	else
#endif /* HAVE_SYS_UN_H */
	{
		SOCKADDR_STORAGE *localaddr = new_malloc(sizeof *localaddr);
		socklen_t address_len = sizeof *localaddr;

		if (getsockname(new_des, (struct sockaddr *) localaddr, &address_len)
		    >= 0)
		{
			server_list[from_server].localaddr = localaddr;
			server_list[from_server].localaddrlen = address_len;
		}
		else
		{
			close(new_des);
			say("Could not getsockname(): %s", strerror(errno));
			new_free(&localaddr);
			return -1;
		}
	}
	if (port)
	{
		server_list[from_server].read = new_des;
		server_list[from_server].write = new_des;
	}
	else
		server_list[from_server].read = new_des;
	if (!server_list[from_server].res0 && r && r0)
	{
		server_list[from_server].res = r;
		server_list[from_server].res0 = r0;
	}

	server_list[from_server].operator = 0;
	return (0);
}

/*
 * connect_to_server_process: handles the tcp connection to a server.  If
 * successful, the user is disconnected from any previously connected server,
 * the new server is added to the server list, and the user is registered on
 * the new server.  If connection to the server is not successful,  the
 * reason for failure is displayed and the previous server connection is
 * resumed uniterrupted. 
 *
 * This version of connect_to_server() uses the ircio process to talk to a
 * server 
 */
static	int
connect_to_server_process(u_char *server_name, int port, u_char *nick, int server_index)
{
	int	write_des[2],
		read_des[2],
		pid,
		c;
	u_char	*path,
		*name = NULL,
		*s;
	u_char	buffer[BIG_BUFFER_SIZE];
	int	old_timeout;

	path = UP(IRCIO_PATH);
	if ((s = my_rindex(path, '/')) != NULL)
		malloc_strcpy(&name, s + 1);
	if (!name)
		name = path;
	if (*path == '\0')
		return (connect_to_server_direct(server_name, port, nick, server_index));
	if (server_index >= 0)
		server_list[server_index].oper_command = 0;
	write_des[0] = -1;
	write_des[1] = -1;
	if (pipe(write_des) || pipe(read_des))
	{
		if (write_des[0] != -1)
		{
			new_close(write_des[0]);
			new_close(write_des[1]);
		}
		say("Couldn't start new process: %s", strerror(errno));
		return (connect_to_server_direct(server_name, port, nick, server_index));
	}
	switch (pid = fork())
	{
	case -1:
		say("Couldn't start new process: %s\n", strerror(errno));
		return (-1);
	case 0:
		(void) MY_SIGNAL(SIGINT, (sigfunc *)SIG_IGN, 0);
		dup2(read_des[1], 1);
		dup2(write_des[0], 0);
		new_close(read_des[0]);
		new_close(read_des[1]);
		new_close(write_des[0]);
		new_close(write_des[1]);
		snprintf(CP(buffer), sizeof buffer, "%u", port);
		(void)setuid(getuid());
		execl(CP(path), CP(name), server_name, buffer, NULL);
		printf("-5 0\n"); /* -1 - -4 returned by connect_by_number() */
		fflush(stdout);
		_exit(1);
	default:
		new_close(read_des[1]);
		new_close(write_des[0]);
		break;
	}
	old_timeout = dgets_timeout(3);
	c = dgets(buffer, sizeof buffer, read_des[0]);
	(void) dgets_timeout(old_timeout);
	if ((c == 0) || ((c = my_atoi(buffer)) != 0))
	{
		if (c == -5)
			return (connect_to_server_direct(server_name, port, nick, server_index));
		else
		{
			u_char *ptr;

			if ((ptr = my_index(buffer, ' ')) != NULL)
			{
				ptr++;
				if (my_atoi(ptr) > 0)
		say("Unable to connect to port %d of server %s: %s",
			port, server_name, strerror(my_atoi(ptr)));
				else
		say("Unable to connect to port %d of server %s: Unknown host",
							port, server_name);
			}
			else
		say("Unable to connect to port %d of server %s: Unknown host",
							port, server_name);
			if (is_server_open(from_server))
				say("Connection to server %s resumed...",
						server_list[from_server].name);
			new_close(read_des[0]);
			new_close(write_des[1]);
			return (-1);
		}
	}
	update_all_status();
	add_to_server_list(server_name, port, NULL/*proxy*/, 0/*proxy port*/,
			   NULL, nick, -1, server_get_version(from_server),
			   SL_ADD_OVERWRITE);
	server_list[from_server].read = read_des[0];
	server_list[from_server].write = write_des[1];
	server_list[from_server].pid = pid;
	server_list[from_server].operator = 0;
	return (0);
}

/*
 * connect_to_server: Given a name and portnumber, this will attempt to
 * connect to that server using either a direct connection or process
 * connection, depending on the value of using_ircio().  If connection
 * is successful, the proper NICK, USER, and PASS commands are sent to the
 * server.  If the c_server parameter is not -1, then the server with that
 * index will be closed upon successful connection here. Also, if connection
 * is successful, the attempting_to_connect variable is incremented.  This is
 * checked in the notice.c routines to make sure that connection was truely
 * successful (and not closed immediately by the server). 
 */
int
connect_to_server(u_char *server_name, int port, u_char *nick, int c_server)
{
	int	server_index;
	SOCKADDR_STORAGE	sa;
	socklen_t salen = sizeof sa;
	int rv;

	save_message_from();
	message_from(NULL, LOG_CURRENT);
	server_index = find_in_server_list(server_name, port, nick);
	if (server_index < 0)
	{
		yell("connect_to_server: server_index returned -1 from find_in_server_list()");
		yell("aborting!");
		abort();
	}
	server_list[server_index].attempting_to_connect = 1;
	/*
	 * check if the server doesn't exist, or that we're not already
	 * connected to it.  note that "connected" also means logged in.
	 */
	if (!is_server_connected(server_index))
	{
		if (is_server_open(server_index))
			close_server(server_index, empty_string());
		if (port == -1)
			port = server_list[server_index].port;
		if (port == -1)
			port = CHOOSE_PORT(server_list[server_index].version);
		say("Connecting to port %d of server %s", port, server_name);
		
		if (!ignore_ircrc())
			load_ircquick();
		
		if (using_ircio())
			rv = connect_to_server_process(server_name, port, nick, server_index);
		else
			rv = connect_to_server_direct(server_name, port, nick, server_index);
		if (rv)
		{
			server_list[server_index].attempting_to_connect = 0;
			restore_message_from();
			return -1;
		}
		if ((c_server != -1) && (c_server != from_server))
		{
#ifdef NON_BLOCKING_CONNECTS
#if defined(GKM)
			say("--- server %s will be closed when we connect", server_list[c_server].name);
			if (server_list[c_server].flags & CLOSE_PENDING)
				say("--- why are we flagging this for closing a second time?");
#endif /* GKM */
			server_list[from_server].close_serv = c_server;
			server_list[c_server].flags |= CLOSE_PENDING;
			server_list[c_server].connected = 0;
#else
			close_server(c_server, empty_string());
#endif /* NON_BLOCKING_CONNECTS */
		}
		else
		{
			server_list[from_server].close_serv = -1;
		}
		if (server_list[from_server].nickname == NULL)
			malloc_strcpy(&server_list[from_server].nickname, my_nickname());
		server_list[from_server].flags &= ~LOGGED_IN;
		/*
		 * this used to be an ifndef NON_BLOCKING_CONNECTS .. we want to do this
		 * whenever the connection is valid, it's possible for a connect to be
		 * "immediate".
		 */
		if (is_server_open(from_server) &&
		    (using_ircio() ||
		    getpeername(server_list[from_server].read, (struct sockaddr *) &sa, &salen) != -1))
			login_to_server(from_server);
	}
	else
	{
		if (port == -1)
		{
			if (server_index != -1)
				port = server_list[server_index].port;
			else
				port = CHOOSE_PORT(server_get_version(server_index));
		}
		say("Connected to port %d of server %s", port, server_name);
		from_server = server_index;
		if ((c_server != -1) && (c_server != from_server))
			close_server(c_server, empty_string());
	}
	update_all_status();
	restore_message_from();
	return 0;
}

static void login_to_server_nonblocking(int);

static void
login_to_server_nonblocking(int server)
{
#ifdef NON_BLOCKING_CONNECTS
	int	old_serv = server_list[server].close_serv;

	/* clean up after ourselves */
	if (server_list[server].res0)
	{
		freeaddrinfo(server_list[server].res0);
		server_list[server].res0 = 0;
	}
	server_list[server].res = 0;
	if (old_serv != -1)
	{
#if defined(GKM)
		say("--- closing server %s - changing servers", server_list[server_list[server].close_serv].name);
		if (!(server_list[server_list[server].close_serv].flags & CLOSE_PENDING))
			say("--- uh oh. closing a server that wasn't CLOSE_PENDING");
#endif /* GKM */
		if (server_list[old_serv].flags & CLEAR_PENDING)
			clear_channel_list(old_serv);   /* Channels were
							   transfered -Sol */
		server_list[old_serv].flags &= ~(CLOSE_PENDING|CLEAR_PENDING);
		close_server(old_serv, empty_string());
		server_list[server].close_serv = -1;
		/* should we pause here to let the net catch up with us? */
	}
#if defined(GKM)
	else
	{
		say("--- no server to close in login_to_server()");
	}
#endif /* GKM */
#endif /* NON_BLOCKING_CONNECTS */
}

/*
 * Check if we should attempt to initiate an SSL connection or not.
 * Returns SSL_INIT_OK if there was no error, either no SSL or SSL
 * started OK.  Returns SSL_INIT_FAIL if SSL failed, either to work
 * at all or if certificate verification failed.  Returns
 * SSL_INIT_PENDING if the connection establishment is still ongoing.
 */
static	ssl_init_status
server_check_ssl(int server)
{
	ssl_init_status	status;

	if (server_list[server].ssl_level == SSL_OFF)
		return SSL_INIT_OK;
	
	if (server_list[server].flags & SSL_DONE)
		return SSL_INIT_OK;

	status = ssl_init_connection(server, server_list[server].read,
				     &server_list[server].ssl_info);
	Debug(DB_SERVER, "server = %d; status = %d; set: %p", server, status,
	      server_list[server].ssl_info);
	if (status == SSL_INIT_FAIL)
	{
		if (server_list[server].ssl_level == SSL_OFF)
		{
			yell("SSL off, why did we try to allocate one?");
			Debug(DB_SERVER, "SSL off");
		}
		else
		{
			Debug(DB_SERVER, "ssl_init_connection failed");
			yell("-- SSL init failed; closing server %s",
			     server_list[server].name);
		}
	}
	else if (status == SSL_INIT_OK)
	{
		server_list[server].flags |= SSL_DONE;
		dgets_set_ssl_info(server_list[server].read,
				   server_list[server].ssl_info);
	}

	return status;
}

/*
 * we're looking for eg:
 *
 * HTTP/1.1 200 Connection established
 * Proxy-Agent: Privoxy/3.0.19
 * <blank line>
 *
 * return values:
 *   -1  -  proxy login not completed, retry 
 *    0  -  proxy login complete
 *    1  -  failed some how; close shop.
 */
static int
server_check_http_response(int server, u_char *lbuf, size_t buflen)
{
	Debug(DB_PROXY, "server %d: buf \"%s\"", server, lbuf);
	if ((server_list[server].flags & PROXY_REPLY) == 0)
	{
		u_char	*arg, *args = lbuf;

		arg = next_arg(args, &args);

		if (arg &&
		    ((my_stricmp(UP("HTTP/1.0"), arg) == 0) ||
		     (my_stricmp(UP("HTTP/1.1"), arg) == 0)))
		{
			arg = next_arg(args, &args);

			if (my_stricmp(UP("200"), arg) == 0)
			{
				server_list[server].flags |= PROXY_REPLY;
				Debug(DB_PROXY, "got HTTP 200 reply!");
				return -1;
			}
		}
		Debug(DB_PROXY, "did not get HTTP 200 reply.");
		yell("-- proxy reply when we expected 200 was: \"%s\"", lbuf);
		return 1;
	}
	/* be generous */
	if (lbuf[0] == '\0' ||
	    (lbuf[0] == '\n' && lbuf[1] == '\0') ||
	    (lbuf[0] == '\n' && lbuf[1] == '\r' && lbuf[2] == '\0') ||
	    (lbuf[0] == '\r' && lbuf[1] == '\n' && lbuf[2] == '\0'))
	{
		Debug(DB_PROXY, "got empty string, end of http reply! done!");
		return 0;
	}

	/* probably just a header, we could check? */
	Debug(DB_PROXY, "not empty, keep going.  should be http header.");
	return -1;
}

static	void
login_to_server(int server)
{
	ssl_init_status status;
	u_char	*proxy_name;

	Debug(DB_SERVER, "server %d", server);

	if ((server_list[server].flags & CONNECTED) == 0)
	{
		/*
		 * If we're CONNECTED, we've gone through the non-blocking
		 * connect.  If we're LOGGED_IN, that means that we've sent
		 * the login sequence, and for SSL that means SSL has
		 * successfully initialised.
		 */
		server_list[server].flags |= CONNECTED;

#ifdef NON_BLOCKING_CONNECTS
		if (using_ircio() == 0)
		{
			set_blocking(server_list[server].read);
			if (server_list[server].read != server_list[server].write)
				set_blocking(server_list[server].write);
		}
#endif /* NON_BLOCKING_CONNECTS */
	}

	proxy_name = server_get_proxy_name(server, 1);
	if (proxy_name &&
	    (server_list[server].flags & PROXY_CONNECT) == 0)
	{
		u_char	lbuf[BIG_BUFFER_SIZE];
		int	len;

		len = snprintf(CP(lbuf), sizeof lbuf,
			 "CONNECT %s:%d HTTP/1.1\nHost:\n\n",
		         server_list[server].name,
		         server_list[server].port);
		if (len > sizeof lbuf)
		{
			yell("--- lbuf at %s:%d is too small", __func__, __LINE__);
			Debug(DB_PROXY, "lbuf at %s:%d is too small", __func__, __LINE__);
			goto failed_recover;
		}
		if (write(server_list[server].write, CP(lbuf), len) != len)
		{
			yell("--- proxy login failed.");
			Debug(DB_PROXY, "proxy write failed, closing.");
			goto failed_recover;
		}

		server_list[server].flags |= PROXY_CONNECT;
		Debug(DB_PROXY, "proxy: send CONNECT %s:%d (from_server = %d)",
		      server_list[server].name, server_list[server].port, from_server);

		/* XXX need to eat HTTP reply, before doing final login */
		return;
	}

	/*
	 * if we've sent proxy stuff, but haven't completed it yet, we
	 * need to eat the HTTP reply.
	 */
	if ((server_list[server].flags & PROXY_CONNECT) != 0 &&
	    (server_list[server].flags & PROXY_DONE) == 0)
	{
		u_char	lbuf[BIG_BUFFER_SIZE];
		u_char 	*bufptr;
		u_char	*s;
		int	old_timeout;
		int	junk;
			size_t	len;

		old_timeout = dgets_timeout(1);
		s = server_list[server].buffer;
		bufptr = lbuf;
		if (s && *s)
		{
			len = my_strlen(s);
			my_strncpy(lbuf, s, len);
			bufptr += len;
		}
		else
			len = 0;
		if (len >= sizeof(lbuf))
			goto buffer_is_full_hack;	/* XXX */
		junk = dgets(bufptr, sizeof(lbuf), server_list[server].read);
		(void) dgets_timeout(old_timeout);

		switch (junk)
		{
		case -2:
			/* shouldn't happen! SSL retry */
			yell("--- proxy http reponse got -2 / SSL retry!?");
			Debug(DB_PROXY, "proxy http reponse got -2 / SSL retry, closing.");
			goto failed_recover;
		case -1:
			Debug(DB_PROXY, "added \"%s\" to the server buffer for proxy.");
			add_to_server_buffer(server, lbuf);
			return;
		case 0:
			goto failed_recover;
		default:
buffer_is_full_hack:
			switch (server_check_http_response(server, lbuf, sizeof lbuf))
			{
			case -1:
				Debug(DB_PROXY, "got retry on http response server %d", server);
				return;
			case 0:
				Debug(DB_PROXY, "proxy setup complete, server %d!", server);
				server_list[server].flags |= PROXY_DONE;
				return;
			case 1:
				yell("--- proxy http login failed, closing");
				Debug(DB_PROXY, "proxy http login failed, closing server %d", server);
				goto failed_recover;
			default:
				yell("--- help! this shouldn't happen.  not -1/0/1");
				goto failed_recover;
			}
		}
	}

	status = server_check_ssl(server);
	if (status == SSL_INIT_PENDING)
	{
		return;
	}
	if (status == SSL_INIT_FAIL)
	{
		int	old_serv;
		int	logged_in;

		Debug(DB_SERVER, "SSL failed, closing.");
failed_recover:
		old_serv = server_list[server].close_serv;
		logged_in = server_list[server].flags & LOGGED_IN;

		close_server(server, UP("proxy/SSL Failed."));
		if (!logged_in && old_serv != -1 && old_serv != server)
			reestablish_close_server(server, old_serv);
		return;
	}
	server_list[server].flags |= LOGGED_IN;
	login_to_server_nonblocking(server);
	if (server_get_version(server) == ServerICB)
		icb_login_to_server(server);
	else
		irc2_login_to_server(server);
	window_copy_prev_server(server);
}

static	void
irc2_login_to_server(int server)
{
	if (server_get_version(server) == ServerICB)
	{
		yell("--- ICB called irc2_login_to_server???");
		return;
	}

	if (server_list[server].password)
		send_to_server("PASS %s", server_list[server].password);
	send_to_server("NICK %s", server_list[server].nickname);
	send_to_server("USER %s %s %s :%s", my_username(), irc_umode(),
		my_hostname(), server_list[server].name, my_realname());
}

/*
 * 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.  
 * oldconn is set if this connection is really an old connection being
 * resurected (eg. connection to server failed).
 */
void
get_connected(int server)
{
	int	s,
		ret = -1;

	if (server_list)
	{
		int	already_connected = 0;

		if (server == number_of_servers())
			server = 0;
		else if (server < 0)
			server = number_of_servers() - 1;
		s = server;
		if (reconnect_to_server(server, primary_server))
		{
			while (server_list[server].read == -1)
			{
				server++;
				if (server == number_of_servers())
					server = 0;
				if (server == s)
				{
					clean_whois_queue();
					say("Use /SERVER to connect to a server");
					break;
				}
				from_server = server;
				already_connected = is_server_connected(server);
				ret = reconnect_to_server(server, primary_server);
			}
			if (!ret)
				from_server = server;
			else
				from_server = -1;
		}
		if (from_server != -1) {
			int flags;

			flags = (already_connected ? 0 : WIN_TRANSFER);
			window_set_server(-1, from_server, flags);
		}
	}
	else
	{
		clean_whois_queue();
		say("Use /SERVER to connect to a server");
	}
}

/*
 * 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(void)
{
	FILE *fp;
	u_char format[11];
	u_char *file_path = NULL;
	u_char	buffer[BIG_BUFFER_SIZE];

	if ((file_path = my_getenv("IRCSERVERSFILE")) == NULL)
	{
		malloc_strcpy(&file_path, my_irc_lib());
		malloc_strcat(&file_path, UP(SERVERS_FILE));
	}
	snprintf(CP(format), sizeof format, "%%%ds", (int)sizeof buffer);
	fp = fopen(CP(file_path), "r");
	new_free(&file_path);
	if (NULL != fp)
	{
		while (fscanf(fp, CP(format), buffer) != EOF)
			build_server_list(buffer);
		fclose(fp);
		return (0);
	}
	return (1);
}

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

	if (from_server >= number_of_servers())
		from_server = -1;

	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>");
		if (client_default_is_icb())
			say("Using ICB connections by default");
		if (default_proxy_name)
			say("Using proxy server: %s port %d by default",
			    default_proxy_name, default_proxy_port);
		say("Server list:");
		for (i = 0; i < number_of_servers(); i++)
		{
			u_char	proxy_msg[BIG_BUFFER_SIZE];
			u_char	*icb_msg, *group_msg, lbuf[BIG_BUFFER_SIZE];
			u_char	*pname = NULL;
			int	pport = 0;

			icb_msg = server_list[i].version == ServerICB ? (u_char *) " (ICB connection)" : empty_string();
			if (server_list[i].server_group)
			{
				snprintf(CP(lbuf), sizeof lbuf, " [group: %s]",
				    find_server_group_name(server_list[i].server_group));
				group_msg = lbuf;
			}
			else
				group_msg = empty_string();

			pport = server_get_proxy_port(i, 0);
			if (pport == -1)
				snprintf(CP(proxy_msg), sizeof proxy_msg,
					" (no proxy)");
			else if ((pname = server_get_proxy_name(i, 0)))
				snprintf(CP(proxy_msg), sizeof proxy_msg,
					 " (proxy on server %s port %d)",
					 pname, pport);
			else
				proxy_msg[0] = '\0';

			if (!server_list[i].nickname)
			{
				say("\t%d) %s %d%s%s%s%s", i,
					server_list[i].name,
					server_list[i].port,
					server_list[i].read == -1 ? UP(" (not connected)") : empty_string(),
					group_msg,
					icb_msg,
					proxy_msg);
			}
			else
			{
				say("\t%d) %s %d (%s%s)%s%s%s", i,
					server_list[i].name,
					server_list[i].port,
					(server_list[i].read == -1) ? UP("was ") : empty_string(),
					server_list[i].nickname,
					group_msg,
					icb_msg,
					proxy_msg);
			}
#ifdef GKM
			say("\t\tflags: %s%s%s%s%s%s%s",
				server_list[i].flags & SERVER_2_6_2 ? UP("SERVER_2_6_2 ") : empty_string(),
				server_list[i].flags & USER_MODE_I ? UP("USER_MODE_I ") : empty_string(),
				server_list[i].flags & USER_MODE_W ? UP("USER_MODE_W ") : empty_string(),
				server_list[i].flags & USER_MODE_S ? UP("USER_MODE_S ") : empty_string(),
				server_list[i].flags & CLOSE_PENDING ? UP("CLOSE_PENDING ") : empty_string(),
				server_list[i].flags & CLEAR_PENDING ? UP("CLEAR_PENDING ") : empty_string(),
				server_list[i].flags & LOGGED_IN ? UP("LOGGED_IN ") : empty_string() );
			say("\t\tclose_serv=%d, connected=%d, read=%d, eof=%d", server_list[i].close_serv, server_list[i].connected, server_list[i].read, server_list[i].eof);
#endif /* GKM */
		}
	}
	else
		say("The server list is empty");
}

void
MarkAllAway(u_char *command, u_char *message)
{
	int	old_server;

	old_server = from_server;
	for (from_server = 0; from_server < number_of_servers(); from_server++)
	{
		if (is_server_connected(from_server))
			send_to_server("%s :%s", command, message);
	}
	from_server = old_server;
}


/*
 * server_set_password: this sets the password for the server with the given
 * index.  If password is null, the password for the given server is returned 
 */
u_char	*
server_set_password(int server_index, u_char *password)
{

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

/*
 * ICB support
 */
void
server_set_icbgroup(int server_index, u_char *group)
{

	malloc_strcpy(&server_list[server_index].group, group);
}

void
server_set_icbmode(int server_index, u_char *mode)
{

	malloc_strcpy(&server_list[server_index].icbmode, mode);
}

/*
 * server: the /SERVER command. Read the SERVER help page about 
 */
void
servercmd(u_char *command, u_char *args, u_char *subargs)
{
	u_char	*server,
		*port,
		*proxy_name = NULL,
		*proxy_port_str = NULL,
		*extra,
		*newmode,
		*password = NULL,
		*nick = NULL,
		*group = NULL;
	int	port_num,
		proxy_port = 0,
		i,
		new_server_flags,
		type = -1;
	server_ssl_level level = SSL_OFF;

	if ((server = next_arg(args, &args)) != NULL)
	{
		while (*server == '-')
		{
			size_t	len;

			/*
			 * old usage of `/server -' handled here.
			 */
			if (*++server == '\0')
			{
				get_connected(primary_server - 1);
				return;
			}
			upper(server);
			len = my_strlen(server);
			/*
			 * just don't return if you want to perform some action in one of
			 * the flag handling sections.
			 */
			if (!my_strncmp(server, "ICB", len))
				type = ServerICB;
			else if (!my_strncmp(server, "IRC", len))
				type = DEFAULT_SERVER_VERSION;
			else if (!my_strncmp(server, "SSL", len))
				level = SSL_VERIFY;
			else if (!my_strncmp(server, "SSLNOCHECK", len))
				level = SSL_ON;
			else if (!my_strncmp(server, "NOSSL", len))
				level = SSL_OFF;
			else if (!my_strncmp(server, "DELETE", len))
			{
				if ((server = next_arg(args, &args)) != NULL)
				{
					if ((i = parse_server_index(server)) == -1)
					{
						if (-1 == (i = find_in_server_list(server, 0, 0)))
						{
							say("No such server in list");
							return;
						}
					}
					if (server_list[i].connected)
					{
						say("Can not delete server that is already open");
						return;
					}
					remove_from_server_list(i);
					return;
				}
				say("Need server number for -DELETE");
				return;
			}
			else if (!my_strncmp(server, "GROUP", len))
			{
				if ((group = next_arg(args, &args)) == NULL)
				{
					say("SERVER -GROUP needs <group> and <server>");
					return;
				}
			}
			else if (!my_strncmp(server, "PROXY", len))
			{
				if ((proxy_name = next_arg(args, &args)) == NULL ||
				    (proxy_port_str = next_arg(args, &args)) == NULL)
				{
					say("SERVER -PROXY needs <proxy> and <host>");
					return;
				}
				proxy_port = my_atoi(proxy_port_str);
			}
			else
			{
				say("SERVER: %s is an unknown flag", server);
				return;
			}
			if ((server = next_arg(args, &args)) == NULL)
			{
				say("SERVER: need a server name");
				return;
			}
		}

		if (my_index(server, ':') != NULL)
		{
			parse_server_info(&server, &port, &password, &nick,
			    group ? 0 : &group, &extra, &type, &level,
			    &proxy_name, &proxy_port);
			if (!my_strlen(server))
			{
				say("Server name required");
				return;
			}
			if (port && *port) {
				port_num = my_atoi(port);
				if (!port_num)
					port_num = CHOOSE_PORT(type);
			} else
				/* consider port = next_arg() */
				port_num = CHOOSE_PORT(type);
		}
		else
		{
			if ((port = next_arg(args, &args)) != NULL)
			{
				port_num = my_atoi(port);
				if (!port_num)
					port_num = CHOOSE_PORT(type);
				if ((password = next_arg(args, &args)) != NULL)
					nick = next_arg(args, &args);
			}
			else
				port_num = CHOOSE_PORT(type);

			extra = NULL;
		}

		/*
		 * We need to not do this in the case "server" is really a
		 * valid server number.
		 */
		if (parse_server_index(server) == -1)
			add_to_server_list(server, port_num,
					   proxy_name, proxy_port, password,
					   nick, -1, type,
					   ssl_level_to_sa_flags(level));

		if (group && *group)
			server_list[from_server].server_group = find_server_group(group, 1);

		if (extra && type == ServerICB)
		{
			if ((newmode = my_index(extra, ':')))
			{
				*newmode++ = 0;
				malloc_strcpy(&(server_list[from_server].icbmode), newmode);
			}
			malloc_strcpy(&(server_list[from_server].group), extra);
		}

		if (*server == '+' || *server == '=' || *server == '~')
		{
			if (group)
				add_server_to_server_group(
				    window_get_server(curr_scr_win), group);

			if (*(server+1))
			{
				u_char	servinfo[INPUT_BUFFER_SIZE+1];

				if (*server == '+')
					server++;
				/* Reconstitute whole server info so
				  window_get_connected can parse it -Sol */
				snprintf(CP(servinfo), sizeof servinfo, "%s:%d:%s:%s",
					server, port_num,
					password ? password : empty_string(),
					nick ? nick : empty_string());
				window_get_connected(curr_scr_win, servinfo,
				    -1, NULL, proxy_name, proxy_port,
				    group, type, level);
			}
			else
				get_connected(primary_server + 1);
			return;
		}
		/*
		 * work in progress.. window_get_prev_server() needs to be set for
		 * all windows that used to be associated with a server as it
		 * switches [successfully] to a new server.
		 * this'll be fun since that can happen in server.c and
		 * window.c and non-blocking-connects will throw yet another
		 * wrench into things since we only want it to happen on
		 * a successful connect. - gkm
		 */
		else if (*server == '.')
		{
			if (*(++server))
			{
				say("syntax error - nothing may be specified after the '.'");
				return;
			}
			if (get_current_screen() && curr_scr_win &&
			    (i = window_get_prev_server(curr_scr_win)) != -1)
			{
				if (group)
					add_server_to_server_group(i, group);

				window_restore_server(i);
				if (!proxy_name && server_list[i].proxy_name)
					proxy_name = server_list[i].proxy_name;
				if (proxy_port == 0 && server_list[i].proxy_port)
					proxy_port = server_list[i].proxy_port;
				window_get_connected(curr_scr_win, NULL,
				    window_get_server(curr_scr_win), NULL,
				    proxy_name, proxy_port,
				    group, type, level);
			}
			else
				say("No server previously in use in this window");
			return;
		}
		if ((i = parse_server_index(server)) != -1)
		{
			server = server_list[i].name;
			if (server_list[i].port != -1)
				port_num = server_list[i].port;
			if (server_list[i].nickname && !nick)
				nick = server_list[i].nickname;
		}
		else
			i = find_in_server_list(server, port_num, nick);

		if (group)
			add_server_to_server_group(i, group);

		if (is_server_connected(i))
		{
			/*
			 * We reset the log level only if the "new" server
			 * already has windows associated with it : here it's
			 * equivalent to its already being connected. -Sol
			 */
			new_server_flags = 0;
		}
		else
			new_server_flags = WIN_TRANSFER;
		if (connect_to_server(server, port_num, nick, primary_server) != -1)
		{
			if (primary_server > -1 && from_server != primary_server &&
			    !server_list[from_server].away &&
			    server_list[primary_server].away)
				malloc_strcpy(&server_list[from_server].away,
				    server_list[primary_server].away);
			window_set_server(-1, from_server, new_server_flags);
		}
	}
	else
		display_server_list();
}

/*
 * 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 time_out;
	int	flushing = 1;
	int	des;
	int	old_timeout;
	u_char	buffer[BIG_BUFFER_SIZE];

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

/*
 * server_set_whois: sets the whois value for the given server index.  If the
 * whois value is 0, it assumes the server doesn't send End of WHOIS commands
 * and the whois.c routines use the old fashion way of getting whois info. If
 * the whois value is non-zero, then the server sends End of WHOIS and things
 * can be done more effienciently 
 */
void
server_set_whois(int server_index, int value)
{
	server_list[server_index].whois = value;
}

/* server_get_whois: Returns the whois value for the given server index */
int
server_get_whois(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	return (server_list[server_index].whois);
}


void
server_set_2_6_2(int server_index, int value)
{
	server_set_flag(server_index, SERVER_2_6_2, value);
}

int
server_get_2_6_2(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	return (server_get_flag(server_index, SERVER_2_6_2));
}

void
server_set_flag(int server_index, int flag, int value)
{
	if (server_index == -1)
		server_index = primary_server;
	if (value)
		server_list[server_index].flags |= flag;
	else
		server_list[server_index].flags &= ~flag;
}

int
server_get_flag(int server_index, int value)
{
	if (server_index == -1)
		server_index = primary_server;
	return server_list[server_index].flags & value;
}

/* get ICB group */
u_char *
server_get_icbgroup(int server_index)
{
	u_char	*group;

	if (server_index == -1)
		server_index = primary_server;
	group = server_list[server_index].group ? server_list[server_index].group : empty_string();
	return (group);
}

/* get ICB mode */
u_char *
server_get_icbmode(int server_index)
{
	u_char	*mode;

	if (server_index == -1)
		server_index = primary_server;
	mode = server_list[server_index].icbmode ? server_list[server_index].icbmode : empty_string();
	return (mode);
}

/*
 * server_get_password: get the passwor for this server.
 */
u_char *
server_get_password(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	return (server_list[server_index].password);
}

/*
 * server_set_version: Sets the server version for the given server type.  A
 * zero version means pre 2.6, a one version means 2.6 aso. (look server.h
 * for typedef)
 */
void
server_set_version(int server_index, int version)
{
	if (server_index == -1)
		server_index = primary_server;
	server_list[server_index].version = version;
}

/*
 * server_get_version: returns the server version value for the given server
 * index 
 */
int
server_get_version(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	if (server_index == -1)
		return DEFAULT_SERVER_VERSION;
	else
		return (server_list[server_index].version);
}

/* server_get_name: returns the name for the given server index */
u_char	*
server_get_name(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	return (server_list[server_index].name);
}

/* server_set_itsname: returns the server's idea of its name */
u_char	*
server_get_itsname(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	if (server_list[server_index].itsname)
		return server_list[server_index].itsname;
	else if (server_list[server_index].name)
		return server_list[server_index].name;
	else
		return UP("<None>");
}

void
server_set_itsname(int server_index, u_char *name)
{
	if (server_index == -1)
		server_index = primary_server;
	malloc_strcpy(&server_list[server_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 server_index)
{
	if (server_index < 0)
		return (0);
	return (server_list[server_index].read != -1);
}

/*
 * 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 server_index)
{
	if (server_index < 0)
		return (0);
	return (server_list[server_index].connected && (server_list[server_index].flags & LOGGED_IN));
}

/* server_get_port: Returns the connection port for the given server index */
int
server_get_port(int server_index)
{
	if (server_index == -1)
		server_index = primary_server;
	return (server_list[server_index].port);
}

/*
 * server_get_nickname: returns the current nickname for the given server
 * index 
 */
u_char	*
server_get_nickname(int server_index)
{
	if ((server_index != -1) && server_list[server_index].nickname)
		return (server_list[server_index].nickname);
	else
		return my_nickname();
}



/* server_get_qhead - get the head of the whois queue */
WhoisQueue *
server_get_qhead(int server_index)
{
	if (server_index >= 0)
		return server_list[server_index].WQ_head;
	return NULL;
}

/* server_get_whois_stuff */
WhoisStuff *
server_get_whois_stuff(int server_index)
{
	if (server_index >= 0)
		return &server_list[server_index].whois_stuff;
	return NULL;
}

/* server_get_qtail - get the tail of the whois queue */
WhoisQueue *
server_get_qtail(int server_index)
{
	if (server_index >= 0)
		return server_list[server_index].WQ_tail;
	return NULL;
}


/* server_set_qhead - set the head of the whois queue */
void
server_set_qhead(int server_index, WhoisQueue *value)
{
	if (server_index >= 0)
		server_list[server_index].WQ_head = value;
}

/* server_set_qtail - set the tail of the whois queue */
void
server_set_qtail(int server_index, WhoisQueue *value)
{
	if (server_index >= 0)
		server_list[server_index].WQ_tail = value;
}


/*
 * server_get_operator: returns true if the user has op privs on the server,
 * false otherwise 
 */
int
server_get_operator(int server_index)
{
	return (server_list[server_index].operator);
}

/*
 * server_set_operator: If flag is non-zero, marks the user as having op
 * privs on the given server.  
 */
void
server_set_operator(int server_index, int flag)
{
	server_list[server_index].operator = flag;
}

/*
 * server_set_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
server_set_nickname(int server_index, u_char *nick)
{
	if (server_index >= 0)
	{
		malloc_strcpy(&(server_list[server_index].nickname), nick);
		if (server_index == primary_server)
			set_nickname(nick);
	}
	update_all_status();
}

void
server_set_motd(int server_index, int flag)
{
	if (server_index >= 0)
		server_list[server_index].motd = flag;
}

int
server_get_motd(int server_index)
{
	if (server_index >= 0)
		return(server_list[server_index].motd);
	return (0);
}

void
server_is_connected(int server_index, int value)
{
	server_list[server_index].connected = value;
	if (value)
		server_list[server_index].eof = 0;
}

/* send_to_server: sends the given info the the server */
void
send_to_server(const char *format, ...)
{
	static	int	in_send_to_server = 0;
	u_char	lbuf[BIG_BUFFER_SIZE];	/* make this buffer *much*
					 * bigger than needed */
	u_char	*buf = lbuf;
	int	des;
	size_t	len;
	int	server = from_server;
	va_list vlist;

	va_start(vlist, format);

	if (in_send_to_server)
		return;
	memset(lbuf, 0, sizeof(lbuf));
	in_send_to_server = 1;
	if (server == -1)
		server = primary_server;
	if (server != -1 && ((des = server_list[server].write) != -1) &&
	    (server_list[server].flags & LOGGED_IN) )
	{
		/* save space for the packet length */
		if (server_get_version(server) == ServerICB)
			buf++;
		server_list[server].sent = 1;
		vsnprintf(CP(buf), sizeof lbuf, format, vlist);
		va_end(vlist);
		len = my_strlen(buf);
		if (len > (IRCD_BUFFER_SIZE - 2))
			lbuf[IRCD_BUFFER_SIZE - 2] = '\0';
		/*
		 * for ICB, we send a final nul, and for IRC, we have
		 * a final newline.
		 */
		len++;
		if (do_hook(RAW_SEND_LIST, "%s", lbuf))
		{
			if (server_get_version(server) == ServerICB)
			{
				/*
				 * we depend on our caller to split things
				 * up for the ICB server
				 */
				if (len > 254)
					len = 254;
				lbuf[len] = 0;
				lbuf[0] = (u_char)len;
				lbuf[++len] = 0;
			}
			else
				my_strmcat(buf, "\n", IRCD_BUFFER_SIZE);

			/* XXX retval */
			ssl_write(server_list[server].ssl_info,
				  server_list[server].write, CP(lbuf), len);
		}
	}
	else if (!in_redirect() && !connected_to_server())
		say("You are not connected to a server, use /SERVER to connect.");
	in_send_to_server = 0;
}

#ifdef HAVE_SYS_UN_H
/*
 * Connect to a UNIX domain socket. Only works for servers.
 * submitted by Avalon for use with server 2.7.2 and beyond.
 */
int
connect_to_unix(int port, u_char *path)
{
	struct	sockaddr_un un;
	int	    sock;

	sock = socket(AF_UNIX, SOCK_STREAM, 0);

	un.sun_family = AF_UNIX;
	snprintf(un.sun_path, sizeof un.sun_path, "%-.100s/%-.6d", path, port);

	if (connect(sock, (struct sockaddr *)&un, (int)my_strlen(path)+2) == -1)
	{
		new_close(sock);
		return -1;
	}
	return sock;
}
#endif /* HAVE_SYS_UN_H */

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

	for (i = 0; i < number_of_servers(); i++)
	{
		if (server_list[i].read != -1)
			new_close(server_list[i].read);
		if (server_list[i].write != -1)
			new_close(server_list[i].write);
	}
}

u_char	*
create_server_list(void)
{
	int	i;
	u_char	*value = NULL;
	u_char	buffer[BIG_BUFFER_SIZE];

	*buffer = '\0';
	for (i = 0; i < number_of_servers(); i++)
		if (server_list[i].read != -1)
		{
			my_strmcat(buffer, server_get_itsname(i), sizeof buffer);
			my_strmcat(buffer, " ", sizeof buffer);
		}
	malloc_strcpy(&value, buffer);

	return value;
}

static	void
add_to_server_buffer(int server, u_char *buf)
{
	if (buf && *buf)
	{
		if (server_list[server].buffer)
			malloc_strcat(&server_list[server].buffer, buf);
		else
			malloc_strcpy(&server_list[server].buffer, buf);
	}
}

void
disconnectcmd(u_char *command, u_char *args, u_char *subargs)
{
	u_char	*server;
	u_char	*message;
	int	i;
	int	old_serv;

	if ((server = next_arg(args, &args)) != NULL && server[0] != '*' && server[1] != '\0')
	{
		i = parse_server_index(server);
		if (-1 == i)
		{
			say("No such server!");
			return;
		}
	}
	else
		i = get_window_server(0);
	/*
	 * XXX - this is a major kludge.  i should never equal -1 at
	 * this point.  we only do this because something has gotten
	 * *really* confused at this point.  .mrg.
	 */
	if (i == -1)
	{
		for (i = 0; i < number_of_servers(); i++)
		{
			server_list[i].eof = -1;
			server_list[i].connected = 0;
			new_close(server_list[i].read);
			new_close(server_list[i].write);
		}
		goto done;
	}
	if (!args || !*args)
		message = UP("Disconnecting");
	else
		message = args;
	if (-1 == server_list[i].write)
	{
		say("That server isn't connected!");
		return;
	}
	server = server_list[i].itsname ? server_list[i].itsname :
		server_list[i].name ? server_list[i].name : (u_char *) "unknown?";
	say("Disconnecting from server %s", server);
	old_serv = server_list[i].close_serv;
	close_server(i, message);
	server_list[i].eof = 1;
	if (old_serv != -1 && old_serv != i)
	{
		Window *tmp;
		Win_Trav wt;

		say("Connection to server %s resumed...", server_list[old_serv].name);
		server_list[i].close_serv = -1;
		server_list[old_serv].flags &= ~(CLOSE_PENDING|CLEAR_PENDING);
		server_list[old_serv].flags |= LOGGED_IN;
		server_list[old_serv].connected = 1;
		wt.init = 1;
		while ((tmp = window_traverse(&wt)))
			if (window_get_server(tmp) == i)
			{
				window_set_server(window_get_refnum(tmp), old_serv, WIN_ALL);
				break;
			}
	}
done:
	clean_whois_queue();
	window_check_servers();
	if (!connected_to_server())
		say("You are not connected to a server. Use /SERVER to connect.");
}

/*
 * parse_server: feed this line into the right server parser
 */
static void
parse_server(u_char *line)
{
	server_list[parsing_server_index].parse_server(line);
}

int
server_get_server_group(int server_index)
{
	if (server_index < 0 && server_index >= number_of_servers())
		server_index = from_server;

	return server_list[server_index].server_group;
}

void
server_set_server_group(int server_index, int group)
{
	if (server_index < 0 && server_index >= number_of_servers())
		server_index = from_server;

	server_list[server_index].server_group = group;
}

static void
server_group_get_connected_next(int si)
{
	int i, group;

	group = server_list[si].server_group;

	for (i = si + 1; i != si; i = (i + 1) % number_of_servers())
		if (server_list[i].server_group == group)
		{
			say("Ok, trying server %s...", server_get_itsname(si));
			get_connected(i);
			return;
		}
	say("No servers available.");
}

void
add_server_to_server_group(int server, u_char *group)
{
	int	i = find_server_group(group, 1);

	server_list[server].server_group = i;
	say("Server %s's server group is now %s", server_get_itsname(server), group);
}

int
find_server_group(u_char *group, int add)
{
	static	int	next = 1;
	SGroup *g;

	if (!group || !*group)
		return 0;

	g = (SGroup *) find_in_list((List **)(void *)&server_group_list, group, 0);
	if (g)
		goto end;

	if (!add)
		return 0;

	g = new_malloc(sizeof *g);
	g->name = NULL;
	malloc_strcpy(&g->name, group);
	g->number = next++;
	add_to_list((List **)(void *)&server_group_list, (List *) g);
end:
	return g->number;
}

u_char	*
find_server_group_name(int number)
{
	SGroup *g = server_group_list;

	for (; g; g = g->next)
		if (g->number == number)
			return g->name;
	return empty_string();
}

int
active_server_group(int sgroup)
{
	int i;

	/* kinda blah - returns first active server in that group */
	for (i = 0; i < number_of_servers(); i++)
		if (server_list[i].connected)
			return i;

	return -1;
}

void
server_set_version_string(int server, u_char *ver)
{
	if (ver)
		malloc_strcpy(&server_list[server].version_string, ver);
	else
		new_free(&server_list[server].version_string);
}

u_char *
server_get_version_string(int server)
{
	return server_list[server].version_string;
}

void
server_set_away(int server, u_char *away)
{
	if (away)
		malloc_strcpy(&server_list[server].away, away);
	else
		new_free(&server_list[server].away);
}

u_char *
server_get_away(int server)
{
	return server_list[server].away;
}

void
server_get_local_ip_info(int server, SOCKADDR_STORAGE **sa, socklen_t *salen)
{
	if (sa)
		*sa = server_list[from_server].localaddr;
	if (salen)
		*salen = server_list[from_server].localaddrlen;
}

void
server_set_chan_list(int server, ChannelList *chan)
{
	server_list[server].chan_list = chan;
}

ChannelList *
server_get_chan_list(int server)
{
	return server_list[server].chan_list;
}

void
server_set_attempting_to_connect(int server, int attempt)
{
	server_list[server].attempting_to_connect = attempt;
}

int
server_get_attempting_to_connect(int server)
{
	return server_list[server].attempting_to_connect;
}

void
server_set_sent(int server, int sent)
{
	server_list[server].sent = sent;
}

int
server_get_sent(int server)
{
	return server_list[server].sent;
}

CtcpFlood *
server_get_ctcp_flood(int server_index)
{
	if (server_index < 0 && server_index >= number_of_servers())
		server_index = from_server;

	return server_list[server_index].ctcp_flood;
}

int
number_of_servers(void)
{
	return number_of_servers_count;
}

void
unset_never_connected(void)
{
	never_connected_local = 0;
}

int
never_connected(void)
{
	return never_connected_local;
}

void
set_connected_to_server(int val)
{
	connected_to_server_local = val;
}

int
connected_to_server(void)
{
	return connected_to_server_local;
}

WhoInfo *
server_get_who_info(void)
{
	return server_list[from_server].who_info;
}

int
server_get_oper_command(void)
{
	return server_list[from_server].oper_command;
}

void
server_set_oper_command(int val)
{
	server_list[from_server].oper_command = val;
}

int
parsing_server(void)
{
	return parsing_server_index;
}

int
get_primary_server(void)
{
	return primary_server;
}

void
set_primary_server(int server)
{
	primary_server = server;
}

int
get_from_server(void)
{
	return from_server;
}

int
set_from_server(int server)
{
	int	old_from_server = from_server;

	from_server = server;
	return old_from_server;
}

server_ssl_level
server_do_ssl(int server)
{
	return server_list[server].ssl_level;
}

void
server_default_encryption(u_char *proto, u_char *type)
{
	if (my_stricmp(proto, UP("ICB")) == 0)
	{
		yell("No ICB SSL support yet.");
		return;
	}
	if (my_stricmp(proto, UP("IRC")) != 0)
	{
		yell("Only support IRC and ICB.");
		return;
	}

	/* OK, irc SSL here. */
	if (my_stricmp(type, UP("check")) == 0)
	{
		irc_ssl_level = SSL_VERIFY;
		return;
	}

	if (my_stricmp(type, UP("nocheck")) == 0)
	{
		irc_ssl_level = SSL_ON;
		return;
	}

	if (my_stricmp(type, UP("off")) == 0)
	{
		irc_ssl_level = SSL_OFF;
		return;
	}
}

void	*
server_get_server_private(int server)
{
	return server_list[server].server_private;
}

void
server_set_server_private(int server, void *data, server_private_cb_type cb)
{
	if (server < 0 && server >= number_of_servers())
		server = from_server;

	server_list[server].server_private = data;
	server_list[server].server_private_cb = cb;
}

u_char	*
server_get_proxy_name(int server, int use_default)
{
	u_char	*name;

	if (server < 0 && server >= number_of_servers())
		return NULL;
	name = server_list[server].proxy_name;
	if (server_list[server].proxy_port == -1)
		name = NULL;
	else if (name == NULL && use_default)
		name = default_proxy_name;

	Debug(DB_PROXY, "returning name '%s'", name ? name : UP("<>"));
	return name;
}

int
server_get_proxy_port(int server, int use_default)
{
	int port;

	if (server < 0 && server >= number_of_servers())
		return 0;
	port = server_list[server].proxy_port;
	if (port == 0 && use_default)
		port = default_proxy_port;

	Debug(DB_PROXY, "returning port %d", port);
	return port;
}

void
server_set_default_proxy(u_char *proxy_and_port)
{
	server_split_proxy_port(proxy_and_port,
				&default_proxy_name, &default_proxy_port);
	Debug(DB_PROXY, "set server '%s' port '%d'", default_proxy_name, default_proxy_port);
}
