/* Copyright (C) 2000-1 drscholl@users.sourceforge.net
   This is free software distributed under the terms of the
   GNU Public License.  See the file COPYING for details.

   $Id: main.c,v 1.51 2002/09/24 00:13:40 khaytsus Exp $ */

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

#ifdef WIN32
#include "win32-support.h"
#include <errno.h>
#endif /* WIN32 */

#ifdef SOLARIS
#include <procfs.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <time.h>
#include <sys/stat.h>
#include <ctype.h>

#ifndef WIN32
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <sys/time.h>
#include <limits.h>
#endif /* !WIN32 */
#include "opennap.h"
#include "debug.h"

#if DEBUG
#define dprint0(a)	printf(a);
#define dprint1(a,b) printf(a,b);
#else
#define dprint0(a)
#define dprint1(a,b)
#endif

/* offset into global.poll[] for the given file descriptor */
#define POFF(fd)	global.fdmap[fd]

#if defined(WIN32) && !defined(CYGWIN)
#define SOFT_ERROR(e)   ((e) == WSAEINTR || \
                         (e) == WSAEWOULDBLOCK || \
                         (e) == EWOULDBLOCK || \
                         (e) == EINTR || \
                         (e) == EAGAIN || \
                         (e) == 0)

#endif 

/*
 * Global Variables
 */

global_t global;

LIST   *Bans = 0;		/* bans on ip addresses / users */
LIST   *IBL = 0;		/* internal banlist */
LIST   *UserClass = 0;

LIST   *SearchCache = 0;        /* List of cached remote searches */
int     SearchCacheEntries = 0; /* Number of entries in the search cache */

char    Buf[2048];		/* global scratch buffer */

HASH   *Channel_Db;
int     Client_Queue_Length;
HASH   *Channels = 0;		/* global channel list */
HASH   *Client_Versions;
HASH   *Clones;
int     Compression_Level = 0;
char   *Config_Dir = SHAREDIR;
LIST   *Destroy = 0;
int	    EjectAfter;
int     Flood_Commands;
int     Flood_Time;
int     Flood_Eject;
int     Flood_Ban_TTL;
LIST   *Flooders = 0;
HASH   *Hotlist;		/* global hotlist */
u_int   Interface = INADDR_ANY;
#ifdef USE_INVALID_CLIENTS
char   *Invalid_Clients = 0; /* Added by winter_mute */
char   *Valid_Clients = 0;
#endif
#ifdef USE_INVALID_NICKS
char   *Invalid_Nicks = 0; /* Added by winter_mute */
#endif
#ifdef USE_PROTNET
char   *Protnet = 0; /* Added by winter_mute */
#endif
#if defined (USE_INVALID_CLIENTS) || defined (USE_INVALID_NICKS)
char   *Set_Server_Nicks = 0;
#endif
char   *Listen_Addr = 0;
int     Login_Interval;
int     Login_Timeout;
int	Log_Level;
int     Max_Browse_Result;
int     Max_Client_String;
int     Max_Clones;
int     Max_Command_Length;
int     Max_Connections;
int     Max_Hotlist;
int     Max_Ignore;
int     Max_Reason;
int     Max_Search_Results;
int     Max_Searches_Pending;
int     Max_Time_Delta;
int     Max_Topic;
int     Max_User_Channels;	/* default, can be changed in config */
LIST   *Mods = 0;		/* local mods+ */
int     Nick_Expire;
double  Num_Gigs = 0;		/* in kB */
int     Num_Files = 0;
int     Ping_Interval;
int     Register_Interval;
int     Search_Timeout;
char   *Server_Alias = 0;
LIST   *Server_Auth = 0;
int     Server_Chunk = 0;
u_int   Server_Ip = 0;
u_int   Server_Flags = 0;
char   *Server_Name = 0;
LIST   *Server_Ports = 0;	/* which port(s) to listen on for connections */
int     Server_Queue_Length;
int     SigCaught = 0;
time_t  Server_Start;		/* time at which the server was started */
int     User_Db_Interval;	/* how often to save the user database */
HASH   *Users;			/* global users list */
int     Warn_Time_Delta;
HASH   *Who_Was;
int     Who_Was_Time = 0;

/* Because the useage in browse.c makes this necessary even on a router. */
unsigned int    Max_Shared;


#ifndef ROUTING_ONLY
int     File_Count_Threshold;
HASH   *File_Table;		/* global file list */
unsigned int Local_Files = 0;	/* number of files shared by local users */
int     Index_Path_Depth;	/* how many levels including the filename to
				   include when indexing */

#if RESUME
HASH   *MD5;			/* global hash list */
#endif /* RESUME */
int     Stats_Port;		/* port to listen on for stats info */
#endif /* ! ROUTING_ONLY */

#ifndef WIN32
int     Uid;
int     Gid;
int     Connection_Hard_Limit;
int     Max_Data_Size;
int     Max_Rss_Size;
#endif
int     Max_Nick_Length;
int     Max_Channel_Length = 0;

int     checkdelay_level;
char   *checkdelay_names [4];
time_t  checkdelay_times [4];

/* local server list.  NOTE that this contains pointers into the global.clients
   list to speed up server-server message passing */
LIST   *Servers = 0;

/* list of all servers in the cluster */
LIST   *Server_Links = 0;

/* Cache of server names for caching the user->server pointers */
LIST   *Server_Names = 0;

/* server output histogram (10125) */
LIST   *Hist_Out = 0;

stats_t stats;



void
set_write (int fd)
{
#if HAVE_POLL
    global. poll[POFF (fd)].events |= POLLOUT;
#else
    FD_SET (fd, &global.write_fds);
#endif
}

void
clear_write (int fd)
{
#if HAVE_POLL
    global. poll[POFF (fd)].events &= ~POLLOUT;
#else
    FD_CLR (fd, &global.write_fds);
#endif
}

void
set_read (int fd)
{
#if HAVE_POLL
    global. poll[POFF (fd)].events |= POLLIN;
#else
    FD_SET (fd, &global.read_fds);
#endif
}

void
clear_read (int fd)
{
#if HAVE_POLL
    global. poll[POFF (fd)].events &= ~POLLIN;
#else
    FD_CLR (fd, &global.read_fds);
#endif
}

#define CLICK 64


