/*
 * conn.cpp -- part of the ezbounce IRC proxy.
 *
 * (C) 1998-2002 Murat Deligonul
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 */

#include "autoconf.h"

#include <sys/types.h>
#include <stdlib.h>
#include <unistd.h>

#ifdef HAVE_SYS_IOCTL_H
    #include <sys/ioctl.h>
#endif
#ifdef HAVE_SYS_TIME_H
    #include <sys/time.h>
#endif
#ifdef HAVE_STDARG_H
    #include <stdarg.h>
#endif
#ifdef HAVE_POLL_H
#   include <poll.h>
#endif
#ifdef HAVE_SYS_POLL_H
#   include <sys/poll.h>
#endif
#ifdef _USE_SSL
    #include <openssl/ssl.h>
    #include <openssl/err.h>
    #include <openssl/rand.h>
#endif

#include <sys/socket.h>
#include <sys/resource.h>
#include <arpa/inet.h>
#include <time.h>
#include <netdb.h>
#include <errno.h>

#include "ezbounce.h"
#include "conn.h"
#include "linkedlist.h"
#include "commands.h"
#include "dcc.h"
#include "server.h"
#include "debug.h"
#include "ruleset.h"
#include "general.h"
#include "lib_mdidentd.h"
#include "messages.h"
#include "ircaddr.h"


extern bool port_in_set(u_short, const char *);
int mk_ppchar(char *, char *(*buff)[MAX_PPCHAR_ARGS]);

unsigned conn::num_active = 0;
unsigned conn::current_id = 0;
unsigned conn::bytes_tos   = 0;
unsigned conn::bytes_froms = 0;
unsigned conn::bytes_toc   = 0;
unsigned conn::bytes_fromc = 0;

list<dcc> conn::dcc_list;

htbl conn::cmdhash(41);
htbl conn::incoming_hash(7);

conn::conn(int fd2accept, bool in_ssl)
{
    socklen_t size = sizeof(struct sockaddr_in);
    int fd_client = accept(fd2accept, (struct sockaddr *) &client_saddr, &size);
    bool success;
    last_recved = connect_time = ircproxy_time();
    failed_passwords = 0;
    stat    = 0;
    client  = 0;
    loglist = 0;
    server  = 0;
    log     = 0;
    user    = 0;
    config  = 0;
    detach_timer = 0;
    ++num_active;

    if (fd_client < 0)
    {
        printlog("failure in accept(): %s\n", strerror(errno));
        return;
    }

    client = new psock(this, fd_client, &success, pcfg.min_buffer_size, pcfg.max_buffer_size);

    if (!success)
    {
        printlog("Cannot accept connection from %s. Out of sockets.\n", inet_ntoa(client_saddr.sin_addr));
        close(fd_client);
        delete client;
        client = 0;
        return;
    }

    id = current_id++;

    nonblocking(client->fd);

    uinfo.irc = new ircaddr("(unknown)");
    uinfo.fulladdr = new char[40];
    sprintf(uinfo.fulladdr, "%lu:(unknown)@%s", id, inet_ntoa(client_saddr.sin_addr));
    size = sizeof(struct sockaddr_in);
    getsockname(client->fd, (struct sockaddr *) &local_saddr, &size);

    printlog("CONNECTION FROM: %s (assigned id %d)\n", inet_ntoa(client_saddr.sin_addr), id);

#ifdef _USE_SSL
    if (in_ssl) {
    	if (client->accept_to_ssl()!=1) {
             printlog("Killing connection -- SSL negotiation failed\n");
       	     printlog("ssl_accept() returned: %s\n", ERR_error_string(ERR_get_error(), NULL));
             close(fd_client);
             delete client;
             client = 0;
             return;	
    	}
	}
#endif 	

    printlog("CONNECTION FROM: %s (assigned id %d)\n", inet_ntoa(client_saddr.sin_addr), id);
    if (!is_authorized())
        on_client_disconnect(0);
}

/**
 * FIXME: when to call user->remove() ??
 */
conn::~conn()
{
    DEBUG("conn::~conn() -- %p\n", this);
    assert(num_active >= 0 && num_active < 500000);
    die();
    if (user)
        user->remove(this);
    num_active--;
}

/**
 * Effectively puts the conn in a zombie state
 */
void conn::die()
{
    DEBUG("conn::die() -- %p\n", this);
    on_server_disconnect();
    on_client_disconnect(0);
    disown_dccs(0);
    delete config;
    config = 0;
    assert(!server && !client);
}

int conn::check_server(int events)
{
    int ret = 0;
    if (dead())
        return CONN_KILLME;        /* dead object */
    if (!server)
        return CONN_NOTCONNECTED;  /* client has not connected to any irc server */

    if ((events & POLLERR) || (events & POLLHUP))
         goto error;
    else if ((events & POLLOUT))
    {
        if (checkf(CONNECTING))
        {
            /* Test if the non-blocking connect worked */
            char dummy;
#ifdef _USE_SSL
            if (server->ssl)
                ret = SSL_pending(server->ssl);
            else
#endif
                ret = recv(server->fd, &dummy, 1, MSG_PEEK);

            if (ret < 0 && errno != EAGAIN)
            {
                on_server_connect(errno);     /*  Will call on_server_disconnect() and stuff */
                return 1;
            }
            on_server_connect(0);
            return CONN_LINK_ACTIVATED;
        }
        /* Not connecting, send queued data --
         * if there's none left, remove writability check */
        if (server->flushO() < 0)
            goto error;
        if (!server->obuff->get_size())
        {
			server->set_events(POLLIN);
        	if (client)
    	        client->set_events(client->events() | POLLIN);
		}
    }
    if (!(events & POLLIN))
        return 1;
    /* get from server and  *
     *   ... check results  */
    ret = server->read();
    switch (ret)
    {
    case 0:
        /* getting 0 from FIONREAD if socket is marked readable
         * means it has been closed */
    error:
    case SOCK_ERR_READ:
        on_server_disconnect();
        if (checkf(DETACHED))
           return CONN_KILLME;
        cprintf(msg_disconnected);
        return CONN_DISCONNECTED;

    case SOCK_ERR_FULL:
        return CONN_FULL;

    case SOCK_ERR_MEM:
        printlog("FATAL: memory allocation failure -- aborting server\n");
        abort();

    default:
        bytes_froms += ret;
        if (parse_from_server() < 0)
            return CONN_KILLME;
        return 1;
    }
    return 0;
}

/*
 * Check client's socket. Parse and relay.
 */
