/*
 * commands.cpp
 *
 * (C) 2001-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 <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/utsname.h>
#include <sys/resource.h>
#include "commands.h"
#include "server.h"
#include "dynbuff.h"
#include "general.h"
#include "conn.h"
#include "messages.h"
#include "ircaddr.h"
#include "debug.h"

/*******************************************************
 * Stupid hacks to work around GCC 2.95.x compiler bugs
 *******************************************************
 * It chokes if it encounters to pointer-to-member function inside
 * a struct, so if we detect buggy GCC, we just store function
 * pointers in array, and replace relevant struct element with an
 * array index */
#ifdef H
#undef H
#endif

#ifdef GCC_COMPILER_BUG
#warning Buggy GCC 2.95.x detected -- trying to work around ...
#define H(x,y) (x)
const cmd_handler_t handlers[] = {
        &conn::do_registration_cmd,     /* 0 */
        &conn::do_login_cmd,
        &conn::do_conn_cmd,
        &conn::do_misc_cmd,
        &conn::do_ident_cmd,
        &conn::do_motd_cmd,             /* 5 */
        &conn::do_help_cmd,
        &conn::do_vhost_cmd,
        &conn::do_vhosts_cmd,
        &conn::do_detach_cmd,
        &conn::do_reattach_cmd,         /* 10 */
        &conn::do_disconnect_cmd,
        &conn::do_log_cmd,
		&conn::do_sessions_cmd,
		&conn::do_traffic_cmd,
		&conn::do_set_cmd,    			/* 15 */
		&conn::do_echo_cmd,
		&conn::do_save_cmd,
		&conn::do_allowed_cmd,
		&conn::do_debug_cmd,
		&conn::do_version_cmd,			/* 20 */
		&conn::do_about_cmd,
		&conn::do_ezb_cmd,
		&conn::do_quit_cmd,
		&conn::do_privmsg_cmd,
		&conn::do_status_cmd,			/* 25 */
		&conn::do_rehash_cmd,
		&conn::do_write_cmd,
		&conn::do_hash_cmd,
		&conn::do_adminmisc_cmd,
        &conn::do_whois_cmd,            /* 30 */
		&conn::do_trace_cmd,
		&conn::do_reload_cmd,
#ifdef __DEBUG__
		&conn::do_dccsend_cmd,			/* 33 */
#else
		0,								/* 33 */
#endif
		0,								
		0,                              /* 35 -- reserved for future use ???*/
		0,
		0,
		0,
		0,
		&conn::do_privmsg_incoming_cmd,	/* 40 */
		&conn::do_nick_incoming_cmd,
		&conn::do_mode_incoming_cmd,
		&conn::do_join_incoming_cmd,
		&conn::do_part_incoming_cmd,
		&conn::do_kick_incoming_cmd,    /* 45 */
		&conn::do_topic_incoming_cmd,
		&conn::do_notice_incoming_cmd,
		&conn::do_quit_incoming_cmd,
		&conn::do_servinfo_cmd,
		&conn::do_pong_incoming_cmd,    /* 50 */
		&conn::do_ping_incoming_cmd,
		&conn::do_error_incoming_cmd
};
#else
#define H(x,y) (y)
#endif

/*
 * The NDMs are No Direct Match (when Connected).
 * 'ezb' command is directly matched when connected.
 * the other commands are not.
 *
 * PPE = hack to make sure Ping/pong/error don't match
 * on initial command scan
 */

const struct cmd command_table[] = {
    /* str          int-id        req_flag                                 bad_flag                       handler               help */
    /* -------------------------------------------------------------------------------------------------------------------------------------------*/
    { "USER",       CMD_USER,       0,                            NDM | USERED | CONNECTING,  H(0, &conn::do_registration_cmd),   __HELP_ENTRY(user)},
    { "NICK",       CMD_NICK,       0,                            NDM | CONNECTING,           H(0, &conn::do_registration_cmd),   __HELP_ENTRY(nick)},
    { "PASS",       CMD_PASS,       0,                            NDM | PWED | CONNECTING,    H(1, &conn::do_login_cmd),          __HELP_ENTRY(pass)},
    { "LOGIN",      CMD_LOGIN,      0,                            NDM | PWED | CONNECTING,    H(1, &conn::do_login_cmd),          __HELP_ENTRY(login)},
    { "CONN",       CMD_CONN,       REGISTERED,                   BOUNCED | CONNECTING,       H(2, &conn::do_conn_cmd),           __HELP_ENTRY(conn)},
    { "CANCEL" ,    CMD_CANCEL,     REGISTERED | CONNECTING,      0,                          H(3, &conn::do_misc_cmd),           __HELP_ENTRY(cancel)},
    { "IDENT",      CMD_IDENT,      REGISTERED,                   NDM | CONNECTING,           H(4, &conn::do_ident_cmd),          __HELP_ENTRY(ident)},
    { "MOTD",       CMD_MOTD,       REGISTERED,                   NDM | CONNECTING,           H(5, &conn::do_motd_cmd),           __HELP_ENTRY(motd)},
    { "HELP",       CMD_HELP,       REGISTERED,                   NDM | CONNECTING,           H(6, &conn::do_help_cmd),           __HELP_ENTRY(help)},

    { "INTERFACE",  CMD_INTERFACE,  REGISTERED,                   BOUNCED | CONNECTING,       H(7, &conn::do_vhost_cmd),          __HELP_ENTRY(vhost)},
    { "VHOST",      CMD_INTERFACE,  REGISTERED,                   BOUNCED | CONNECTING,       H(7, &conn::do_vhost_cmd),          __HELP_ENTRY(vhost)},
    { "VHOSTS",     CMD_VHOSTS,     REGISTERED,                   NDM | CONNECTING,           H(8, &conn::do_vhosts_cmd),         __HELP_ENTRY(vhosts)},

    { "DETACH",     CMD_DETACH,     REGISTERED,                   NDM | DETACHED | CONNECTING,H(9, &conn::do_detach_cmd),         __HELP_ENTRY(detach)},
    { "REATTACH",   CMD_REATTACH,   REGISTERED,                   NDM | CONNECTING,           H(10, &conn::do_reattach_cmd),       __HELP_ENTRY(reattach)},
    { "ATTACH",     CMD_REATTACH,   REGISTERED,                   NDM | CONNECTING,           H(10, &conn::do_reattach_cmd),       __HELP_ENTRY(reattach)},
    { "DISCONNECT", CMD_DISCONNECT, REGISTERED | BOUNCED,         NDM | CONNECTING,           H(11, &conn::do_disconnect_cmd),     __HELP_ENTRY(disconnect)},
    { "LOG",        CMD_LOG,        REGISTERED,                   NDM | CONNECTING,           H(12, &conn::do_log_cmd),            __HELP_ENTRY(log)},

    { "SESSIONS",   CMD_SESSIONS,   REGISTERED,                   CONNECTING,                 H(13, &conn::do_sessions_cmd),       __HELP_ENTRY(sessions)},
    { "TRAFFIC",    CMD_TRAFFIC,    REGISTERED,                   NDM | CONNECTING,           H(14, &conn::do_traffic_cmd),        __HELP_ENTRY(traffic)},
    { "SET",	    CMD_SET,	    REGISTERED,				      NDM | CONNECTING,	          H(15, &conn::do_set_cmd),            __HELP_ENTRY(set)},
    { "ECHO",		CMD_ECHO,		REGISTERED,					  NDM,                        H(16, &conn::do_echo_cmd),           __HELP_ENTRY(echo)},
    { "SAVE",       CMD_SAVE,       REGISTERED,                   NDM | CONNECTING,           H(17, &conn::do_save_cmd),           __HELP_ENTRY(save)},
    { "ALLOWED",    CMD_ALLOWED,    REGISTERED,                   NDM | CONNECTING,           H(18, &conn::do_allowed_cmd),        __HELP_ENTRY(allowed)},
    { "DEBUG",      CMD_DEBUG,      REGISTERED,                   NDM,                        H(19, &conn::do_debug_cmd),          __HELP_ENTRY(debug)},
    { "VERSION",    CMD_VERSION,    REGISTERED,                   NDM | CONNECTING,           H(20, &conn::do_version_cmd),        __HELP_ENTRY(version)},
    { "ABOUT",      CMD_ABOUT,      REGISTERED,                   NDM | CONNECTING,           H(21, &conn::do_about_cmd),          __HELP_ENTRY(about)},

    { "EZBOUNCE",   CMD_EZBOUNCE,   REGISTERED,                   CONNECTING,                 H(22, &conn::do_ezb_cmd),            __HELP_ENTRY(ezbounce)},
    { "EZB",        CMD_EZBOUNCE,   REGISTERED,                   CONNECTING,                 H(22, &conn::do_ezb_cmd),            __HELP_ENTRY(ezbounce)},
    { "QUIT",       CMD_QUIT,       REGISTERED | BOUNCED,         CONNECTING,                 H(23, &conn::do_quit_cmd),           __HELP_ENTRY(quit)},
    { "PRIVMSG",    CMD_PRIVMSG,    REGISTERED | BOUNCED,         CONNECTING,                 H(24, &conn::do_privmsg_cmd),        NULL},

    { "STATUS",     CMD_STATUS,     REGISTERED | ADMIN,           NDM | CONNECTING,           H(25, &conn::do_status_cmd),         __HELP_ENTRY(status)},
    { "REHASH",     CMD_REHASH,     REGISTERED | ADMIN,           NDM | CONNECTING,           H(26, &conn::do_rehash_cmd),         __HELP_ENTRY(rehash)},
    { "WRITE",      CMD_WRITE,      REGISTERED | ADMIN,           NDM | CONNECTING,           H(27, &conn::do_write_cmd),          __HELP_ENTRY(write)},
    { "HASH",       CMD_HASH,       REGISTERED | ADMIN,           NDM | CONNECTING,           H(28, &conn::do_hash_cmd),           __HELP_ENTRY(hash)},
    { "KILL",       CMD_KILL,       REGISTERED | ADMIN,           NDM | CONNECTING,           H(29, &conn::do_adminmisc_cmd),      __HELP_ENTRY(kill)},
    { "DIE" ,       CMD_DIE,        REGISTERED | ADMIN,           NDM | CONNECTING,           H(29, &conn::do_adminmisc_cmd),      __HELP_ENTRY(die)},
    { "DIENOW",     CMD_DIENOW,     REGISTERED | ADMIN,           NDM | CONNECTING,           H(29, &conn::do_adminmisc_cmd),      __HELP_ENTRY(dienow)},
    { "WHOIS",      CMD_WHOIS,      REGISTERED | ADMIN,           NDM | CONNECTING,           H(30, &conn::do_whois_cmd),          __HELP_ENTRY(whois)},
    { "TRACE",      CMD_TRACE,      REGISTERED | ADMIN,           NDM | CONNECTING,           H(31, &conn::do_trace_cmd),          __HELP_ENTRY(trace)},
    { "RELOAD",     CMD_RELOAD,     REGISTERED | ADMIN,           NDM | CONNECTING,           H(32, &conn::do_reload_cmd),         __HELP_ENTRY(reload)},
#ifdef __DEBUG__
    { "CHATSEND",   CMD_CHATSEND,   REGISTERED | ADMIN,           NDM | CONNECTING,           H(33, &conn::do_dccsend_cmd),        NULL},
    { "DCCSEND",    CMD_DCCSEND,    REGISTERED | ADMIN,           NDM | CONNECTING,           H(33, &conn::do_dccsend_cmd),        NULL},
    { "ZOMBIFY",    CMD_ZOMBIFY,    REGISTERED,                   NDM,                        H(34, &conn::do_misc_cmd), NULL},
#endif
};