void
add_fd (int fd)
{
#if HAVE_POLL
    int     off;

    if (global.poll_max == global.poll_num)
    {
	global. poll_max += CLICK;
	global. poll = REALLOC (global.poll,
				sizeof (struct pollfd) * global.poll_max);

	for (off = global.poll_num; off < global.poll_max; off++)
	{
	    global. poll[off].fd = -1;
	    global. poll[off].events = 0;
	    global. poll[off].revents = 0;
	}
    }
#endif

    /* keep track of the biggest fd we've seen */
    if (fd > global.max_fd)
    {
#if HAVE_POLL
	global. fdmap = REALLOC (global.fdmap,
				 sizeof (int) * (fd + 1));

	for (off = global.max_fd + 1; off < fd + 1; off++)
	    global. fdmap[off] = -1;
#endif
	global. max_fd = fd;
    }

#if HAVE_POLL
    off = global.fdmap[fd] = global.poll_num++;

    global. poll[off].fd = fd;
    global. poll[off].events = 0;
    global. poll[off].revents = 0;
#endif
}

#if HAVE_POLL
void
remove_fd (int fd)
{
    if (fd == -1)
    {
	ASSERT (0);
	return;
    }

    if (global.fdmap[fd] == -1)
    {
	ASSERT (0);
	return;
    }

    if (global.fdmap[fd] < global.poll_num - 1)
    {
	/* swap with the last client */
	int     i = global.poll[global.poll_num - 1].fd;

	ASSERT (i != -1);
	ASSERT (global.poll[POFF (fd)].fd == fd);
	ASSERT (global.poll[POFF (i)].fd == i);

	memcpy (&global.poll[POFF (fd)], &global.poll[POFF (i)],
		sizeof (struct pollfd));
	global. fdmap[i] = POFF (fd);
    }

    /* mark as unused */
    global. fdmap[fd] = -1;
    global. poll_num--;

    /* reset the pollfd struct */
    global. poll[global.poll_num].fd = -1;
    global. poll[global.poll_num].events = 0;
    global. poll[global.poll_num].revents = 0;
}
#endif /* HAVE_POLL */

int
add_client (CONNECTION * con, int is_server)
{
    /* allocate more space if required */
    if (global.clients_max == global.clients_num)
    {
	global. clients_max += CLICK;
	global. clients = REALLOC (global.clients,
				   sizeof (CONNECTION *) *
				   global.clients_max);
    }
    con->id = global.clients_num++;
    global. clients[con->id] = con;

    add_fd (con->fd);

    con->class = CLASS_UNKNOWN;
    con->timer = global.current_time;	/* set a login timer */

    set_nonblocking (con->fd);
    set_keepalive (con->fd, 1);	/* enable tcp keepalive messages */

    if (is_server)
    {
	/* we are doing a non-blocking connect, wait for the socket to become
	 * writable
	 */
	con->connecting = 1;
	set_write (con->fd);
    }
    else
    {
	/* user connection, wait for some input */
	set_read (con->fd);
    }
    return 0;
}

void
send_all_clients (int tag, const char *fmt, ...)
{
    va_list ap;
    int     len;
    int     i;

    va_start (ap, fmt);
    vsnprintf (Buf + 4, sizeof (Buf) - 4, fmt, ap);
    va_end (ap);
    len = strlen (Buf + 4);
    set_tag (Buf, tag);
    set_len (Buf, len);
    len += 4;
    for (i = 0; i < global.clients_num; i++)
	if (ISUSER (global.clients[i])) {
	    queue_data (global.clients[i], Buf, len);
	}
}

#ifndef ROUTING_ONLY
static void
report_stats (int fd)
{
    int     n;
    struct sockaddr_in sin;
    socklen_t sinsize = sizeof (sin);

    n = accept (fd, (struct sockaddr *) &sin, &sinsize);
    if (n == -1)
    {
	nlogerr ("report_stats", "accept");
	return;
    }
    log_message_level (LOG_LEVEL_STATS, "report_stats: connection from %s:%d", inet_ntoa (sin.sin_addr),
	 htons (sin.sin_port));

    snprintf (Buf, sizeof (Buf), "%d %d %0.2f %.0f %d\n", Users->dbsize,
	      Num_Files, stats.load_avg, Num_Gigs * 1024., global.clients_num - list_count(Servers));

    WRITE (n, Buf, strlen (Buf));
    CLOSE (n);
}
#endif /* !ROUTING_ONLY */

