Logo Search packages:      
Sourcecode: iacd version File versions  Download package

irc.c

#define _GNU_SOURCE

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>
#include <sys/time.h>
#include <unistd.h>

#include "types.h"
#include "irc.h"
#include "log.h"
#include "util.h"
#include "net.h"
#include "timer.h"
#include "servlink.h"
#include "conf.h"
#include "dump.h"
#include "script.h"

rl_htable_t *iac_users, *iac_channels;
extern char *iac_motd;

extern iac_server_configuration_t iac_cfg;
extern iac_server_status_t iac_status;
extern iac_cmd_usage_t iac_cmd_usage;

extern rl_list_t *iac_client_conns;
extern rl_list_t *iac_server_conns;
extern long iac_starttime;

static void iac_irc_inform_away( iac_client_conn_t *src );
static void iac_irc_send_pings(const char *unused);

iac_client_conn_t *
p_iac_get_nick( const char *nick )
{
    iac_client_conn_t *cc;

    cc = (iac_client_conn_t *) rl_hashtable_get (iac_users, nick) ;

    if ( !cc )
        return NULL;

    if ( cc->flags & IAC_OK )
        return cc;
    else
        return NULL;
}

#define IAC_SPLIT(dst,src)     \
    dst = strchr ( src, ' ' ); \
    if ( dst ) {               \
        *dst = '\0';           \
        dst++;                 \
    } else


int
iac_irc_valid_nick(const char *nick)
{
    int i;


    if (!nick)
        return 0;

    if ( nick[0] < 65 || nick[0] > 125 )
        return 0;

    for (i=1; nick[i] != '\0'; i++)
    {
        if ((nick[i] < 30 && nick[i] != 45) ||
            (nick[i] > 57 && nick[i] < 65)  ||
            nick[i] > 125)
        {
            return 0;
        }
    }

    return 1;
}


void
iac_irc_nick(iac_client_conn_t *src)
{
    /*
     * NICK <nickname>
     */

    char *tnick = NULL;
    char *tnick2 = NULL;
      rl_token_list_t *tl;
    iac_channel_link_t *cl;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.nick++;
#endif

    tl = rl_token_split(src->msg, " ");

      if (tl->rtl_ntokens != 2) {

            iac_net_client_send(src, ":%s 431 %s :No nickname given\r\n",
                  iac_cfg.servername,
            (src->nick) ? src->nick : "*");

      } else {

            char *nick;

            rl_token_get_next(tl);              /* NICK         */
            nick = rl_token_get_next(tl);       /* <nickname>   */

        if ( strlen(nick) > IAC_NICKLEN )
            nick[IAC_NICKLEN] = '\0';

        if (*nick == ':')
            nick = &nick[1];

        if ( !iac_irc_valid_nick(nick) )
        {
            iac_net_client_send(src, ":%s 432 * %s :Erroneous Nickname\r\n",
                iac_cfg.servername,
                (nick) ? nick : "*");

            goto invalid_nick;
        }


        /*
         * build a lower case version of the nick
         */
        tnick = strdup(nick);
        ti_tolower(tnick);


        /*
         * check if the nick is already in use. there is a
         * special case were the same person wants to change
         * the case of the nick. like from Foo to foo.
         */
        if ( ( rl_hashtable_get (iac_users, tnick) ) && 
             ( rl_stricmp (src->nick, nick) != 0   ) ) {

            iac_net_client_send(src, ":%s 433 %s %s :Nickname " \
                "is already in use.\r\n",
                iac_cfg.servername,
                src->nick ? src->nick : "*",
                nick);

#ifdef HAVE_SCRIPTS

        } else if ( iac_script_check_nick ( tnick ) ) {

            iac_net_client_send(src, ":%s 433 %s %s :Nickname " \
                "is already in use.\r\n",
                iac_cfg.servername,
                src->nick ? src->nick : "*",
                nick);

#endif

        } else {

            /*
             * Avoid useless nick change
             */
            if ( src->nick )
                if (strcmp(src->nick, nick) == 0)
                    goto invalid_nick;

            /*
             * remove the user from the nick hashtable if
             * it does exist. it will be added again below
             */
            if (src->flags & IAC_NICKSENT && src->nick) {

                tnick2 = strdup(src->nick);
                ti_tolower(tnick2);

                rl_hashtable_rem(iac_users, tnick2);
                iac_log(IAC_DEBUG, "[   ] C Removed nick %s from nick " \
                    "hashtable.\n", (tnick2) ? tnick2 : "(null)");
                RL_FREE(tnick2);

            }

            if (src->nick)
                RL_FREE(src->nick);

            src->nick = strdup(nick);

            /*
             * (re)add nick to nick hashtable
             */
            rl_hashtable_add(iac_users, tnick, (void *) src);

            iac_log(IAC_DEBUG, "[   ] C Added nick %s to nick hashtable\n",
                (tnick) ? tnick : "(null)");

            /*
             * if it was a nick change instead of the registration
             * NICK command, a notice to all channels and the owner
             * must be send
             */
            if ( src->flags & IAC_NICKSENT ) {

                /*
                 * notice owner
                 */
                iac_net_client_send(src, ":%s NICK %s\r\n",
                    (src->hostmask) ? src->hostmask : "(null)",
                    (src->nick) ? src->nick : "(null)");

                /*
                 * notice all channels
                 */
                cl = src->channels;
                while (cl) {
                    iac_channel_t *chan = cl->link;

                    iac_irc_channel_bcast(src, chan, ":%s NICK %s\r\n",
                        (src->hostmask) ? src->hostmask : "(null)",
                        (src->nick) ? src->nick : "(null)");

                    cl = cl->next;
                }

                iac_server_bcast(":%s NICK %s\r\n",
                    (src->hostmask) ? src->hostmask : "*",
                    (src->nick) ? src->nick : "*");

            } else {

                /*
                 * update lastlog entry
                 */
                if ( iac_cfg.flags & IAC_LASTLOG ) {

                    iac_lastlog_t *ll = iac_cfg.lastlog;

                    while (ll) {

                        /*
                         * maybe not very safe but fast 8)
                         */
                        if (ll->signon == src->signon &&
                            ll->port == src->port) {

                            ll->nick = strdup(src->nick);
                        }

                        ll = ll->next;
                    }
                } /* end lastlog stuff */
               
                src->flags &= ~(IAC_PONG);
                iac_irc_send_ping(src);
            }

            /*
             * NICK was sent (used for a complete logon)
             */
            src->flags |= IAC_NICKSENT;
           
            IAC_GENHOSTMASK(src);
        }

invalid_nick:

        if (tnick)
            RL_FREE(tnick);
      }

      rl_token_del_token_list(tl);
}

void
iac_irc_user(iac_client_conn_t *src)
{
      /*
       * USER <user> <usermode> <unused> :<real name>
       */
      rl_token_list_t *tl;

    /*
     * USER is allowd once
     */
    if ( src->flags & IAC_USERSENT || src->flags & IAC_OK )
        return;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.user++;
#endif
    
    tl = rl_token_split(src->msg, "    ");

      if (tl->rtl_ntokens != 5) {

            iac_net_client_send(src, ":%s 461 %s USER :Not enough parameters\r\n",
                  iac_cfg.servername,
            (src->nick) ? src->nick : "*");

      } else {
            char *user, *mode, *ircname;

        if (src->flags & IAC_USERSENT)
            goto invalid_user;
      
            rl_token_get_next(tl);              /* USER        */
            user = rl_token_get_next(tl);       /* <user>      */
            mode = rl_token_get_next(tl);       /* <mode>      */
            rl_token_get_next(tl);              /* unused      */
            ircname = rl_token_get_next(tl);    /* <real name> */

        if ( strlen(user) > IAC_USERLEN )
            user[IAC_USERLEN] = '\0';

        if ( strlen(ircname) > IAC_IRCNAMELEN )
            ircname[IAC_IRCNAMELEN] = '\0';

        if (src->user)
            RL_FREE(src->user);
        src->user = strdup(user);

        src->flags |= IAC_USERSENT;

        if ( ircname[0] == ':')
            ircname = &ircname[1];

        if (src->ircname)
            RL_FREE(src->ircname);

        src->ircname = strdup(ircname);

        /*
         * update lastlog entry
         */
        if ( iac_cfg.flags & IAC_LASTLOG ) {

            iac_lastlog_t *ll = iac_cfg.lastlog;

            while (ll) {

                /*
                 * maybe not very safe but fast 8)
                 */
                if (ll->signon == src->signon &&
                    ll->port == src->port) {

                    ll->username = strdup(user);
                    ll->ircname = strdup(ircname);
                }

                ll = ll->next;
            }
        } /* end lastlog stuff */

        /*
         * local connection, therefore local servername and serverinfo
         */
        src->server = iac_cfg.servername;
        src->server_info = iac_cfg.info;

        IAC_GENHOSTMASK(src);
      }

invalid_user:

      rl_token_del_token_list(tl);
}

void
iac_irc_ping(iac_client_conn_t *src)
{
    /*
     * PING [server1 [server2]] :<seq>
     *
     * this server stuff is kinda stupid in my eyes, well maybe some dude
     * willa actually implement it correctly. Myself is now rewritten this
     * handler by just sending a dummy answer to the client back in case
     * server is not ommited.
     */

    char *reply = strchr(src->msg, ':');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.ping++;
#endif

    if (reply) {
        iac_net_client_send(src, ":%s PONG %s %s\r\n",
            iac_cfg.servername, iac_cfg.servername, reply);
    } else {

        /*
         * don't know what to do else
         * some server send "No origin specified", stupid idea in my eyes
         */
        iac_net_client_send(src, ":%s PONG %s\r\n",
            iac_cfg.servername, iac_cfg.servername);
    }
}

void
iac_irc_msg(iac_client_conn_t *src)
{
    /*
     * PRIVMSG <target> :<msg>
     */

    char *type;
    char *target;
    char *text;
      char b;
      iac_channel_t *chan;
      iac_client_conn_t *dst;
    int len;

#ifdef IAC_CMD_USAGE
    /*
     * NOTICE will count as privmsg as well
     */
    iac_cmd_usage.privmsg++;
#endif

    type = src->msg;

    IAC_SPLIT ( target, src->msg )
    {
        iac_net_client_send(src, ":%s 411 %s :No recipient given\r\n",
            iac_cfg.servername,
            src->nick);
        return;
    }

    IAC_SPLIT ( text, target )
    {
        iac_net_client_send(src, ":%s 412 %s :No text to send\r\n",
            iac_cfg.servername,
            src->nick);
        return;
    }

    if ( strlen(target) > IAC_TARGETLEN )
        target[IAC_TARGETLEN] = '\0';

    ti_tolower(target);

    len = strlen(text); 
    b = 0;

    if ( !rl_stricmp(type, "privmsg") )
        type = "PRIVMSG";
    else
        type = "NOTICE";


    /*
     * You wouldn't believe how many irc clients do not split too
     * long messages into multiple ones but do not accept too long
     * messages in reply ;) all bakas, we have to do the work.
     */
    while ( len > 0 ) {

        /*
         * check if text is too long, mark string as end of
         * string and keep the character replaced.
         */
        if (len > IAC_TEXTLEN) {
            b = text[IAC_TEXTLEN];
            text[IAC_TEXTLEN] = '\0';
        }

        /*
         * send actual message
         */
        if ((chan = rl_hashtable_get(iac_channels, target))) {

            /*
             * target is a channel. broadcast it localy.
             */
            iac_irc_channel_bcast(src, chan, ":%s %s %s %s%s\r\n",
                src->hostmask,
                type,
                target,
                (text[0] == ':') ? "" : ":",
                text);

            /*
             * broadcast to all servers if the message is from
             * a local user.
             */
            if ( src->flags & IAC_LOCAL ) {
                iac_server_bcast(":%s %s %s %s\r\n",
                    src->hostmask,
                    type,
                    (target) ? target : "-",
                    text);
            }
            

        } else if ((dst = p_iac_get_nick( target ))) {

            /*
             * target is a nickname. Send message to client if the
             * nick is local or broadcast it to find the nick on other
             * servers
             */
            if ( dst->flags & IAC_LOCAL ) {
                iac_net_client_send(dst, ":%s %s %s %s\r\n",
                    (src->hostmask) ? src->hostmask : "(null)",
                    type,
                    (dst->nick) ? dst->nick : "*",
                    (text) ? text : "(null)");
            } else {
                iac_server_bcast(":%s %s %s %s\r\n",
                    (src->hostmask) ? src->hostmask : "(null)",
                    type,
                    (dst->nick) ? dst->nick : "*",
                    (text) ? text : "(null)");
            }

        }  else {

#ifdef HAVE_SCRIPTS

            iac_script_t *s;

            for ( s = iac_cfg.scripts; s ; s = s->next )
            {
                if ( ! rl_stricmp ( target, s->nick ) )
                {
                    iac_log ( IAC_DEBUG, "[   ] Calling script %s\n", s->cmd );

                    if ( *text == ':' )
                        text++;

                    iac_script_call ( src, s, text );
                    
                    goto out;
                }
            }
#endif
            

            /*
             * unknown nick, don't broadcast this stuff
             */
            iac_net_client_send(src, ":%s 401 %s %s :No such nick\r\n",
                iac_cfg.servername,
                (src->nick) ? src->nick : "*",
                 target);
            break;
        }
#ifdef HAVE_SCRIPTS
out:
#endif

        /*
         * restore the backed up character if the message was splitted
         */
        if ( b ) {
            text[IAC_TEXTLEN] = b;
            text = &text[IAC_TEXTLEN];
            b = 0;
        }

        /*
         * shrink len by the message length just sent, will be 0
         * if no characters are left to be sent.
         */
        len -= IAC_TEXTLEN;
    }
}