/* table for incoming traps */
const struct cmd incoming_command_table[] = {
    {"PRIVMSG",     INCOMING_PRIVMSG,           0,                  0,                  H(40, &conn::do_privmsg_incoming_cmd)},
    {"NICK",        INCOMING_NICK,              0,                  0,                  H(41, &conn::do_nick_incoming_cmd)},
    {"MODE",        INCOMING_MODE,              DETACHED,           0,                  H(42, &conn::do_mode_incoming_cmd)},
    {"JOIN",        INCOMING_JOIN,              0,                  0,                  H(43, &conn::do_join_incoming_cmd)},
    {"PART",        INCOMING_PART,              0,                  0,                  H(44, &conn::do_part_incoming_cmd)},
    {"KICK",        INCOMING_KICK,              0,                  0,                  H(45, &conn::do_kick_incoming_cmd)},
    {"TOPIC",       INCOMING_TOPIC,             DETACHED,           0,                  H(46, &conn::do_topic_incoming_cmd)},
    {"NOTICE",      INCOMING_NOTICE,            DETACHED,           0,                  H(47, &conn::do_notice_incoming_cmd)},
    {"QUIT",        INCOMING_QUIT,              DETACHED,           0,                  H(48, &conn::do_quit_incoming_cmd)},
    {"003",         INCOMING_003,               0,                  GOTSERVINFO,        H(49, &conn::do_servinfo_cmd)},
    {"004",         INCOMING_004,               0,                  GOTSERVINFO,        H(49, &conn::do_servinfo_cmd)},
    {"005",         INCOMING_005,               0,                  GOTSERVINFO3,       H(49, &conn::do_servinfo_cmd)},
    {"PONG",        INCOMING_PONG,              0,                  0,                  H(50, &conn::do_pong_incoming_cmd)},
    {"PING",        INCOMING_PING,              PPE,                0,                  H(51, &conn::do_ping_incoming_cmd)},
    {"ERROR",       INCOMING_ERROR,             DETACHED | PPE,     0,                  H(52, &conn::do_error_incoming_cmd)},

};

int conn::init_command_hash(void)
{
    int size = sizeof(command_table) / sizeof(struct cmd);
    int i;
    for (i = 0; i < size; ++i)
        cmdhash.insert(&command_table[i]);
    size = sizeof(incoming_command_table) / sizeof(struct cmd);
    for (i = 0; i < size; ++i)
        incoming_hash.insert(&incoming_command_table[i]);
    return 1;
}

int htbl::insert(const struct cmd * c)
{
    int idx = hash(c->msg) % buckets;
    list_add(&table[idx], (void *) c);
    return 1;
}

/* note: str must be capitalized */
const struct cmd * htbl::lookup(const char * str, int flags)
{
    int idx = hash(str) % (buckets);
    struct hashlist * l = &table[idx];
    struct node * n;
    const struct cmd * c;

    lookups ++;

    for (n = l->head; n; n = n->next)
    {
        c = (const struct cmd *) n->data;
        /* No required flags for this command or we meet all required flags */
        if (((flags & c->req_flags) == c->req_flags) || (!c->req_flags))
            if (!strcmp(c->msg, str))
                /* Check that we don't have any of the bad flags */
                if (hits++, !c->bad_flags ||
                    !((flags & c->bad_flags) & c->bad_flags))
                    /* Found it */
                    return c;

    }
    return 0;
}


/*
 * Show some info on 'c' */
void conn::show_whois(conn * c) const
{
    char timebuff[10];
    char flagbuff[10];
    c->mkstat(flagbuff);
    duration(ircproxy_time() - c->connect_time, 0, timebuff, sizeof(timebuff));
    cprintf("---> Connection ID %d\r\n",c->id);
    cprintf("     Online:       %s\r\n",timebuff);
    cprintf("     From:         %s\r\n", inet_ntoa(c->client_saddr.sin_addr));
    cprintf("     IRC Nick:     %s\r\n", c->uinfo.irc->nick);
    if (c->server)
        cprintf("     Connected to: %s:%d (running %s)\r\n", inet_ntoa(c->serv_saddr.sin_addr), c->uinfo.port, c->uinfo.serverversion);
    cprintf("     Flags:        %s (0x%X)\r\n", flagbuff, c->stat);
    cprintf("<--- [end of info]\r\n");
}

/* Command functions below.
 * Type: int conn::do_XXX_cmd(int type, int argc, char * argv[])
 *       .. or CMDFUNC(XXX)
 *
 * Return:
 *    0 - Unhandled, relay to IRC server
 *    1 - Handled properly
 *   -1 - Handled, but destroy object immediately
 */

/* int */ CMDFUNC(registration) /* (int type, int argc, char ** argv) */
{
    switch (type)
    {
    case CMD_USER:
        /* Check for non-blank non-whitespace valid arguments */
        if (!argc)
            return 1;
        uinfo.usercmd = my_strdup(argv[0]);
        setf(USERED);
        if (checkf(USERED) && checkf(NICKED))
            goto success;
        return 1;

    case CMD_NICK:
      {
        if (!argc)
            return 1;

        if (strlen(argv[1]) > NICKNAME_LENGTH)
            argv[1][NICKNAME_LENGTH] = 0;

        /* First time */
        delete[] uinfo.irc->nick;
        uinfo.irc->nick = my_strdup(argv[1]);

        /* If we all these set, then merely exit with updated nickname information */
        if (checkf(USERED) && checkf(NICKED) && checkf(PWED))
        {
            client->printf(":%s!unknown@%s NICK :%s\r\n", uinfo.irc->nick, inet_ntoa(client_saddr.sin_addr), argv[1]);
            printlog("NICK CHANGE: %s --> %s\n", addr(), argv[1]);
            return 1;
        }
        setf(NICKED);
        if (checkf(USERED) && checkf(NICKED))
            goto success;

        return 1;
      }
    }
    return 1;
success:

    /* Got password? */
    if (!checkf(PWED))
        cprintf("[awaiting login/pass command]\r\n");
    /* Yes, we have password */
    else if (checkf(PWED) && checkf(NICKED) && checkf(USERED))
        on_client_connect(0);
    return 1;
}