static void
update_stats (void)
{
    int     numServers = list_count (Servers);
    time_t  delta;
    char   *tin, *tout;
#if defined(LINUX) || defined(SOLARIS)
    int     fd;
    char    path[_POSIX_PATH_MAX];
#endif
#ifdef LINUX
    extern int errno;
    float   loadavg = 0;
    char    inbuf[128];
#endif
#ifdef SOLARIS
    struct psinfo psinfo;
#endif

    delta = global.current_time - global.last_click;

    strcpy (Buf, ctime (&Server_Start));
    Buf[strlen (Buf) - 1] = 0; 
    log_message_level (LOG_LEVEL_STATS, "stats: server was started on %s", Buf);
    strcpy (Buf, ctime (&global.current_time));

    Buf[strlen (Buf) - 1] = 0; 
    log_message_level (LOG_LEVEL_STATS, "stats: current time is %s", Buf);
    log_message_level (LOG_LEVEL_STATS, "stats: library is %d GB, %d files, %d users, %d flagged uploading lists",
         (int) (Num_Gigs / 1048576.), Num_Files, Users->dbsize, global.users_uploading_lists);
    log_message_level (LOG_LEVEL_STATS, "stats: %d local clients, %d z-clients, %d linked servers",
         global.clients_num - numServers, stats.zusers, numServers);

#ifndef ROUTING_ONLY
    log_message_level (LOG_LEVEL_STATS, "stats: %d local files", Local_Files);
    log_message_level (LOG_LEVEL_STATS, "stats: File_Table contains %d entries", File_Table->dbsize);
    log_message_level (LOG_LEVEL_STATS, "stats: Search Filter contains %d entries", Filter->dbsize);
#endif
    if (delta > 0)
        log_message_level (LOG_LEVEL_STATS, "stats: %d searches/sec - %d pending", global.search_count / delta, Pending_Searches);

    log_message_level (LOG_LEVEL_STATS, "stats: User_Db %d, IBL %d, Flooders %d", User_Db->dbsize, stats.ibl_db, stats.flood_db);
    log_message_level (LOG_LEVEL_STATS, "stats: %d channels", Channels->dbsize);
    if (delta > 0)
        log_message_level (LOG_LEVEL_STATS, "stats: %d kbytes/sec in, %d kbytes/sec out",
             (int) (global.bytes_in / 1024 / delta),
             (int) (global.bytes_out / delta / 1024));
    global.total_bytes_in += global.bytes_in;
    global.bytes_in = 0;
    global.total_bytes_out += global.bytes_out;
    global.bytes_out = 0;
    tin = print_size( Buf, sizeof (Buf), global.total_bytes_in );
    tout = print_size( Buf + 16, sizeof (Buf) - 16, global.total_bytes_out );
    log_message_level (LOG_LEVEL_STATS, "stats: %.0f (%s) bytes sent, %.0f (%s) bytes received",
         global.total_bytes_out, tout, global.total_bytes_in, tin);

#ifdef LINUX
    {
	if ((fd = open ("/proc/loadavg", O_RDONLY))!=-1) {
      if (read(fd, inbuf, 127)!=-1) {
        sscanf (inbuf, "%f", &loadavg);
        close(fd);
        }
	} else {
	   log_message_level (LOG_LEVEL_STATS, "update_stats: /proc/loadavg: %s (errno %d)",
		 STRERROR (errno), errno);
	}
    }
    log_message_level (LOG_LEVEL_STATS, "stats: %0.2f total load average", loadavg);
    stats.load_avg = loadavg;

    snprintf(path, sizeof (path), "/proc/%d/status", getpid());
    if ((fd = open (path, O_RDONLY))==-1) {
       log_message_level (LOG_LEVEL_ERROR, "update_stats: ERROR: can't open %s: %s (erno %d)", 
		   path, STRERROR (errno), errno);
    } else {
       while ((fake_fgets(inbuf,127,fd))!=NULL) {
          if (strncmp("VmSize:", inbuf,7)==0) {
            stats.mem_usage=atol(inbuf+7);
            break;
            }
         
       }
       close(fd);
       log_message_level(LOG_LEVEL_STATS, "stats: %d kB memory used", stats.mem_usage);
    }
#endif
#ifdef SOLARIS
    {
    snprintf(path, sizeof (path), "/proc/%d/psinfo", (int)getpid());
    fd = open (path, O_RDONLY, 0);
    if (fd==-1)
      {
      log_message_level (LOG_LEVEL_ERROR, "update_stats: ERROR: can't open %s: %s (errno %d)", path, STRERROR (errno), errno);
      }
    else
      {
      if (read(fd, &psinfo, sizeof(struct psinfo))!=sizeof(struct psinfo))
        {
	      log_message_level (LOG_LEVEL_ERROR, "update_stats: ERROR: Unable to read entry from %s", path);
	      }
      else
        {
		    stats.mem_usage=psinfo.pr_size;
		    stats.load_avg=psinfo.pr_pctcpu/100;
        }
     } 
  close(fd);
  }	  

    log_message_level (LOG_LEVEL_STATS, "stats: %0.2f total load average", stats.load_avg);
    log_message_level(LOG_LEVEL_STATS, "stats: %d kB memory used", stats.mem_usage);
#endif

    log_message_level (LOG_LEVEL_STATS, "stats: %d total logins, %d disconnects", stats.logins, stats.disconnects_total );
    if (delta)
    log_message_level (LOG_LEVEL_STATS, "stats: %d connects, %d disconnects, delta: %d",
                    stats.connects, stats.disconnects, stats.connects - stats.disconnects);
    stats.connects = stats.disconnects = 0;
    if (delta)
	log_message_level (LOG_LEVEL_STATS, "stats: %d tags (%d/sec)", stats.tags, stats.tags / delta);
    stats.tags = 0;
    log_message_level (LOG_LEVEL_STATS, "stats: %d connection reset, %d timeouts, %d no route, %d broken pipe", stats.con_104, stats.con_110, stats.con_113, stats.con_032 );
    log_message_level (LOG_LEVEL_STATS, "stats: wrong parameters %d client, %d server", stats.login_ce_params, stats.login_se_params );
    log_message_level (LOG_LEVEL_STATS, "stats: %d auto reg is off, %d already logged in", stats.login_ce_autoreg_off, stats.login_ce_already );
    log_message_level (LOG_LEVEL_STATS, "stats: %d connecting too fast", stats.login_ce_too_fast );
#if ROUTING_ONLY
    log_message_level (LOG_LEVEL_STATS | LOG_LEVEL_SECURITY, "stats: %d error not admin+", stats.login_ce_not_admin );
#endif
    if (stats.login_ce_restricted > 0) {
       log_message_level (LOG_LEVEL_STATS, "stats: %d error server is restricted", stats.login_ce_restricted );
    }
    if (stats.login_ce_nick_already_registered > 0) {
       log_message_level (LOG_LEVEL_STATS, "stats: %d error nick already registered", stats.login_ce_nick_already_registered );
    }
    log_message_level (LOG_LEVEL_STATS, "stats: %d opennap clients banned", stats.login_ce_client_banned );
    log_message_level (LOG_LEVEL_STATS, "stats: %d clones detected, %d banned, %d max_connections reached",
    	stats.login_ce_clone, stats.login_ce_banned, stats.login_ce_max_connections );

    log_message_level (LOG_LEVEL_STATS, "stats: login invalid: %d nick, %d password, %d speed, %d port",
    	stats.login_ce_invalid_nick, stats.login_ce_password, stats.login_ce_speed, stats.login_ce_port );

    log_message_level (LOG_LEVEL_STATS, "stats: %d searches, %d cancelled, %d expired, %d not found", 
    	stats.search_total, stats.search_cancelled, stats.search_expired, stats.search_nosuch );

    /* reset counters */
    global. search_count = 0;
    global. last_click = global.current_time;

    /* since we send the same data to many people, optimize by forming
       the message once then writing it out */
    send_all_clients (MSG_SERVER_STATS, "%d %d %d", Users->dbsize,
		      Num_Files, (int) (Num_Gigs / 1048576.));

#ifndef ROUTING_ONLY
    /* send live stats to stat server */
    stat_server_push ();
    log_message_level (LOG_LEVEL_DEBUG, "Finished emitting server stats.");
#endif
}