int conn::check_client(int events)
{
    int ret = 0;
    if (checkf(DETACHED))
        return 1;
    if (dead())
        return CONN_KILLME;

    if ((events & POLLERR))
    {
    	printlog("POLLERR caught: tell author\n");
    	goto error;
	}
    else if ((events & POLLHUP))
        goto error;
    else if (events & POLLOUT)
    {
        /* Send queued data */
        if (client->flushO() < 0)
            goto error;
        if (!client->obuff->get_size())
        {
			client->set_events(POLLIN);
        	if (server)
        	    server->set_events(server->events() | POLLIN);
		}
    }

    if (!(events & POLLIN))
        return 1;

    ret = client->read();

    switch (ret)
    {
    case 0:
        /* getting 0 from FIONREAD if socket is marked readable
         * means it has been closed */
    case SOCK_ERR_READ:
        printlog("Disconnect: %s (while reading).\n", addr());
    error:
        if ((config) && config->decide(PREF_AUTO_DETACH)
                 && checkf(BOUNCED))
        {
            if (do_auto_detach())
                return 1;
        }
        /* If client died, terminate server connection as well */
        on_server_disconnect();
        on_client_disconnect();
        return CONN_KILLME;

    case SOCK_ERR_FULL:
        return CONN_FULL;

    case SOCK_ERR_MEM:
        printlog("FATAL: failed to allocate some memory -- bailing out.\n");
        abort();

    default:
        /* Update stats & parse & relay ... */
        last_recved = ircproxy_time();
        bytes_fromc += ret;
        if (parse() < 0)
            return CONN_KILLME;
        return 1;
    }
    return 0;
}


/*
 * return values for parse() and parse_from_server():
 *   1: OK
 *  -1: something occured requring destruction of the object
 */
int conn::parse(void)
{
    char command[12];
    int r;
    const struct cmd * c;
    char * lp;
    db_parser db(client->ibuff);

    while ((client) && (lp = db.get_next_line(1)))
    {
        if (!*lp)
            continue; /* blank line */
        bool del_lp = 0;
        char *argv[MAX_PPCHAR_ARGS];
        gettok(lp, command,12,' ', 1);
        ToUpper(command);
        c = cmdhash.lookup(command,
             stat | ((stat & BOUNCED) ? NDM : 0));

        if (c)
        {
            memset(argv, 0, sizeof (argv));
            int argc = mk_ppchar(lp + strlen(command) + 1,
                    &argv);

            argv[1] = no_leading(argv[1]);
            DEBUG(">>>>>> HANDLER: %s(%d, %d, %s)\n", c->msg, c->id, argc, argv[0]);
#ifndef GCC_COMPILER_BUG
            r = (this->*(c->handler))(c->id, argc, argv);
#else
			r = (this->*(handlers[c->idx]))(c->id, argc, argv);
#endif			
            if (r < 0)
            {
                delete[] argv[0];
                if (client)
                    db.zap();
                if (server)
                	server->flushO();
                return -1;
            }
            else if (r > 0)
            {
                delete[] argv[0];
                continue;
            }
            /* This is for commands that are detected but not handled: */
            lp = argv[0];
            del_lp = 1;
        }
        if (checkf(BOUNCED))
        {
            /* If we get here, we are sending it to the server:
             * if (del_lp), original line has been clobbered, and we have
             * argv[0] that is missing the original command. so queue
             * that up too */
            if (del_lp)
            {
                server->queue(command, strlen(command));
                server->queue(" ", 1);
            }
            if (lp)
                server->queue(lp, strlen(lp));
            server->queue("\r\n", 2);
        }
        if (del_lp)
            delete[] lp;
    }

    if (checkf(BOUNCED))
    {
        /* Send everything and do rate limiting */
        r = server->flushO();
        if (r > 0)
            bytes_tos += r;
        r = (server) ? server->obuff->get_size() : 0;
        if (r)
        {
            if ((unsigned) r > (pcfg.max_buffer_size / 2))
            {
                DEBUG("conn::parse() -- limiting client read -- about to blow buffer\n");
                client->set_events(client->events() & ~POLLOUT);
                server->set_events(POLLIN | POLLOUT);
	        }
	        else {
	            DEBUG("conn::parse() -- %d left in server buffer: doing POLLOUT\n", r);
	            server->set_events(POLLIN | POLLOUT);
	        }
    	}
	}
	if (client)
        db.zap();
    return 1;
}

/*
 *  Handles the connection process to a server.
 *  Checks if rule sets permit it, and handles registering
 *    w/ the rule sets.
 */
int conn::do_connect(const char *where, u_short port, const char *pass, int inssl)
{
    struct sockaddr_in sin;
    int s;
    if ((s = socket(AF_INET, SOCK_STREAM, 0)) < 0)
        return 0;
    memset(&sin, 0, sizeof(sin));

    DEBUG("do_connect(%s, %d, %s, %d)\n", where, port, pass, inssl);

    sin.sin_addr.s_addr = config->get(PREF_VHOST);
    sin.sin_family = AF_INET;
    if ((bind(s, (const struct sockaddr *) &sin, sizeof(sin)) < 0)
        || (!resolve_address(where, &sin)))
    {
        close(s);
        return -1;
    }

    sin.sin_family = AF_INET;
    sin.sin_port   = htons(port);
    nonblocking(s);

    /*
     * Right about to connect.. we have local port and remote? port
     * so we register the fake ident if needed
     */
    char * fake = config->get(PREF_FAKE_IDENT, 0);
    if (config->checkf(OPT_ENABLE_FAKE_IDENTS) && fake)
    {
 	    int x = sizeof(struct sockaddr_in);
	    struct sockaddr_in sin2;

	    getsockname(s, (struct sockaddr *) &sin2, (socklen_t *) &x);
        x = register_fake_ident(MDIDENTD_PATH, fake, ntohs(sin2.sin_port), port);
	    if (x != IDENT_OK)
        {
            cprintf("Error registering fake ident '%s' for you (but continuing anyway): %s\r\n",
                        fake, ident_error(x));
            printlog("Fake ident FAILED: '%s' for %s: %s\n",
                              fake, addr(), ident_error(x));
        } else
            printlog("Fake ident registered: '%s' for %s\n", fake, addr());
    }

    switch (connect(s, (struct sockaddr *) &sin, sizeof(sin)))
    {
    case -1:
        /* Non blocking connect in progress or something f*cked up */
        bool succ;
        if (errno != EINPROGRESS)
            break;

        server = new psock(this, s, &succ, pcfg.min_buffer_size, pcfg.max_buffer_size);

#ifdef _USE_SSL
        if (succ&&inssl) {
    		DEBUG("Switching to SSL mode...\n");
        	succ = server->switch_to_ssl();
	        if (succ) {
	    	    cprintf("Switched to SSL...\r\n");
       	        DEBUG("Switched to SSL...\n");
    		} else {
                printlog("Connection failed: Failed switching to SSL\n");
                cprintf("Connection failed: Failed switching to SSL\r\n");
                delete server;
                server = 0;
	            break;
	    	}
        }
#endif
        if (!succ)
        {
            printlog("Connection failed: socket table full\n");
            cprintf("Unable to connect: socket table full\r\n");
            delete server;
            server = 0;
            break;
        }

        setf(CONNECTING);
        uinfo.port = port;
        delete[] uinfo.ircpass;
        if (pass)
            uinfo.ircpass = my_strdup(pass);
        goto success;

    case 0:
        /* Connection worked immediately, this *might* happen, and we'll
         * handle it to be safe */
        server = new psock(this, s, &succ);
#ifdef _USE_SSL
        if (succ&&inssl)
        {
    		DEBUG("Switching to SSL mode...\n");
        	succ = server->switch_to_ssl();
        }
#endif
        if (!succ)
        {
            printlog("Connection failed: socket table full\n");
            cprintf("Unable to connect: socket table full\n");
            delete server;
            server = 0;
            break;
        }
        uinfo.port = port;
        delete[] uinfo.ircpass;
        if (pass)
            uinfo.ircpass = my_strdup(pass);
        on_server_connect(0);

        goto success;

    }
    close(s);
    return 0;

success:

    server->set_events(POLLIN | POLLOUT);

    ruleset::list_register_to(&rulesets, where, port);
    setf(TO_RULESETS_REGISTERED);
    return 1;
}