int
iac_irc_user_of_channel(iac_client_conn_t *user, const char *channame)
{
    iac_channel_link_t *cl;

    cl = user->channels;
    while (cl) {
        iac_channel_t *c = cl->link;
        if (!rl_stricmp(c->name, channame))
            return 1;
        cl = cl->next;
    }

    return 0;
}

int
iac_irc_friend(iac_client_conn_t *user, iac_client_conn_t *tocheck)
{
    iac_channel_link_t *cl;

    cl = user->channels;
    while (cl) {
        iac_channel_t *c = cl->link;
        if ( iac_irc_user_of_channel(tocheck, c->name) )
            return 1;
        cl = cl->next;
    }

    return 0;
}


void
iac_irc_destroy_channel(iac_channel_t *chan)
{
    int i;
    iac_user_link_t *ul, *next;

    iac_log(IAC_DEBUG, "[   ] - Destroying channel %s\n",
        chan->name);

    rl_hashtable_rem(iac_channels, chan->name);

    ul = chan->users;
    while (ul) {
        next = ul->next;
        RL_FREE(ul);
        ul = next;
    }
    
    RL_FREE(chan->name);

    for (i=0; i < IAC_TLIST_MAX; i++) {
        RL_FREE(chan->topic[i]);
        RL_FREE(chan->topic_owner[i]);
    }

    RL_FREE(chan);

    iac_status.nr_channels--;
}

int
iac_irc_create_channel(iac_client_conn_t *src, const char *name)
{
      iac_channel_t *chan;
    char *tchan;
    
    if ( iac_status.nr_channels > IAC_MAX_CHANNELS ) {

        iac_irc_notice_ops("Warning: Max channel limit of %d reached!\r\n",
            IAC_MAX_CHANNELS);

        return 1;
    }

    iac_status.nr_channels++;

    /*
     * allocate channel handle
     */
      if ( !( chan = (iac_channel_t *) malloc( sizeof(iac_channel_t) ) ) )
            iac_log(IAC_ERR, "[EEE] - Unable to allocate memory for channel handle\n");

      memset(chan, 0, sizeof(iac_channel_t));

    chan->name = strdup(name);
    chan->owner = strdup(src->nick);

    /*
     * build lower case channel name
     */
    tchan = strdup(name);
    ti_tolower(tchan);

    /*
     * add channel to channel hashtable
     */
      rl_hashtable_add(iac_channels, tchan, (void *) chan);

    RL_FREE(tchan);

      iac_log(IAC_DEBUG, "[   ] - New channel: %s\n",
        chan->name);

    return 0;
}

void
iac_irc_channel_bcast(iac_client_conn_t *src, iac_channel_t *chan, const char *fmt, ...)
{
      va_list args;
    char *msg = NULL;
      iac_client_conn_t *cc;
    iac_user_link_t *ul;

    if ( !iac_irc_user_of_channel(src, chan->name) )
        return;


#ifdef IAC_COUNT_IDLETIME
    src->last_action = time(0);
#endif

    /*
     * build message string
     */
      va_start(args, fmt);
      vasprintf(&msg, fmt, args);
      va_end(args);


    /*
     * send to all people in the channel
     */
    ul = chan->users;
      while (ul) {

            cc = ul->link;

        /*
         * do not send messages to the sender
         */
        if (cc != src)
            iac_net_client_send(cc, "%s", msg);

        ul = ul->next;
      }

    RL_FREE(msg);
}

void
iac_irc_join(iac_client_conn_t *src)
{
    /*
     * JOIN <channel1>[,| ][<channel2>][,| ][channel3]
     */

      iac_channel_t *chan;
    iac_client_conn_t *cc;
    iac_user_link_t *ul;
    iac_channel_link_t *cl;
      int found, i, len, n, count;
    char *msg = NULL, *name = NULL;
   
    char *channels = strchr(src->msg, ' ');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.join++;
#endif

    if (!channels) {
            iac_net_client_send(src, ":%s 461 %s JOIN :Not enough parameters\r\n",
                  iac_cfg.servername,
            src->nick);
        return;
    }

    channels++;

    if (*channels == ':')
        channels++;

    if (*channels == '\0')
        return;

    iac_log(IAC_DEBUG, "[   ] * JOIN: %s\n",
        (channels) ? channels : "(null)");

    /*
     * set to channels, the whole string, name will always point
     * to the current channel name being extraced
     */
    name = channels;

    len = strlen(channels);

    for (i=0; i <= len; i++) {

        if ( ( channels[i] == ' ' ) ||
             ( channels[i] == ',' ) ||
             ( channels[i] == '\0') ) {

            channels[i] = '\0';
            i++;

            if (strlen(name) > IAC_CHANNAMELEN)
                name[IAC_CHANNAMELEN] = '\0';

            /*
             * channelname must begin with a &,+,# or ! character
             */
            if ( ! (name[0] == '&' || name[0] == '#' ||
                    name[0] == '+' || name[0] == '!') ) {

                iac_net_client_send(src, ":%s 403 %s %s :No such channel\r\n",
                    iac_cfg.servername,
                    src->nick,
                    name);

                name = &channels[i];
                continue;
            }

            /*
             * check for invalid characters in channel name
             */
            for (n=0; name[n] != '\0'; n++) {
                if (!isprint((int) name[n])) {

                    iac_net_client_send(src, ":%s 403 %s %s :No such " \
                        "channel\r\n", iac_cfg.servername,
                        src->nick,
                        name);
                    
                    name = &channels[i];
                    continue;
                }
            }

          /*
           * check if the channel is in the list of authorized channels, 
           * if configured
           */

          if (iac_cfg.channels) {
            if(!rl_hashtable_get(iac_cfg.channels, name)) {
                iac_net_client_send(src, ":%s 403 %s %s :No such " \
                  "channel\r\n", iac_cfg.servername,
                  src->nick,
                  name);

                name = &channels[i];
                continue;
            }
          }

            ti_tolower(name);

            /*
             * count channels you are in
             */
            count = 0;
            cl = src->channels;
            while (cl) {
                count++;
                cl = cl->next;
            }

            /*
             * limit max channels you can be part of
             */
            if ( count > IAC_MAX_CHANNEL_JOINS ) {

                /*
                 * no continue, because all further joins would fail
                 */
                break;
            }
           
            /*
             * create channel if it doesnt exist yet
             */
            if (!(chan = (iac_channel_t *) rl_hashtable_get(iac_channels, name))) {
                if (iac_irc_create_channel(src, name))
                    break;
                chan = (iac_channel_t *) rl_hashtable_get(iac_channels, name);
            }

            /*
             * check if user already joined this channel
             */
            found = 0;
            ul = chan->users;
            while (ul) {

                cc = ul->link;

                if (!rl_stricmp(cc->nick, src->nick))
                    found = 1;

                ul = ul->next;
            }

            /*
             * join if user hasnt join this channel yet
             */
            if (!found) {

                iac_user_link_t *ul;
                iac_channel_link_t *cl;

                /*
                 * add to list of users in the channel
                 */
                if ( !(ul = (iac_user_link_t *) malloc(sizeof(iac_user_link_t))) )
                    iac_log(IAC_ERR, "Out of memory!\n");
                memset(ul, 0, sizeof(iac_user_link_t));

                ul->link = src;
                ul->next = chan->users;
                chan->users = ul;

                /*
                 * add channel to list of channesl joined
                 */
                if ( !(cl = (iac_channel_link_t *) malloc(sizeof(iac_channel_link_t))) )
                    iac_log(IAC_ERR, "Out of memory!\n");
                memset(cl, 0, sizeof(iac_channel_link_t));

                cl->link = chan;
                cl->next = src->channels;
                src->channels = cl;

                /*
                 * notice all others in the channel
                 */
                iac_irc_channel_bcast(src, chan, ":%s JOIN %s\r\n",
                    src->hostmask, chan->name);

                /*
                 * do some stuff only if this is the local server of the
                 * client. this avoids multiple channel join message generated
                 * different servers
                 */
                if (src->flags & IAC_LOCAL) {

                    /*
                     * send a JOIN message to the sender of the message
                     */
                    iac_net_client_send(src, ":%s JOIN :%s\r\n",
                        src->hostmask, chan->name);

                    /*
                     * send topic to client if one is set
                     */
                    if (chan->topic[chan->itopic]) {
                        iac_net_client_send(src, ":%s 332 %s %s :%s\r\n",
                            iac_cfg.servername, src->nick, chan->name,
                            chan->topic[chan->itopic]);

#ifndef IAC_RFC_STRICT
                        /*
                         * send topic owner
                         */
                        if (chan->topic_owner[chan->itopic]) {

                            /*
                             * Oh dear this is not in the RFC, RFC suck
                             * anyway.
                             *
                             * :<hostmask> 333 <source nick> <channame>
                             *                 <nick> <unixtime>
                             */
                            iac_net_client_send(src, ":%s 333 %s %s %s " \
                                "%ld\r\n", iac_cfg.servername,
                                src->nick,
                                chan->name,
                                chan->topic_owner[chan->itopic],
                                chan->topic_time[chan->itopic]);
                        }
#endif

                    }
                   
                    /*
                     * send list of users currently in the channel in
                     * NAMES format
                     * 
                     * fixed to send only 10 names per line - smoli
                     */
                    for (ul = chan->users; ul;) {
                      
                        int x;
                      
                        for (x = 0; x < 10 && ul; x++, ul = ul->next) {
                            iac_client_conn_t *cc = ul->link;
                          
                            iac_astrcat(&msg, cc->nick);
                            iac_astrcat(&msg, " ");
                        }
                      
                        if (x) {
                            iac_net_client_send(src, ":%s 353 %s = %s :%s\r\n",
                                                iac_cfg.servername,
                                                src->nick,
                                                chan->name,
                                                msg);
                            RL_FREE(msg);
                        }
                    }

                    iac_net_client_send(src, ":%s 366 %s %s :End of NAMES list\r\n",
                        iac_cfg.servername,
                        src->nick,
                        chan->name);

                    /*
                     * broadcast the join to all servers
                     */
                    if ( src->flags & IAC_LOCAL ) {
                        iac_server_bcast(":%s JOIN %s\r\n",
                            src->hostmask,
                            chan->name);
                    }
                }

                /*
                 * log this event
                 */
                iac_log(IAC_DEBUG, "[   ] C Channel Join: %s (%s:%d) " \
                    "joined %s\n",
                    src->nick,
                    src->host,
                    src->port,
                    chan->name);

                /*
                 * set name to next channel in channels string
                 */
                name = &channels[i];

            }
        }
    }
}

void
iac_irc_topic(iac_client_conn_t *src)
{
    /*
     * TOPIC <channel name> :<topic>
     */

    char *chan_name;
    char *topic;
    char *nt = NULL;
      iac_channel_t *chan;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.topic++;
#endif

#ifdef IAC_COUNT_IDLETIME
    src->last_action = time(0);
#endif

    IAC_SPLIT ( chan_name, src->msg )
    {
            iac_net_client_send(src, ":%s 461 %s TOPIC :Not enough parameters\r\n",
                  iac_cfg.servername,
            (src->nick) ? src->nick : "*");
        return;
    }

    IAC_SPLIT ( topic, chan_name )
    {
        if ( strlen ( chan_name ) > IAC_CHANNAMELEN )
            chan_name[IAC_CHANNAMELEN] = '\0';

            ti_tolower(chan_name);

        if ( (chan = (iac_channel_t *) rl_hashtable_get(iac_channels, chan_name)) &&
             iac_irc_user_of_channel(src, chan_name) ) {

            /*
             * send topic to client if one is set
             */
            if (chan->topic[chan->itopic]) {
                iac_net_client_send(src, ":%s 332 %s %s :%s\r\n",
                    iac_cfg.servername, src->nick, chan->name,
                    chan->topic[chan->itopic]);
            }

        } else {

                  iac_net_client_send(src, ":%s 403 %s %s :No such channel\r\n",
                        iac_cfg.servername,
                src->nick,
                chan_name);
        }

        return;
    }

    if ( strlen ( chan_name ) > IAC_CHANNAMELEN )
        chan_name[IAC_CHANNAMELEN] = '\0';

    ti_tolower(chan_name);

    if ( !(chan = rl_hashtable_get(iac_channels, chan_name)) ||
         !iac_irc_user_of_channel(src, chan_name) ) {

        iac_net_client_send(src, ":%s 403 %s %s :No such channel\r\n",
            iac_cfg.servername, src->nick, chan_name);

    } else {

        if (topic[0] == ':') {
            topic = &topic[1];

            iac_log(IAC_DEBUG, "[   ] * Topic Change %s to %s by %s\n",
                chan_name, topic, src->nick);

            if (topic[0] == '+' || topic[0] == '|') {
                /* make sure we have already a topic set */
                if (chan->topic[chan->itopic]) {
                              asprintf(&nt, "%s | %s", (topic+1), chan->topic[chan->itopic]);
                        } else {
                            topic = &topic[1];
                        }
                  }

            /*
             * rfc doesn't say anything about a topic history
             * so skip having a list and just overwrite the old
             * topic
             */
#ifndef IAC_RFC_STRICT
            if (chan->itopic == (IAC_TLIST_MAX - 1))
                chan->itopic = 0;
            else
                chan->itopic++;
#endif

            if (chan->topic[chan->itopic])
                RL_FREE(chan->topic[chan->itopic]);

#ifndef IAC_RFC_STRICT
            if (chan->topic_owner[chan->itopic])
                RL_FREE(chan->topic_owner[chan->itopic]);

            chan->topic_time[chan->itopic] = time(0);
#endif

            /*
             * unset the topic if no string is given
             */
            if (topic[0] == '\0')
                chan->topic[chan->itopic] = strdup("");
            else if (topic[0] == '+' || topic[0] == '|')
                chan->topic[chan->itopic] = nt;
            else
                chan->topic[chan->itopic] = strdup(topic);

#ifndef IAC_RFC_STRICT
            chan->topic_owner[chan->itopic] = strdup(src->nick);
#endif
            
            /*
             * broadcast in local channel
             */
            iac_irc_channel_bcast(src, chan, ":%s TOPIC %s :%s\r\n",
                src->hostmask, chan->name, chan->topic[chan->itopic]);

            /*
             * send to user that changed the topic
             */
            iac_net_client_send(src, ":%s TOPIC %s :%s\r\n",
                src->hostmask, chan->name, chan->topic[chan->itopic]);

            if ( src->flags & IAC_LOCAL ) {
                /*
                 * broadcast topic change to all servers
                 */
                iac_server_bcast(":%s TOPIC %s :%s\r\n", src->hostmask,
                    chan->name, chan->topic[chan->itopic]);
            }
        }
    }


}