/* accept all pending connections */
static void
accept_connection (int s)
{
    CONNECTION *cli = 0;
    socklen_t sinsize;
    struct sockaddr_in sin;
    int     f;

    for (;;)
    {
	sinsize = sizeof (sin);
#if HAVE_ALARM
	/* set an alarm just in case we end up blocking when a client
	 * disconnects before we get to the accept()
	 */
	alarm (3);
#endif
	if ((f = accept (s, (struct sockaddr *) &sin, &sinsize)) < 0)
	{
#if HAVE_ALARM
	    alarm (0);
#endif
	    if (N_ERRNO != EWOULDBLOCK)
#ifdef WIN32
		log_message( "accept_connection: accept: %s (%d)", win32_strerror (N_ERRNO), N_ERRNO);
#else
		nlogerr ("accept_connection", "accept");
#endif
	    return;
	}
#if HAVE_ALARM
	alarm (0);
#endif
	stats.connects++;
/*    log_message_level (LOG_LEVEL_DEBUG, "accept_connection: socket %d, no.: %u, IP: %s", */
/*                    s, stats.connects, my_ntoa (BSWAP32 (sin.sin_addr.s_addr))); */
    if (ibl_check (BSWAP32 (sin.sin_addr.s_addr))) {
        stats.disconnects++;
        stats.disconnects_total++;
        CLOSE (f);
        continue;
    }

	if (!acl_connection_allowed (BSWAP32 (sin.sin_addr.s_addr)))
	{
	    log_message_level (LOG_LEVEL_SECURITY | LOG_LEVEL_ERROR, "accept_connection: connection from %s denied by ACLs",
		 inet_ntoa (sin.sin_addr));
        stats.disconnects++;
        stats.disconnects_total++;
	    CLOSE (f);
	    continue;
	}

	if ((cli = new_connection ()) == 0)
	    goto error;
	cli->fd = -1;

	/* if we have a local connection, use the external
	   interface so others can download from them */
	if (sin.sin_addr.s_addr == inet_addr ("127.0.0.1"))
	{
	    cli->ip = BSWAP32 (Server_Ip);
	    cli->host = STRDUP (Server_Name);
	}
	else
	{
	    cli->ip = BSWAP32 (sin.sin_addr.s_addr);
	    cli->host = STRDUP (inet_ntoa (sin.sin_addr));
	}

	if (!cli->host)
	{
	    OUTOFMEMORY ("accept_connection");
	    goto error;
	}

	cli->port = ntohs (sin.sin_port);
	cli->fd = f;

	if (add_client (cli, 0 /* not a server */ ))
	    goto error;
    }

    /* not reached */
    ASSERT (0);
    return;
  error:
    if (cli) {
        if (cli->fd != -1)
            CLOSE (cli->fd);
        if (cli->host)
            FREE (cli->host);
        FREE (cli);
    } else
        CLOSE (f);
    stats.disconnects++;
    stats.disconnects_total++;
}

static void
usage (void)
{
    fprintf (stderr,
	     "usage: %s [ -bhrsv ] [ -c DIR ] [ -p PORT ] [ -l IP ]\n",
	     PACKAGE);
    fprintf (stderr, "  -c DIR	read config files from DIR (default: %s)\n",
	     SHAREDIR);
    fprintf (stderr, "  -b		run as a background process (daemon)\n");
    fprintf (stderr, "  -h		print this help message\n");
    fprintf (stderr, "  -l IP		listen only on IP instead of all interfaces\n");
    fprintf (stderr, "  -p PORT	listen on PORT for connections (default: 8888)\n");
    fprintf (stderr, "  -r		disable remote configuration commands\n");
    fprintf (stderr, "  -s		channels may only be created by privileged users\n");
    fprintf (stderr, "  -v		display version information\n");
    exit (0);
}

static void
version (void)
{
    fprintf (stderr, "%s %s\n", PACKAGE, VERSION);
    fprintf (stderr, "Copyright (C) 2000-2001 drscholl@users.sourceforge.net\n");
    fprintf (stderr, "Copyright (C) 2002 Whole Opennap-ng Team http://opennap-ng.sf.net\n");
    exit (0);
}

inline void checkdelay_enter (char *name) {
/*    log_message_level (LOG_LEVEL_DEBUG, "checkdelay_enter: section %s", name); */
    if (checkdelay_level < (int) sizeof (checkdelay_names)) {
        checkdelay_names [checkdelay_level] = name;
        checkdelay_times [checkdelay_level] = time (0);
        checkdelay_level++;
    } else {
        log_message_level (LOG_LEVEL_ERROR, "checkdelay_enter: exceeded nest limit.");
    }
}


int checkdelay_leave (void) {
 time_t current_time;

    if (checkdelay_level > 0) {
        checkdelay_level--;
/*        log_message_level (LOG_LEVEL_DEBUG, "checkdelay_leave: section %s", checkdelay_names [checkdelay_level]); */
        current_time = time (0);
        if (current_time >= checkdelay_times [checkdelay_level] + global.critical_delay) {
            log_message_level (LOG_LEVEL_ERROR, "checkdelay: level %d, stuck for %d seconds in block \"%s\"",
                checkdelay_level + 1,
                current_time - checkdelay_times [checkdelay_level],
                checkdelay_names [checkdelay_level]);
            return current_time - checkdelay_times [checkdelay_level];
        }
    } else {
        log_message_level (LOG_LEVEL_ERROR, "checkdelay_leave: not in section.");
    }
    return 0;
}