/**
 * Look for a conn with an id 'i' in list 'l'
 */
/* static */ conn * conn::lookup(list<conn> * l, unsigned i)
{
    list_iterator<conn> li(l);

    while (li.has_next())
    {
        conn * c = li.next();
        if (c->id == i)
            return c;
    }
    return 0;
}

void conn::kill(const char *killer, const char *reason)
{
    if (client)
    {
        cprintf("You were killed by %s: %s\r\n", killer, reason);
        client->printf("ERROR :Closing Link: %s (Killed)\r\n", addr());
    }
    die();
}

/*
 * Go thru rules and make sure he can connect there.
 * Display nice little warning message if can't.
 */
bool conn::can_connect(const char *towhere, u_short port)
{
    char reasonbuff[200];

    if (checkf(ADMIN))
        return 1;

    /* First, check for self connection attempts if needed */
    if (pcfg.flags & PREVENT_SELF_CONNECTS)
    {
        struct sockaddr_in sin;
        bool resolved = resolve_address(towhere, &sin);
        /*
         * Deny access if:
         * port matches one of the ones we're listening on
         * AND:
         *    client is trying to connect to 127.*
         *    OR
         *    client is trying to connect to interface we are listening on
         */
        if ((port_in_set(port, pcfg.ports) || port_in_set(port, pcfg.sslports))
            && ((resolved && sin.sin_addr.s_addr == pcfg.iface_listen.s_addr) ||
                (resolved && wild_match("127.*", inet_ntoa(sin.sin_addr)))
               )
           )
        {
            cprintf(msg_cannot_connect, towhere, "Self-connects are not permitted");
            return 0;
        }
    }

    int r = ruleset::list_is_allowed_to(&rulesets, inet_ntoa(client_saddr.sin_addr), towhere, port,
                                             reasonbuff, sizeof(reasonbuff));
    if (r < 0)
    {
        cprintf(msg_cannot_connect, towhere, reasonbuff);
        return 0;
    } else if (r > 0)
        return 1;

    cprintf(msg_cannot_connect, towhere, "Host and/or port is not on my allowed-to list");
    return 0;
}

/**
 * Sends a message to all users
 */
/* static */ void conn::broadcast(list<conn> * l, const char *msg)
{
    list_iterator<conn> iter(l);
    while (iter.has_next())
    {
        conn * c = iter.next();
        if (c->client)
            c->cprintf("%s\r\n", msg);
    }
}

/**
 * Called socket gets connected, err != 0 in case it failed.
 *  use 0x29A for err for user abort.
 */
int conn::on_server_connect(int err)
{
    socklen_t size = sizeof(serv_saddr);
    if (!err)
    {
        /* If connection ok, update internal variables */
        getpeername(server->fd, (struct sockaddr *) &serv_saddr, &size);
        clearf(CONNECTING);
        setf(BOUNCED);
        server->set_events(POLLIN);

        client->clearO();
        cprintf(msg_conn_successful, uinfo.server);
        /* Now register with the server */
        if (uinfo.ircpass)
            server->printf("PASS :%s\n", uinfo.ircpass);
        server->printf("USER %s\nNICK :%s\n", uinfo.usercmd, uinfo.irc->nick);

        printlog("Connect SUCCESSFUL: %s to %s:%d\n",
                          addr(), uinfo.server, uinfo.port);

        delete[] uinfo.ircpass;
        uinfo.ircpass = NULL;
        return 1;
    }

    const char * error = (err == 0x29A) ? "cancel request from user" : strerror(errno);
    cprintf(msg_conn_failed, uinfo.server, error);
    printlog("Connect attempt FAILED: %s to %s:%d: %s\n", addr(), uinfo.server, uinfo.port,
                          error);
    on_server_disconnect();
    return 1;
}

/*
 *  Return:
 *          0 -- Not idle
 *          1 -- Idle too long
 *          2 -- Idle too long; didn't register in time
 *
 *  Idle time limits do not apply to admins.
 *  Idle time limits do not apply when when connected to an irc server.
 */
int conn::is_idle(time_t now)
{
    if (dead() || checkf(ADMIN) || checkf(BOUNCED) || checkf(DETACHED))
        return 0;

    /* User not registered */
    if (!checkf(USERED) || !checkf(NICKED))
        if (now - connect_time > pcfg.max_register_time
            && pcfg.max_register_time != 0)
            return 2;
    if (checkf(USERED) && checkf(NICKED) && checkf(PWED))
        if (now - last_recved > config->get(OPT_MAX_IDLE_TIME)
            && config->get(OPT_MAX_IDLE_TIME) != 0)
            return 1;

    return 0;
}


int conn::cprintf(const char *message, ...) const
{
    static const char *hdr = ":" EZBOUNCE_HEADER " NOTICE ";
    int i;
    va_list ap;
    if (!client)
    {
        DEBUG("Warning: cprintf() for non-existant client %p\n", this);
        return 0;
    }

    bytes_toc += client->printf("%s%s :", hdr, uinfo.irc->nick);
    va_start(ap, message);
    i = client->printf_raw(message, &ap);
    va_end(ap);
    client->flushO();
    bytes_toc +=i;
    return i;
}

/* Allow sending of multi-lined messages. \n is the seperator. */
int conn::cprintf_multiline(const char * message) const
{
    char line[128];
    int cur = 1;
    while (gettok(message, line, sizeof(line), '\n', cur++))
        cprintf("%s\n", line);
    return 1;
}

/*
 * Detach client from proxy but keep his connection to server alive
 *
 * client must:
 *    be connected to a server
 *    not already be detached
 *   (we assume caller has checked so)
 */