void
iac_irc_whois(iac_client_conn_t *src)
{
    /*
     * WHOIS <target>
     */

    iac_client_conn_t *cc;
    char *nick = strchr(src->msg, ' ');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.whois++;
#endif

    if (!nick) {
            iac_net_client_send(src, ":%s 461 %s WHOIS :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);
        return;
    }

    nick++;

    if (*nick == ':')
        nick++;

    if (*nick == '\0')
        return;

    if ( strlen(nick) > IAC_NICKLEN )
        nick[IAC_NICKLEN] = '\0';

    ti_tolower(nick);

    if ((cc = p_iac_get_nick( nick ))) {

        /*
         * Paranoia mode
         */
        if ( iac_cfg.flags & IAC_PARANOIA ) {
            if ( !iac_irc_friend(cc, src) )
                goto iac_irc_whois_not_found;
        }
        
        /*
         * :<servermask> 311 <source nick> <nick> <user> <host> * :<ircname>
         */
        iac_net_client_send(src, ":%s 311 %s %s %s %s * :%s\r\n",
            iac_cfg.servername, src->nick, cc->nick, cc->user, cc->host, cc->ircname);

        /*
         * :<servermask> 312 <source nick> <nick> <server> <server info>
         */
        iac_net_client_send(src, ":%s 312 %s %s %s :%s\r\n",
            iac_cfg.servername, src->nick, cc->nick,
            cc->server ? cc->server : "none",
            cc->server_info ? cc->server_info : "none");

        if ( cc->away ) {
            /*
             * :<servermask> 301 <source nick> <nick> :<away message>
             */
            iac_net_client_send(src, ":%s 301 %s %s :%s\r\n",
                iac_cfg.servername, src->nick, cc->nick, cc->away);
        }

#ifdef IAC_COUNT_IDLETIME

        /*
         * :<servermask> 317 <source nick> <nick> <idle> <signon> :seconds
         *               idle, signon time
         */
        iac_net_client_send(src, ":%s 317 %s %s %ld %ld :seconds idle, " \
            "signon time\r\n", iac_cfg.servername, src->nick, cc->nick,
            (time(0) - cc->last_action), cc->signon);
#endif

        /*
         * :<servermask> 318 <source nick> <nick> :End of WHOIS list
         */
        iac_net_client_send(src, ":%s 318 %s %s :End of WHOIS list\r\n",
            iac_cfg.servername, src->nick, cc->nick);
    }
    else
    {
iac_irc_whois_not_found:
        iac_net_client_send(src, ":%s 401 %s %s :No such nick\r\n",
            iac_cfg.servername, src->nick, nick);
    }
}

void
iac_irc_whowas(iac_client_conn_t *src)
{
    /*
     * WHOWAS <nick> [count]
     */

    char *nick = strchr(src->msg, ' ');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.whowas++;
#endif

    if (!nick) {
            iac_net_client_send(src, ":%s 461 %s WHOWAS :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);
        return;
    }

    nick++;

    if (*nick == ':')
        nick++;

    if (*nick == '\0')
        return;

    if ( strlen(nick) > IAC_NICKLEN )
        nick[IAC_NICKLEN] = '\0';

    /*
     * :<prefix> 406 <source nick> <nick> :There was no such nickname
     */
    iac_net_client_send(src, ":%s 406 %s %s :There was no such nickname\r\n",
        iac_cfg.servername, src->nick, nick);

    /*
     * :<prefix> 369 <source nick> <nick> :End of WHOWAS
     */
    iac_net_client_send(src, ":%s 369 %s %s :End of WHOWAS\r\n",
        iac_cfg.servername, src->nick, nick);
}

void
iac_irc_who(iac_client_conn_t *src)
{
    /*
     * WHO <target>
     */

    char *target = strchr(src->msg, ' ');
    iac_channel_t *chan;
    iac_client_conn_t *cc;
    iac_user_link_t *ul;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.who++;
#endif

    if (!target) {
            iac_net_client_send(src, ":%s 461 %s WHO :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);
        return;
    }

    target++;

    if (*target == ':')
        target++;

    if (*target == '\0')
        return;

    if ( strlen(target) > IAC_TARGETLEN )
        target[IAC_TARGETLEN] = '\0';

    ti_tolower(target);

    /*
     * check if target is a channel
     */
    if ( (chan = rl_hashtable_get(iac_channels, target)) &&
         (iac_irc_user_of_channel(src, target)) ) {

        ul = chan->users;
        while (ul) {

            cc = ul->link;

            /*
             * :<servermask> 352 <source nick> <channel name> <user> <host>
             * <server> <nick> H :0 <ircname>
             */
            iac_net_client_send(src, ":%s 352 %s %s %s %s %s %s H :%d %s\r\n",
                iac_cfg.servername,
                src->nick,
                chan->name,
                cc->user,
                cc->host,
                cc->server,
                cc->nick,
                cc->hopcount,
                cc->ircname);

            ul = ul->next;
        }

        /*
         * :<servermask> 315 <source nick> <channel name> :End of WHO list
         */
        iac_net_client_send(src, ":%s 315 %s %s :End of /WHO list.\r\n",
            iac_cfg.servername, src->nick, chan->name);

    } else if ( (cc = p_iac_get_nick( target ) )) {

        if ( iac_cfg.flags & IAC_PARANOIA ) {
            if ( !iac_irc_friend(cc, src) )
                goto iac_irc_who_not_found;
        }

        /*
         * :<servermask> 352 <sourec nick> * <user> <host> <server>
         *                   <nick> H :<hopcount> <ircname>
         */
        iac_net_client_send(src, ":%s 352 %s * %s %s %s %s H :%d %s\r\n",
            iac_cfg.servername,
            src->nick,
            cc->user,
            cc->host,
            cc->server,
            cc->nick,
            cc->hopcount,
            cc->ircname);

        /*
         * :<servermask> 315 <source nick> <nick> :End of WHO list
         */
        iac_net_client_send(src, ":%s 315 %s %s :End of /WHO list.\r\n",
            iac_cfg.servername, src->nick, (cc->nick) ? cc->nick : "*");

    } else {
iac_irc_who_not_found:
        iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
            iac_cfg.servername, src->nick, target);
    }
}

void
p_irc_channel_remove_user(iac_channel_t *chan, iac_client_conn_t *cc)
{
    iac_user_link_t *ul, *prev;

    prev = NULL;
    ul = chan->users;

    while ( ul ) {
        if ( ul->link == cc ) {
            if (prev)
                prev->next = ul->next;
            else
                chan->users = ul->next;
            RL_FREE(ul);
            break;
        }
        prev = ul;
        ul = ul->next;
    }
}

void
p_irc_user_remove_channel(iac_client_conn_t *cc, iac_channel_t *chan)
{
    iac_channel_link_t *cl, *prev;

    prev = NULL;
    cl = cc->channels;
    while (cl) {
        if (cl->link == chan) {
            if (prev)
                prev->next = cl->next;
            else
                cc->channels = cl->next;
            RL_FREE(cl);
            break;
        }
        prev = cl;
        cl = cl->next;
    }
}

void
iac_irc_part(iac_client_conn_t *src)
{
    /*
     * PART <channel> [:part message]
     */

    rl_token_list_t *tl;
    char *channel, *quitmsg;
    iac_channel_t *chan;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.part++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if (tl->rtl_ntokens < 2) {

            iac_net_client_send(src, ":%s 461 %s PART :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);

    } else {

        rl_token_get_next(tl);                   /* PART      */
        channel = rl_token_get_next(tl);         /* <channel> */

        if ( strlen(channel) > IAC_CHANNAMELEN )
            channel[IAC_CHANNAMELEN] = '\0';

        ti_tolower(channel);

        if ( tl->rtl_ntokens > 2 ) {

            quitmsg = rl_token_get_next(tl);     /* <part message> */

            if ( strlen(quitmsg) > IAC_QUITMSGLEN )
                quitmsg[IAC_QUITMSGLEN] = '\0';

            if (strlen(quitmsg) > 1)
                quitmsg = &quitmsg[1];
            else
                quitmsg = "No reason";

        } else {
            quitmsg = "No reason";
        }

        if ((chan = rl_hashtable_get(iac_channels, channel))) {

            /*
             * send PART message to all in the channel
             * :<source hostmask> PART <channel name> :<quit message>
             */
            iac_irc_channel_bcast(src, chan, ":%s PART %s :%s\r\n",
                src->hostmask, chan->name, quitmsg);

            /*
             * send PART message to the sender
             * :<source hostmask> PART <channel name> :<quit message>
             */
            iac_net_client_send(src, ":%s PART %s :%s\r\n",
                src->hostmask, chan->name, quitmsg);

            if ( src->flags & IAC_LOCAL ) {

                /*
                 * send PART message to all other servers
                 */
                iac_server_bcast(":%s PART %s :%s\r\n", src->hostmask,
                    chan->name, quitmsg);
            }

            /*
             * remove user from channels user list
             */
            p_irc_channel_remove_user(chan, src);

            if (!chan->users)
                iac_irc_destroy_channel(chan);

            /*
             * remove channel from users channels list
             */
            p_irc_user_remove_channel(src, chan);
            
        } else {

            iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
                iac_cfg.servername, src->nick, channel);
        }
    }

    rl_token_del_token_list(tl);
}

/*
 * quit a user, this is not a parsing function
 */
void
iac_irc_real_quit(iac_client_conn_t *src)
{
    iac_channel_t *chan;
    iac_channel_link_t *cl;

    cl = src->channels;
    while (cl) {

        chan = cl->link;

        /*
         * :<source hostmask> QUIT :<reason>
         */
        iac_irc_channel_bcast(src, chan, ":%s QUIT :%s\r\n",
            (src->hostmask) ? src->hostmask : "-",
            src->quitmsg);

        /*
         * remove user from channels user list
         */
        p_irc_channel_remove_user(chan, src);

        if (!chan->users)
            iac_irc_destroy_channel(chan);

        cl = cl->next;
    }

    /*
     * broadcast the quit to all servers
     */
    if ( src->flags & IAC_LOCAL )
        iac_server_bcast(":%s QUIT :%s\r\n",
            (src->hostmask) ? src->hostmask : "-",
            src->quitmsg);

    /*
     * remove user from nick hashtable if the user was
     * in the nick hashtable
     */
    if (src->nick) {
        rl_hashtable_rem(iac_users, ti_tolower(src->nick));

        iac_log(IAC_DEBUG, "[   ] C Removed %s from nick hashtable\n",
            src->nick);
    }

}


void
iac_irc_quit(iac_client_conn_t *src)
{
    /*
     * QUIT <reason>
     */

    char *quitmsg = strchr(src->msg, ' ');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.quit++;
#endif

    if (src->quitmsg)
        RL_FREE(src->quitmsg);

    if (!quitmsg)
        src->quitmsg = strdup("No reason");
    else
    {
        if (*quitmsg == ':')
            quitmsg++;

        if ( strlen(quitmsg) > IAC_QUITMSGLEN )
            quitmsg[IAC_QUITMSGLEN] = '\0';

        src->quitmsg = strdup(quitmsg);
    }

    src->flags |= IAC_KILLED;
    src->flags |= IAC_SUICIDE;
}

void
iac_irc_check_pong(const char *unused)
{
    iac_client_conn_t *src;
    rl_list_item_t *li, *prev;
    struct timeval tv;

    iac_log(IAC_DEBUG, "[   ] C Checking for PONG replies\n");

    li = iac_client_conns->rl_list_start;
    prev = NULL;

    while ( li ) {

        src = (iac_client_conn_t *) li->rli_data;

        if ( src->flags & IAC_PONG ) {

            src->flags &= ~(IAC_PONG);

            /*
             * PONG recevied send new PING
             */
            iac_irc_send_ping(src);


        } else {

            if (src->flags & IAC_LOCAL) {

                if (src->quitmsg)
                    RL_FREE(src->quitmsg);
                src->quitmsg = strdup("Ping timeout");

                /*
                 * Warning, all src->* members may be NULL
                 */
                iac_net_client_send(src, "ERROR :Closing Link: %s[%s] by %s " \
                    "(Ping timeout for %s[%s]\r\n",
                    (src->nick) ? src->nick : "*",
                    (src->host) ? src->host : "-",
                    (src->server) ? src->server : "(null)",
                    (src->nick) ? src->nick : "*",
                    (src->host) ? src->host : "-");

                iac_net_close_client_conn(src);

            } else {
                
                /*
                 * Note: Not possible
                 */
            }

            if (prev)
                li = prev;
            else
                li = iac_client_conns->rl_list_start;
        }
        prev = li;
        if (li)
            li = li->rli_next;
    }


    tv.tv_sec = (IAC_PING_TIMEOUT/3);
    tv.tv_usec = 0;
    ti_add_callback(&tv, &iac_irc_send_pings, "");

    tv.tv_sec = (IAC_PING_TIMEOUT/2);
    tv.tv_usec = 0;
    ti_add_callback(&tv, &iac_irc_send_pings, "");

    tv.tv_sec = ((IAC_PING_TIMEOUT/3)*2);
    tv.tv_usec = 0;
    ti_add_callback(&tv, &iac_irc_send_pings, "");


    tv.tv_sec = IAC_PING_TIMEOUT;
    tv.tv_usec = 0;

    ti_add_callback(&tv, &iac_irc_check_pong, "");
}

void
iac_irc_pong(iac_client_conn_t *src)
{
    src->flags |= IAC_PONG;
}

static void
iac_irc_send_pings(const char *unused)
{
    /*
     * PING <seq>
     */

    iac_client_conn_t *src;
    rl_list_item_t *li;

    for (li = iac_client_conns->rl_list_start; li; li = li->rli_next )
    {
        src = (iac_client_conn_t *) li->rli_data;

        /*
         * do not send PING if a reply was already received
         */
        if ( !(src->flags & IAC_PONG) )
            iac_irc_send_ping(src);
    }
}

void
iac_irc_send_ping(iac_client_conn_t *src)
{
    /*
     * PING <seq>
     */

    if (src->flags & IAC_LOCAL)
        iac_net_client_send(src, "PING :%d\r\n", rand());
}

void
iac_irc_userhost(iac_client_conn_t *src)
{
    /*
     * USERHOST <nick>
     */

    char *nick, a;
    iac_client_conn_t *cc;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.userhost++;
#endif

    nick = strchr(src->msg, ' ');

    if (!nick) {
            iac_net_client_send(src, ":%s 461 %s USERHOST :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);
        return;
    }

    nick++;

    if (*nick == ':')
        nick++;

    if (*nick == '\0')
        return;

    if ( strlen(nick) > IAC_NICKLEN )
        nick[IAC_NICKLEN] = '\0';

    ti_tolower(nick);

    if ((cc = p_iac_get_nick( nick ))) {

        if ( iac_cfg.flags & IAC_PARANOIA ) {
            if ( !iac_irc_friend(cc, src) )
                goto iac_irc_userhost_notfound;
        }

        a = '-';
        if (cc->away)
            a = '+';

        /*
         * :<servermask> 302 <source nick> :<nick>=+<user>@<host>
         */
        iac_net_client_send(src, ":%s 302 %s :%s=%c%s@%s\r\n",
            iac_cfg.servername, src->nick, cc->nick, a, cc->user, cc->host);

    } else {
iac_irc_userhost_notfound:

        iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
            iac_cfg.servername, src->nick, nick);
    }
}

void
iac_irc_names(iac_client_conn_t *src)
{
    /*
     * NAMES <channel>
     */

    char *channel = strchr(src->msg, ' ');
    char *msg = NULL;
    iac_channel_t *chan;
    iac_user_link_t *ul;
    iac_client_conn_t *cc;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.names++;
#endif

    if ( !channel ) {
        /*
         * NAMES w/o any args would mean to send a list of all users.
         * But this is not the way i would like to have IRC.
         */
        return;
    }

    channel++;

    if (channel[0] == ':')
        channel++;

    if (channel[0] == '\0')
        return;

    if ( strlen(channel) > IAC_CHANNAMELEN )
        channel[IAC_CHANNAMELEN] = '\0';

    ti_tolower(channel);

    if ( (chan = rl_hashtable_get(iac_channels, channel)) &&
         (iac_irc_user_of_channel(src, channel)) ) {
      
        /*
         * fixed to send only 10 names per line - smoli
         */
        for (ul = chan->users; ul;) {
          
            int x;
                      
            for (x = 0; x < 10 && ul; x++, ul = ul->next) {
              
                cc = ul->link;
                iac_astrcat(&msg, cc->nick);
                iac_astrcat(&msg, " ");
            }
                      
            if (x) {
                iac_net_client_send(src, ":%s 353 %s = %s :%s\r\n",
                                    iac_cfg.servername,
                                    src->nick,
                                    chan->name,
                                    msg);
                RL_FREE(msg);
            }
        }
       
        /*
         * :<servermask> 366 <source nick> <channel name> :End of NAMES list
         */
        iac_net_client_send(src, ":%s 366 %s %s :End of NAMES list\r\n",
            iac_cfg.servername, src->nick, chan->name);

    } else {

        iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
            iac_cfg.servername, src->nick, channel);
    }
}

void
iac_irc_list(iac_client_conn_t *src)
{
#ifdef IAC_CMD_USAGE
    iac_cmd_usage.list++;
#endif

    iac_net_client_send(src, ":%s 323 %s :End of LIST\r\n",
        iac_cfg.servername, src->nick);
}

void
iac_irc_unknown_mode(iac_client_conn_t *src, char mode, const char *target)
{
    iac_net_client_send(src, ":%s 472 %s %c :is unknown mode char to me " \
        "for %s\r\n", iac_cfg.servername, src->nick, mode, target);
}

void
iac_irc_change_user_mode ( iac_client_conn_t *src, char op, char mode, char *arg )
{
    iac_log ( IAC_DEBUG, "[   ] C Changing user mode for %s op=%c mode=%c arg=%s",
                (src->nick) ? src->nick : "*",
                op,
                mode,
                (arg) ? arg : "(null)" );


#define IAC_MODE_CHANGE(flag) \
    do { \
        if ( op == '+' ) \
            src->flags |= flag; \
        else  \
            src->flags &= ~(flag); \
    } while ( 0 );



    switch ( mode )
    {
        case 'i':
            IAC_MODE_CHANGE ( IAC_INVISIBLE );
            break;

        case 'w':
            IAC_MODE_CHANGE ( IAC_WALLOPS );
            break;

        case 'd':
#ifdef IAC_PROTECT_MODE_D
            if ( !(src->flags & IAC_OPERATOR) )
            {
                iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                    "You're not an IRC operator\r\n", iac_cfg.servername,
                    src->nick);
            }
            else
#endif
                IAC_MODE_CHANGE ( IAC_DEBUGMSG );
            break;

#ifdef IAC_AUTOAWAY
        case 'A':

            if ( op == '-' )
            {
                src->autoaway = 0;
            }
            else
            {
                if ( arg )
                    src->autoaway = atoi ( arg );
                else
                    src->autoaway = IAC_AUTOAWAY_DEFAULT;
            }

            break;
#endif
        case 'm':
            IAC_MODE_CHANGE( IAC_AWAY_INFORMS );
            break;
            

        default:
            iac_irc_unknown_mode ( src, mode, src->nick );
            break;
    }

#undef IAC_MODE_CHANGE
}