CMDFUNC(login)
{
    char login[50], pw[50];
    userdef * u = 0;
    int as_ptr = 3;

    if (!argc)
        return 1;

    safe_strcpy(login, argv[1], sizeof(login));

    if (type == CMD_PASS)
    {
        if (gettok(login, pw, sizeof(pw), ':',2))
        {
            *strchr(login, ':') = 0;
            as_ptr = 2;
        } else {
            strcpy(pw, login);
            strcpy(login, "default");
            as_ptr = 2;
        }
    } else {
        if (argc < 2)
            return 1;
        safe_strcpy(pw, argv[2], sizeof(pw));
    }

    u = userdef::find(users, login);
    if (!u)
    {
        cprintf("Invalid username: `%s'\r\n", login);
        return 1;
    }

    char * host = inet_ntoa(client_saddr.sin_addr);
    unsigned short port =ntohs(client_saddr.sin_port);

    switch (u->add(this, &rulesets, pw, host, port) )
    {
    case 1:
        /* ->add() gave us rulesets, now check them all */
        char reason[100];
        switch (ruleset::list_is_allowed_from(&rulesets,
             host, port, reason, sizeof(reason)))
        {
        case 0:
            u->remove(this);
            goto no_authorization;

        case -1:
            cprintf(msg_banned, reason, reason);
            printlog("Connection DENIED: (banned) from %s on port %d\n", host, port);
            u->remove(this);
            on_client_disconnect(0);
            return -1;
        }
        /* Ready to go */
        user = u;
        config = new user_options(&user->cfg);
        config->clear(OPT_PASSWORD);

        /* for admins, just wipe out the list. they can connect wherever the
         * hell they want, and don't need to bother with limits */
        if (config->checkf(OPT_USER_IS_ADMIN))
            rulesets.clear();
        else
            ruleset::list_register_from(&rulesets, host, port);

        setf(PWED | FROM_RULESETS_REGISTERED);

        /* Auto server: */
        if (argv[as_ptr])
            config->set(OPT_AUTOSERVER, argv[as_ptr]);

        if (checkf(USERED) && checkf(NICKED))
            on_client_connect(0);
        return 1;

    case 0:
        cprintf("LOGIN: Incorrect password for `%s'\r\n", login);
        printlog("Incorrect password from %s\n", addr());
        if (++failed_passwords >= pcfg.max_failed_passwords
            && pcfg.max_failed_passwords)
        {
            cprintf(msg_too_many_failures);
            printlog("... disconnected client for giving too many incorrect passwords!\n");
            on_client_disconnect(0);
            return -1;
        }

        break;

    case -1:
    no_authorization:
        on_client_disconnect(0);
        cprintf("No authorization\r\n");
        printlog("DENIED: Connection from %s: No authorization\n", addr());
        return -1;
    }
    return 0;
}

/* the /conn command */
CMDFUNC(conn)
{
    char  port[6] = "", * pass = 0;
    int tmp,i=1,inssl=0;
    u_short p;

    if (!argc)
    {
        cprintf(msg_not_enuff_args, "CONN");
        return 1;
    }
    if (strcasecmp("-ssl",argv[i]) == 0) {
        if (argc<2) {
            cprintf(msg_not_enuff_args, "CONN");
            return 1;
         }
         inssl=1;
         i++;
    }
    if (gettok(argv[i], port, sizeof(port), ':', 2))
        *strchr(argv[i],':') = 0;

    p = (strcmp(port, "") == 0) ? 6667: (u_short) atoi(port);

    pass = argv[i+1];

    /* Let's see if we CAN connect to this place */
    if (!can_connect(argv[i], p))
    {
        printlog("Connection attempt DENIED: %s to %s:%d\n",
                         addr(), server, p);
        return 1;
    }

    /* we can now try to connect.. */
    uinfo.server = my_strdup(argv[i]);
    printlog("Connection attempt: %s to %s:%d\n",
                      addr(), argv[i], p);
    if ((tmp = do_connect(argv[i], p,pass,inssl)) == 1)
    {
        in_addr in = { config->get(PREF_VHOST) };
        cprintf("[\002connecting to\002]: %s:%d\r\n", argv[i], p);
        cprintf("[\002vhost\002]:         %s\r\n", inet_ntoa(in));
        cprintf("Use `/quote cancel' to bail out\r\n");
    }
    else
    {
       const char *err;
       if (tmp == -1)
           err = "unknown host";
       else
           err = strerror(errno);
       cprintf(msg_conn_failed, argv[i], err);
       printlog("Connection attempt FAILED: %s to %s:%d (failed prematurely): %s\n", addr(),
                        argv[i], p, err);
    }
    return 1;
}

/* ezb help system */
CMDFUNC(help)
{
    if (!argc)
    {
        cprintf_multiline(__HELP_ENTRY(command_list));
        return 1;
    }

    ToUpper(argv[1]);
    const struct cmd * c = cmdhash.lookup(argv[1], 0);
    if (!c || !c->help)
        cprintf("HELP: no help found for command %s\n", argv[1]);
    else
        cprintf_multiline(c->help);
    return 1;
}

/* VHOST & INTERFACE commands */
CMDFUNC(vhost)
{
    if (!config->checkf(OPT_ENABLE_VHOST_COMMAND))
    {
       cprintf("This command has been disabled here.\r\n");
       return 1;
    }
    if (!argc)
    {
        /*
         * No arguments given: set vhost to default
         * interface
         */
        config->set(PREF_VHOST, user->cfg.get(PREF_VHOST));
        cprintf("VHOST: Set vhost to default interface\r\n");
        return 1;
    }
    /*
     * Arguments given: check if the vhost exists in the table
     * and then set it if so
     * (ignore whats on the table for admin)
     */
    if (!checkf(ADMIN))
    {
        list_iterator<char> vi(vhosts);
        while (vi.has_next())
        {
            char * r = vi.next();
            if (!strcasecmp(r, "all") ||
                !strcasecmp(r, argv[1]))
                    goto found;
        }

        if (user->vhosts)
        {
            list_iterator<char> vi2(user->vhosts);
            while (vi2.has_next())
            {
                char * r = vi2.next();
                if (!strcasecmp(r, "all") ||
                    !strcasecmp(r, argv[1]))
                       goto found;
            }
        }

        cprintf("Host is on not on my vhost list.\r\n");
        return 1;
    }

found:

    /* Resolve interface and fill it into client->iface */
    struct in_addr in;
    switch (fill_in_addr(argv[1], &in))
    {
    case 0:
        cprintf(msg_interface_failed, strerror(errno));
        break;
    case 1:
        config->set(PREF_VHOST, in.s_addr);
        cprintf(msg_interface_set, argv[1], inet_ntoa(in));
           break;
    case -1:
        cprintf(msg_interface_failed, "unknown host\n");
    default:
        break;
    }
    return 1;
}

CMDFUNC(vhosts)
{
    if (!config->checkf(OPT_ENABLE_VHOST_COMMAND))
    {
        cprintf("This command has been disabled here.\r\n");
        return 1;
    }
    cprintf("Available virtual hosts:\r\n");

    /* BLAH! duplicated code */

    list_iterator<char> vi(vhosts);
    int n = 0;

    while (vi.has_next())
        cprintf("%d) %s=\r\n", ++n, vi.next());

    if (user->vhosts)
    {
       list_iterator<char> vi2(user->vhosts);
       while (vi2.has_next())
	       cprintf("%d) %s\r\n", ++n, vi2.next());
    }
    cprintf("End of list.\r\n");
    return 1;
}

CMDFUNC(motd)
{
    if (pcfg.motdfile)
        show_motd();
    else
        cprintf("There is no MOTD.\r\n");
    return 1;
}


CMDFUNC(disconnect)
{
    on_server_disconnect();
    cprintf("Ok, broke your connection to the irc server.\r\n");
    printlog("DISCONNECT: %s from IRC server by his request.\n",
                          addr());
    return 1;
}


/*
 * Set a fake ident. Fake idents require MDIDENTD to be running
 */