int conn::detach(const char * password)
{
    /* get rid of these */
    delete[] uinfo.ircpass;
    delete[] uinfo.usercmd;
    uinfo.ircpass = uinfo.usercmd = NULL;

    uinfo.detachpass = my_strdup(password);

    /*
     * Setup logging now. Must have proper options or be admin.
     * Inform user of errors and wonderful options
     */
    if (config->get(OPT_LOG_OPTIONS))
    {
        int result;
        log = new logfile(pcfg.logdir, user->name, id, config->get(OPT_LOG_OPTIONS),
                              (checkf(ADMIN) ? 0 : config->get(OPT_MAX_LOGFILE_SIZE)), password, &result);
        if (result < 0)
        {
            cprintf("Tried to setup logging but failed. Error is: %s\r\n", strerror(errno));
            printlog("Failed log setup for %s: %s\n", addr(), strerror(errno));
            delete log;
            log = 0;
        }
        else if (result == 0)
        {
            cprintf("Some errors setting up logs, but trying to continue. Error was: %s\r\n", strerror(errno));
            printlog("Errors during log setup for %s: %s (but continuing)\n", addr(), strerror(errno));
        }
        else
        {
            char buff[15];
            logfile::intflags_to_char(config->get(OPT_LOG_OPTIONS), buff);
            cprintf("Session will be logged.\r\n");
            cprintf("Logging options are: %s.\r\n", buff);
            cprintf("Use '/quote ezb log send all' to retrieve log files after reattach...\r\n");
            printlog("Log setup OK for %s\n", addr());
        }
    }

    cprintf("Detach succesful.\r\n");
    cprintf("Use `/quote sessions' after reconnect to attach to session.\r\n");
    if (password)
        cprintf("Remember, your password for this session is: %s\r\n", password);
    cprintf("---> Closing connection to you...\r\n");
    client->printf("ERROR :Closing Link: %s (Detaching)\r\n", addr());
    printlog("Detach SUCCESFUL: %s from connection to %s:%d\n",
                      addr(), uinfo.server, uinfo.port);

    detach_time = ircproxy_time();
    disown_dccs(0);

    /**
     * Create the anti-idle timer
     */
    detach_timer = new generic_timer(300, &conn::detached_timer_proc, this);
    detach_timer->enable();

    /* Pretend user has disconnected. Protect the FROM rulesets,
     * because they are needed on reattach
     * We have to reset some flags as on_client_disconnect() zeroes out stat */
    clearf(FROM_RULESETS_REGISTERED);
    on_client_disconnect(0);
    setf(FROM_RULESETS_REGISTERED);
    setf(DETACHED | USERED | NICKED | PWED | BOUNCED);
    return 1;
}

/* The reattaching process:
 *
 * 1) We simulate connection to server by sending numerics 000-003
 * 2) We then send a dummy motd
 * 3) And finally a bunch of JOINS
 */
int conn::reattach(const char * password, conn * c2)
{
    if (!c2->checkf(DETACHED))
    {
        cprintf("REATTACH: Nonexistant or bad target userid.\r\n");
        return 0;
    }
    if (password && c2->uinfo.detachpass &&
        strcasecmp(password, c2->uinfo.detachpass))
    {
        cprintf("REATTACH: password incorrect.\r\n");
        return 0;
    }

    char * server = c2->uinfo.server;
    char * nick =   c2->uinfo.irc->nick;

    /* It's simpler to do the text sending first so do that */

    /* Sync nickname first -- we do it twice, to be safe */
    client->printf(":%s!%s NICK :%s\r\n", uinfo.irc->nick, addr(), c2->uinfo.irc->nick);
    /*
     * first: send fake numerics and pretend client has connected to an irc server
     */
    client->printf(":%s 001 %s :Welcome to the Internet Relay Network %s\r\n",
             server, nick, nick);
    client->printf(":%s 002 %s :Your host is %s, running version %s\r\n",
             server, nick, server, c2->uinfo.serverversion);
    client->printf(":%s NOTICE %s :Your host is %s running version %s\r\n",
             server, nick, server, c2->uinfo.serverversion);
    client->printf(":%s 003 %s :This server was created %s\r\n",
             server, nick, c2->uinfo.servercreated);
    client->printf(":%s 004 %s %s %s %s\r\n",
             server, nick, server, c2->uinfo.serverversion, c2->uinfo.servermodes);
    if (c2->uinfo.server005_1)
        client->printf(":%s 005 %s %s\r\n",
             server, nick, c2->uinfo.server005_1);
    if (c2->uinfo.server005_2)
        client->printf(":%s 005 %s %s\r\n",
             server, nick, c2->uinfo.server005_2);	     
    client->printf(":%s NOTICE %s :Your host is %s and %s\r\n",
             server, nick, server, EZBOUNCE_VERSION);
    client->printf(":%s 375 %s :-%s Message of the Day -\r\n",
             server, nick, server);
    client->printf(":%s 372 %s :This is a dummy MOTD\r\n",
             server, nick);
    client->printf(":%s 376 %s :End of /MOTD command.\r\n",
             server, nick);

    c2->server->printf("LUSERS\r\n");
    c2->server->printf("AWAY\r\n");
    c2->server->printf("MODE %s\r\n", nick);

    /* Sync the nickname again -- I'm not sure if this is needed, or even wise ?? */
    client->printf(":%s!%s NICK :%s\r\n", uinfo.irc->nick, addr(), nick);

    /*
     * Simulate joins
     */
    list_iterator<char> i(&c2->uinfo.channels);
    while (i.has_next())
    {
    	char * chan = i.next();  	
        client->printf(":%s!%s@%s JOIN :%s\r\n",
                 nick, c2->uinfo.irc->ident, c2->uinfo.irc->host, chan);
        c2->server->printf("NAMES %s\r\n", chan);
        c2->server->printf("TOPIC %s\r\n", chan);
    }

    printlog("Reattach SUCCESFUL: %s ----> id %d; is now %s on server %s\n",
                  addr(), c2->id, nick, server);

    /* Transfer settings.
     *  Actual reattaching happens here... */
    c2->clearf(DETACHED);
    c2->setf(BOUNCED);
    c2->setf(GOTSERVINFO | GOTSERVINFO2);

    if (c2->config->checkf(OPT_USER_IS_ADMIN))
        c2->setf(ADMIN);

    c2->uinfo.usercmd = uinfo.usercmd;
    uinfo.usercmd = 0;

    /* Update the 'from' info */
    memcpy(&c2->client_saddr, &this->client_saddr, sizeof(client_saddr));
    delete[] c2->uinfo.fulladdr;
    c2->uinfo.fulladdr = new char[10 + strlen(c2->uinfo.irc->nick) + 18];
    sprintf(c2->uinfo.fulladdr, "%lu:%s@%s", c2->id, c2->uinfo.irc->nick, inet_ntoa(c2->client_saddr.sin_addr));

    /*
     * What we do w/ rulesets on detach:
     *  Keep them alive. It is easier that way.
     *  On reattach, the user simply unregisters his and he becomes
     *  the target so rulesets are taken care of there.
     * Remember that the from rulesets of the detached target match the address
     * of the original user and not necessarily the user attempting to reattach.
     * So, we keep the original address and port of connection in local_saddr.
     */
    unregister_rulesets(ruleset::FROM);
    stat = 0;

    client->clear();
    c2->client        = this->client;
    c2->client->owner = c2;
    this->client      = 0;

    disown_dccs(c2);
    on_client_disconnect(); /* We don't exist anymore.. */

    /** Destroy the detached anti-idle timer */
    c2->detach_timer->disable();
    delete c2->detach_timer;
    c2->detach_timer = 0;


    /* Do the logfiles now */
    if (c2->log)
    {
        char *names[2];
        c2->log->dump("****** Reattach Completed *******\n");
        c2->log->stop();

        c2->log->get_filenames(names);

        c2->cprintf("-------------------------------------------------\r\n");
        c2->cprintf("- Ended log files\r\n");
        c2->cprintf("- You may RETRIEVE your log files by typing\r\n");
        c2->cprintf("- /quote ezb \002LOG SEND\002 all\r\n");
        c2->cprintf("-    --or--\r\n");
        c2->cprintf("- /quote ezb \002LOG VIEW\002 all\r\n");
        c2->cprintf("-  \r\n");
        c2->cprintf("- Type /quote ezb log help for logging help\r\n");
        c2->cprintf("-------------------------------------------------\r\n");
        delete c2->log;
        c2->log = 0;
        destroy_list(c2->loglist, 1);

        delete c2->loglist;
        c2->loglist =  new strlist;
        if (names[0])
            c2->loglist->add(names[0]); /* already duplicated by get_filenames() call */
        if (names[1])
            c2->loglist->add(names[1]);
    }

    c2->cprintf("Reattach successful: you are now %s!%s@%s on %s:%d\r\n",
            nick, c2->uinfo.irc->ident, c2->uinfo.irc->host,
            server, c2->uinfo.port);
    return 1;
}