const char *
iac_irc_gen_user_mode ( iac_client_conn_t *src )
{
    static char   modestr[256];
    int           i              = 0;

    memset ( modestr, 0, sizeof(modestr) );

#define IAC_CHECK_MODE(flag, key) \
    do { \
        if ( src->flags & flag ) \
            if ( i < (sizeof(modestr) - 1) ) \
                modestr[i++] = key; \
    } while ( 0 );

    IAC_CHECK_MODE ( IAC_INVISIBLE, 'i' );
    IAC_CHECK_MODE ( IAC_WALLOPS, 'w' );
    IAC_CHECK_MODE ( IAC_DEBUGMSG, 'd' );
    IAC_CHECK_MODE ( IAC_AWAY_INFORMS, 'm' );

#ifdef IAC_AUTOAWAY
    if ( src->autoaway != 0 )
    {
        if ( i < (sizeof(modestr) - 1) )
            modestr[i++] = 'A';
    }
#endif
        

#undef IAC_CHECK_MODE

    return modestr;
}

void
iac_irc_change_channel_mode ( iac_client_conn_t *src, iac_channel_t *chan,
                              char op, char mode, char *arg )
{
    iac_log ( IAC_DEBUG, "[   ] C Changing channel mode for %s op=%c mode=%c arg=%s",
                (chan->name) ? chan->name : "(null)",
                op,
                mode,
                (arg) ? arg : "(null)" );

#define IAC_MODE_CHANGE(flag) \
    do { \
        if ( op == '+' )  \
            chan->flags |= flag; \
        else \
            chan->flags &= ~(flag); \
    } while ( 0 );

    switch ( mode )
    {
        case 'x': /* encrypted */
            IAC_MODE_CHANGE ( IAC_ENCRYPTED );
            break;

        default:
            iac_irc_unknown_mode ( src,
                                   mode,
                                   (chan->name) ? chan->name : "-" );
    }

#undef IAC_MODE_CHANGE
}

const char *
iac_irc_gen_channel_mode ( iac_channel_t *chan )
{
    static char   modestr[256];
    int           i              = 0;

    memset ( modestr, 0, sizeof(modestr) );

#define IAC_CHECK_MODE(flag, key) \
    do { \
        if ( chan->flags & flag ) \
            if ( i < (sizeof(modestr) - 1) ) \
                modestr[i++] = key; \
    } while ( 0 );

    IAC_CHECK_MODE ( IAC_ENCRYPTED, 'x' );

#undef IAC_CHECK_MODE

    return modestr;
}


void
iac_irc_mode ( iac_client_conn_t *src )
{
    /*
     * MODE <nick/channel> <mode changes>
     */

    char *          target;
    char *          mode_changes;
    char            op = ' ';
    int             i;
    iac_channel_t * chan;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.mode++;
#endif

    IAC_SPLIT ( target, src->msg )
    {
            iac_net_client_send(src, ":%s 461 %s MODE :Not enough parameters\r\n",
                  iac_cfg.servername,
            (src->nick) ? src->nick : "*" );
        return;
    }

    if ( strlen(target) > IAC_TARGETLEN )
        target[IAC_TARGETLEN] = '\0';


    IAC_SPLIT ( mode_changes, target )
    {
        ti_tolower ( target );

        /*
         * user requested modes of channel/user
         *
         * if target == src->nick then the client wants to know his
         * user mode
         */
        if ( !rl_stricmp(target, src->nick) )
        {
            iac_net_client_send(src, ":%s 221 %s +%s\r\n",
                iac_cfg.servername,
                (src->nick) ? src->nick : "*",
                iac_irc_gen_user_mode ( src ) );
        }
        else if ( (chan = rl_hashtable_get(iac_channels, target) ) )
        {
            if ( iac_irc_user_of_channel(src, chan->name) ) {

                /*
                 * User is part of the channel
                 *
                 * :<servermask> 324 <source nick> <channel name>
                 */
                iac_net_client_send(src, ":%s 324 %s %s +%s\r\n",
                    iac_cfg.servername,
                    (src->nick) ? src->nick : "*",
                    (chan->name) ? chan->name : "-",
                    iac_irc_gen_channel_mode ( chan ) );
            }
        }

        return;
    }

    ti_tolower ( target );

    if ( !rl_stricmp(target, src->nick) )
    {
        /*
         * user wants to change his own modes
         */
        char *      p          = mode_changes;
        char *      changes    = NULL;
        char *      parg       = NULL;
        char *      args       = NULL;
        int         more       = 1;

        while ( more )
        {
            if ( *p != '+' && *p != '-' )
                break;

            op = *p;
            p++;

            changes = p;

            IAC_SPLIT ( args, p )
            {
            }

            for ( i = 0; changes[i] != '\0'; i++ )
            {
                int arg = 0;
                int optional = 1;

                switch ( changes[i] )
                {
                    /*
                     * list all modes that need an argument
                     */
                    case 'A':   /* auto away */
                        arg = 1;
                        optional = 1;
                }

                if ( arg )
                {
                    if ( ! args )
                    {
                        if ( optional )
                            parg = NULL;
                        else
                            break;
                    }
                    else
                    {
                        parg = args;

                        IAC_SPLIT ( args, args )
                        {
                        }
                    }
                }

                iac_irc_change_user_mode ( src, op, changes[i], parg );
            }

            if ( args )
            {
                more = 1;
                p = args;
            }
        }

        iac_net_client_send(src, ":%s 221 %s +%s\r\n",
            iac_cfg.servername,
            (src->nick) ? src->nick : "*",
            iac_irc_gen_user_mode ( src ) );
    }
    else if ( (chan = rl_hashtable_get(iac_channels, target) ) )
    {
        if ( iac_irc_user_of_channel ( src, chan->name )
#ifdef IAC_OPERATOR_MODE
                || (src->flags & IAC_OPERATOR)
#endif
           )
        {
            char *      p          = mode_changes;
            char *      changes    = NULL;
            char *      parg       = NULL;
            char *      args       = NULL;
            int         more       = 1;

            while ( more )
            {
                if ( *p != '+' && *p != '-' )
                {
                    switch ( *p )
                    {
                        case 'b':
                            iac_net_client_send ( src, ":%s 368 %s %s " \
                                ":End of Channel Ban List\r\n",
                                iac_cfg.servername,
                                (src->nick) ? src->nick : "*",
                                (chan->name) ? chan->name : "-" );
                            break;
                    }

                    break;
                }

                op = *p;
                p++;

                changes = p;

                IAC_SPLIT ( args, p )
                {
                }

                for ( i = 0; changes[i] != '\0'; i++ )
                {
                    int arg = 0;
                    int optional = 1;

                    switch ( changes[i] )
                    {
                        /*
                         * list all modes that need an argument
                         */
#if 0
                        case 'k':   /* channel limit */
                            arg = 1;
                            optional = 0;
#endif
                    }

                    if ( arg )
                    {
                        if ( ! args )
                        {
                            if ( optional )
                                parg = NULL;
                            else
                                break;
                        }
                        else
                        {
                            parg = args;

                            IAC_SPLIT ( args, args )
                            {
                            }
                        }
                    }

                    iac_irc_change_channel_mode ( src, chan, op, changes[i], parg );
                }

                if ( args )
                {
                    more = 1;
                    p = args;
                }
            }

            iac_net_client_send(src, ":%s 324 %s %s +%s\r\n",
                iac_cfg.servername,
                (src->nick) ? src->nick : "*",
                (chan->name) ? chan->name : "-",
                iac_irc_gen_channel_mode ( chan ) );
        }
    }
    else 
    {
        iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
                iac_cfg.servername, src->nick, target);
    }
}