CMDFUNC(ident)
{
    if (!config->checkf(OPT_ENABLE_FAKE_IDENTS))
    {
       cprintf("Sorry, fake idents have been disabled here.\r\n");
       return 1;
    }

    if (argc)
    {
        config->set(PREF_FAKE_IDENT, argv[1]);
        cprintf("Ok, set your fake ident to %s\r\n", argv[1]);
    } else
        cprintf(msg_not_enuff_args, "IDENT");
    return 1;
}

CMDFUNC(quit)
{
    if (config->decide(PREF_AUTO_DETACH))
    {
        do_auto_detach();
        return 1;
    }
    return 0;
}

/* other commands */
CMDFUNC(misc)
{
    switch (type)
    {
    case CMD_CANCEL:
        on_server_connect(0x29A);
#ifdef __DEBUG__
    case CMD_ZOMBIFY:
        die();
#endif
    }
    return 1;
}

/**
 ** The gigantor LOG command
 **/
CMDFUNC(log)
{
    if (!config->checkf(OPT_LOG_OPTIONS))
    {
        cprintf("Detached-logging has been disabled here.\r\n");
        return 1;
    }

    char *cmd = argv[1];
    char buff[20];

    /* Status Command */
    if (!argc)
    {
        logfile::intflags_to_char(config->get(OPT_LOG_OPTIONS), buff);
        cprintf("Your current log options are: %s\r\n", buff);
        cprintf("Use /quote [ezb] LOG HELP for description of options\r\n");
        cprintf("Use /quote [ezb] LOG LIST to list any log files ready for sending\r\n");
    }
    else if (strcasecmp(cmd, "LIST") == 0)
    {
     //   if (loglist && !loglist->size())
       // {
       //     delete loglist;
       //     loglist = 0;
       // }

        if ((!loglist) || (loglist && !loglist->size()))
            cprintf("LOG LIST: List is empty\r\n");
        else
            goto list_logs;
    }
    else if (strcasecmp(cmd, "SEND") == 0 || strcasecmp(cmd,"VIEW") == 0)
    {
        bool view = 0;
        if (strcasecmp(cmd,"VIEW") == 0)
            view = 1;

        if (argc < 2)
        {
            cprintf("LOG SEND/VIEW: Specify log file # or 'all'\r\n");
            return 1;
        }
        if (!loglist || !loglist->size())
        {
            cprintf("LOG SEND/VIEW: There are no log files to send. Try '/quote [ezb] log find'\r\n");
            return 1;
        }
        int idx = (unsigned int) atoi(argv[2]);
        int num2send = 1;
        if (strcasecmp(argv[2], "all") == 0)
        {
            cprintf("LOG SEND/VIEW: All requested: I will only send two at a time...\r\n");
            num2send = 2;
            idx = 1;
        }
        else if (!idx || idx > loglist->size())
        {
            cprintf("LOG: Log idx %d out of range: try <1-%d>\r\n", idx, loglist->size());
            return 1;
        }

        char * file = 0;
        int x = 0;

        do {
            if (idx > loglist->size())
                break;
           	
            file = loglist->get(idx - 1);
                    	
            if (logfile::is_locked(file))
            {
                cprintf("LOG: Unable to send log file %d: logfile is locked\r\n", idx);
                continue;
            }

            dcc * d = dcc_send_file(file, logfile::fixup_logname(file), NULL, 1, view);
            if (!d)
            {
                cprintf("LOG: Unable to send log file %d: %s\r\n", idx + 1, strerror(errno));
                continue;
            }
            logfile::lock(file);
            if (!view)
            {
                printlog("LOG SEND: Sent logfile %s to %s\n", file, addr());
                cprintf("LOG SEND: Sent %s...\r\n", file);
            }
            else
            {
                printlog("LOG VIEW: Sent (as DCC CHAT) logfile %s to %s\n", file, addr());
                cprintf("LOG VIEW: Sent %s as DCC CHAT\r\n", file);
            }
            x++;
        } while (idx++, --num2send != 0);
        cprintf("Sent %d log files...\r\n", x);
    }
    else if (strcasecmp(cmd, "SET") == 0)
    {
        if (argc < 2)
        {
            cprintf(msg_not_enuff_args, "LOG SET");
            return 1;
        }
        if (!config->checkf(OPT_LOG_OPTIONS))
        {
            cprintf("LOG SET: Error: Detached-Logging is disabled here.\r\n");
            return 1;
        }
        /* Set the log options. Reverse lookup the log options if the
         * user stuck any invalid ones in there */
        int tmp = logfile::charflags_to_int(argv[2]);
        if (tmp == logfile::LOG_NONE)
        {
            cprintf("LOG SET: Ok, disabling logging for this session\r\n");
            config->set(OPT_LOG_OPTIONS, logfile::LOG_NONE);
            return 1;
        }
        if (!(tmp & logfile::LOG_ALL))
        {
            cprintf("LOG SET: Error: you did not specify a A,C,P option, assuming you want to log everything...\r\n");
            tmp |= logfile::LOG_ALL;
        }
        /* Enforce config file restrictions */
        if (!checkf(ADMIN))
        {
            if (!config->checkf(OPT_ENABLE_PRIVATE_LOGGING) && (tmp & logfile::LOG_PRIVATE))
            {
                cprintf("LOG SET: Error: logging of private messages is disabled here.\r\n");
                tmp &= ~logfile::LOG_PRIVATE;
            }
            if (!config->checkf(OPT_ENABLE_PUBLIC_LOGGING) && (tmp & logfile::LOG_PUBLIC))
            {
                cprintf("LOG SET: Error: logging of public messages is disabled here.\r\n");
                tmp &= ~logfile::LOG_PUBLIC;
            }
            if (!config->checkf(OPT_ENABLE_SEPERATE_LOGGING) && (tmp & logfile::LOG_SEPERATE))
            {
                cprintf("LOG SET: Error: logging to seperate files is disabled here.\r\n");
                tmp &= ~logfile::LOG_SEPERATE;
            }
        }

        config->set(OPT_LOG_OPTIONS, tmp);
        logfile::intflags_to_char(tmp, buff);
        cprintf("Set your log options to: %s\r\n", buff);
        cprintf("Use /quote HELP LOG for description of options\r\n");
    }

    /* Look up old log files */
    else if (strcasecmp(cmd, "FIND") == 0)
    {
        int num;

        if (argc < 2 && checkf(DEFAULT_USER))
        {
            cprintf("Usage: /quote [ezb] LOG FIND <password for detached session>\r\n");
            return 1;
        }

        cprintf("LOG FIND: Searching for old log files of user `%s' w/ password `%s'\r\n",
                user->name, argv[2]);

        /* List is destroyed each and every time */
        if (loglist)
            destroy_list(loglist, 1);
        delete loglist;
        loglist = new list<char>;

        num = logfile::find_log_files(pcfg.logdir, user->name,
                                         argv[2], -1, loglist, 10);
        if (num)
        {
            cprintf("Found %d matching log files: \r\n", num);

list_logs:
            list_iterator<char> li(loglist);
            int i = 1;
            while (li.has_next())
                cprintf("   %d. %s\r\n", i++, li.next());
            cprintf("---\r\n");
            cprintf("Use:\r\n");
            cprintf("   ----> /quote [ezb] log SEND <#> to retrieve a log file.\r\n");
            cprintf("   ----> /quote [ezb] log VIEW <#> to view a log file thru DCC CHAT\r\n");
            return 1;
        }

      //  delete loglist;
       // loglist = 0;
        cprintf("LOG FIND: Couldn't find anything\r\n");
        cprintf("LOG FIND: Be sure to check your logfile send list (with /quote [ezb] LOG LIST)\r\n");

    }

    else if (strcasecmp(cmd, "HELP") == 0)
        cprintf_multiline(__HELP_ENTRY(log));

    return 1;
}

/* ezb command
 * bump up the pointer and call the lookup function
 * again. we do a few checks on the result afterwards.. */