/*
 * Responds to server pings, handles numeric stuff
 * does incoming dcc proxying.
 */
int conn::parse_from_server(void)
{
    char command[40];
    const struct cmd * c;
    char * lp;
    db_parser db(server->ibuff);
    int r;

    while ((server) && (lp = db.get_next_line(1)))
    {
        if (!*lp)
            continue;   /* blank line */
        bool del_lp = 0;
        char *argv[MAX_PPCHAR_ARGS];
        gettok(lp, command,sizeof(command),' ', 2);
        ToUpper(command);
        c = incoming_hash.lookup(command,stat);
        if (!c)
        {
            /* Didn't match. It could be a server ping, or a pong
             * or an ERROR string. In that case the command is the first
             * token. So try the hash lookup again on that */
            gettok(lp, command, sizeof(command), ' ', 1);
            ToUpper(command);
            c = incoming_hash.lookup(command, stat | PPE);
        }
        if (c)
        {
            memset(argv, 0, sizeof (argv));
            int argc = mk_ppchar(lp, &argv);
            DEBUG("<<<<<< HANDLER: %s(%d, %d, %s)\n", c->msg, c->id, argc, argv[0]);
#ifndef GCC_COMPILER_BUG
            r = (this->*(c->handler))(c->id, argc, argv);
#else
			r = (this->*(handlers[c->idx]))(c->id, argc, argv);
#endif			
            if (r < 0)
            {
                delete[] argv[0];
                return -1;
            }
            else if (r > 0)
            {
                delete[] argv[0];
                continue /* ??? */;
            }

            lp = argv[0];
            del_lp = 1;
        }

        if (!checkf(DETACHED)) {
            /* Wasn't handled or intercepted: relay */
            client->queue(lp, strlen(lp));
            client->queue("\r\n", 2);
        }
        if (del_lp)
            delete[] lp;
    }

    if (!checkf(DETACHED))
    {
		r = client->flushO();
		if (r > 0)
       		bytes_toc += r;
    	r = client ? client->obuff->get_size() : 0;
	    if (r)
	    {
	        if ((unsigned) r > (pcfg.max_buffer_size / 2))
	        {
	            DEBUG("conn::parse_from_server() -- limiting server read -- buffer about to blow up\n");
	            client->set_events(POLLIN | POLLOUT);
	            server->set_events(server->events() & ~POLLIN);
	        } else {
	            DEBUG("conn::parse_from_server() %d left in client buffer: checking for POLLOUT\n", r);
	            client->set_events(POLLIN | POLLOUT);
	        }
	    }
	}
	if (server)
      	db.zap();
    return 1;
}


bool conn::copy_fake_ident()
{
    char id[20];
    if (config->get(PREF_FAKE_IDENT, 0))
        return 0; /* Already got a fake ident set */
    if (gettok(uinfo.usercmd, id, sizeof(id), ' ', 1))
    {
        config->set(PREF_FAKE_IDENT, id);
        return 1;
    }
    return 0;
}

/*
 * Handle trapped CTCPs and do any special things we want for
 * them. Right now it's only used for DCC proxying.
 * Handles both outgoing and incoming CTCPs, as long as you
 * tell it that ;)
 *
 * Return:
 * 0 - not handled; relay to IRC server
 * 1 - handled; will not relay to IRC server
 */