static int *
args (int argc, char **argv, int *sockfdcount)
{
    int     i;
    LIST   *ports = 0, *tmpList;
    int     iface = -1;
    int    *sockfd;
    int     not_root = 1;
    int     port;
    int	disable_remote = 0;

#ifndef WIN32
    not_root = (getuid () != 0);
#endif

    while ((i = getopt (argc, argv, "bc:hl:p:rsvD")) != -1)
    {
	switch (i)
	{
	case 'b':
	    Server_Flags |= ON_BACKGROUND;
	    break;
	case 'D':
	    Server_Flags |= ON_NO_LISTEN;	/* dont listen on stats port */
	    break;
	case 'c':
	    /* ignore the command line option if we're running as root.
	     * we don't allow non-root users to specify their own config
	     * files to avoid possible security problems.
	     */
	    if (not_root)
		Config_Dir = optarg;
	    else
	    {
 /*		log_message_level (LOG_LEVEL_SECURITY | LOG_LEVEL_ERROR, "args: can't use -c when run as root");
		exit (1); */
		Config_Dir = optarg;
	    }
	    break;
	case 'l':
	    iface = inet_addr (optarg);
	    break;
	case 'p':
	    /* don't allow a privileged port to be used from the command line
	     * if running as root.  this can only be specified in the
	     * configuration file defined at compile time.
	     */
	    port = atoi (optarg);
	    if (not_root || port > 1024)
	    {
		tmpList = CALLOC (1, sizeof (LIST));
		tmpList->data = STRDUP (optarg);
		tmpList->next = ports;
		ports = tmpList;
	    }
	    else
	    {
		log_message_level (LOG_LEVEL_ERROR, "args: privileged ports not allowed on command line");
		exit (1);
	    }
	    break;
	case 'r':
	    disable_remote = 1;
	    break;
	case 's':
	    Server_Flags |= ON_STRICT_CHANNELS;
	    break;
	case 'v':
	    version ();
	    break;
	default:
	    usage ();
	}
    }

#if USE_CHROOT
    /* we always use the compiled directory instead of the one on the command
     * line here to avoid problems.
     */
    if (chroot (SHAREDIR))
    {
	perror ("chroot");
	exit (1);
    }
    if (chdir ("/"))
    {
	perror ("chdir");
	exit (1);
    }
    /* force the config files to be relative to the chroot jail */
    Config_Dir = "/";
    /* privs will be dropped later.  we still need them to read the the
     * config file and set resources.
     */
#endif

#if !defined(WIN32) && !defined(__EMX__)
    /* check whether to run in the background */
    if (Server_Flags & ON_BACKGROUND)
    {
	if (fork () == 0)
	    setsid ();
	else
	    exit (0);
    }
#endif

    if (init_server ())
	exit (1);

    /* if requested, disable remote configuration */
    if (disable_remote)
	    Server_Flags &= ~ON_REMOTE_CONFIG;
    if (!(Server_Flags & ON_REMOTE_CONFIG))
	    log_message_level(LOG_LEVEL_SECURITY, "args: remote configuration disabled");

    /* if the interface was specified on the command line, override the
     * value from the config file
     */
    if (iface != -1)
    {
	Interface = iface;
	Server_Ip = iface;
    }

    /* if port(s) were specified on the command line, override the values
       specified in the config file */
    if (!ports)
	ports = Server_Ports;

    /* create the incoming connections socket(s) */
    *sockfdcount = list_count (ports);
    /* ensure at least one valid port */
    if (*sockfdcount < 1)
    {
	log_message_level (LOG_LEVEL_ERROR, "args: no server ports defined");
	exit (1);
    }
    sockfd = CALLOC (*sockfdcount, sizeof (int));

    log_message_level (LOG_LEVEL_STATS, "args: listening on %d sockets", *sockfdcount);
    for (i = 0, tmpList = ports; i < *sockfdcount;
	 i++, tmpList = tmpList->next)
    {
	if ((sockfd[i] = new_tcp_socket (ON_NONBLOCKING | ON_REUSEADDR)) < 0)
	    exit (1);
	if (bind_interface (sockfd[i], Interface, atoi (tmpList->data)) == -1)
	    exit (1);
	if (listen (sockfd[i], SOMAXCONN) < 0)
	{
	    nlogerr ("args", "listen");
	    exit (1);
	}
    log_message_level (LOG_LEVEL_STATS, "args: listening on %s port %d, queue size: %d",
                    my_ntoa (Interface), atoi (tmpList->data), SOMAXCONN);
	if (sockfd[i] > global.max_fd)
	    global. max_fd = sockfd[i];
    }
    if (ports != Server_Ports)
	list_free (ports, free_pointer);
    return sockfd;
}

/* sync in-memory state to disk so we can restore properly */
static void
dump_state (void)
{
    userdb_dump ();		/* write out the user database */
/*    log_message_level (LOG_LEVEL_DEBUG, "Saving bans."); */
#ifndef ROUTING_ONLY
    save_bans ();		/* write out server bans */
/*    log_message_level (LOG_LEVEL_DEBUG, "Saving channels."); */
    dump_channels ();		/* write out persistent channels file */
/*    log_message_level (LOG_LEVEL_DEBUG, "Saving ACLs."); */
    acl_save ();		/* save acls */
#endif
}

#ifndef ROUTING_ONLY
static int
init_stats_port (void)
{
    int     sp = -1;

    if (!option (ON_NO_LISTEN) && Stats_Port != -1)
    {
	/* listen on port 8889 for stats reporting */
	if ((sp = new_tcp_socket (ON_REUSEADDR)) == -1)
	    exit (1);
	if (bind_interface (sp, Interface, Stats_Port))
	    exit (1);
	if (listen (sp, SOMAXCONN))
	{
	    logerr ("main", "listen");
	    exit (1);
	}
	if (sp > global.max_fd)
	    global. max_fd = sp;
    }
    return sp;
}
#endif

int     num_reaped = 0;



/* puts the specified connection on the destroy list to be reaped at the
 * end of the main event loop
 */
void destroy_connection (CONNECTION * con) {
    LIST   *list;

    ASSERT (validate_connection (con));

    /* already destroyed */
    if (con->fd == -1) {
        log_message_level (LOG_LEVEL_DEBUG, "destroy_connection: already destroyed!");
        return;
    }
    stats.disconnects++;
    stats.disconnects_total++;

/*    dprint1 ("destroy_connection: destroying fd %d\n", con->fd); */
/*    log_message ("%d:KILL", con->fd); */

    if (con->destroy) {
        list = list_find (Destroy, con);
        if (list) {
            log_message_level (LOG_LEVEL_DEBUG, "destroy_connection: already in list!");
            return;		/* already destroyed */
        }
        log_message_level (LOG_LEVEL_ERROR, "destroy_connection: error, destroyed connection not on Destroy list");
        log_message_level (LOG_LEVEL_ERROR, "destroy_connection: con->host = %s", con->host);
        if (ISUSER (con)) {
            log_message_level (LOG_LEVEL_CLIENT, "destroy_connection: con->user->nick = %s", con->user->nick);
        }
    }
    else {
        num_reaped++;
    }

    /* append to the list of connections to destroy */
    list = CALLOC (1, sizeof (LIST));
    if (!list) {
        OUTOFMEMORY ("destroy_connection");
        return;
    }
    list->data = con;
    ASSERT (list_validate (Destroy));
    Destroy = list_push (Destroy, list);
    ASSERT (Destroy->data == con);
    con->destroy = 1;

    /* we don't want to read/write anything furthur to this fd */
#if HAVE_POLL
    remove_fd (con->fd);
#else
    FD_CLR (con->fd, &global.read_fds);
    FD_CLR (con->fd, &global.write_fds);
#endif /* HAVE_POLL */

    ASSERT (list_count (Destroy) == num_reaped);
}