CMDFUNC(ezb)
{
    const struct cmd  * c;
    int r = 0;

    extern int mk_ppchar(char * string, char *(*buff)[MAX_PPCHAR_ARGS]);

    if (!argc)
    {
        cprintf("EZB: usage `/quote ezb <command> [args]'\r\n");
        return 1;
    }

    ToUpper(argv[1]);
    c = cmdhash.lookup(argv[1], stat);

    if (!c)
    {
        cprintf("EZB: bad or unknown command\r\n");
        return 1;
    }
    else {
        /* Special commands we should be aware of.. prevent double ezbounce command
         * and don't engage the privmsg handler */
        switch (c->id)
        {
        case CMD_EZBOUNCE:
        case CMD_PRIVMSG:
            cprintf("EZB: Hey! Cannot use %s command with 'ezb' command!\r\n", c->msg);
            return 1;
        case CMD_QUIT:
            /* EZB QUIT: Force disconnection from server */
            /* When auto-detach enabled, use this to send quit
              to IRC server */
            server->printf("QUIT :%s\r\n", gettok_ptr(argv[0], ' ', 2));
            return 1;
        }
        /* just call the handler then */
        /* but first assemble a new argv[] table */
        char *__argv[MAX_PPCHAR_ARGS];
        memset(__argv, 0, sizeof (__argv));
        argc = mk_ppchar(no_leading(argv[0] + strlen(argv[1])),
                    &__argv);
        DEBUG("       ###### HANDLER: %s(%d, %d, %s)\n", c->msg, c->id, argc, __argv[0]);
#ifdef GCC_COMPILER_BUG
        r = (this->*(handlers[c->idx]))(c->id, argc, __argv);
#else
        r = (this->*(c->handler))(c->id, argc, __argv);
#endif
        delete[] __argv[0];
        return r;
    }

    return 1;
}

/*
 * Outgoing PRIVMSG handler -- Current does:
 *  Outgoing DCC proxying (and that's it) */
CMDFUNC(privmsg)
{
    if (config->decide(PREF_DCC_OUT))
    {
        /* If we get here, the message is in the form
         * PRIVMSG target :[CTCP] xxxx
         */
        if (argc < 2 || argv[2][1] != '\001')
            return 0;

        char * args = gettok_ptr(argv[0], ' ', 3);
        return do_ctcp(0, &argv[2][2], uinfo.irc->nick, argv[1], args);
    }
    return 0;
}


/************************
 * ADMIN commands below
 ************************/

/*
 * Dump information about the proxy
 */

CMDFUNC(status)
{
    char timebuff[100];
    duration(ircproxy_time() - start_time, 1, timebuff, sizeof(timebuff));
    cprintf(msg_status_uptime, timestamp(), timebuff);
#ifdef HAVE_GETRUSAGE
    {
        struct rusage ru;
        char buff[100];
        getrusage(RUSAGE_SELF, &ru);
        time_t mins = (int) ((ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) / 60);
        time_t sex = (int) ((ru.ru_utime.tv_sec + ru.ru_stime.tv_sec) - (mins * 60));
        sprintf(buff, "%02d:%02d", (int) mins, (int) sex);
        cprintf(msg_status_cputime, buff);
    }
#endif

    cprintf(msg_status_connections, num_active);
    cprintf("Active DCCs: %d\r\n", dcc_list.size());
    client->printf(msg_status_listheader, uinfo.irc->nick);

    list_iterator<conn> iter(ircproxy_conn_list());

    while (iter.has_next())
    {
        conn * c = iter.next();
        char str_stat[9] = "?";
        c->mkstat(str_stat);

        duration(ircproxy_time() - c->connect_time, 0, timebuff, sizeof(timebuff));
        /* ":" "info!ezb" " NOTICE %s :ID    TIME  NICK       FROM         TO             STAT           VHOST\n"; */
        client->printf(":%s NOTICE %s :%-6d", EZBOUNCE_HEADER, uinfo.irc->nick,
                 c->id);
        client->printf("%-6s",timebuff);
        client->printf("%-10s ", (c->user ? strtrunc(c->user->name, 10) : "(none)"));
        client->printf("%-15s ",inet_ntoa(c->client_saddr.sin_addr));
        client->printf("%-15s ",(c->server) ?
                 inet_ntoa(c->serv_saddr.sin_addr) :
                 "(n/a)");
        client->printf("%-9s\r\n",   str_stat);
        client->flushO();
    }

    return 1;
}


CMDFUNC(rehash)
{
    printlog("%s is rehashing proxy configuration file..\n", addr());
    switch (ircproxy_rehash())
    {
    case 1:
        printlog("Rehash successful\n");
        cprintf("REHASH successful\r\n");
        set_dns_timeout(pcfg.max_dns_wait_time);
        break;
    case 0:
        printlog("Rehash failed -- errors loading file: %s\n", strerror(errno));
        cprintf("REHASH failed: %s\r\n", strerror(errno));
        break;
    default:
        printlog("Rehash failed.\n");
        cprintf("REHASH failed -- check log file for details\r\n");
        break;
    }
    return 1;
}

/* Dump hashing stats */
CMDFUNC(hash)
{
    struct __htbl::hash_stat_t ht;
    extern struct __htbl::hash_stat_t cfght;
    memset(&ht, 0, sizeof(ht));

    if (!argc)
    {
        cprintf("Usage: HASH <1,2, or 3>\n");
        return 1;
    }

    switch (atoi(argv[1]))
    {
    case 1:
        cprintf("User Command Parser stats ...\r\n");
        cmdhash.stat(&ht);
        break;
    case 2:
        cprintf("Incoming Command Parser stats ...\r\n");
        incoming_hash.stat(&ht);
        break;
    case 3:
        cprintf("Config File Parser stats ...\r\n");
        memcpy(&ht, &cfght, sizeof(ht));
        break;
    default:
        cprintf("HASH: Unknown option... Use 1-3\r\n");
        return 1;
    }

    cprintf("   Buckets:   %d\n", ht.buckets);
    cprintf("   In Use:    %d (%.1f%%)\n", ht.size, (float) ht.size / ht.buckets * 100);
    cprintf("     [empty]: %d (%.1f%%)\n", ht.sizes[0], (float) ht.sizes[0] / ht.buckets * 100);
    cprintf("     [1]:     %d (%.1f%%)\n", ht.sizes[1], (float) ht.sizes[1] / ht.buckets * 100);
    cprintf("     [2]:     %d (%.1f%%)\n", ht.sizes[2], (float) ht.sizes[2] / ht.buckets * 100);
    cprintf("     [3]:     %d (%.1f%%)\n", ht.sizes[3], (float) ht.sizes[3] / ht.buckets * 100);
    cprintf("     [4+]:    %d (%.1f%%)\n", ht.sizes[4], (float) ht.sizes[4] / ht.buckets * 100);
    cprintf("   Lookups:   %d\n", ht.lookups);
    cprintf("   Hits:      %d (%.1f%%)\n", ht.hits,float(ht.hits) / ht.lookups * 100);
    return 1;
}

/*
 * Send a message to a single user or everybody
 * using the proxy. Unfortunately there is no
 * way for the users to respond back ;)
 */
CMDFUNC(write)
{
    if (argc < 2)
    {
        cprintf("Usage: WRITE <userid/ALL> <message>\n");
        return 1;
    }
    if (strcasecmp(argv[1], "ALL") == 0)
    {
        char buff[72];
        sprintf(buff, "*** GLOBAL MESSAGE FROM %s ***", uinfo.irc->nick);
        broadcast(ircproxy_conn_list(), buff);
        broadcast(ircproxy_conn_list(), argv[0] + strlen(argv[1])+ 1);
    }
    else {
        conn * c = NULL;
        if ((isdigit(argv[1][0])) && (c = lookup(ircproxy_conn_list(), atoi(&argv[1][0]))) && (c->client))
            c->cprintf("Message from %s: %s\n", uinfo.irc->nick, argv[0] + strlen(argv[1])+ 1);
        else if (c && !c->client)
            cprintf("WRITE: Can't deliver message: user is detached or dead.\r\n");
        else
            cprintf("Can't find user by that id\n");
    }
    return 1;
}


/* The other admin commands that no one really
 * cares about */
CMDFUNC(adminmisc)
{
    switch (type)
    {
    case CMD_DIENOW:
    case CMD_DIE:
        if (argc)
        {
            cprintf(type == CMD_DIENOW ? msg_dieing_now : msg_dieing);
            ircproxy_die((type == CMD_DIENOW), argv[0]);
        }
        else
            cprintf(msg_not_enuff_args, "DIE/DIENOW");
        return 1;


    case CMD_KILL:
      {
        conn *victim;
        unsigned i;

        if (argc < 2)
            return cprintf(msg_not_enuff_args,"KILL"), 1;
        i = (unsigned int) atoi(argv[1]);

        if (i == id)
        {
            printlog("%s tried to kill himself\n", addr());
            cprintf("Suicides not allowed.\r\n");
            return 1;
        }
        if ((victim = conn::lookup(ircproxy_conn_list(), i)))
        {
            const char * reason2 = argv[0] + strlen(argv[1]) + 1;
            if (victim->dead())
            {
                cprintf("Cannot kill: already dead\r\n");
                return 1;
            }
            victim->kill(uinfo.irc->nick, reason2);
            cprintf(msg_killed, i, victim->uinfo.irc->nick);
            printlog("KILLED: %s killed by %s: %s\n",
                              victim->addr(), addr(), reason2);
        }
        else
            cprintf(msg_cant_kill, i);
      }
    }
    return 1;
}