int conn::do_ctcp(bool incoming, const char * ctcp, const char * source,
                  const char * target, const char * args)
{
    psock * out = server, * in = client;
    if (incoming)   /* Coming from IRC server to the proxy */
    {
        in = server;
        out = client;
    }

    if (strcasecmp(ctcp, "DCC") == 0)
    {
        static const char *dcc_types[] = {
            "SEND",
            "CHAT",
            "TSEND",
            /* Gay VIRC extensions */
            "TVIDEO",
            "TVOICE",
            /* mIRC DCC RESUME is NOT yet supported */
            NULL
        };
        /*
         * Format is:
         * DCC [TYPE] [FILENAME] [IP] [PORT] [size]
         * FIXME: test with corrupted input
         */
        char arg1[8], arg2[256];
        unsigned long ip, size;
        unsigned short port;

        int num_args = sscanf(args, "%7s %255s %lu %hu %lu",
                              arg1,arg2, &ip, &port, &size);
        if (num_args < 4)
            return 0;

        /* Find out first what type of dcc this is */
        int t = 0;
        do {
            if (strcasecmp(arg1, dcc_types[t]) == 0)
                break;
        } while (++t && dcc_types[t]);
        if (!dcc_types[t])
            return 0;
    	else
        {
            unsigned short plisten;
            struct sockaddr_in sin;
            socklen_t dummy = sizeof(sin);

            /*
             * We want to bind to and send the ip of the interface that
             * we are either connected to IRC to (if outgoing)
             * or the client connected to us on (if its incoming)
             */
            getsockname(out->fd, (struct sockaddr *) &sin, &dummy);
            sin.sin_port = 0;
            ip = htonl(ip);

            struct in_addr in_ip = { ip };

            if (!incoming)
            {
                if (ip != client_saddr.sin_addr.s_addr)
                {
                    char * tmp = my_strdup(inet_ntoa(in_ip));

                    printlog("DCC Proxy: Warning: IP of sender (%s) doesn't match IP in CTCP DCC (%s), using sender's IP\n",
                             inet_ntoa(client_saddr.sin_addr),
                             tmp);
                    in_ip.s_addr = ip = client_saddr.sin_addr.s_addr;
                    delete[] tmp;
                }
            }

            dcc * d = new dccpipe(this, ntohl(ip),port, &sin, &plisten);
            if (!d || !plisten)
            {
                printlog("DCC proxy FAILED: (%s) for %s: %s:%hu (errno=%s)" ,
                                  dcc_types[t],
                                  addr(), inet_ntoa(in_ip), port, strerror(errno));
                delete d;
                return 0;
            }
            printlog("DCC Proxy STARTED: (%s) for %s: (sender) %s:%hu\n",
                                  dcc_types[t],
                                  addr(), inet_ntoa(in_ip), port);
            printlog("     (me) %s:%hu \n", inet_ntoa(sin.sin_addr), plisten);

            dcc_list.add(d);

            if (incoming)   /* From IRC server to proxy */
                out->printf(":%s PRIVMSG %s :\001DCC %s %s %lu %hu",
                         source, target, dcc_types[t], arg2, ntohl(sin.sin_addr.s_addr),
                         ntohs(sin.sin_port));
            else
                out->printf( "PRIVMSG %s :\001DCC %s %s %lu %hu",
                         target, dcc_types[t], arg2, ntohl(sin.sin_addr.s_addr), ntohs(sin.sin_port));

            if (num_args == 5)
                out->printf(" %lu", size);
            out->printf("\001\r\n");
        }
        return 1;
    }
    return 0;
}

/* static */ void conn::kill_dccs(void)
{
    destroy_list(&dcc_list, 1);
}

void conn::disown_dccs(conn * target)
{
    printlog("DEBUG: Transfering DCC ownership of %p to %p\n", this, target);
    list_iterator<dcc> i(&dcc_list);
    while (i.has_next())
    {
        dcc * d = i.next();
        /* impossible
        if (!d)
            break;
         */
        if (d->get_owner() == this)
            d->set_owner(target);
    }
}

int conn::unregister_rulesets(char type)
{
    if (type == ruleset::FROM && checkf(FROM_RULESETS_REGISTERED))
    {
        ruleset::list_unregister_from(&rulesets, inet_ntoa(client_saddr.sin_addr),
                        ntohs(local_saddr.sin_port));
        clearf(FROM_RULESETS_REGISTERED);
        return 1;
    }
    else if (type == ruleset::TO && checkf(TO_RULESETS_REGISTERED))
    {
        ruleset::list_unregister_to(&rulesets, uinfo.server, ntohs(serv_saddr.sin_port));
        clearf(TO_RULESETS_REGISTERED);
        return 1;
    }
    return 0;
}

int conn::on_client_disconnect(int /* error -- not used right now.. */)
{
    unregister_rulesets(ruleset::FROM);    /* Function does all needed checks */

    if (client)
    {
        delete client;
        client = 0;
    }

    destroy_list(loglist, 1);
    delete loglist;
    delete[] uinfo.usercmd;
    delete[] uinfo.ircpass;

    loglist = 0;
    uinfo.usercmd = uinfo.ircpass = NULL;
    clearf(PWED | NICKED | USERED | ADMIN | CONNECTING | DEFAULT_USER);
    return 1;
}

int conn::on_server_disconnect(int /* error -- not used right now.. */)
{
    if (server)
    {
        delete server;
        server = 0;
    }

    unregister_rulesets(ruleset::TO);  /* Does all needed checks */

    delete[] uinfo.server;

    destroy_list(&uinfo.channels, 1);

    if (log)
    {
        /* Welp.. user disappeared */
        log->dump("******* Connection to server was lost *******\n");
        log->stop();
        delete log;
        log = 0;
    }

    if (detach_timer)
    {
        detach_timer->disable();
        delete detach_timer;
        detach_timer = 0;
    }

    uinfo.server = NULL;

    clearf(BOUNCED | CONNECTING | GOTSERVINFO | GOTSERVINFO2);
    return 1;
}

/*
 * Not really what it seems..
 * The constructor is the true on_client_connect..
 * This one greets the user and informs them of special crap
 * and sends motd and such
 */
int conn::on_client_connect(int /* error */)
{
    delete[] uinfo.fulladdr;
    uinfo.fulladdr = new char[10 + strlen(uinfo.irc->nick) + 18];
    sprintf(uinfo.fulladdr, "%lu:%s@%s", id, uinfo.irc->nick, inet_ntoa(client_saddr.sin_addr));

    printlog("LOGIN: %s as user `%s'\n", addr(), user->name);

    /* MOTD */
    if (pcfg.motdfile)
        show_motd();

    /* Greet user */
    cprintf("  \r\n");
    cprintf("             [%s]\r\n", EZBOUNCE_VERSION);
    cprintf("  \r\n");
    cprintf("\02login\02:       %s\r\n", user->name);
    cprintf("\02from\02:        %s@%s\r\n", uinfo.irc->nick, inet_ntoa(client_saddr.sin_addr));
    cprintf("\02time\02:        %s\r\n", timestamp());

    /* Establish a few settings */
    if (!strcasecmp(user->name, "default"))
        setf(DEFAULT_USER);
    if (config->checkf(OPT_USER_IS_ADMIN))
    {
        /* Admins get all privs */
        cprintf("\02privs\02:       admin!!\r\n");

        setf(ADMIN);
        config->setf(OPT_ENABLE_INCOMING_DCC_PROXYING
                 | OPT_ENABLE_OUTGOING_DCC_PROXYING
                 | OPT_LOG_OPTIONS
                 | OPT_ENABLE_AUTO_DETACH
                 | OPT_ENABLE_FAKE_IDENTS
                 | OPT_ENABLE_VHOST_COMMAND);

        printlog("Granted admin privileges to %s\n", addr());
    }

    /* Show detached connections */
    if (!checkf(DEFAULT_USER))
    {
        cprintf("  \r\n");
        do_sessions_cmd(CMD_SESSIONS, 0, 0);
    }
    cprintf(" \r\n");
    cprintf("[use /quote CONN <server> to connect]\r\n");
    setf(PWED);
    /* If auto-fake-ident... store first part of user into fake_ident */
    if (config->checkf(OPT_AUTO_FAKE_IDENT))
        copy_fake_ident();

    /* Auto-detach crap */
    if (config->decide(PREF_AUTO_DETACH))
    {
        cprintf("You will be automatically detached unless you do `/quote ezb quit'\r\n");
        if (checkf(DEFAULT_USER) && !config->get(PREF_AUTO_PASS, 0))
            cprintf("You must first set a password with `/quote SET auto-detach-pass'\r\n");
    }

    /* Now, do Automatic server connection */
    char * s = my_strdup(config->get(OPT_AUTOSERVER, 0));
    if (s)
    {
        char *argv[MAX_PPCHAR_ARGS];
        memset(argv, 0, sizeof (argv));

        cprintf("Automatically connecting you to: %s\r\n", s);
        printlog("CONNECT: Attempting auto connect of %s to %s\n", addr(), s);

        int argc = mk_ppchar(s,&argv);
        do_conn_cmd(CMD_CONN, argc, argv);
        delete[] s;
        delete[] argv[0];
    }
    return 1;
}