static void reap_dead_connection (CONNECTION * con)
{
#if DEBUG
    int     i;
#endif
    ASSERT (validate_connection (con));

#if HAVE_POLL
    ASSERT (global.fdmap[con->fd] == -1);

#if DEBUG
    /* be certain the fd isn't being polled */
    for (i = 0; i < global.poll_num; i++)
    ASSERT (global.poll[i].fd != con->fd);
#endif /* DEBUG */

#else
    /* this should have already happened, but to it here just to be safe */
    FD_CLR (con->fd, &global.read_fds);
    FD_CLR (con->fd, &global.write_fds);
#endif

    if (con->id < global.clients_num - 1) {
/* swap this place with the last connection in the array */
        global.clients[con->id] = global.clients[global.clients_num - 1];
        global. clients[con->id]->id = con->id;
    }
    global. clients_num--;
    global. clients[global.clients_num] = 0;

    /* close either the current descriptor */
/*    log_message_level (LOG_LEVEL_DEBUG, "reap_dead_connection: going to close fd %d", con->fd); */
    CLOSE (con->fd);

    /* mark that the descriptor has been closed */
    con->fd = -1;

    /* remove from flood list (if present) */
    if (Flooders) {
        Flooders = list_delete (Flooders, con);
    }

    /* this call actually free's the memory associated with the connection */
    remove_connection (con);
}

/* we can't use list_free(Destroy, reap_dead_connection) here because
 * reap_dead_connection might try to access `Destroy', which will be pointed
 * to free'd memory.  so this function updates `Destroy' in an atomic
 * fashion such that if `Destroy' is updated, we have the correct new value.
 */
static void reap_connections (void) {
    LIST   *tmp;

/*    if (num_reaped) { */
/*        log_message_level (LOG_LEVEL_DEBUG, "reap_connections: going to reap %d connections.", num_reaped); } */
    while (Destroy && !SigCaught) {
        tmp = Destroy;
        Destroy = Destroy->next;
        num_reaped--;
        reap_dead_connection (tmp->data);
        FREE (tmp);
    }
    ASSERT (num_reaped == 0);
}

static void
flood_expire (void)
{
    LIST  **list, *tmp;
    CONNECTION *con;

    stats.flood_db = 0;
    for (list = &Flooders; *list;) {
	con = (*list)->data;
	if (con->flood_start + Flood_Time < global.current_time) {
	    /* flood timer expired, resume reading commands */
	    set_read (con->fd);
	    tmp = *list;
	    *list = (*list)->next;
	    FREE (tmp);
	} else {
	    list = &(*list)->next;
	    stats.flood_db++;
	}
    }
}



/* since server->server data is always queued up so it can be compressed
 * in one shot, we have to explicitly call send_queued_data() for each
 * server here.
 */
static void
flush_server_data (CONNECTION * con, void *unused)
{
    (void) unused;
    ASSERT (validate_connection (con));
    if (send_queued_data (con) == -1) {
        log_message_level (LOG_LEVEL_ERROR, "Error sending queue to server %s; disconnecting!",
            my_ntoa (BSWAP32 (con->ip)));
        destroy_connection (con);
    }
}

#if HAVE_POLL
#define TIMEOUT timeout
#define READABLE(c)	(global.poll[global.fdmap[c]].revents & POLLIN)
#define WRITABLE(c)	(global.poll[global.fdmap[c]].revents & POLLOUT)
#else
#define TIMEOUT tv.tv_sec
#define READABLE(c)	FD_ISSET(c,&read_fds)
#define WRITABLE(c)	FD_ISSET(c,&write_fds)
#endif

static void
server_input (CONNECTION * con, void *arg)
{
#if HAVE_POLL
    (void) arg;
    ASSERT (global.fdmap[con->fd] != -1);

    ASSERT ((global.poll[POFF (con->fd)].events & POLLIN) !=0);
    if (global.poll[POFF (con->fd)].revents & POLLIN)
	handle_connection (con);
#else
    fd_set *read_fds = (fd_set *) arg;

    if (FD_ISSET (con->fd, read_fds))
	handle_connection (con);
#endif
}




/* Main function *************************************************************/