/*
 * Sessions [username] -- list all detached connections for
 * a particular user */
CMDFUNC(sessions)
{
    userdef * u;
    int idx = 0;
    bool hShown = 0;

    /* Argument specified ... show sessions for a user.. only
     * if we are admin though */
    if (argc)
    {
        if (checkf(ADMIN))
        {
            u = userdef::find(users, argv[1]);
            if (!u)
            {
                cprintf("SESSIONS: unknown user `%s'\n", argv[1]);
                return 1;
            }
        }
        else {
            cprintf("SESSIONS: you must be admin to view sessions for other users\n");
            return 1;
        }
    } else
        u = this->user;

    list_iterator<conn> i(u->conns);
    conn * c = 0;
    while (i.has_next())
    {
    	c = i.next();
        if (c->checkf(DETACHED))
        {
            char timebuff[15];
            if (!hShown)
            {
                cprintf("Current detached sessions for user %s:\n", u->name);
                cprintf("ID  IRC NICK             TO                   TIME\n");
                hShown = 1;
            }
            duration(ircproxy_time() - c->detach_time, 0, timebuff, sizeof(timebuff));
            cprintf("%-3d %-20s %-20s %s\n", ++idx, c->uinfo.irc->nick, c->uinfo.server, timebuff);
        }
    }
    if (!hShown)
        cprintf("No detached sessions found for %s\n", u->name);
    return 1;
}


CMDFUNC(detach)
{
    /* We'll check for this here, to be more user-friendly */
    if (!config->checkf(OPT_ENABLE_DETACH_COMMAND))
    {
        cprintf("Sorry, the Detach command has been disabled here\r\n");
        return 1;
    }
    if (!checkf(BOUNCED))
    {
        cprintf("DETACH: you must be connected to an IRC server first ...\r\n");
        return 1;
    }
    if (!argc)
    {
        if (checkf(DEFAULT_USER))
        {
            cprintf("DETACH: you must supply a password\r\n");
            return 1;
        }
    }

    if (!detach(argv[1]))
        cprintf("Detach failed!\r\n");
    return 1;
}


CMDFUNC(reattach)
{
    /* We'll check for this here, to be more user-friendly */
    if (checkf(BOUNCED))
    {
        cprintf("REATTACH: You must NOT be connected "
                      "to an irc server to use this command\r\n");
        return 1;
    }

    if (!config->checkf(OPT_ENABLE_DETACH_COMMAND))
    {
        cprintf("Sorry, the detach/reattach commands have been disabled here\r\n");
        return 1;
    }

    if (checkf(DEFAULT_USER) && argc < 2)
    {
        cprintf("REATTACH: default user must supply a password\r\n");
        return 1;
    }

    int ctr = 0;
    userdef * u;
    char * pw;
    int __id;
#if 0
    if (!isdigit(argv[1][0]) && argc >= 2)
    {
        if (!checkf(ADMIN))
        {
            cprintf("Must be admin to reattach to different user\r\n");
            return 1;
        }
        /* Assume it's 'reattach user id [pass]' */
        u = userdef::find(users, argv[1]);
        if (!u)
        {
            cprintf("REATTACH: Unknown user `%s'\n", argv[1]);
            return 1;
        }

        cprintf("REATTACH: Ok, trying user `%s'\n", argv[1]);
        __id = atoi(argv[2]);
        pw = argv[3];
    }
    else
#endif
    { /* No user name specified. Go straight for the id */
        __id = argc ? atoi(argv[1]) : 1;
        pw = argv[2];
        u = user;
    }

    /* Find it in the list */
    list_iterator<conn> i(u->conns);
	while (i.has_next())
	{
    	conn * c = i.next();
        if (c->checkf(DETACHED))
        {
            if (++ctr == __id)
            {
                if (!reattach(pw, c))
                   cprintf("Reattach to id '%d' failed\r\n", __id);
                return 1;
            }
        }
    }

    cprintf("REATTACH: Could not find id %d: try `/quote sessions' to list current detached sessions\r\n", __id);
    return 1;
}



CMDFUNC(traffic)
{
    cprintf("ezbounce traffic stats @ %s\n", timestamp());
    cprintf("clients ---> ezbounce          : %.2f kB\r\n", (float)bytes_fromc / 1024);
    cprintf("             ezbounce ---> IRC : %.2f kB\r\n", (float)bytes_tos / 1024);
    cprintf("             ezbounce <--- IRC : %.2f kB\r\n", (float)bytes_froms / 1024);
    cprintf("clients <--- ezbounce          : %.2f kB\r\n", (float)bytes_toc / 1024);

    return 1;
}

CMDFUNC(whois)
{
    userdef * u;
    if (!argc)
    {
        cprintf("WHOIS: must specify connection id or user name\r\n");
        return 1;
    }
    u = userdef::find(users, argv[1]);
    if (!u)
    {
        conn * c = conn::lookup(ircproxy_conn_list(), atoi(argv[1]));
        if (c)
        {
            cprintf("Connection '%d' is user '%s'\r\n", c->id, c->user->name);
            show_whois(c);
            return 1;
        }
        else {
            cprintf("WHOIS: not found: %s\r\n", argv[1]);
            return 1;
        }
    }

    cprintf("User info for %s\r\n", argv[1]);
    cprintf("---> Active connections: %d\r\n", u->conns->size());
    list_iterator<conn> i(u->conns);
    while (i.has_next())
        show_whois(i.next());
    return 1;
}


CMDFUNC(set)
{
    if (!argc)
    {
        struct in_addr addr = { (unsigned long) config->get(PREF_VHOST) };
        cprintf("Your Current Preferences:\r\n");
        cprintf("  VHost:         %s\r\n", inet_ntoa(addr));
        cprintf("  Auto-Server:   %s\r\n", config->get(OPT_AUTOSERVER, 0));
        cprintf("  Fake Ident:    %s\r\n", config->get(PREF_FAKE_IDENT, 0));
        cprintf("  Auto-Detach:   %s\r\n", config->checkp(PREF_AUTO_DETACH) ? "on" : "off");
        cprintf("  Proxy DCC-in:  %s\r\n", config->checkp(PREF_DCC_IN) ? "on" : "off");
        cprintf("  Proxy DCC-out: %s\r\n", config->checkp(PREF_DCC_OUT) ? "on" : "off");
        cprintf("  [flags prefs]: %d %d\r\n",  config->getf(), config->getp());

        return 1;
    }
    int cmd = user_options::lookup_cmd(argv[1]);

    switch (cmd)
    {
    case PREF_AUTO_PASS:
    case OPT_AUTOSERVER:
    case PREF_FAKE_IDENT:
        config->set(cmd, argv[2]);
        cprintf("Set %s to: %s\r\n", argv[1], argv[2]);
        break;

    case PREF_DCC_IN:
    case PREF_DCC_OUT:
    case PREF_AUTO_DETACH:
        if (argc < 2)
            cprintf("%s is currently set to: %s\r\n", argv[1], config->checkf(cmd) ? "ON" : "OFF");
        else if (strcasecmp(argv[2],"ON") == 0 || atoi(argv[2]))
        {
            config->setp(cmd);
            cprintf("Set %s to ON\n", argv[1]);
            if (!config->checkf(cmd))
                cprintf("NOTE: The proxy configuration does not allow you to use this feature..\r\n");
        } else {
            config->clearp(cmd);
            cprintf("Set %s to OFF\r\n", argv[1]);
        }
        break;

    case PREF_VHOST:
    {
        char *__argv[] = { argv[2], argv[2], 0 };
        /* call VHOST command */
        do_vhost_cmd(CMD_CONN, argc - 1, __argv);
        break;
    }

    case PREF_LOG:
        cprintf("SET: use /quote [ezb] LOG command to manage logging settings\r\n");
        break;

    default:
        cprintf("SET: Unknown variable `%s'\r\n", argv[1]);
    }

	return 1;
}