/* Dump motd -- very simple */
void conn::show_motd(void) const
{
    cprintf("Message of the day: \r\n");

    if (!pcfg.motdfile)
        return;

    int fd = open(pcfg.motdfile, O_RDONLY);
    if (fd < 0)
    {
        printlog("Request for MOTD file %s failed: %s\r\n", pcfg.motdfile,
                           strerror(errno));
        return;
    }

    dynbuff d(512, 65536);
    char * line;

    d.add(fd);
    close(fd);

    db_parser dparser(&d);

    while ((line = dparser.get_next_line(1)))
    {
        /* check for empty lines */
        if (!*line)
            cprintf(" \r\n");
        else
            cprintf("%s\r\n", line);
    }

    cprintf("  \r\n");
    cprintf("---\r\n");
    return;
}


dcc * conn::dcc_send_file(const char * file, const char * send_as, struct sockaddr_in * psin, bool islog, bool ischat)
{
    unsigned short port;
    struct sockaddr_in sin;
    unsigned long filesize;
    socklen_t len = sizeof(sin);
    memset(&sin, 0, sizeof(sin));

    getsockname(client->fd, (struct sockaddr *) &sin, &len);
    sin.sin_port = 0;

    dccsend * d = new dccsend(this, file, &sin, &port, &filesize);
    if (!port)
    {
        printlog("DCC Send: Error sending %s: %s\n", file, strerror(errno));
        delete d;
        return 0;
    }

    struct dcc::dcc_priv p = {
        (char *) file,
        0,
        islog
    };

    d->set_priv(&p);

    if (ischat)
    {
        printlog("DCC SEND: Sending %s to %s (as DCC CHAT)\n", file, addr());
        cprintf("Ok.. sending %s (as DCC CHAT).. waiting on port %d\r\n", file, port);

        client->printf(":%s PRIVMSG %s :\001DCC CHAT CHAT %lu %hu %lu\001\r\n",
             EZBOUNCE_HEADER, uinfo.irc->nick, ntohl(sin.sin_addr.s_addr),
                 ntohs(sin.sin_port), filesize);
    }
    else
    {
        printlog("DCC SEND: Sending %s to %s.\n", file, addr());
        cprintf("Ok.. sending %s.. waiting on port %d\r\n", file, port);

        client->printf(":%s PRIVMSG %s :\001DCC SEND %s %lu %hu %lu\001\r\n",
             EZBOUNCE_HEADER, uinfo.irc->nick, (send_as ? remove_path(send_as) : remove_path(file)), ntohl(sin.sin_addr.s_addr),
                 ntohs(sin.sin_port), filesize);
    }

    if (psin)
        memcpy(psin, &sin, sizeof(sin));

    dcc_list.add(d);
    return d;
}

int conn::psock::event_handler(const struct pollfd *)
{
    /* One of the socks has an event pending.
     * Might as well check both. Always check server first.
     * This is kinda dangerous. We might be deleted by our
     * owner. There is no guarantee we will even exist in some of the
     * next few lines, so save the pointer!!!
     *
     * Also, clear the data ready flags for the other socket pair.
     * We don't want to be called again. */
    conn * c = owner;
    int e = c->server ? c->server->revents() : 0;

    if (e)
    {
        c->server->set_revents(0);
        switch (c->check_server(e))
        {
        case CONN_DISCONNECTED:
            printlog("DISCONNECT: %s lost connection to server: %s.\n",
                     c->addr(), strerror(errno));
            if (c->config->checkf(OPT_DROP_ON_DISCONNECT))
            {
                printlog("... removed client from proxy\n");
                delete c;
                ircproxy_conn_list()->remove(c);
                goto out;
            }
            break;

        case CONN_KILLME:
            printlog("DISCONNECT: %s lost connection to server: %s -- killing.\n",
                     c->addr(), strerror(errno));
            delete c;
            ircproxy_conn_list()->remove(c);
            goto out;

        case CONN_FULL:
            if (pcfg.flags & KILL_WHEN_FULL)
            {
                printlog("KILLING: %s -- full input queue(s)\n",
                        c->addr());
                delete c;
                ircproxy_conn_list()->remove(c);
            }
            goto out;

        default:
            break;
        }
    }

    e = c->client ? c->client->revents() : 0;
    if (e)
    {
        c->client->set_revents(0);
        switch (c->check_client(e))
        {
        case CONN_LINK_DEACTIVATED:
            printlog("DISCONNECT: %s lost connection to server: %s.\n",
                 c->addr(), strerror(errno));
            if (c->config->checkf(OPT_DROP_ON_DISCONNECT))
            {
                printlog("... removed client from proxy\n");
                delete c;
                ircproxy_conn_list()->remove(c);
                goto out;
            }
            break;

        case CONN_DISCONNECTED:
        case CONN_KILLME:
            delete c;
            ircproxy_conn_list()->remove(c);
            goto out;

        case CONN_FULL:
            if (pcfg.flags & KILL_WHEN_FULL)
            {
                printlog("KILLING: %s -- full input queue(s).\n",
                         c->addr());
                delete c;
                ircproxy_conn_list()->remove(c);
            }
            goto out;

        default:
            break;
        }
    }
out:
    return 1;
}

/*
 * Like above function, we may be destroyed w/o knowing it so
 * be a little careful */