#ifdef IAC_AUTOAWAY
void
iac_irc_check_auto_away ( const char *unused )
{
    iac_client_conn_t *       src;
    rl_list_item_t *          li;
    struct timeval            tv;
    time_t                    now           = time ( 0 );

    iac_log(IAC_DEBUG, "[   ] C Checking for auto aways\n");

    li = iac_client_conns->rl_list_start;

    while ( li )
    {
        src = (iac_client_conn_t *) li->rli_data;

        if ( (src->autoaway != 0) && (src->autoaway <= (now - src->last_action)) && (!src->away) )
        {
            if ( src->away )
                RL_FREE ( src->away );

            src->away = strdup ( "AutoAway by server" );
            
            iac_net_client_send(src, ":%s 306 %s :You have been marked as " \
                "being away\r\n", iac_cfg.servername, src->nick);

            iac_irc_inform_away( src );
        }

        li = li->rli_next;
    }

    tv.tv_sec = IAC_AUTOAWAY_CHECK;
    tv.tv_usec = 0;

    ti_add_callback(&tv, &iac_irc_check_auto_away, "");
}
#endif

void
iac_irc_inform_away( iac_client_conn_t *src )
{
    iac_channel_link_t *cl;
    iac_user_link_t * ul;

    for ( cl = src->channels; cl; cl = cl->next )
    {
        for ( ul = cl->link->users; ul; ul = ul->next )
        {
            iac_client_conn_t *cc = ul->link;

            if ( cc )
            {
                if ( cc->flags & IAC_AWAY_INFORMS )
                {
                    if ( src->away )
                    {
                        iac_irc_notice_user( cc, "%s has been marked as being away (%s)\r\n",
                                (src->nick) ? src->nick : "nobody", src->away );
                    }
                    else
                    {
                        iac_irc_notice_user( cc, "%s is no longer marked as being away\r\n",
                                (src->nick) ? src->nick : "nobody" );
                    }
                }
            }
        }

    }
}


void
iac_irc_away(iac_client_conn_t *src)
{
    /*
     * AWAY <msg>
     */

    char *reason = strchr(src->msg, ' ');

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.away++;
#endif

    if (!reason) {
        if (src->away)
            RL_FREE(src->away);

        iac_net_client_send(src, ":%s 305 %s :You are no longer marked as " \
            "being away\r\n", iac_cfg.servername, src->nick);
        iac_irc_inform_away( src );
        return;
    }

    reason++;

    if ( reason[0] == ':' )
        reason++;

    if ( strlen(reason) > IAC_AWAYLEN )
        reason[IAC_AWAYLEN] = '\0';

    if ( reason[0] == '\0') {
        if (src->away)
            RL_FREE(src->away);

        iac_net_client_send(src, ":%s 305 %s :You are no longer marked as " \
            "being away\r\n", iac_cfg.servername, src->nick);

    } else  {

        if (src->away)
            RL_FREE(src->away);
        
        src->away = strdup(reason);

        iac_net_client_send(src, ":%s 306 %s :You have been marked as " \
            "being away\r\n", iac_cfg.servername, src->nick);
    }

    iac_irc_inform_away( src );
}

void
iac_irc_set(iac_client_conn_t *src)
{
    /*
     * SET IRCNAME <ircname>
     *     HOSTNAME <hostname>
     *     USER <username>
     */

    rl_token_list_t *tl;
    char *id, *value;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.set++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if (tl->rtl_ntokens < 3) {

            iac_net_client_send(src, ":%s 461 %s SET :Not enough parameters\r\n",
                  iac_cfg.servername, src->nick);

    } else {
        rl_token_get_next(tl);              /* SET        */
        id = rl_token_get_next(tl);         /* <id>       */
        value = rl_token_get_next(tl);      /* <value>    */

            if (!rl_stricmp(id, "hostname")) {

            if ( strlen(value) > IAC_HOSTLEN )
                value[IAC_HOSTLEN] = '\0';

            if (src->host != src->ip)
                if (src->host)
                    free(src->host);

            src->host = strdup(value);
                  IAC_GENHOSTMASK(src);

            } else if (!rl_stricmp(id, "user")) {

            if ( strlen(value) > IAC_USERLEN )
                value[IAC_USERLEN] = '\0';

            if (src->user)
                RL_FREE(src->user);

            src->user = strdup(value);
                  IAC_GENHOSTMASK(src);

            } else if (!rl_stricmp(id, "ircname")) {

            if ( strlen(value) > IAC_IRCNAMELEN )
                value[IAC_IRCNAMELEN] = '\0';

            if (src->ircname)
                RL_FREE(src->ircname);

            src->ircname = strdup(value);
            }
    }

    rl_token_del_token_list(tl);
}

void
iac_irc_ison(iac_client_conn_t *src)
{
    /*
     * ISON <nick1> <nick2> <nick3> ...
     */

    iac_client_conn_t *cc;
    char *msg = NULL;
    char *nicks, *nick;
    int len, i;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.ison++;
#endif

    nicks = strchr(src->msg, ' ');

    if (!nicks) {
        iac_net_client_send(src, ":%s 461 %s ISON: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);
        return;
    }

    nicks++;

    if (nicks[0] == ':')
        nicks++;

    nick = nicks;
    len = strlen(nicks);

    for (i=0; i <= len; i++) {

        if ( (nicks[i] == ' ' ) ||
             (nicks[i] == ',' ) ||
             (nicks[i] == '\0') ) {

            nicks[i] = '\0';

            if ( strlen(nick) > IAC_NICKLEN )
                nick[IAC_NICKLEN] = '\0';

            ti_tolower(nick);

            if ((cc = p_iac_get_nick( nick ))) {

                if ( iac_cfg.flags & IAC_PARANOIA ) {
                    if ( !iac_irc_friend(cc, src) )
                        goto iac_irc_ison_you_suck;
                }

                iac_astrcat(&msg, cc->nick);
                iac_astrcat(&msg, " ");
            }

iac_irc_ison_you_suck:

            if ( i != len )
                nick = &(nicks[++i]);
        }
    }

    if (msg) {

        iac_net_client_send(src, ":%s 303 %s :%s\r\n",
            iac_cfg.servername, src->nick, msg);

    } else {

        iac_net_client_send(src, ":%s 303 %s :\r\n",
            iac_cfg.servername, src->nick);
    }
}

void
iac_irc_admin(iac_client_conn_t *src)
{
    /*
     * ADMIN
     */

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.admin++;
#endif

    iac_net_client_send(src, ":%s 256 %s :Administrative info about %s\r\n",
        iac_cfg.servername, src->nick, iac_cfg.servername);
    iac_net_client_send(src, ":%s 257 %s :%s\r\n",
        iac_cfg.servername, src->nick, iac_cfg.info);
    iac_net_client_send(src, ":%s 258 %s :nobody\r\n",
        iac_cfg.servername, src->nick);
    iac_net_client_send(src, ":%s 259 %s :nobody <nobody@nobody>\r\n",
        iac_cfg.servername, src->nick);
}

void
iac_irc_version(iac_client_conn_t *src)
{
    /*
     * VERSION
     */

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.version++;
#endif

    iac_net_client_send(src, ":%s 351 %s %s %s :\r\n",
        iac_cfg.servername, src->nick, IAC_VERSION, iac_cfg.servername);
}

void
iac_irc_stats(iac_client_conn_t *src)
{
    /*
     * STATS <query>
     */
    long d, h, m, s;
    int i;
    iac_operator_line_t *ol;
    iac_link_line_t *ll;

    rl_token_list_t *tl;
    rl_list_item_t *li;
    iac_server_conn_t *sc;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.stats++;
#endif

    tl = rl_token_split(src->msg, " ");

    if ( tl->rtl_ntokens < 2 ) {

        iac_net_client_send(src, ":%s 461 %s STATS: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);

    } else {
        char *query;

        rl_token_get_next(tl);

        query = rl_token_get_next(tl);

        i = 0;
        switch ( query[i] )
        {
            case 'l':
                /*
                 * local server links
                 */

                li = iac_server_conns->rl_list_start;
                while ( li ) {
                    sc = (iac_server_conn_t *) li->rli_data;

                    /*
                     * :<servermask> 211 <source nick> <linkname> <sendq>
                     *       <sent messages> <sent kbytes> <received messages>
                     *       <received kbytes> <time open>
                     */
                    iac_net_client_send(src, ":%s 211 %s %s_%d %u %u %u %u " \
                        "%u %u\r\n",
                        iac_cfg.servername,
                        src->nick,
                        sc->hostname,
                        sc->numeric,
                        sc->queued,
                        sc->msgs_sent,
                        (sc->bytes_sent / 1024),
                        sc->msgs_recvd,
                        (sc->bytes_recvd / 1024),
                        (time(0) - sc->time_init) );

                    li = li->rli_next;
                }

                /*
                 * :<prefix> 219 <source nick> <query> :End of STATS report
                 */
                iac_net_client_send(src, ":%s 219 %s %c :End of STATS report.\r\n",
                    iac_cfg.servername, src->nick, query[i]);

                break;

            case 'm':

#ifdef IAC_CMD_USAGE

#ifdef IAC_PROTECT_STATS_M
                /*
                 * check if user is an operator
                 */
                if ( !(src->flags & IAC_OPERATOR) ) {
                    iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                        "You're not an IRC operator\r\n", iac_cfg.servername,
                        src->nick);
                    rl_token_del_token_list(tl);
                    return;
                }
#endif


#define IAC_SCMD(cmd, cnt, bytes, rcnt) { \
    iac_net_client_send(src, ":%s 212 %s %s %u %u %u\r\n", \
        iac_cfg.servername, src->nick, cmd, cnt, bytes, rcnt); }

                IAC_SCMD("privmsg", iac_cmd_usage.privmsg, 0, 0);
                IAC_SCMD("notice", iac_cmd_usage.notice, 0, 0);
                IAC_SCMD("nick", iac_cmd_usage.nick ,0, 0);
                IAC_SCMD("user", iac_cmd_usage.user ,0, 0);
                IAC_SCMD("join", iac_cmd_usage.join ,0, 0);
                IAC_SCMD("part", iac_cmd_usage.part ,0, 0);
                IAC_SCMD("topic", iac_cmd_usage.topic ,0, 0);
                IAC_SCMD("mode", iac_cmd_usage.mode ,0, 0);
                IAC_SCMD("quit", iac_cmd_usage.quit ,0, 0);
                IAC_SCMD("whois", iac_cmd_usage.whois ,0, 0);
                IAC_SCMD("whowas", iac_cmd_usage.whowas ,0, 0);
                IAC_SCMD("who", iac_cmd_usage.who ,0, 0);
                IAC_SCMD("names", iac_cmd_usage.names ,0, 0);
                IAC_SCMD("kill", iac_cmd_usage.kill ,0, 0);
                IAC_SCMD("die", iac_cmd_usage.die ,0, 0);
                IAC_SCMD("rehash", iac_cmd_usage.rehash ,0, 0);
                IAC_SCMD("ison", iac_cmd_usage.ison ,0, 0);
                IAC_SCMD("userhost", iac_cmd_usage.userhost ,0, 0);
                IAC_SCMD("away", iac_cmd_usage.away ,0, 0);
                IAC_SCMD("admin", iac_cmd_usage.admin ,0, 0);
                IAC_SCMD("list", iac_cmd_usage.list ,0, 0);
                IAC_SCMD("users", iac_cmd_usage.users ,0, 0);
                IAC_SCMD("summon", iac_cmd_usage.summon ,0, 0);
                IAC_SCMD("version", iac_cmd_usage.version ,0, 0);
                IAC_SCMD("ping", iac_cmd_usage.ping ,0, 0);
                IAC_SCMD("oper", iac_cmd_usage.oper ,0, 0);
                IAC_SCMD("wallops", iac_cmd_usage.wallops ,0, 0);
                IAC_SCMD("squit", iac_cmd_usage.squit ,0, 0);
                IAC_SCMD("connect", iac_cmd_usage.connect ,0, 0);
                IAC_SCMD("sync", iac_cmd_usage.sync ,0, 0);
                IAC_SCMD("block", iac_cmd_usage.block ,0, 0);
                IAC_SCMD("unblock", iac_cmd_usage.unblock, 0, 0);
                IAC_SCMD("dump", iac_cmd_usage.dump ,0, 0);
                IAC_SCMD("stats", iac_cmd_usage.stats ,0, 0);
                IAC_SCMD("motd", iac_cmd_usage.motd ,0, 0);
                IAC_SCMD("links", iac_cmd_usage.links ,0, 0);
                IAC_SCMD("set", iac_cmd_usage.set ,0, 0);
                IAC_SCMD("thistory", iac_cmd_usage.thistory ,0, 0);
                IAC_SCMD("unknown", iac_cmd_usage.unknown ,0, 0);

                /*
                 * :<prefix> 219 <source nick> <query> :End of STATS report
                 */
                iac_net_client_send(src, ":%s 219 %s %c :End of STATS report.\r\n",
                    iac_cfg.servername, src->nick, query[i]);

#undef IAC_SCMD



#endif
                break;

            case 'o':

#ifdef IAC_PROTECT_STATS_O
                /*
                 * check if user is an operator
                 */
                if ( !(src->flags & IAC_OPERATOR) ) {
                    iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                        "You're not an IRC operator\r\n", iac_cfg.servername,
                        src->nick);
                    rl_token_del_token_list(tl);
                    return;
                }
#endif

                ol = iac_cfg.operators;
                while (ol) {

                    /*
                     * :<servermask> 243 <source nick> 0 <mask> * <nick>
                     */
                    iac_net_client_send(src, ":%s 243 %s O %s * %s\r\n",
                        iac_cfg.servername, src->nick, ol->mask, ol->nick);
                    
                    ol = ol->next;
                }

                /*
                 * :<prefix> 219 <source nick> <query> :End of STATS report
                 */
                iac_net_client_send(src, ":%s 219 %s %c :End of STATS report.\r\n",
                    iac_cfg.servername, src->nick, query[i]);

                break;

            case 'u':
                /*
                 * uptime
                 */
                d = h = m = s = 0;

                s = time(0);

                s = (s - iac_starttime);

                /*
                 * avoid /0
                 */
                if (s) {

                    d = s / 86400;
                    s = s % 86400;

                    h = s / 3600;
                    s = s % 3600;

                    m = s / 60;
                    s = s % 60;
                }

                /*
                 * :<prefix> 242 <source nick> :Server Up <days> days <hour>:<min>:<sec>
                 */
                iac_net_client_send(src, ":%s 242 %s :Server Up %d days %d:%02d:%02d\r\n",
                    iac_cfg.servername, src->nick, d, h, m, s);

                /*
                 * :<prefix> 219 <source nick> <query> :End of STATS report
                 */
                iac_net_client_send(src, ":%s 219 %s %c :End of STATS report.\r\n",
                    iac_cfg.servername, src->nick, query[i]);
                
                break;

            case 'c':

#ifdef IAC_PROTECT_STATS_C
                /*
                 * check if user is an operator
                 */
                if ( !(src->flags & IAC_OPERATOR) ) {
                    iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                        "You're not an IRC operator\r\n", iac_cfg.servername,
                        src->nick);
                    rl_token_del_token_list(tl);
                    return;
                }
#endif

                ll = iac_cfg.links;
                while ( ll ) {
                    /*
                     * :<servermask> 213 <source nick> C <mask> <host> <port>
                     */
                    iac_net_client_send(src, ":%s 213 %s C %s %s %d %s\r\n",
                        iac_cfg.servername, src->nick,
                        (ll->mask) ? ll->mask : "*",
                        (ll->host) ? ll->host : "*",
                        ll->port,
                        (ll->options) ? ll->options : "");

                    ll = ll->next;
                }
                break;
        }
    }

    rl_token_del_token_list(tl);
}