CMDFUNC(allowed)
{
    userdef * user = this->user;
    user_options * config = this->config;
    if (argc && isadmin())
    {
        user = userdef::find(users, argv[1]);
        if (!user)
        {
            cprintf("ALLOWED: invalid user %s\n", argv[1]);
            return 1;
        }
        config = &user->cfg;
    }

    cprintf("Configuration for user %s: \n", user->name);
    cprintf("  'vhost' command:                   %s\r\n", config->checkf(OPT_ENABLE_VHOST_COMMAND) ? "yes" : "NO");
    cprintf("  Fake Ident services:               %s\r\n", config->checkf(OPT_ENABLE_FAKE_IDENTS) ? "yes" : "NO");
    cprintf("  Drop on disconnect:                %s\r\n", config->checkf(OPT_DROP_ON_DISCONNECT) ? "yes" : "NO");
 //   cprintf("  Max Idle Time:                     FIXME!!!\r\n");
    cprintf("  Logging:                           %s\r\n", config->checkf(OPT_LOG_OPTIONS) ? "yes" : "NO");
    cprintf("    ---> Log file size limit:        %d\r\n", 0);
    cprintf("  'detach' command:                  %s\r\n", config->checkf(OPT_ENABLE_DETACH_COMMAND) ? "yes" : "NO");
    cprintf("    ---> automatic detach feature:   %s\r\n", config->checkf(OPT_ENABLE_AUTO_DETACH) ? "yes" : "NO");
    cprintf("  DCC Proxying\r\n");
    cprintf("    ---> Incoming:                   %s\r\n", config->checkf(OPT_ENABLE_INCOMING_DCC_PROXYING) ? "yes" : "no");
    cprintf("    ---> Outgoing:                   %s\r\n", config->checkf(OPT_ENABLE_OUTGOING_DCC_PROXYING) ? "yes" : "no");
    return 1;
}

CMDFUNC(echo)
{
    if (argc)
        client->printf(":%s\r\n", argv[0]);
    return 1;
}


CMDFUNC(reload)
{
    if (!pcfg.userfile)
        cprintf("Can't reload user preferences: user file wasn't set in the config\r\n");
    else {
        printlog("%s is reloading user preferences ...\n", addr());
        cprintf("Reloading User Preferences ...\r\n");
        if (ircproxy_load_prefs(::users, pcfg.userfile) < 1)
            cprintf("   .... failed\r\n");
        else
            cprintf("   .... success\r\n");
    }
    return 1;
}

CMDFUNC(save)
{
    if (checkf(DEFAULT_USER))
    {
        cprintf("Sorry, can't save preferences for default user\r\n");
        return 1;
    }

    cprintf("Saving preferences for user `%s'... \r\n", user->name);
    /* blah -- copy() will wipe out user record's password field,
     * because config->password == null -- so save it */

	char * pass = my_strdup(user->cfg.get(OPT_PASSWORD, 0));
    user->cfg.copy(config);
    user->cfg.set(OPT_PASSWORD, pass);
    delete[] pass;

    cprintf("    .... Success\r\n");

    if (isadmin())
    {
        if (!pcfg.userfile)
        {
            cprintf("Can't save options to disk: no user file was set in the config\r\n");
            return 1;
        }
        cprintf("Saving to disk ...\r\n");
        if (ircproxy_save_prefs(users, pcfg.userfile) < 1)
            cprintf("    .... Failed: %s\r\n", strerror(errno));
        else
            cprintf("    .... Success\r\n");
    }

    return 1;
}

/*
 * This thing is spiffy */
CMDFUNC(trace)
{
    char addrs[4][23]   /* 4 to hold 255.255.255.255:65535 */ =
          {"client is detached",
           "",
           "",
           "  not connected"};

    struct sockaddr_in out_addr;
    socklen_t st = sizeof(out_addr);
    conn * c = this;

    if (argc && isadmin())
    {
        c = conn::lookup(ircproxy_conn_list(), atoi(argv[1]));
        if (!c)
        {
            cprintf("TRACE: can't find user id %s\n", argv[1]);
            return 1;
        }
        if (c->dead())
        {
            cprintf("TRACE: can't trace id %s: zombie connection\n", argv[1]);
            return 1;
        }
    }

    cprintf("Showing network info for connection id %d (%s)\r\n", c->id, c->user->name);

    if (c->client)
    {
        sprintf(addrs[0], "%s:%u", inet_ntoa(c->client_saddr.sin_addr), ntohs(c->client_saddr.sin_port));
        sprintf(addrs[1], "%s:%u", inet_ntoa(c->local_saddr.sin_addr), ntohs(c->local_saddr.sin_port));
    }
    if (c->server)
    {
        getsockname(c->server->fd,  (sockaddr *) &out_addr, &st);
        sprintf(addrs[2], "%s:%u", inet_ntoa(out_addr.sin_addr), ntohs(out_addr.sin_port));
        sprintf(addrs[3], "%s:%u", inet_ntoa(c->serv_saddr.sin_addr), ntohs(c->serv_saddr.sin_port));
    }

    cprintf(" ________________________\r\n");
    cprintf(" [%-22s]\r\n", (c->client) ? c->user->name : "this");
    cprintf(" [%-22s]\r\n", addrs[0]);
    cprintf(" \\______________________/\r\n");
    cprintf("         |\r\n");
    cprintf("         |      /----------------------\\\r\n");
    cprintf("         \\----->[%22s]\r\n", addrs[1]);
    cprintf("                [\002%22s\002]\r\n", "ezbounce");
    cprintf("                [%22s]\r\n", EZBOUNCE_VERSION);
    if (server)
        cprintf("                [%22s]\r\n", addrs[2]);
    cprintf("                \\----------------------/\r\n");
    cprintf("                          |\r\n");
    cprintf("                          |\r\n");
    cprintf("                          \\----->[%22s]\r\n", addrs[3]);
    if (server)
    {
        cprintf("                                 [%22s]\r\n", c->uinfo.irc->nick);
        cprintf("                                 [                      ]\r\n");
        cprintf("                                 [%22s]\r\n", c->uinfo.server);
        cprintf("                                 [%22s]\r\n", c->uinfo.serverversion);
    }
    return 1;
}

CMDFUNC(about)
{
    struct utsname uts;
    uname(&uts);
    cprintf("This is \002" EZBOUNCE_VERSION "\002 [built " __DATE__ " " __TIME__ "]\r\n");
    cprintf("Running on: %s %s\r\n", uts.sysname, uts.release);
    cprintf("   Visit the ezbounce web site at http://druglord.freelsd.org/ezbounce/\r\n");
    return 1;
}

CMDFUNC(version)
{
    cprintf(EZBOUNCE_VERSION "\r\n");
    return 1;
}

CMDFUNC(debug)
{
    cprintf("DEBUG: not compiled with debugging info\r\n");
    return 1;
}


#ifdef __DEBUG__
CMDFUNC(dccsend)
{
#if 0
    if (argc < 1)
        return 1;
    if (type == CMD_DCCSEND)
        if (!dcc_send_file(argv[1]))
            cprintf("Unable to send :(\r\n");
    if (type == CMD_CHATSEND)
        if (!dcc_send_file(argv[1], 0, 0, 0, 1))
            cprintf("Unable to send :-P\r\n");
#else
    cprintf("Feature disabled\r\n");
#endif
    return 1;
}
#endif


/*********************************************************
 * INCOMING command handlers
 * Notes:
 *   Return values and their meanings are the same
 *   Return values are meaningless when detached
 *   'argv[0]' argument to the function contains ENTIRE line,
 *      so that the token before the command may be read
 *********************************************************/

/*
 * Gathers misc. info about the server during connect, so we can
 * dump it back during reattach .. */
CMDFUNC(servinfo)
{
    char * tmp = 0;
    switch (type)
    {
    case INCOMING_003:

        /* Here we find out when the server was created,
         * as well as the server's true name,
         * and, we might as well get a positive identification on our
         * nickname */

        /**** example line:
         * :irc.Prison.NET 003 druglord_ :This server was created Oct 23 2001 02:34:33
         */
        if (strcasecmp(argv[3], uinfo.irc->nick))
        {
            printlog("NICK CHANGE: %s ----> %s\n", addr(), argv[3]);
            delete[] uinfo.irc->nick;
            uinfo.irc->nick = my_strdup(argv[3]);

            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));
        }

        delete[] uinfo.servercreated;
        delete[] uinfo.server;
        tmp = gettok_ptr(argv[0], ' ', 8);
        uinfo.server        = my_strdup(no_leading(argv[1]));
        uinfo.servercreated = my_strdup(tmp);
        break;

    case INCOMING_004:
        /* Here we obtain the server version
         * and the crap it sends right afterward... i want to keep them
         * seperate because we will use the version string in 'trace' */
        /**** example line:
         * :irc.arcti.ca 004 _druglord irc.arcti.ca 2.8/hybrid-6.2 oOiwszcrkfydnxb biklmnopstve */
        delete[] uinfo.serverversion;
        delete[] uinfo.servermodes;
        tmp = gettok_ptr(argv[0],' ', 6);
        uinfo.serverversion = my_strdup(argv[5]);
        uinfo.servermodes = my_strdup(tmp);
        setf(GOTSERVINFO);
        break;

    case INCOMING_005:
	/* sample line: 
         * :elysium.ga.us.dal.net 005 druglord NOQUIT WATCH=128 SAFELIST MODES=6 MAXCHANNELS=10
         * MAXBANS=100 NICKLEN=30 TOPICLEN=307 KICKLEN=307 CHANTYPES=&# PREFIX=(ov)@+ NETWORK=DALnet
         * SILENCE=10 :are available on this server
         *
         * -- so just copy all from 4th token */
        if (!checkf(GOTSERVINFO2))
        {
            delete[] uinfo.server005_1;
            tmp = gettok_ptr(argv[0], ' ', 4);
            uinfo.server005_1 = my_strdup(tmp);
            setf(GOTSERVINFO2);
        } else if (!checkf(GOTSERVINFO3)) {
            delete[] uinfo.server005_2;
            tmp = gettok_ptr(argv[0], ' ', 4);
            uinfo.server005_2 = my_strdup(tmp);
            setf(GOTSERVINFO3);
        }
        break;
    }
    return 0;
}