int dcc::dccsock::event_handler(const struct pollfd *)
{
    struct sockaddr_in sin;
    struct dcc::dcc_priv * dp = owner->get_priv();
    conn * c = owner->owner;
    const char * name;
    const char * filename = dp ? dp->str1 : 0;
    bool islog = dp ? dp->i : 0;
    dcc * d = owner;

    if (c)
        name = c->addr();
    else
        name = "[orphan]";

    int ret = owner->poll(&sin);

    switch (ret)
    {
    case DCC_PIPE_TIMEDOUT:
        printlog("DCC: Proxy session timed out for %s\n", name);
        if (c)
            c->cprintf("DCC: Proxy session timed out\r\n");
        goto delete_dcc;

    case DCC_SEND_TIMEDOUT:
        printlog("DCC: TIMEOUT: Send of file %s to %s timed out after 90 secs\n", filename, name);
        if (c)
           c->cprintf("DCC Send Timed Out: %s\r\n", nopath(filename));
        if (islog)
            logfile::release(filename);
        goto delete_dcc;

    case DCC_SEND_COMPLETE:
        printlog("DCC: Send complete of file %s to %s\n", filename, name);
        if (c)
            c->cprintf("DCC Send complete: %s\r\n", nopath(filename));

        if (islog)
        {
            unlink(filename);
            logfile::release(filename);
            if (c)
                strlist_remove(c->get_loglist(), filename);
            printlog("DCC: ... unlocking and deleting finished log file dcc send.\n");
        }
        goto delete_dcc;

    case DCC_CLOSED:
    case DCC_ERROR:
        if (filename)
        {
            if (islog)
                logfile::release(filename);
            printlog("DCC: Send of %s to %s incomplete: %s\n", filename, name, strerror(errno));
            if (c)
                c->cprintf("DCC Send of %s incomplete: %s\r\n", nopath(filename), strerror(errno));

        } else
        {
            printlog("DCC: Closing DCC session of %s: %s\n", name, strerror(errno));
            if (c)
                c->cprintf("DCC: Closing DCC Session: %s\r\n", strerror(errno));
        }

delete_dcc:
        DEBUG("Reached delete_dcc for %p\n", d);
        delete d;
        conn::dcc_list.remove(d);
        break;

    case DCC_PIPE_ESTABLISHED:
        printlog("DCC: Full DCC link established for proxied DCC of %s\n",
                              name);
        break;

    case DCC_CONNECTION_ACCEPTED:
        printlog("DCC: Connection from receipient accepted for proxied DCC of %s\n",
                              name);
        printlog("DCC: Receipient is from %s:%hu\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
        break;

    case DCC_SEND_ESTABLISHED:
        printlog("DCC: Send established to %s (%s:%hu)\n", name,
                             inet_ntoa(sin.sin_addr), ntohs(sin.sin_port));
        break;
    }
    return 1;
}

conn::__uinfo::__uinfo()
{
    usercmd = server = ircpass = detachpass =
        serverversion = servercreated =
        server005_1 = server005_2 = servermodes = fulladdr = NULL;
    port = 0;
    irc = 0;
}

conn::__uinfo::~__uinfo()
{
    delete[] usercmd;
    delete[] ircpass;
    delete[] server;
    delete[] detachpass;
    delete[] serverversion;
    delete[] servercreated;
    delete[] servermodes;
    delete[] server005_1;
    delete[] server005_2;
    delete[] fulladdr;
    delete irc;
    destroy_list(&channels, 1);
}

int conn::do_auto_detach(void)
{
    char * pass = config->get(PREF_AUTO_PASS, 0);
    printlog("Auto-detaching %s (%s) -- password is: %s \n",
                      addr(), strerror(errno), pass);
    detach(pass);
    return 1;
}

/* Called upon client connection:
 *  - make sure not on global ban list
 *  - make sure there exists a matching user definition
 */
int conn::is_authorized(void)
{
    char * address = my_strdup(inet_ntoa(client_saddr.sin_addr));
    char reason[200];
    u_short port = ntohs(local_saddr.sin_port);

    /* Check if on global ban list */
    DEBUG("conn::is_authorized() -- checking %s\n", address);
    if (ruleset::list_is_allowed_from(shitlist, address, port,
            reason, sizeof(reason)) == -1)
    {
        if (!(pcfg.flags & SILENT_REJECTION))
            cprintf(msg_banned, reason, reason);
        printlog("Connection DENIED: (banned) from %s on port %d\n", address, port);
        on_client_disconnect(0);
        delete[] address;
        return 0;
    }

    list_iterator<userdef> i(users);
    while (i.has_next())
    {
    	userdef * u = i.next();
        if (!u->is_obsolete() && ruleset::list_is_allowed_from(u->rulesets, address,port,
                reason, sizeof(reason)) == 1)
        {
            delete[] address;
            return 1;
        }
    }
    if (!(pcfg.flags & SILENT_REJECTION))
        cprintf("No Authorization\r\n");
    printf("Connection DENIED: (no authorization) from %s on port %d\n", address, port);

    on_client_disconnect(0);
    delete[] address;
    return 0;
}


/* convert numeric flags to something human
 * readable */
char * conn::mkstat(char * str_stat) const
{
    int z = 0;
    if (stat & BOUNCED)    str_stat[z++] = 'b';
    if (stat & CONNECTING) str_stat[z++] = 'c';
    if ((stat & NICKED) &&
        (stat & USERED))  str_stat[z++] = 'r';
    if (stat & ADMIN)    str_stat[z++] = 'a';
    if (stat & PWED)       str_stat[z++] = 'p';
    if (stat & DETACHED)   str_stat[z++] = 'd';
    if (!stat)              str_stat[z++] = 'w';        /* (waiting) */
    if (dead())
    {
        strcpy(str_stat, "(zombie)");
        z = 8;
    }
    str_stat[z] = 0;

    return str_stat;
}


/**
 * Called every so often so we can dump a random message
 * for anti-idling purposes
 */
int  conn::detached_timer_proc(time_t t, int i, void * data)
{
    DEBUG("conn::detached_timer_proc() -- %p\n", data);
    conn * c = (conn *) data;
    assert(c->checkf(DETACHED));
    srand(t);
    static const char *anti_idle[5] = {
            "2md :hey, whats up?",
            "~habib :i need to crash",
            "987987 :somebody set up us the bomb!",
            "@^542 :fool",
            ".ds :my fellow americans, i feel your pain, and your "
    };
    c->server->printf("PRIVMSG %s\r\n", anti_idle[random()%5]);
    return 1;
}

/*
 *  Original string will be modified
 *  Copy will be made, refered as argv[0]
 *  So we have char * pointer, and char *(*buff)[x]
 *  set buff[0] = to newly created string
 *  buff[1] = original.
 */
int mk_ppchar(char * string, char *(*buff)[MAX_PPCHAR_ARGS])
{
    char * p = string;
    int   w = 2;
    int isw = 0;

    if (!strlen(string))
        return 0;

    (*buff)[0] = my_strdup(string);
    (*buff)[1] = string;

    while (*p && w < MAX_PPCHAR_ARGS + 1)
    {
        if (isspace(*p))
        {
            isw = 1;
            *p = 0;
        } else if (isw) {
            isw = 0;
            if (w < MAX_PPCHAR_ARGS)
               (*buff)[w++] = p;
        }
        p++;
    }

    return w - 1;
}


syntax highlighted by Code2HTML, v. 0.9.1