int main (int argc, char **argv) {
    int    *sockfd;		/* server sockets */
    int     sockfdcount;	/* number of server sockets */
    int     i;			/* generic counter */
    int     numfds;
#ifndef HAVE_POLL
#ifdef WIN32
    int     sel_error; /* select error number */
    int     selectErrors = 0;
#endif
#endif
    int     accepted_connections;

#ifndef ROUTING_ONLY
    int     sp;
#endif
#if HAVE_POLL
    int     timeout;
#else
    struct timeval tv;
    fd_set  read_fds, write_fds;
#endif

#ifdef WIN32
    WSADATA wsaData;
    int wsaErr;
	
    wsaErr = WSAStartup (MAKEWORD(2, 0), &wsaData);
    if (wsaErr != 0) {
        logerr("main", "WSAStartup error!!!");
        exit(1);
    }

#endif /* !WIN32 */

    memset (&global, 0, sizeof (global_t));
    
    /* Initialize some counters ... */
/*
    global.count205=0;
    global.count205mx=0;
    global.size205=0;
    global.size205mx=0;
*/
    
    /* minimize the stack space for the main loop by moving the command line
       parsing code to a separate routine */
    sockfd = args (argc, argv, &sockfdcount);

#ifndef ROUTING_ONLY
    sp = init_stats_port ();
#endif

#if HAVE_POLL
    global. poll_max = global.max_fd + 1;
    global. poll = CALLOC (global.poll_max, sizeof (struct pollfd));
    for (i = 0; i < global.poll_max; i++)
        global.poll[i].fd = -1;
    global. fdmap =
    CALLOC (global.poll_max, sizeof (int) * (global.max_fd + 1));
    memset (global.fdmap, -1, sizeof (int) * (global.max_fd + 1));
#endif

    for (i = 0; i < sockfdcount; i++) {
#if HAVE_POLL
        struct pollfd *p;
        global. fdmap[sockfd[i]] = global.poll_num++;
        p = &global.poll[global.fdmap[sockfd[i]]];
        p->fd = sockfd[i];
        p->events = POLLIN;
#else
        FD_SET (sockfd[i], &global.read_fds);
#endif
    }

#ifndef ROUTING_ONLY
    if (sp != -1) {
#if HAVE_POLL
        global. fdmap[sp] = global.poll_num++;
        global. poll[POFF (sp)].fd = sp;
        global. poll[POFF (sp)].events = POLLIN;
#else
        FD_SET (sp, &global.read_fds);
#endif /* HAVE_POLL */
    }
#endif /* ROUTING_ONLY */

    /* schedule periodic events */
    add_timer (global.stat_click, -1, (timer_cb_t) update_stats, 0);
    add_timer (User_Db_Interval, -1, (timer_cb_t) dump_state, 0);
    add_timer (60, -1, (timer_cb_t) expire_bans, 0);
    add_timer (60, -1, (timer_cb_t) ibl_expire, 0);
    add_timer (Ping_Interval, -1, (timer_cb_t) lag_detect, 0);
    add_timer (Who_Was_Time, -1, (timer_cb_t) expire_whowas, 0);

    /* initialize so we get the correct delta for the first call to
       update_stats() */
    global.last_click = global.current_time;

    /* auto connect remote servers if requested */
    if (option (ON_AUTO_LINK))
        auto_link ();
    global.main_loop_counter = 0;
    global.last_loop_time = time (0);


/* Start of main loop ###################################################### */

    while (!SigCaught) {
        global.last_loop_time = global.current_time;
        global.current_time = time (0);
        TIMEOUT = next_timer ();
        global.main_loop_counter++;
/*        log_message_level (LOG_LEVEL_DEBUG, "main: Entered main loop."); */

        if (!(global.main_loop_counter % global.loopcount_output_interval)) {
            log_message_level (LOG_LEVEL_DEBUG, "mainloop: iteration %d", global.main_loop_counter);
        } else if (global.current_time > global.last_loop_time + global.critical_delay) {
            log_message_level (LOG_LEVEL_ERROR, "mainloop: stuck for %d seconds! Iteration: %d",
                global.current_time - global.last_loop_time, global.main_loop_counter);
        }

/*  if we have a flood list and the timeout is greater than when the flood
    expires, reset the timeout */

        if (Flooders && Flood_Time > 0 && TIMEOUT > Flood_Time)
            TIMEOUT = Flood_Time;

#if HAVE_POLL
#if DEBUG
    /* check to make sure the poll[] array looks kosher */
        for (i = 0; i < global.poll_num; i++) {
            ASSERT (global.poll[i].fd != -1);
            ASSERT (global.fdmap[global.poll[i].fd] == i); }
        for (i = global.poll_num; i < global.poll_max; i++) {
            ASSERT (global.poll[i].fd == -1);
            ASSERT (global.poll[i].events == 0);
            ASSERT (global.poll[i].revents == 0); }
#endif /* DEBUG */

        numfds = poll (global.poll, global.poll_num, timeout * 1000);

        if (numfds == -1) {
            log_message_level (LOG_LEVEL_DEBUG, "main: poll error");
            nlogerr ("main", "poll");
            continue; }

#else /* HAVE_POLL */

        read_fds = global.read_fds;
        write_fds = global.write_fds;
/*        tv.tv_sec = max (TIMEOUT, 1); */
        tv.tv_usec = 0;

/*        log_message_level (LOG_LEVEL_DEBUG, "Going to select(). Timeout: %d", tv.tv_sec); */

/*        do { */
            numfds = select (global.max_fd + 1, &read_fds, 
#ifdef WIN32 /* dont't pass empty fd set */
                (&write_fds.fd_count > 0 ? &write_fds : NULL),
#else
                &write_fds,
#endif /* WIN32 */
                NULL, &tv);
/*            log_message_level (LOG_LEVEL_DEBUG, "main_loop: select num_fds: %d", numfds); */
            if (numfds <= 0) {
#ifdef WIN32
                sel_error = WSAGetLastError();
/*                if (!SOFT_ERROR (sel_error) && sel_error != WSAEINVAL) { */
/*                    log_message_level(LOG_LEVEL_ERROR, "select had an error... sel_error is %d", sel_error); */
/*                    logerr("main", "select loop - bombed"); */
/*                    exit(1); */
/*                } else { */
                    if (sel_error) {
                        log_message_level (LOG_LEVEL_ERROR, "select() had an error: %d", sel_error);
                    }
/*                    break; */
/*                } */
#else
                log_message_level (LOG_LEVEL_ERROR, "select() had an error: %d (%s)", errno, STRERROR (errno));
/*                break; */
#endif
            }
/*        } while (numfds <= 0); */
#endif /* HAVE_POLL */

/* execute any pending events now */
        if (!TIMEOUT) {
/*            log_message_level (LOG_LEVEL_DEBUG, "Checking for pending events now. TIMEOUT: %d", TIMEOUT); */
            checkdelay_enter ("Pending events.");
            exec_timers (global.current_time);
            checkdelay_leave ();
            log_message_level (LOG_LEVEL_DEBUG, "Finished executing pending events.");
        }
        if (numfds <= 0) {
            if (TIMEOUT) {
                log_message_level (LOG_LEVEL_ERROR, "main: short loop! select() / poll() flawed. Numfds: %d, errno: %d", numfds, errno);
            } else {
                log_message_level (LOG_LEVEL_DEBUG, "main: short loop, no I/O, processed events only.");
            }
            continue;
        }

/* pre-read server links */
        checkdelay_enter ("list_foreach (servers)");
        list_foreach (Servers, (list_callback_t) server_input,
#ifndef HAVE_POLL
            &read_fds
#else /* HAVE_POLL */
            NULL
#endif /* HAVE_POLL */
        );
        checkdelay_leave ();

/* do client i/o */
        checkdelay_enter ("Client I/O");
/*         log_message_level (LOG_LEVEL_DEBUG, "main: client I/O."); */
        for (i = 0; i < global.clients_num; i++) {
#if HAVE_POLL
    	    int off = POFF (global.clients[i]->fd);

            if (global.poll[off].revents & (POLLNVAL | POLLHUP | POLLERR)) {
                if (global.poll[off].revents & POLLERR) {
                    int err;
                    socklen_t errlen = sizeof (err);
                    if (getsockopt (global.poll[off].fd, SOL_SOCKET, SO_ERROR, &err, &errlen))
                        logerr ("main", "getsockopt");
                    else {
                        if (err == 104) {
                            stats.con_104++;
                        } else if (err == 110) {
                            stats.con_110++;
                        } else if (err == 113) {
                            stats.con_113++;
                        } else if (err == 32) {
                            stats.con_032++;
                        } else {
                            log_message_level (LOG_LEVEL_ERROR, "main: fd %d (%s): %s (errno %d)",
                                global.poll[off].fd,
                                global.clients[i]->host, STRERROR (err), err);
                        }
                    }
                } else
                    log_message_level (LOG_LEVEL_ERROR, "main: fd %d %s", global.poll[off].fd,
                        (global.poll[off].
                        revents & POLLNVAL) ? "is invalid" : "got hangup");
                destroy_connection (global.clients[i]);
                continue;
            }
#endif /* HAVE_POLL */

            if (READABLE (global.clients[i]->fd)) {
                if (!global.clients[i]->destroy) {
		            handle_connection (global.clients[i]);
                }
            }
            if (!global.clients[i]->destroy) {
                if (WRITABLE (global.clients[i]->fd)) {
/*                    log_message_level (LOG_LEVEL_DEBUG, "Writable handle: %d", i); */
                    if (global.clients[i]->connecting) {
                        checkdelay_enter ("Complete connect");
                        complete_connect (global.clients[i]);
                        checkdelay_leave ();
                    } else {
#if HAVE_POLL
/* sanity check - make sure there was actually data to write. */

                        if (!ISSERVER (global.clients[i]) && !global.clients[i]->sendbuf ) {
                            log_message_level (LOG_LEVEL_ERROR,
                                "main: ERROR, fd %d (id %d) was writable with no pending data",
                                global.clients[i]->fd,
                                global.clients[i]->id);
                            clear_write (global.clients[i]->fd);
                        }
#endif /* HAVE_POLL */
                        if (send_queued_data (global.clients[i]) == -1) {
                            log_message_level (LOG_LEVEL_ERROR,
                                            "Error sending queued data to %s!%s; disconnecting!",
                                            global.clients[i]->user ? global.clients[i]->user->nick : "(server)",
                                            my_ntoa (BSWAP32 (global.clients[i]->ip)));
                            destroy_connection (global.clients[i]);
                        }
                    }
                }
            }
/* reap connections which have no logged in after `Login_Timeout' seconds */

            if (ISUNKNOWN (global.clients[i]) && global.current_time - global.clients[i]->timer > Login_Timeout) {
                log_message_level ( LOG_LEVEL_LOGIN, "main: terminating %s (login timeout)",
                    global.clients[i]->host);
                send_cmd (global.clients[i], MSG_SERVER_ERROR, "Idle timeout");
                destroy_connection (global.clients[i]);
            }
/*            checkdelay_leave (); */
        }
        checkdelay_leave ();

/* handle timed-out remote searches */
/*        log_message_level (LOG_LEVEL_DEBUG, "main: expire searches."); */
        expire_searches ();

#ifndef ROUTING_ONLY
/* check for stat server i/o */
        checkdelay_enter ("Stat server I/O");
        if (global.stat_server_fd != -1) {
/*            log_message_level (LOG_LEVEL_DEBUG, "main: stat_server_fd %d", global.stat_server_fd); */
            if (WRITABLE (global.stat_server_fd)) {
                int code;
                log_message_level (LOG_LEVEL_DEBUG, "main: stat_server_fd signalled writable.");
                socklen_t codesize = sizeof (code);
                clear_write (global.stat_server_fd);
/* nonblocking connect complete - check connection code */
                if (getsockopt (global.stat_server_fd, SOL_SOCKET, SO_ERROR,
                    SOCKOPTCAST &code, &codesize)) {
                    logerr ("main","getsockopt");
#if HAVE_POLL
                    remove_fd (global.stat_server_fd);
#endif
                    CLOSE (global.stat_server_fd);
                    global.stat_server_fd = -1;
                } else if (code) {
                    log_message_level (LOG_LEVEL_ERROR, "main: connection to stat server failed (%s)", STRERROR (code));
#if HAVE_POLL
                    remove_fd (global.stat_server_fd);
#endif
                    CLOSE (global.stat_server_fd);
                    global.stat_server_fd = -1;
                } else
                    set_read (global.stat_server_fd);
            } else if (READABLE (global.stat_server_fd)) {
                log_message_level (LOG_LEVEL_DEBUG, "main: stat_server_fd signalled readable.");
                stat_server_read ();
            }
        }
        checkdelay_leave ();

/* check for stats port connections */
        if (sp != -1) {
#if HAVE_POLL
            if (global.poll[POFF (sp)].revents & POLLIN)
#else /* HAVE_POLL */
            if (FD_ISSET (sp, &read_fds))
#endif /* HAVE_POLL */
                report_stats (sp);
        }
#endif /* ROUTING_ONLY */

/* check for new clients */
/*        log_message_level (LOG_LEVEL_DEBUG, "main: accepting incoming connections."); */
        accepted_connections = 0;
        for (i = 0; i < sockfdcount; i++) {
#if HAVE_POLL
            if (global.poll[POFF (sockfd[i])].revents & POLLIN) {
#else
            if (FD_ISSET (sockfd[i], &read_fds)) {
#endif /* HAVE_POLL */
                accept_connection (sockfd[i]);
                accepted_connections++;
            }
        }
/*        if (accepted_connections) { */
/*            log_message_level (LOG_LEVEL_DEBUG, "main: accepted %d connections.", accepted_connections); } */
    	list_foreach (Servers, (list_callback_t) flush_server_data, 0);
        flood_expire ();

/* remove destroyed connections from the client list. This MUST be the last
   operation in the loop since all the previous can cause connections to be
   terminated. */

        reap_connections ();
    }

/* End of main loop ######################################################## */

    /* close all open file descriptors properly */
    for (i = 0; i <= global.max_fd; i++)
	CLOSE (i);

    dump_state ();

#if DEBUG

#if HAVE_POLL
    FREE (global.poll);
    FREE (global.fdmap);
#endif
    for (i = 0; i < global.clients_num; i++)
    {
	global. clients[i]->fd = -1;
	remove_connection (global.clients[i]);
    }
    FREE (global.clients);

    FREE (sockfd);

#ifndef ROUTING_ONLY
    free_hash (Filter);
    free_hash (File_Table);
#endif

    free_hash (Users);
    free_hash (Channels);
    Channels = 0;
    free_hash (Hotlist);
    free_hash (User_Db);
    free_hash (Who_Was);
    free_hash (Clones);
    free_hash (Client_Versions);
    free_timers ();

    list_free (Bans, (list_destroy_t) free_ban);
    list_free (IBL, (list_destroy_t) free_ban);
    list_free (Server_Auth, (list_destroy_t) free_server_auth);
    list_free (Server_Names, free_pointer);
    list_free (Destroy, 0);
    list_free (Hist_Out, free_pointer);
    list_free (SearchCache, (list_destroy_t) free_cache);
    

    /* free up memory associated with global configuration variables */
    free_config ();
    acl_destroy ();

    /* this displays a list of leaked memory.  pay attention to this. */
    CLEANUP ();
#endif

#ifdef WIN32
    WSACleanup ();
#endif

    global. current_time = time (0);
    log_message_level (LOG_LEVEL_SERVER, "main: server ended at %s", ctime (&global.current_time));

    exit (0);
}