void
iac_irc_summon(iac_client_conn_t *src)
{
    /*
     * SUMMON <user> [ <target> [ <channel> ] ]
     */

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.summon++;
#endif

    /*
     * I like this feature. I think write(1) would be the correct way
     * Anyway, for now it is not implement and we have to tell the client
     * about that.
     */

    iac_net_client_send(src, ":%s 445 %s :SUMMON has been disabled\r\n",
        iac_cfg.servername, src->nick);
}

void
iac_irc_users(iac_client_conn_t *src)
{
    /*
     * USERS [<target>]
     */

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.users++;
#endif

    /*
     * I think this i kinda stupid. Well, on a small network with all trusted
     * users it may be useful.
     */

    iac_net_client_send(src, ":%s 446 %s :USERS has been disabled\r\n",
        iac_cfg.servername, src->nick);
}

void
iac_irc_connect(iac_client_conn_t *src)
{
    /*
     * CONNECT <target server> <port> <password> [<options>]
     *
     * or
     *
     * CONNECT <servername>
     */

    rl_token_list_t *tl;
    iac_link_line_t *ll;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.connect++;
#endif

    tl = rl_token_split(src->msg, "    ");

    if ( tl->rtl_ntokens == 2 ) {

        ll = iac_cfg.links;
        while (ll) {
            char *mask;
            rl_token_get_next(tl);
            mask = rl_token_get_next(tl);

            if ( iac_match_mask(mask, ll->host) ) {
                iac_servlink_connect(src, ll);
            }

            ll = ll->next;
        }


    } else if ( tl->rtl_ntokens < 4 ) {

        iac_net_client_send(src, ":%s 461 %s CONNECT: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);

    } else {

        char *target_server;
        char *port;
        char *password;
        char *options = NULL;
        long p;

        rl_token_get_next(tl);                    /* CONNECT          */
        target_server = rl_token_get_next(tl);    /* target server    */
        port = rl_token_get_next(tl);             /* port             */
        password = rl_token_get_next(tl);         /* password         */

        if ( strlen(target_server) > IAC_HOSTLEN )
            target_server[IAC_HOSTLEN] = '\0';

        if ( strlen(port) > 12 )
            port[12] = '\0';

        if ( strlen(password) > 128 )
            password[128] = '\0';

        if ( tl->rtl_ntokens > 4 ) {
            options = rl_token_get_next(tl);
            if ( strlen(options) > IAC_SERV_OPTIONSLEN )
                options[IAC_SERV_OPTIONSLEN] = '\0';
        }

#ifdef IAC_PROTECT_CONNECT
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }
#endif


        p = strtol(port, NULL, 10);

        if ( p < 1 || p > 65536 || p == LONG_MIN || p == LONG_MAX ) {

            /*
             * invalid range
             */

        } else {

            iac_link_line_t *ll;

            if ( !(ll = (iac_link_line_t *) malloc(sizeof(iac_link_line_t))) )
                iac_log(IAC_ERR, "Out of memory!\n");
            memset(ll, 0, sizeof(iac_link_line_t));

            ll->host = strdup(target_server);
            ll->port = p;
            ll->password = strdup(password);
            ll->options = strdup(options);

            ll->next = iac_cfg.links;
            iac_cfg.links = ll;

            iac_servlink_connect(src, ll);
            
        }
    }

    rl_token_del_token_list(tl);
}

void
iac_irc_squit(iac_client_conn_t *src)
{
    /*
     * SQUIT <server> <comment>
     */

    rl_token_list_t *tl;
    rl_list_item_t *li;
    iac_server_conn_t *sc = NULL;
    int found = 0;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.squit++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if ( tl->rtl_ntokens != 3 ) {

        iac_net_client_send(src, ":%s 461 %s SQUIT: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);

    } else {

        char *server;
        char *comment;

        rl_token_get_next(tl);            /* SQUIT       */
        server = rl_token_get_next(tl);   /* <server>    */
        comment = rl_token_get_next(tl);  /* <comment>   */

        if ( strlen(server) > IAC_HOSTLEN )
            server[IAC_HOSTLEN] = '\0';

        if ( strlen(comment) > 128 )
            comment[128] = '\0';

#ifdef IAC_PROTECT_SQUIT
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }
#endif

        /*
         * search server in connection list
         */
        li = iac_server_conns->rl_list_start;
        while (li) {
            sc = (iac_server_conn_t *) li->rli_data;

            if ( iac_match_mask(server, sc->hostname) ) {
                iac_irc_notice_user(src, "Closing server link to %s", sc->hostname);
                iac_net_close_server_conn((void *) sc);
                li = iac_server_conns->rl_list_start;
                found = 1;
                continue;
            }
            
            li = li->rli_next;
        }

        if (!found) {

            /*
             * server not found, notify user
             */
            iac_net_client_send(src, ":%s 402 %s %s :No such server\r\n",
                iac_cfg.servername, src->nick, server);
        }

        rl_token_del_token_list(tl);
        return;
    }
    
    rl_token_del_token_list(tl);
}

void
iac_irc_kill(iac_client_conn_t *src)
{
    /*
     * KILL <nick> <reason>
     */

    rl_token_list_t *tl;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.kill++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if (tl->rtl_ntokens != 3) {

        iac_net_client_send(src, ":%s 461 %s KILL: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);

    } else {
        
        char *nick;
        char *reason;
        iac_client_conn_t *cc;

        rl_token_get_next(tl);               /* KILL         */
        nick = rl_token_get_next(tl);        /* <nick>       */
        reason = rl_token_get_next(tl);      /* <reason>     */

        if ( strlen(nick) > IAC_NICKLEN )
            nick[IAC_NICKLEN] = '\0';

        if ( strlen(reason) > 128 )
            reason[128] = '\0';

        if (*reason == ':')
            reason = &reason[1];

#ifdef IAC_PROTECT_KILL
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }
#endif

        /*
         * looooooooower case
         */
        ti_tolower(nick);

        /*
         * choose my target
         */
        if ( !(cc = (iac_client_conn_t *) rl_hashtable_get(iac_users, nick)) ) {

                iac_net_client_send(src, ":%s 401 %s %s :No such nick/channel\r\n",
                        iac_cfg.servername, src->nick, nick);

                /*
                 * get the fuck out of here
                 */
                rl_token_del_token_list(tl);
                return;
        }

        if (cc->quitmsg)
            RL_FREE(cc->quitmsg);

        asprintf(&(cc->quitmsg), "Killed by %s (%s)", src->nick, reason);

        /*
         * 'forward' quit
         */
        if  ( !(cc->flags & IAC_LOCAL) ) {
            iac_server_bcast(":%s QUIT %s :%s\r\n",
                cc->hostmask,
                cc->nick,
                cc->quitmsg);
        }

        cc->flags |= IAC_KILLED;

        if ( src == cc )
            src->flags |= IAC_SUICIDE;
        else
            iac_net_close_client_conn( cc );

    }

    rl_token_del_token_list(tl);
}

void
iac_irc_sync(iac_client_conn_t *src)
{
    /*
     * SYNC
     */

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.sync++;
#endif

#ifdef IAC_PROTECT_SYNC
    /*
     * check if user is an operator
     */
    if ( !(src->flags & IAC_OPERATOR) ) {
        iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
            "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
        return;
    }
#endif

    iac_irc_notice_user(src, "Beginning sync\r\n");

    iac_servlink_start_sync();
}

void
iac_irc_links(iac_client_conn_t *src)
{
    /*
     * LINKS [ [ <remote server> ] <server mask> ]
     */

    iac_server_t *s;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.links++;
#endif

    s = iac_status.servers;
    while ( s ) {

        /*
         * :<servermask> 364 <source nick> * <server> :<hopcount>
         *       (<server numeric>) <server info>
         */
        iac_net_client_send(src, ":%s 364 %s * %s :%d (%d) %s\r\n",
            iac_cfg.servername, src->nick, s->name, s->hopcount,
            s->numeric, s->info);

        s = s->next;
    }

    /*
     * :<servermask> 365 <source nick> * :End of LINKS list
     */
    iac_net_client_send(src, ":%s 365 %s * :End of LINKS list\r\n",
        iac_cfg.servername, src->nick);
    
}