CMDFUNC(privmsg_incoming)
{
    /* What all we need to do:
     * -- Handle CTCP DCC (only when !detached and DCC_IN)
     * -- Handle CTCP PING (only when detached)
     * -- Handle all CTCP (only when detached)
     * -- Handle all Other (only when detached) */
    /* Ensure that we really need to handle this.. */
    if (!checkf(DETACHED) && !config->decide(PREF_DCC_IN))
        return 0;

    char * ctcp = 0;
    char * ctcp_args = 0;
    ircaddr_simple ia(argv[1]);

    if (argc > 3 && argv[4][1] == '\001')
    {
        ctcp = &argv[4][2];
        if (argc > 4)
            ctcp_args = gettok_ptr(argv[0], ' ', 5);
    }

    /* The hook for Incoming DCC Proxying */
    if (!checkf(DETACHED) && config->decide(PREF_DCC_IN) && ctcp_args)
        /* Possibly a DCC -- have do_ctcp handle it (will relay too) */
        return do_ctcp(true, ctcp, no_leading(argv[1]), no_leading(argv[3]), ctcp_args);

    /* The hook for CTCP responses when detached
     * :nick!source@address PRIVMSG target :\001[CTCP] args... */
    if (checkf(DETACHED) && ctcp_args)
    {
        /* Check for and reply to CTCP PINGs  -- only once per 5 secs */
        if (ircproxy_time() - last_recved <= 5
           || strcmp(ctcp, "PING") != 0)
           return 0;

        last_recved = ircproxy_time();

        /* And reply -- note, no trailing \001, we just use the one that
         * was sent in the original ping */
        server->printf("NOTICE %s :\001PING %s\r\n", ia.nick, ctcp_args);
        return 1;
    }

    /* Finally, logging */
    if (log)
    {
        if (ctcp)
            log->write((ischan(argv[3]) ? logfile::EVENT_PUBLIC : logfile::EVENT_PRIVATE) |
                        logfile::EVENT_CTCP, &ia, argv[3], ctcp_args, ctcp);
        else
            log->write((ischan(argv[3]) ? logfile::EVENT_PUBLIC : logfile::EVENT_PRIVATE),
                        &ia, argv[3], no_leading(gettok_ptr(argv[0], ' ', 4)), 0);
    }
    return 0;
}

CMDFUNC(nick_incoming)
{
    ircaddr_simple ia(argv[1]);

    /*
     * Do the logging stuff first, cause we fuck with the ia object
     * in the nick change handler below */
    if (log)
    {
        log->write(logfile::EVENT_NICK | logfile::EVENT_PUBLIC,
            &ia, no_leading(argv[3]), NULL, NULL);

    }

    if (strcasecmp(ia.nick, uinfo.irc->nick) == 0
         && argc > 2)
    {
        printlog("NICK CHANGE: %s ----> %s\n", addr(), argv[3]+1);
        if (!uinfo.irc->host)
        {
            /* If we don't know our host, we do now */
            uinfo.irc->host = my_strdup(ia.host);
            uinfo.irc->ident = my_strdup(ia.ident);
        }
        delete[] uinfo.irc->nick;
        uinfo.irc->nick = my_strdup(argv[3]+1);

        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));
    }
    return 0;
}


CMDFUNC(mode_incoming)
{
    ircaddr_simple ia(argv[1]);
    if (log)
        if (ischan(argv[3]))
            log->write(logfile::EVENT_MODE | logfile::EVENT_PUBLIC,
                    &ia, argv[3],
                    no_leading(gettok_ptr(argv[0], ' ', 4)), NULL);

    return 0;
}

CMDFUNC(join_incoming)
{
    /* See if we joined, in that case update the IRC server */
    ircaddr_simple ia(argv[1]);
    if (!strcasecmp(ia.nick, uinfo.irc->nick))
    {
        uinfo.channels.add(my_strdup(no_leading(argv[3])));
        DEBUG("Adding %s to channel list!\n", argv[3]);
        /* If we don't know our hostname... we do now..
         * update accordingly */
        if (!uinfo.irc->host)
        {
            delete uinfo.irc;
            uinfo.irc = new ircaddr(ia);
            DEBUG("Found out address for %s (%s@%s)\n", uinfo.irc->nick, uinfo.irc->ident, uinfo.irc->host);
        }
    }

    if (log)
    {
        log->write(logfile::EVENT_JOIN | logfile::EVENT_PUBLIC,
                &ia, no_leading(argv[3]), NULL, NULL);
    }
    return 0;
}

CMDFUNC(part_incoming)
{
    ircaddr_simple ia(argv[1]);
    if (!strcasecmp(ia.nick, uinfo.irc->nick))
    {
        DEBUG("Removing %s from channel list\n", no_leading(argv[3]));
        strlist_remove(&uinfo.channels, no_leading(argv[3]));
    }

    if (log)
    {
        log->write(logfile::EVENT_PART | logfile::EVENT_PUBLIC,
                &ia, no_leading(argv[3]),
                no_leading(gettok_ptr(argv[0], ' ', 4)), NULL);
    }
    return 0;
}

CMDFUNC(kick_incoming)
{
    if (!strcasecmp(argv[4], uinfo.irc->nick))
    {
        DEBUG("Removing %s from channel list\n", argv[3]);
        strlist_remove(&uinfo.channels, argv[3]);
        if (checkf(DETACHED))
            server->printf("JOIN :%s\r\n", argv[3]);
    }

    if (log)
    {
        ircaddr_simple ia(argv[1]);
        log->write(logfile::EVENT_KICK | logfile::EVENT_PUBLIC,
                 &ia,  argv[3],
                 no_leading(gettok_ptr(argv[0], ' ', 5)), argv[4]);
    }
    return 0;
}

/*
 * Handle notices -- so far just used for logging
 * :nick!user@host NOTICE <target> :<message> */
CMDFUNC(notice_incoming)
{
    if (log)
    {
        ircaddr_simple ia(argv[1]);
        log->write( logfile::EVENT_NOTICE | ischan(argv[3]) ? logfile::EVENT_PUBLIC : logfile::EVENT_PRIVATE,
                    &ia, argv[3],
                    no_leading(gettok_ptr(argv[0], ' ', 4)), NULL);
    }
    return 0;
}

CMDFUNC(topic_incoming)
{
    if (log)
    {
        ircaddr_simple ia(argv[1]);
        log->write( logfile::EVENT_TOPIC | logfile::EVENT_PUBLIC,
                    &ia, argv[3],
                    no_leading(gettok_ptr(argv[0], ' ', 4)),
                    NULL);
    }
    return 0;
}

CMDFUNC(quit_incoming)
{
    if (log)
    {
        ircaddr_simple ia(argv[1]);
        log->write( logfile::EVENT_QUIT | logfile::EVENT_PUBLIC,
            &ia, NULL,
            no_leading(gettok_ptr(argv[0], ' ', 3)),
            NULL);
    }
    return 0;
}

/* Handles incoming server pings -- example:
 * PING :irc.lagged.org */
CMDFUNC(ping_incoming)
{
	char * args = gettok_ptr(argv[0], ' ', 2);
    server->printf("PONG %s\r\n", args);
    DEBUG("Replying to server ping: %s\n", args);
    return 0;
}

/* if/when we start pinging the IRC server, this will be
 * the handler */
CMDFUNC(pong_incoming)
{
    DEBUG("Got a server PONG: %s\n", argv[1]);
    return 0;
}

/* get ERROR strings when we are detached and stuff
 */
CMDFUNC(error_incoming)
{
    if (log)
    {
        log->dump(argv[0]);
        log->dump("\n");
    }
    return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1