void
iac_irc_oper(iac_client_conn_t *src)
{
    /*
     * OPER <nick> <password>
     */

    rl_token_list_t *tl;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.oper++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if ( tl->rtl_ntokens != 3 ) {

        iac_net_client_send(src, ":%s 461 %s OPER: Not enough parameters\r\n",
            iac_cfg.servername, src->nick);

    } else {

        char *nick;
        char *password;
        iac_operator_line_t *ol;

#ifdef HAVE_CRYPT
        char *encrypted;
#endif

        rl_token_get_next(tl);               /* OPER         */
        nick = rl_token_get_next(tl);        /* <nick>       */
        password = rl_token_get_next(tl);    /* <password>   */

        if ( strlen(nick) > IAC_NICKLEN )
            nick[IAC_NICKLEN] = '\0';

        if ( strlen(password) > IAC_PASSWORDLEN )
            password[IAC_PASSWORDLEN] = '\0';

        /*
         * Get operator line, maybe we should use the current nickname
         * and not the given
         */
        ol = iac_conf_get_operline(nick);

        if ( !ol ) {
            iac_net_client_send(src, ":%s 491 %s :No O-lines for your " \
                "host\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }

        if ( !iac_match_mask(ol->mask, src->hostmask) ) {
            iac_net_client_send(src, ":%s 491 %s :No O-lines for your " \
                "host\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }


#ifdef HAVE_CRYPT
        if ( !(encrypted = crypt(password, IAC_CRYPT_SALT)) ) {

            /*
             * crypt is not supported, try to compare plain text
             * passwords
             */

            iac_log(IAC_WARN, "[WWW] * crypt() is not implemented. " \
                "crypt(3) Chapter ERRORS\n");

            if ( strcmp(password, ol->password) != 0 )
                goto iac_irc_oper_access_denied;
        } else {

            if ( strcmp(encrypted, ol->password) != 0 )
                goto iac_irc_oper_access_denied;
        }

        /*
         * Password OK
         */

#else

        if ( strcmp(password, ol->password) != 0 )
            goto iac_irc_oper_access_denied;

        /*
         * Password OK
         */
#endif
        src->flags |= IAC_OPERATOR;
        
        iac_net_client_send(src, ":%s 381 %s :You are now an IRC operator\r\n",
            iac_cfg.servername, src->nick);

        /*
         * XXX: Send mode
         */

        iac_irc_notice_ops("%s is now IRC Operator\r\n", src->hostmask);

        /*
         * XXX: Check for errors to report like server linkg not
         * able to establish or other stuff
         */

        rl_token_del_token_list(tl);
        return;


iac_irc_oper_access_denied:

        iac_irc_notice_ops("Wrong IRC Operator password for %s\r\n",
                src->hostmask); 

        iac_net_client_send(src, ":%s 464 %s :Password incorrect\r\n",
            iac_cfg.servername, src->nick);

    }

    rl_token_del_token_list(tl);
}

void
iac_irc_motd(iac_client_conn_t *src)
{
#ifdef IAC_CMD_USAGE
    iac_cmd_usage.motd++;
#endif

    if ( iac_motd ) {

        char *s;
        int i;

        /*
         * :<hostmask> 375 <source nick> :- <servername> Message of the Day -
         */
        iac_net_client_send(src, ":%s 375 %s :- %s Message of the Day -\r\n",
            iac_cfg.servername, src->nick, iac_cfg.servername);

        s = iac_motd;
        for (i=0; iac_motd[i] != '\0'; i++) {
            if (iac_motd[i] == '\n') {
                iac_motd[i] = '\0';

                /*
                 * :<hostmask> 372 <source nick> :- <text
                 */
                iac_net_client_send(src, ":%s 372 %s :- %s\r\n",
                    iac_cfg.servername, src->nick, s);

                iac_motd[i] = '\n';
                s = &iac_motd[i+1];
            }
        }

        /*
         * :<hostmask> 376 <source nick> :End of MOTD command
         */
        iac_net_client_send(src, ":%s 376 %s :End of MOTD command.\r\n",
            iac_cfg.servername, src->nick);

    } else {

        /*
         * :<hostmask> 422 <source nick> :MOTD file is missing
         */
        iac_net_client_send(src, ":%s 422 %s :MOTD file is missing\r\n",
            iac_cfg.servername, src->nick);
    }
}

void
iac_irc_wallops(iac_client_conn_t *src)
{
    /*
     * WALLOPS <text> to send
     */

    rl_token_list_t *tl;
    iac_client_conn_t *cc;
    rl_htable_entry_t *he;
    int i;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.wallops++;
#endif

    tl = rl_token_split(src->msg, " ");

    if ( tl->rtl_ntokens != 2 ) {

        iac_net_client_send(src, ":%s 461 %s WALLOPS: Not enough " \
            "parameters\r\n", iac_cfg.servername, src->nick);

    } else {

        char *text;

        rl_token_get_next(tl);          /* WALLOPS      */
        text = rl_token_get_next(tl);   /* <text>       */

#ifdef IAC_PROTECT_WALLOPS
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }
#endif


        for (i=0; i < iac_users->rh_stsize; i++) {
            if ( (he = iac_users->rh_symtab[i].rhc_el) ) {
                while (he) {
                    cc = (iac_client_conn_t *) he->rhe_data;

                    if ( cc->flags & IAC_LOCAL && cc->flags & IAC_WALLOPS ) {

                        iac_net_client_send(cc, ":%s WALLOPS :%s from %s\r\n",
                            iac_cfg.servername, text, src->nick);

                    }

                    he = he->rhe_next;
                }
            }
        }

    }

    rl_token_del_token_list(tl);
}

void
iac_irc_dump(iac_client_conn_t *src)
{
    /*
     * DUMP CHANNEL <name>
     *      USER <name>
     *      SERVER [<servername>]
     *      SERVER_CONN [<connection name>]
     *      BLOCK [<block name>]
     *      LINK [<linkname>]
     */

    rl_token_list_t *tl;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.dump++;
#endif

    tl = rl_token_split(src->msg, "  ");

#ifdef IAC_PROTECT_DUMP
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            rl_token_del_token_list(tl);
            return;
        }
#endif

    if ( tl->rtl_ntokens < 2 ) {

        iac_irc_notice_user(src, "Usage: DUMP CHANNEL <name>\r\n");
        iac_irc_notice_user(src, "            USER <nick>\r\n");
        iac_irc_notice_user(src, ",           SERVER <servername>\r\n");

    } else {
        char *action;

        rl_token_get_next(tl);            /* DUMP                         */
        action = rl_token_get_next(tl);   /* <action> (CHANNEL|USER|...)  */

        if ( !rl_stricmp(action, "CHANNEL") ) {

            if ( tl->rtl_ntokens < 3 )
                iac_irc_notice_user(src, "Usage: DUMP CHANNEL <name>\r\n");
            else
                iac_dump_channel(src, rl_token_get_next(tl));
            
        } else if ( !rl_stricmp(action, "USER") ) {

            if ( tl->rtl_ntokens < 3 )
                iac_irc_notice_user(src, "Usage: DUMP USER <nick>\r\n");
            else
                iac_dump_user(src, rl_token_get_next(tl));

        } else if ( !rl_stricmp(action, "SERVER") ) {

            if ( tl->rtl_ntokens < 3 )
                iac_dump_server(src, NULL);
            else
                iac_dump_server(src, rl_token_get_next(tl));

        } else if ( !rl_stricmp(action, "SERVER_CONN") ) {

            if ( tl->rtl_ntokens < 3 )
                iac_dump_server_connection(src, NULL);
            else
                iac_dump_server_connection(src, rl_token_get_next(tl));

        } else if ( !rl_stricmp(action, "BLOCK") ) {

            if (tl->rtl_ntokens < 3)
                iac_dump_block(src, NULL);
            else
                iac_dump_block(src, rl_token_get_next(tl));

        } else if ( !rl_stricmp(action, "LINK") ) {

            if (tl->rtl_ntokens < 3)
                iac_dump_link(src, NULL);
            else
                iac_dump_link(src, rl_token_get_next(tl));

        } else if ( !rl_stricmp(action, "SCRIPT") ) {

            if (tl->rtl_ntokens < 3)
                iac_dump_script(src, NULL);
            else
                iac_dump_script(src, rl_token_get_next(tl));

        } else {
            iac_irc_notice_user(src, "Unknown Action\r\n");
        }
    }

    rl_token_del_token_list(tl);
}

void
iac_irc_thistory(iac_client_conn_t *src)
{
    /*
     * THISTORY <channel> [<last>]
     */

    rl_token_list_t *tl;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.thistory++;
#endif

    tl = rl_token_split(src->msg, "  ");

    if ( tl->rtl_ntokens < 2 ) {

        iac_irc_notice_user(src, "Usage: THISTORY <channel> [<last>]\r\n");

    } else {
        char *channel;
        char *last = NULL;
        long l = 3;
        int i, ti;
        iac_channel_t *chan;

        rl_token_get_next(tl);
        channel = rl_token_get_next(tl);

        if ( tl->rtl_ntokens > 2 )
            last = rl_token_get_next(tl);

        if ( last ) {
            l = strtol(last, NULL, 10);

            if (l == LONG_MIN || l < 0 || l > (IAC_TLIST_MAX - 1))
                goto iac_irc_thistory_invalid;
        }

        if ( !(chan = rl_hashtable_get(iac_channels, channel)) ) {
            iac_irc_notice_user(src, "The channel was accessed 42 times and doesn't exist\r\n");
            goto iac_irc_thistory_invalid;
        }
        
        ti = chan->itopic;
        for (i=0; i < (int) l; i++) {

            char *d = asctime(localtime(&(chan->topic_time[ti])));

            if ( strlen(d)-1 )
                d[strlen(d)-1] = '\0';

            if (ti == 0)
                ti = (IAC_TLIST_MAX - 1);
            else
                ti--;

            if (chan->topic[ti]) {

                iac_irc_notice_user(src, "%s %s %s\r\n", d,
                    (chan->topic_owner[ti]) ? chan->topic_owner[ti] : "none",
                    (chan->topic[ti]) ? chan->topic[ti] : "none");
            }
        }
    }

iac_irc_thistory_invalid:

    rl_token_del_token_list(tl);
}

void
iac_irc_block(iac_client_conn_t *src)
{
    /*
     * BLOCK [<mask>] [<reason>]
     */

    char *mask, *reason;
    iac_block_t *b;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.block++;
#endif

#ifdef IAC_PROTECT_BLOCK
    /*
     * check if user is an operator
     */
    if ( !(src->flags & IAC_OPERATOR) ) {
        iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
            "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
        return;
    }
#endif

    mask = strchr(src->msg, ' ');

    if (!mask) {
        b = iac_cfg.blocked;

        iac_irc_notice_user(src, "Being blocked:\r\n");

        while (b) {
            iac_irc_notice_user(src, "%s -> %s (%d)\r\n",
                b->mask, b->reason, b->matches);
            b = b->next;
        }
        return;
    }

    mask++;

    reason = strchr(mask, ' ');

    if (!reason) {
        reason = "You are banned";
    } else {
        *reason = '\0';
        reason++;
    }

    if ( !(b = (iac_block_t *) malloc(sizeof(iac_block_t))) )
        iac_log(IAC_ERR, "Out of memory!\n");
    memset(b, 0, sizeof(iac_block_t));

    b->mask = strdup(mask);
    b->reason = strdup(reason);

    b->next = iac_cfg.blocked;
    iac_cfg.blocked = b;

    iac_irc_notice_user(src, "%s now being blocked (%s)\r\n", b->mask, b->reason);
}

void
iac_irc_unblock(iac_client_conn_t *src)
{
    /*
     * UNBLOCK <mask>
     */

    char *mask = strchr(src->msg, ' ');
    iac_block_t *b, *next, *last;

#ifdef IAC_CMD_USAGE
    iac_cmd_usage.unblock++;
#endif

#ifdef IAC_PROTECT_UNBLOCK
    /*
     * check if user is an operator
     */
    if ( !(src->flags & IAC_OPERATOR) ) {
        iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
            "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
        return;
    }
#endif

    if ( !mask) {
        iac_irc_notice_user(src, "Usage: UNBLOCK <mask>\r\n");
        return;
    }

    mask++;

    if (*mask == ':')
        mask++;

    if (*mask == '\0')
        return;

    b = iac_cfg.blocked;
    last = NULL;
    while ( b ) {
        next = b->next;

        if ( iac_match_mask(mask, b->mask) ) {
            iac_irc_notice_user(src, "Removing block %s (%s)\r\n",
                (b->mask) ? b->mask : "*",
                (b->reason) ? b->reason : "*");
            if (last)
                last->next = b->next;
            else
                iac_cfg.blocked = b->next;

            RL_FREE(b->mask);
            RL_FREE(b->reason);
            RL_FREE(b);
        }

        last = b;
        b = next;
    }
}

void
iac_irc_link(iac_client_conn_t *src)
{
    /*
     * LINK ADD <autoconnect> <mask> <host> <port> <password> [<options>]
     *      REMOVE <host>
     *      CONNECT <host>
     *      CLOSE <host>
     *      STOP <host>
     */

    char *action, *arg1;
    iac_link_line_t *ll, *last, *next;
    rl_list_item_t *li;

#ifdef IAC_PROTECT_LINK
        /*
         * check if user is an operator
         */
        if ( !(src->flags & IAC_OPERATOR) ) {
            iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
                "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
            return;
        }
#endif


    action = strchr(src->msg, ' ');

    /*
     * No action given, print the link table
     */
    if (!action) {
        ll = iac_cfg.links;
        iac_irc_notice_user(src, "Host         Port   Mask       Password   A Flags\r\n");
        while ( ll ) {
            iac_irc_notice_user(src, "%-12s %-6d %-10s %-10s %d %s%s%s%s%s\r\n",
                ll->host, ll->port, ll->mask, ll->password,
                (ll->flags & IAC_AUTOCONNECT) ? 1 : 0,
                (ll->flags & IAC_CONNECTED) ? "Connected" : "Disconnected",
                (ll->flags & IAC_FAILED) ? ", Failed" : "",
                (ll->flags & IAC_RETRY) ? ", Retry" : "",
                (ll->flags & IAC_SLOWRETRY) ? ", Slow Retry" : "",
                (ll->flags & IAC_MASTER) ? ", Master" : "");

            ll = ll->next;
        }
        return;
    }

    action++;

    arg1 = strchr(action, ' ');

    if (arg1) {
        *arg1 = '\0';
        arg1++;
    }

    if ( !rl_stricmp(action, "ADD") ) {

        char *autoconnect, *mask, *host, *port, *password, *options;

        if ( !(autoconnect = arg1) )
            goto iac_irc_link_add_usage;

        if ( !(mask = strchr(autoconnect, ' ')) )
            goto iac_irc_link_add_usage;

        *mask = '\0';
        mask++;
        if ( !(host = strchr(mask, ' ')) )
            goto iac_irc_link_add_usage;

        *host = '\0';
        host++;
        if ( !(port = strchr(host, ' ')) )
            goto iac_irc_link_add_usage;

        if ( !strtol(port, NULL, 10) )
            goto iac_irc_link_add_usage;

        *port = '\0';
        port++;
        if ( !(password = strchr(port, ' ')) )
            goto iac_irc_link_add_usage;

        *password = '\0';
        password++;
        options = strchr(password, ' ');

        if (options) {
            *options = '\0';
            options++;
        }

        if ( !(ll = (iac_link_line_t *) malloc(sizeof(iac_link_line_t))) )
            iac_log(IAC_ERR, "Out of memory!\n");
        memset(ll, 0, sizeof(iac_link_line_t));

        ll->host = strdup(host);
        ll->port = strtol(port, NULL, 10);
        ll->mask = strdup(mask);
        ll->password = strdup(password);
        if (options)
            ll->options = strdup(options);

        if ( strtol(autoconnect, NULL, 10) != 0 )
            ll->flags |= IAC_AUTOCONNECT;

        ll->next = iac_cfg.links;
        iac_cfg.links = ll;

        iac_irc_notice_user(src, "Added %s:%d (%s) to link table\r\n",
            ll->host, ll->port, ll->mask);

        return;
        

iac_irc_link_add_usage:
        iac_irc_notice_user(src, "Usage: LINK ADD <autoconnect> <mask> " \
            "<hhost> <port> <password> [<options>]\r\n");
        return;

    } else if ( !rl_stricmp(action, "REMOVE") ) {
        if (!arg1) {
            iac_irc_notice_user(src, "Usage: LINK REMOVE <hostmask>\r\n");
            return;
        }

        ll = iac_cfg.links;
        last = NULL;

        while ( ll ) {
            next = ll->next;
            if ( iac_match_mask(arg1, ll->host) ) {

                if (last)
                    last->next = ll->next;
                else
                    iac_cfg.links = ll->next;

                iac_irc_notice_user(src, "Removed link entry %s\r\n", ll->host);

                RL_FREE(ll->ip);
                RL_FREE(ll->host);
                RL_FREE(ll->password);
                RL_FREE(ll->options);
                RL_FREE(ll);

                ll = iac_cfg.links;
                continue;
                    
            } else {
                last = ll;
            }
            ll = next;
        }
        
            
    } else if ( !rl_stricmp(action, "CONNECT") ) {

        if ( !arg1 ) {
            iac_irc_notice_user(src, "Usage: LINK CONNECT <host>\r\n");
            return;
        }

        ll = iac_cfg.links;
        while ( ll ) {

            if ( iac_match_mask(arg1, ll->host) )
                iac_servlink_connect(src, ll);

            ll = ll->next;
        }


    } else if ( !rl_stricmp(action, "CLOSE") ) {

        if ( !arg1 ) {
            iac_irc_notice_user(src, "Usage: LINK CLOSE <host>\r\n");
            return;
        }

        li = iac_server_conns->rl_list_start;
        while (li) {
            iac_server_conn_t * sc = (iac_server_conn_t *) li->rli_data;

            if ( iac_match_mask(arg1, sc->hostname) ) {
                iac_irc_notice_user(src, "Closing server link to %s", sc->hostname);
                iac_net_close_server_conn((void *) sc);
                li = iac_server_conns->rl_list_start;
                continue;
            }
            
            li = li->rli_next;
        }
    } else if ( !rl_stricmp(action, "STOP") ) {

        if ( !arg1 ) {
            iac_irc_notice_user(src, "Usage: LINK STOP <host>\r\n");
            return;
        }

        ll = iac_cfg.links;
        last = NULL;

        while ( ll ) {
            if ( iac_match_mask(arg1, ll->host) ) {

                if ( ll->flags & IAC_RETRY ) {
                    ll->flags &= ~(IAC_DONT_RETRY);
                    ll->flags &= ~(IAC_RETRY);
                    iac_irc_notice_user(src, "Link %s no longer being retried\r\n",
                        ll->host);
                }
            }

            ll = ll->next;
        }
    }
}

void
iac_irc_rehash(iac_client_conn_t *src)
{
#ifdef IAC_PROTECT_REHASH
    /*
     * check if user is an operator
     */
    if ( !(src->flags & IAC_OPERATOR) ) {
        iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
            "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
        return;
    }
#endif

        iac_cfg.flags |= IAC_REHASH;
}

void
iac_irc_lastlog(iac_client_conn_t *src)
{
    iac_lastlog_t *ll;
    char *date;

#ifdef IAC_PROTECT_LASTLOG
    /*
     * check if user is an operator
     */
    if ( !(src->flags & IAC_OPERATOR) ) {
        iac_net_client_send(src, ":%s 481 %s :Permission Denied- " \
            "You're not an IRC operator\r\n", iac_cfg.servername, src->nick);
        return;
    }
#endif

    ll = iac_cfg.lastlog;

    while ( ll ) {

        date = asctime(localtime(&(ll->signon)));

        /*
         * remove \n from asctime
         */
        if (date[strlen(date)-1] == '\n')
            date[strlen(date)-1] = '\0';

        iac_irc_notice_user(src, "%s %s!%s@%s [%s] (%s:%d)\r\n",
            date,
            (ll->nick) ? ll->nick : "<none>",
            (ll->username) ? ll->username : "<none>",
            (ll->hostname) ? ll->hostname : "<none>",
            (ll->ircname) ? ll->ircname : "<none>",
            (ll->ip) ? ll->ip : "<none>",
            ll->port);
        
        ll = ll->next;
    }

        
}


void
iac_irc_unknown(iac_client_conn_t *src)
{
    /*
     * do not send any messages if the client isnt registerd
     * yet, it's described in the RFC i dont have to tell the
     * client about this
     */
#ifdef IAC_CMD_USAGE
    iac_cmd_usage.unknown++;
#endif

    if (src->flags & IAC_OK) {

        char * s = strtok( src->msg, " " );

        if ( s ) {
            iac_net_client_send(src, ":%s 421 %s %s :Unknown command\r\n",
                iac_cfg.servername, src->nick, s );
        }
    }
}

void
iac_irc_parse(iac_client_conn_t *src)
{
    int i;
    char *s;

    ti_trim(src->msg);

    if (src->msg[0] == '\0')
        return;
    
      iac_log(IAC_DEBUG, "[---] C %s\n", (src->msg) ? src->msg : "(null)");

    src->flags |= IAC_PONG;

#define IACMP(str, func) \
      if (rs_parse_match_fword(src->msg, str)) { func; } else

#define IACMP_OK(str, func) \
      if (rs_parse_match_fword(src->msg, str) && src->flags & IAC_OK) { func; } else

      IACMP(   "NICK",       iac_irc_nick(src)            )
      IACMP(   "USER",       iac_irc_user(src)            )
      IACMP(   "PING",       iac_irc_ping(src)            )
    IACMP(   "PONG",       iac_irc_pong(src)            )
      IACMP_OK("PRIVMSG",    iac_irc_msg(src)             )
      IACMP_OK("NOTICE",     iac_irc_msg(src)             )
      IACMP_OK("JOIN",       iac_irc_join(src)            )
      IACMP_OK("TOPIC",      iac_irc_topic(src)           )
    IACMP_OK("WHOIS",      iac_irc_whois(src)           )
    IACMP_OK("WHOWAS",     iac_irc_whowas(src)          )
    IACMP_OK("WHO",        iac_irc_who(src)             )
    IACMP(   "QUIT",       iac_irc_quit(src);           )
    IACMP_OK("PART",       iac_irc_part(src)            )
    IACMP_OK("USERHOST",   iac_irc_userhost(src)        )
    IACMP_OK("NAMES",      iac_irc_names(src)           )
    IACMP_OK("MODE",       iac_irc_mode(src)            )
    IACMP_OK("AWAY",       iac_irc_away(src)            )
    IACMP_OK("ISON",       iac_irc_ison(src)            )
    IACMP_OK("ADMIN",      iac_irc_admin(src)           )
    IACMP_OK("VERSION",    iac_irc_version(src)         )
    IACMP_OK("STATS",      iac_irc_stats(src)           )
    IACMP_OK("LIST",       iac_irc_list(src)            )
    IACMP_OK("SUMMON",     iac_irc_summon(src)          )
    IACMP_OK("USERS",      iac_irc_users(src)           )
    IACMP_OK("CONNECT",    iac_irc_connect(src)         )
    IACMP_OK("SQUIT",      iac_irc_squit(src)           )
    IACMP_OK("KILL",       iac_irc_kill(src)            )
    IACMP_OK("SYNC",       iac_irc_sync(src)            )
    IACMP_OK("LINKS",      iac_irc_links(src)           )
    IACMP_OK("OPER",       iac_irc_oper(src)            )
    IACMP_OK("MOTD",       iac_irc_motd(src)            )
    IACMP_OK("WALLOPS",    iac_irc_wallops(src)         )
    IACMP_OK("REHASH",     iac_irc_rehash(src)          )
#ifndef IAC_RFC_STRICT
    IACMP_OK("SET",        iac_irc_set(src)             )
    IACMP_OK("DUMP",       iac_irc_dump(src)            )
    IACMP_OK("THISTORY",   iac_irc_thistory(src)        )
    IACMP_OK("BLOCK",      iac_irc_block(src)           )
    IACMP_OK("UNBLOCK",    iac_irc_unblock(src)         )
    IACMP_OK("LINK",       iac_irc_link(src)            )
    IACMP_OK("LASTLOG",    iac_irc_lastlog(src)         )
#endif
      {
            iac_irc_unknown(src);
      }

    if ( src->flags & IAC_KILLED )
    {
        iac_net_close_client_conn( src );
        return;
    }

      if (!(src->flags & IAC_OK)) {
            if ( (src->flags & IAC_NICKSENT)
              && (src->flags & IAC_USERSENT)
          && (src->flags & IAC_PONG) ) {

            /*
             * check if this mask is blocked
             */
            if ( iac_cfg.blocked ) {
                iac_block_t *b = iac_cfg.blocked;
                char *iphostmask = NULL;

                asprintf(&iphostmask, "%s!%s@%s:%d[%s]",
                    src->nick, src->user, src->ip, src->port, src->ircname);

                while (b) {

                    if ( iac_match_mask(b->mask, iphostmask) ) {
                        /*
                         * sucker, gotta disconnect you
                         *
                         * :<servermask> 465 <source nick> :<reason>
                         */
                        iac_net_client_send(src, ":%s 465 %s :%s\r\n",
                            iac_cfg.servername,
                            (src->nick) ? src->nick : "*",
                            (b->reason) ? b->reason : "You are banned");

                        iac_net_close_client_conn((void *) src);

                        b->matches++;

                        RL_FREE( iphostmask );

                        return;
                    }

                    b = b->next;
                }

                RL_FREE(iphostmask);
            }

            /*
             * the hostmask must have been generated at this point, this might
             * be removed, it's just here to make sure it's really done in case
             * i forgot it somewhere.
             */
            IAC_GENHOSTMASK(src);

                  src->flags |= IAC_OK;

            if (iac_status.waiting > 0)
                iac_status.waiting--;

                  iac_net_client_send(src, ":%s 001 %s :Welcome dude\r\n",
                        iac_cfg.servername, src->nick);
                  iac_net_client_send(src, ":%s 002 %s :Your host is %s, running " \
                "version %s\r\n", iac_cfg.servername, src->nick, src->host, IAC_VERSION);
                  iac_net_client_send(src, ":%s 003 %s :This server was created in " \
                "the past\r\n", iac_cfg.servername, src->nick);
                  iac_net_client_send(src, ":%s 004 %s %s %s iwAm t\r\n",
                        iac_cfg.servername, src->nick, iac_cfg.servername, IAC_VERSION);

            iac_net_client_send(src, ":%s 251 %s :There are %d users and 0 " \
                "services on %d servers\r\n", iac_cfg.servername, src->nick,
                iac_status.total_connections, iac_status.server_connections);
            iac_net_client_send(src, ":%s 255 %s :I have %d clients and %d " \
                "servers\r\n", iac_cfg.servername, src->nick,
                iac_status.local_connections, iac_status.server_connections);

            if (iac_motd) {

                iac_net_client_send(src, ":%s 375 %s :- %s Message of the Day -\r\n",
                    iac_cfg.servername, src->nick, iac_cfg.servername);


                s = iac_motd;
                for (i=0; iac_motd[i] != '\0'; i++) {
                    if (iac_motd[i] == '\n') {
                        iac_motd[i] = '\0';
                        iac_net_client_send(src, ":%s 372 %s :- %s\r\n",
                            iac_cfg.servername, src->nick, s);
                        iac_motd[i] = '\n';
                        s = &iac_motd[i+1];
                    }
                }

                iac_net_client_send(src, ":%s 376 %s :End of MOTD command.\r\n",
                    iac_cfg.servername, src->nick);
            } else {

                iac_net_client_send(src, ":%s 422 %s :MOTD File is missing\r\n",
                    iac_cfg.servername, src->nick);
            }

            /*
             * broadcast new user to all servers
             *
             * NICK <nickname> <hopcount> <username> <host> <servertoken>
             *      <umode> <realname>
             */
            iac_server_bcast("NICK %s 0 %s %s 0 0 %s\r\n",
                src->nick, src->user, src->host, src->ircname);
            }
      }

#undef IACMP_OK
#undef IACMP
}

void
iac_irc_read_motd(const char *filename)
{
    FILE *fd;
    char line[1024];
    int size, len;

    if (!filename)
        return;

    errno = 0;
    if ( (fd = fopen(filename, "r")) == NULL ) {
        iac_log(IAC_ERR, "[EEE] - Unable to read motd file %s: %s\n",
            filename,
            strerror(errno));
    }

    iac_log(IAC_VERBOSE, "[   ] - Reading MOTD file %s\n", filename);

    memset(line, 0, sizeof(line));

    size = 0;
    while (fgets(line, sizeof(line), fd) != NULL) {

        if (!iac_motd) {
            size = (strlen(line) + 3);
            iac_motd = (char *) malloc(size);
            memset(iac_motd, 0, size);
        } else {
            len = strlen(line);
            size += (len + 2);
            iac_motd = (char *) realloc(iac_motd, size);
        }

        strcat(iac_motd, line);
    }

    fclose(fd);
}


void
iac_irc_notice_user(iac_client_conn_t *dst, const char *fmt, ...)
{
    va_list args;
    char *msg = NULL;

    va_start(args, fmt);
    vasprintf(&msg, fmt, args);
    va_end(args);

    iac_net_client_send(dst, ":%s NOTICE %s :%s\r\n",
        iac_cfg.servername,
        (dst->nick) ? dst->nick : "*",
        msg);

    RL_FREE(msg);
}

void
iac_irc_notice_ops(const char *fmt, ...)
{
    iac_client_conn_t *cc;
    rl_htable_entry_t *he;
    int i;
    char *msg = NULL;
    va_list args;

    iac_log(IAC_VERBOSE, "[   ] C Sending message to all operators\n");

    va_start(args, fmt);
    vasprintf(&msg, fmt, args);
    va_end(args);

    for (i=0; i < iac_users->rh_stsize; i++) {
        if ( (he = iac_users->rh_symtab[i].rhc_el) ) {
            while (he) {
                cc = (iac_client_conn_t *) he->rhe_data;

                /*
                 * Message is sent to all operators
                 */
                if ( cc->flags & IAC_OPERATOR ) {

                    if ( cc->flags & IAC_LOCAL ) {

                        /*
                         * operator is connected locally, send the notice
                         * direclty
                         */
                        iac_net_client_send(cc, ":%s NOTICE %s :%s\r\n",
                            iac_cfg.servername, cc->nick, msg);

                    }  else {

                        /*
                         * XXX: Maybe send messages to all operators in the
                         * network?
                         */
                    }
                }

                he = he->rhe_next;
            }
        }
    }

    RL_FREE(msg);
}

Generated by  Doxygen 1.6.0   Back to index