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

servlink.c

#define _GNU_SOURCE

#include "config.h"

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdarg.h>
#include <errno.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <time.h>

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

extern iac_server_configuration_t iac_cfg;
extern iac_server_status_t iac_status;

extern rl_htable_t *iac_users;
extern rl_htable_t *iac_channels;
extern rl_list_t *iac_server_conns;
extern rl_list_t *iac_client_conns;
extern fd_set iac_rfds;

int
p_servlink_connect(iac_client_conn_t *src, const char *server, int port,
        const char *password, const char *options, int *);

int iac_last_sync = 0;

i32 iac_msg_cache[IAC_MAX_SERVER_NUMERIC-1][IAC_MSGCACHE_SIZE];

#define IAC_SL_CHECK(cc) { \
    if (cc->msg) { \
        iac_log(IAC_WARN, "[WWW] S sc->msg not NULL for %s\n", cc->nick); \
    } \
}

void
iac_servlink_retry(const char *servername)
{
    iac_link_line_t *ll = iac_cfg.links;

    while ( ll ) {

        if ( iac_match_mask(servername, ll->host) ) {

            if ( !(ll->flags & IAC_RETRY) )
                return;

            iac_log(IAC_VERBOSE, "[   ] S Retrying connection to %d (%s:%d)\n",
                    ll->numeric, ll->host, ll->port);
            iac_servlink_connect(NULL, ll);
        }

        ll = ll->next;
    }

    
}

void
iac_servlink_autoconnect(void)
{
    iac_link_line_t *ll = iac_cfg.links;

    /*
     * do not use the code below!! pleeeeeease
     */
#if 0
    while (ll) {
        struct hostent *h;

        iac_log(IAC_DEBUG, "[   ] S Looking up %s\n", ll->host);

        if ( !(h = gethostbyname(ll->host)) ) {

            iac_log(IAC_WARN, "[WWW] S Cannot resolve hostname %s: %s\n",
                ll->host, hstrerror(h_errno));
        } else {
            ll->addr = (char *) malloc(h->h_length);
            memcpy(ll->addr, h->h_addr, h->h_length);
        }

        ll = ll->next;
    }

    ll = iac_cfg.links;
#endif

    while (ll) {

        if (ll->flags & IAC_AUTOCONNECT)
            iac_servlink_connect(NULL, ll);

        ll = ll->next;
    }
}

/*
 * hmm... only line left from attila's code? 8)
 */
void iac_server_send_ping(iac_server_conn_t *dst); /* forward declaration */

void
iac_servlink_slowretry(const char *name)
{
    iac_link_line_t *ll;

    ll = iac_cfg.links;
    while ( ll ) {
        if ( !strcmp(ll->host, name) )
            if (ll->flags & IAC_CONNECTED)
                ll->flags &= ~(IAC_SLOWRETRY);
        ll = ll->next;
    }
}

void
iac_servlink_sync_mark(void)
{
    iac_server_t *s;
    iac_client_conn_t *cc;
    rl_htable_entry_t *he;
    int i;

    iac_log(IAC_VERBOSE, "[   ] S Sync: Marking...\n");

    /*
     * mark all server entries as to be removed
     */
    s = iac_status.servers;
    while ( s ) {

        if ( s->numeric != iac_cfg.numeric )
            s->flags |= IAC_TOBEREMOVED;

        s = s->next;
    }

    /*
     * mark all client connection entries as to be removed
     */
    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;

                /*
                 * only send NICK lines for local users
                 */
                if ( !(cc->flags & IAC_LOCAL) ) {
                    cc->flags |= IAC_TOBEREMOVED;
                }

                he = he->rhe_next;
            }
        }
    }
}

void
iac_servlink_remove_server(int numeric)
{
    iac_server_t *s, *prev = NULL;

    s = iac_status.servers;
    while ( s ) {

        if ( s->numeric == numeric ) {

            iac_log(IAC_VERBOSE, "[   ] S Removed server %s (%d) [%s] " \
                "from server list\n", s->name, s->numeric, s->info);

            RL_FREE(s->name);
            RL_FREE(s->info);

            if (prev)
                prev->next = s->next;
            else
                iac_status.servers = s->next;

            RL_FREE(s->name);
            RL_FREE(s->info);
            RL_FREE(s);

            return;
        }

        prev = s;
        s = s->next;
    }

}

void
iac_servlink_sync_check(const char *unused)
{
    int i;
    iac_server_t *s, *next;
    iac_client_conn_t *cc;
    rl_htable_entry_t *he;

    iac_log(IAC_VERBOSE, "[   ] S Sync: Checking...\n");

    /*
     * Check all servers for entries with the flag to be removed
     * still set and remove them.
     */
    s = iac_status.servers;
    while ( s ) {
        next = s->next;

        if ( s->flags & IAC_TOBEREMOVED )
            iac_servlink_remove_server(s->numeric);

        s = next;
    }

    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_TOBEREMOVED ) {
                    
                    if ( cc->quitmsg )
                        RL_FREE(cc->quitmsg);


                    /*
                     * XXX: This is not as in the RFC :)
                     * i think it should be "server1 server2"
                     */
                    cc->quitmsg = strdup("Netsplit");

                    iac_net_close_client_conn((void *) cc);

                    he = iac_users->rh_symtab[i].rhc_el;

                }

                if ( he )
                    he = he->rhe_next;
            }
        }
    }
    
}

void
iac_servlink_do_sync(void)
{
    /*
     * Got a SYNC or started a SYNC between all server. Thus send
     * NICK (server-server format), NJOIN and LINK lines to all
     * servers to sync.
     */

    struct timeval tv;
    rl_htable_entry_t *he;
    iac_client_conn_t *cc;
    iac_channel_t *chan;
    iac_user_link_t *ul;
    char *msg;
    int i;

    iac_log(IAC_VERBOSE, "[   ] S Syncing...\n");


    /*
     * Set up timer to call sync_check later to remove unused
     * connections
     */
    tv.tv_sec = IAC_SYNC_WAIT_BEFORE_CHECK;
    tv.tv_usec = 0;

    ti_add_callback(&tv, &iac_servlink_sync_check, "");

    /*
     * SERVER <servername> <hopcount> <token> <info>
     */
    iac_server_bcast("SERVER %s 0 %d %s\r\n",
        iac_cfg.servername, iac_cfg.numeric, iac_cfg.info);


    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;

                /*
                 * only send NICK lines for local users
                 */
                if ( cc->flags & IAC_LOCAL ) {
                    /*
                     * NICK <nickname> <hopcount> <username> <host>
                     *      <servertoken> <umode> <realname>
                     */
                    iac_server_bcast("NICK %s 0 %s %s 0 0 %s\r\n",
                        cc->nick,
                        cc->user,
                        cc->host,
                        cc->ircname);
                }

                he = he->rhe_next;
            }
        }
    }

    for (i=0; i < iac_channels->rh_stsize; i++) {
        if ( (he = iac_channels->rh_symtab[i].rhc_el) ) {
            while (he) {
                chan = (iac_channel_t *) he->rhe_data;
                msg = NULL;

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

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

                    ul = ul->next;
                }

                if ( msg )
                    iac_server_bcast("NJOIN %s :%s\r\n",
                        chan->name, msg);

                he = he->rhe_next;
            }
        }
    }
}

void
iac_servlink_start_sync(void)
{
    /*
     * limit sync to one per minute
     */
#if 0
    if (!iac_last_sync) {
        iac_last_sync = time(0);
    } else {
        if ( (time(0) - iac_last_sync) > 60 ) {
            iac_log(IAC_VERBOSE, "[www] s throttling sync\n");
            return;
        } else {
            iac_last_sync = time(0);
        }
    }
#endif

    /*
     * Sync with all servers
     */
    iac_server_bcast("SYNC\r\n");

    iac_servlink_sync_mark();

    iac_servlink_do_sync();
}

iac_client_conn_t *
p_get_client(char *hostmask)
{
    int i, n;
    char nick[IAC_NICKLEN];

    memset(nick, 0, sizeof(nick));

    if (*hostmask != ':') {

        return ((iac_client_conn_t *) rl_hashtable_get(iac_users, hostmask));

    } else {

        for (i=1,n=0; hostmask[i] != '!' && hostmask[i] != '\0'; i++,n++) {
            if (n >= IAC_NICKLEN)
                break;
            nick[n] = hostmask[i];
        }
        nick[n] = '\0';

        ti_tolower(nick);

        return ((iac_client_conn_t *) rl_hashtable_get(iac_users, nick));
    }
}

__inline__ i32
iac_msgcache_getbitmask(int bit)
{
    i32 mask = 1;
    mask <<= (bit - 1);
    return mask;
}

void
iac_msgcache_add(int server_numeric, int message_id)
{
    static int init = 1;

    int index = message_id / 32;
    int bit = message_id % 32;

    if (init) {
        memset(&iac_msg_cache, 0, sizeof(iac_msg_cache));
        init = 0;
    }

    switch (index) {

        case 0:
            iac_log(IAC_DEBUG, "[   ] S Message Cache: Cleared " \
                "third segment\n");
            memset(&iac_msg_cache[server_numeric][IAC_MSGCACHE_S2], 0,
                    IAC_MSGCACHE_S1 * sizeof(int) );
            break;

        case IAC_MSGCACHE_S1:
            iac_log(IAC_DEBUG, "[   ] S Message Cache: Cleared " \
                "fourth segment\n");
            memset(&iac_msg_cache[server_numeric][IAC_MSGCACHE_S3], 0,
                IAC_MSGCACHE_S1 * sizeof(int) );
            break;

        case IAC_MSGCACHE_S2:
            iac_log(IAC_DEBUG, "[   ] S Message Cache: Cleared " \
                "first segment\n");
            memset(&iac_msg_cache[server_numeric], 0, IAC_MSGCACHE_S1 * sizeof(int) );
            break;

        case IAC_MSGCACHE_S3:
            iac_log(IAC_DEBUG, "[   ] S Message Cache: Cleared " \
                "second segment\n");
            memset(&iac_msg_cache[server_numeric][IAC_MSGCACHE_S1], 0,
                IAC_MSGCACHE_S1 * sizeof(int) );
            break;
    }

    if (message_id == IAC_MAX_MESSAGE_ID)
        memset(&iac_msg_cache, 0, sizeof(iac_msg_cache)/2);

    iac_msg_cache[server_numeric][index] |= iac_msgcache_getbitmask(bit);
}

int
iac_msgcache_check(int server_numeric, int message_id)
{
    int index = message_id / 32;
    int bit = message_id % 32;

    if ( iac_msg_cache[server_numeric][index] & iac_msgcache_getbitmask(bit) )
        return 1;
    else
        return 0;
}


void
iac_servlink_pass_peer(iac_server_conn_t *sc)
{
    /*
     * PASS <password> <version> <flags> [<options>]
     *
     * password: plain text
     * version: \d\d\d\d\d*
     *          first 4 must be 0210
     * flags: "IRC|<optional>"
     */

    rl_token_list_t *tl;

    /*
     * ignore further PASS messages
     */
    if (sc->flags & IAC_PASSSENT)
        return;


    /*
     * naa, the connecting server wants to authorize using the old
     * unsecure way sending a plain text password. I hope the admin
     * running this server was enough paranoid to disable this feature
     *
     * XXX: implement this ;)
     */


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

    if (tl->rtl_ntokens < 4) {
        /*
         * The RFC says we should send a ERR_NEEDMOREPARAMS but
         * I don't see any sense in this, it's probably a protocol
         * mismatch so we disconnect.
         */

        goto iac_pass_terminate_connection;

    } else {
        int i;
        char *password;
        char *version;
        char *flags;
        char *options = NULL;
        iac_link_line_t *ll;

        rl_token_get_next(tl);                  /* PASS                     */
        password = rl_token_get_next(tl);       /* password (e.g. secret)   */
        version = rl_token_get_next(tl);        /* version (e.g. 02100000)  */
        flags = rl_token_get_next(tl);          /* flags (e.g. IRC|abc)     */

        if (tl->rtl_ntokens > 4)
            options = rl_token_get_next(tl);    /* options (e.g. Z)         */

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

        if ( strlen(version) > 32 )
            version[32] = '\0';

        if ( strlen(flags) > IAC_SERVERFLAGSLEN )
            flags[IAC_SERVERFLAGSLEN] = '\0';

        /*
         * version must be at least 4 chars long
         */
        if ( strlen(version) < 4 )
            goto iac_pass_terminate_connection;

        /*
         * version must be 0210
         */
        version[4] = '\0';
        if ( strcmp(version, "0210") != 0 )
            goto iac_pass_terminate_connection;

        /*
         * flags must be IRC|, i.e. min 4 length
         */
        if ( strlen(flags) < 4 )
            goto iac_pass_terminate_connection;

        flags[4] = '\0';
        if ( strcmp(flags, "IRC|") != 0 )
            goto iac_pass_terminate_connection;


        /*
         * Try to find a matching entry in the link line table
         */
        ll = iac_cfg.links;
        while ( ll ) {

            /*
             * Matching is done using the ip address for now,
             * XXX this has to be changed if server address lookups
             * are implemented
             */
            if ( !strcmp(sc->ip, ll->host) )
                break;

            ll = ll->next;
        }

        if (!ll) {
            iac_log(IAC_WARN, "[WWW] S No link line was found: %s\n", sc->ip);
            goto iac_pass_terminate_connection;
        }

        /*
         * check if password matches
         */
        if ( strcmp(password, ll->password) != 0 ) {
            iac_log(IAC_WARN, "[WWW] S Wrong password\n");
            goto iac_pass_terminate_connection;
        }

        sc->password = strdup(password);
        

        if ( options ) {

            if ( strlen(options) > IAC_SERV_OPTIONSLEN )
                options[IAC_SERV_OPTIONSLEN] = '\0';

            for (i=0; options[i] != '\0'; i++) {

                switch(options[i])
                {
                    case 'Z':
                    case 'z':
                        /*
                         * compressed connection
                         * I'm afraid, this isn't implemented yet
                         */
                        goto iac_pass_terminate_connection;
                        break;

                    case 'M':
                    case 'm':
                        /*
                         * Use message id system
                         */
                        iac_log(IAC_VERBOSE, "[***] S Using Message ID " \
                            "System\n");
                        sc->flags |= IAC_USEMSGID;
                        break;
                    

                    default:
                        /*
                         * unknown option, ignore it
                         */
                        break;
                }
            }

            /*
             * XXX: we use the message id system for everything, as it is
             * the only thing that works
             */
            sc->flags |= IAC_USEMSGID;
            
        }

        /*
         * PASS was sent and is valid
         */
        sc->flags |= IAC_PASSSENT;

        /*
         * mark line line as connected and slave
         */
        ll->flags |= IAC_CONNECTED;
        ll->fd = sc->sock;
        ll->flags &= ~(IAC_SLOWRETRY);
        
        rl_token_del_token_list(tl);
        return;

    }

iac_pass_terminate_connection:

    iac_net_close_server_conn((void *) sc);
    
    rl_token_del_token_list(tl);
}

void
iac_servlink_server_peer(iac_server_conn_t *sc)
{
    /*
     * SERVER <servername> <hopcount> <token> <info>
     *
     * servername: hostname
     * hopcount: number of hops the server is away
     * token: server numeric
     * info: server info (may contain spaces)
     */

    rl_token_list_t *tl;
    iac_server_t *s;

    /*
     * ignore further SERVER message
     * this will also catch wrong forwarded server messages
     */
    if (sc->flags & IAC_OK || !(sc->flags & IAC_PASSSENT) )
        return;

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

    if ( tl->rtl_ntokens < 5 ) {
        /*
         * RFC says we should terminate the connection, that's ok.
         * Just to mention it, it's kinda unlogical to do it here
         * but to send a ERR_NEEDMOREPARAMS in PASS. That's why I
         * terminate the connection in the case of PASS as well.
         */

        goto iac_server_terminate_connection;

    } else {
        char *servername;
        char *token;
        char *info;
        long t;

        rl_token_get_next(tl);                    /* SERVER                  */ 
        servername = rl_token_get_next(tl);       /* servername              */
        rl_token_get_next(tl);                    /* hopcount must be 0      */
        token = rl_token_get_next(tl);            /* server token (number)   */
        info = rl_token_get_next(tl);             /* server info             */

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

        /*
         * make life of strtol easier (2^32 needs 10 characters)
         * Note: it is still possible to overflow
         */
        if ( strlen(token) > 10 )
            token[10] = '\0';

        t = strtol(token, NULL, 10);

        /*
         * check for underflow, overflow or a negative value
         */
        if ( t == LONG_MIN || t == LONG_MAX || t < 0 || t >= INT_MAX)
            goto iac_server_terminate_connection;

        /*
         * Check if the server numeric is already taken
         */
        s = iac_status.servers;
        while ( s ) {
            if (s->numeric == ((int) t)) {

                if ( sc->flags & IAC_USEMSGID ) {

                    /*
                     * When using the message id system, we have to
                     * if the numeric duplicate found is not from the
                     * server that is currently connecting. This may
                     * be true if multiple connections or link loops
                     * or made.
                     */

                    if ( rl_stricmp(s->info, info) != 0 &&
                         rl_stricmp(s->name, servername) != 0 ) {

                        iac_log(IAC_WARN, "[WWW] S Server %s (%s) tried to " \
                            "connect with already taken server numeric %d\n",
                            servername, sc->ip, (int) t);

                        goto iac_server_terminate_connection;

                    }

                } else {

                    /*
                     * Other server can't handle multiple connections
                     */

                    iac_log(IAC_WARN, "[WWW] S Server %s (%s) tried to " \
                        "connect with already taken server numeric %d\n",
                        servername, sc->ip, (int) t);

                    goto iac_server_terminate_connection;
                }
            }
            s = s->next;
        }

        if ( strlen(info) > IAC_SERVERINFOLEN )
            info[IAC_SERVERINFOLEN] = '\0';

        sc->hostname = strdup(servername);
        sc->numeric = (int) t;
        sc->info = strdup(info);
        sc->hops = 0; /* should be 0 already */

        /*
         * PASS was sent for sure, so the server connection is ready now
         */
        sc->flags |= IAC_OK;

        if ( sc->flags & IAC_MASTER ) {

            iac_servlink_start_sync();

        } else {

            char *options = NULL;

            if ( sc->flags & IAC_USEMSGID )
                iac_astrcat(&options, "m");
            

            /*
             * send PASS to connecting server
             */
            if (options)
                iac_server_send(sc, "PASS %s 0210 IRC| %s\r\n",
                    sc->password, options);
            else
                iac_server_send(sc, "PASS %s 0210 IRC|\r\n",
                    sc->password);

            RL_FREE(options);

            /*
             * send SERVER to connecting server
             */
            iac_server_send(sc, "SERVER %s 0 %d %s\r\n",
                iac_cfg.servername, iac_cfg.numeric, iac_cfg.info);
        }

        /* send ping */
        iac_server_send_ping(sc);

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

        iac_log(IAC_VERBOSE, "[***] S New server connection (%s:%d)\n",
            sc->ip, sc->port);

        iac_log(IAC_DEBUG, "[***] S New server %s [%d] 0 (%s) ", servername,
            (int) t, info);

        iac_irc_notice_ops("New server %s (%d) %s:%d [%s]\r\n",
            servername, (int) t, sc->ip, sc->port, info);

        s->name = strdup(servername);
        s->info = strdup(info);
        s->numeric = (int) t;
        s->hopcount = 0;

        s->next = iac_status.servers;
        iac_status.servers = s;

        rl_token_del_token_list(tl);
        return;
    }

iac_server_terminate_connection:

    iac_net_close_server_conn((void *) sc);

    rl_token_del_token_list(tl);
}

void
iac_servlink_ping_peer(iac_server_conn_t *sc)
{
    /*
     * PING time
     */

    rl_token_list_t *tl;

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

    if ( tl->rtl_ntokens == 2 ) {

        rl_token_get_next(tl);
        iac_server_send(sc, "PONG %s\r\n", rl_token_get_next(tl));

    } else {

        iac_server_send(sc, "PONG\r\n");
    }

    rl_token_del_token_list(tl);
     
}

void
iac_servlink_pong_peer(iac_server_conn_t *sc)
{
    /*
     * PONG time
     */

    rl_token_list_t *tl;

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

    if ( tl->rtl_ntokens != 2 ) {

        /*
         * invalid, drop
         */

    } else {

        char *seq;
        long l;

        rl_token_get_next(tl);        /* PONG           */
        seq = rl_token_get_next(tl);  /* time           */

        l = strtol(seq, NULL, 10);

        if (l != LONG_MIN && l != LONG_MAX) {
            sc->pingtime = (int) (time(0) - l);

            if (sc->pingtime < 0)
                sc->pingtime = 0;

            iac_log(IAC_DEBUG, "[   ] S PONG reply from %s:%d, lag: %ds\n",
                sc->ip, sc->port, sc->pingtime);
        }
    }
   
    /*
     * mark as pong sent even if message was a little bit incorrect
     */
    sc->flags |= IAC_PONG;

    rl_token_del_token_list(tl);

}


void
iac_servlink_nick(iac_server_conn_t *sc)
{
    rl_token_list_t *tl;
    iac_server_t *s;

    if ( !(sc->smsg.nick) ) {

        /*
         * NICK <nickname> <hopcount> <username> <host> <servertoken>
         *      <umode> <realname>
         */

        iac_log(IAC_DEBUG, "[   ] S Nick line seems to be a new nick\n");

        tl = rl_token_split(sc->smsg.msg, "       ");

        if ( tl->rtl_ntokens != 8 ) {

            /*
             * invalid, drop it
             */
            iac_log(IAC_DEBUG, "[WWW] S Invalid NICK line\n");

        } else  {

            char *nick;
            char *hopcount;
            char *username;
            char *host;
            char *token;
            char *umode;
            char *realname;
            char *tnick;
            iac_client_conn_t *cc;
            long hc;
            long tok;

            rl_token_get_next(tl);                /* NICK                  */
            nick = rl_token_get_next(tl);         /* <nick>                */
            hopcount = rl_token_get_next(tl);     /* <hopcount>            */
            username = rl_token_get_next(tl);     /* <username>            */
            host = rl_token_get_next(tl);         /* <host>                */
            token = rl_token_get_next(tl);        /* <server token>        */
            umode = rl_token_get_next(tl);        /* <umode> user mode     */
            realname = rl_token_get_next(tl);     /* <real name>           */

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

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

            hc = strtol(hopcount, NULL, 10);

            if (hc == LONG_MIN || hc == LONG_MAX ||
                hc < 0 || hc > INT_MAX) {
                iac_log(IAC_DEBUG, "[WWW] S Invalid range for Hopcount in " \
                    "NICK line\n");
                goto iac_servlink_nick_invalid;
            }

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

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

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

            tok = strtol(token, NULL, 10);

            if (tok == LONG_MIN || tok == LONG_MAX ||
                    tok < 0 || tok > INT_MAX) {
                iac_log(IAC_DEBUG, "[WWW] S Invalid range for token in " \
                    "NICK line\n");
                goto iac_servlink_nick_invalid;
            }

            if ( strlen(umode) > 64 )
                umode[64] = '\0';

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

            /*
             * NICK <nickname> <hopcount> <username> <host> <servertoken>
             *      <umode> <realname>
             */
            iac_server_forward(sc, "%d:%d:NICK %s %d %s %s %d 0 %s\r\n",
                sc->smsg.numeric,
                sc->smsg.msgid,
                nick,
                ((int) (hc + 1)),
                username,
                host,
                tok,
                realname);


            tnick = strdup(nick);
            ti_tolower(tnick);

            if ( (cc = rl_hashtable_get(iac_users, tnick)) ) {

                /*
                 * NICK lines will be received from all servers. each
                 * server sends one NICK line for each local connection.
                 */
                if (cc->flags & IAC_LOCAL) {
                    /*
                     * Nick collision. The connection is marked as local
                     * on this machine but for sure on another, because no
                     * NICK would be sent otherwise.
                     *
                     * KILL local nick
                     * The QUIT being sent should kill all connections with this
                     * nick on the net.
                     */

                    iac_log(IAC_VERBOSE, "[WWW] S Nick collision with %s\n",
                        tnick);

                    if (cc->quitmsg)
                        RL_FREE(cc->quitmsg);
                    cc->quitmsg = strdup("Killed by Server: Nick collision");

                    iac_net_close_client_conn((void *) cc);

                    RL_FREE(tnick);

                    rl_token_del_token_list(tl);
                    return;
                } else {
                    /*
                     * Nick collision, BUT the user in the hashtable over here
                     * is not marked as local and thus it is probably the same
                     * user.
                     */

                    /*
                     * Remove the to be removed mark in case a sync is in
                     * progress.
                     */
                    cc->flags &= ~(IAC_TOBEREMOVED);

                    iac_log(IAC_VERBOSE, "[   ] S Non-local nick already "\
                        "already registered: %s\n", tnick);

                    RL_FREE(tnick);
                    rl_token_del_token_list(tl);
                    return;
                }
            }


            if ( !(cc = (iac_client_conn_t *) malloc(sizeof(iac_client_conn_t))) )
                iac_log(IAC_ERR, "[EEE] - Out of memory\n");
            memset(cc, 0, sizeof(iac_client_conn_t));

            cc->nick = strdup(nick);
            cc->user = strdup(username);
            cc->host = strdup(host);

            IAC_GENHOSTMASK(cc);

            cc->hopcount = (int) hc;
            cc->ircname = strdup(realname);

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

            cc->flags |= IAC_OK;

            s = iac_status.servers;
            while ( s ) {
                if (tok == s->numeric) {
                    cc->server = s->name;
                    cc->server_info = s->info;
                }
                s = s->next;
            }


            rl_hashtable_add(iac_users, tnick, (void *) cc);

            RL_FREE(tnick);

            iac_log(IAC_DEBUG, "[***] S New client [%s]", cc->hostmask);

        }
    } else {
        /*
         * :nick NICK newnick
         */
        iac_client_conn_t *cc;
        iac_channel_link_t *cl;
        char *newnick;
        char *tnick = NULL;
        
        iac_log(IAC_DEBUG, "[   ] S NICK line seems to be a nick change\n");

        tl = rl_token_split(sc->smsg.msg, " ");

        if (tl->rtl_ntokens != 2)
            goto iac_servlink_nick_invalid;

        if ( !(cc = p_get_client(sc->smsg.nick)) )
            goto iac_servlink_nick_invalid;

        rl_token_get_next(tl);
        newnick = rl_token_get_next(tl);

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

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

        /*
         * build a lower case version of the nick
         */
        tnick = strdup(newnick);
        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 (cc->nick, newnick) != 0   ) ) {

            RL_FREE(tnick);

            /*
             * XXX: Nickname is already in use, we have to SYNC
             * or KILL... just do something goddamn..
             */
            goto iac_servlink_nick_invalid;


        } else {

            /*
             * Avoid useless nick change
             */
            if ( cc->nick ) {
                if (strcmp(cc->nick, newnick) == 0) {
                    RL_FREE(tnick);
                    goto iac_servlink_nick_invalid;
                }
            }

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

                char *tnick2 = strdup(cc->nick);
                ti_tolower(tnick2);

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

            }

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

            cc->nick = strdup(newnick);

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

            iac_log(IAC_DEBUG, "[   ] S Added nick %s to nick hashtable\n",
                tnick);

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

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

                cl = cl->next;
            }

            iac_server_forward_simple(sc);

            IAC_GENHOSTMASK(cc);
        }

    }

iac_servlink_nick_invalid:

    rl_token_del_token_list(tl);
}

void
iac_servlink_service(iac_server_conn_t *sc)
{
    /*
     * SERVICE <servicename> <servertoken> <distribution> <type>
     *         <hopcount> <info>
     */
}

void
iac_servlink_server(iac_server_conn_t *sc)
{
    /*
     * SERVER <servername> <hopcount> <token> <info>
     */

    rl_token_list_t *tl;

    tl = rl_token_split(sc->smsg.msg, "    ");

    if (tl->rtl_ntokens != 5) {

        /*
         * invalid, drop
         */
        iac_log(IAC_VERBOSE, "[WWW] S Invalid SERVER line\n");

    } else {

        char *servername;
        char *hopcount;
        char *token;
        char *info;
        iac_server_t *s;
        int found = 0;
        long hc;
        long tok;

        rl_token_get_next(tl);                 /* SERVER         */
        servername = rl_token_get_next(tl);    /* <servername>   */
        hopcount = rl_token_get_next(tl);      /* <hopcount>     */
        token = rl_token_get_next(tl);         /* <token>        */
        info = rl_token_get_next(tl);          /* <info>         */

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

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

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

        tok = strtol(token, NULL, 10);

        if ( tok == LONG_MIN || tok == LONG_MAX || tok < 0 || tok > INT_MAX ) {
            iac_log(IAC_WARN, "[WWW] S Invalid token range in SERVER line\n");
            goto iac_servlink_server_invalid;
        }

        if ( strlen(info) > IAC_SERVERINFOLEN )
            info[IAC_SERVERINFOLEN] = '\0';

        hc = strtol(hopcount, NULL, 10);

        if ( hc == LONG_MIN || hc == LONG_MAX || hc < 0 || hc > INT_MAX ) {
            iac_log(IAC_VERBOSE, "[WWW] S Invalid hopcount range in " \
                "SERVER line\n");
            goto iac_servlink_server_invalid;
        }


        s = iac_status.servers;
        while ( s ) {

            if ( s->numeric == tok ) {

                /*
                 * server entry does already exist, thus server is still
                 * up and running and we should mark the entry as not to
                 * be removed in case a sync is in progress
                 */
                
                found = 1;
                s->flags &= ~(IAC_TOBEREMOVED);
            }

            s = s->next;
        }

        if ( !found ) {

            /*
             * server is new, allocate new entry and add it to list
             */

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

            s->name = strdup(servername);
            s->info = strdup(info);
            s->hopcount = (int) hc;
            s->numeric = sc->smsg.numeric;

            s->next = iac_status.servers;
            iac_status.servers->next = s;
        }

        /*
         * SERVER <servername> <hopcount> <token> <info>
         */
        iac_server_forward(sc, "%d:%d:SERVER %s %d %d %s\r\n",
            sc->smsg.numeric,
            sc->smsg.msgid,
            servername,
            (((int) hc) + 1),
            tok,
            info);
        
        rl_token_del_token_list(tl);
        return;


    }

iac_servlink_server_invalid:

    iac_log(IAC_VERBOSE, "[WWW] S Invalid SERVER line\n");

    rl_token_del_token_list(tl);
}

void
iac_servlink_squit(iac_server_conn_t *sc)
{
    /*
     * SQUIT <server> <comment>
     * server has been disconnected
     * 
     * or
     *
     * :hostmask SQUIT <server> <comment>
     */

    if ( sc->smsg.nick ) {

        /*
         * XXX
         * Yes i think this is useful *g*
         */
        
    } else {
    }
}

void
iac_servlink_njoin(iac_server_conn_t *sc)
{
    /*
     * :<source> NJOIN <channel> :<nick1> <nick2> <nick3>
     *
     * nicks can be prefixed with @@, @ or +
     * @@ = channel creator
     * @ = channel operator (will be ignored)
     * + = user has voice (will be ignored)
     */

    char *channel, *nicks, *nick;
    int len, i;
    iac_client_conn_t *c;

    channel = strchr(sc->smsg.msg, ' ');

     if ( !channel ) {
         iac_log(IAC_DEBUG, "[WWW] S Dropping invvalid NJOIN message\n");
        return;
     }

     channel++;

     nicks = strchr(channel, ' ');

     if (!nicks) {
         iac_log(IAC_DEBUG, "[WWW] S Dropping invvalid NJOIN message\n");
        return;
     }

     *nicks = '\0';
     nicks++;

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

     if (*nicks == '\0') {
         iac_log(IAC_DEBUG, "[WWW] S Dropping invvalid NJOIN message\n");
        return;
     }

     nick = nicks;
     len = strlen(nicks);

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

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

            /*
             * XXX: nicks can have prefixes like @@ or @
             */

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

            ti_tolower(nick);

            c = p_get_client(nick);

            if ( c ) {

                IAC_SL_CHECK(c);

                if (c->msg)
                     RL_FREE(c->msg);
                asprintf( &(c->msg), "JOIN %s", channel);

                iac_irc_join(c);
                RL_FREE(c->msg);
            } else {
                iac_log(IAC_VERBOSE, "[WWW] S NJOIN: Nick %s does not exist\n",
                    nick);
            }

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


    /*
     * forward the message to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_mode(iac_server_conn_t *sc)
{
    /*
     * MODE
     */
}

void
iac_servlink_privmsg(iac_server_conn_t *sc)
{
    /*
     * :hostmask PRIVMSG target :text
     * :hostmask NOTICE target :text
     */

    iac_client_conn_t *c = p_get_client(sc->smsg.nick);

    if (c) {
        IAC_SL_CHECK(c);
        c->msg = sc->smsg.msg;
        iac_irc_msg(c);
        c->msg = NULL;
    }

    /*
     * forward to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_join(iac_server_conn_t *sc)
{
    /*
     * :hostmask JOIN channel,channel2,channel3
     */

    iac_client_conn_t *c = p_get_client(sc->smsg.nick);

    if (c) {
        IAC_SL_CHECK(c);
        c->msg = sc->smsg.msg;
        iac_irc_join(c);
        c->msg = NULL;
    }

    /*
     * forward to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_topic(iac_server_conn_t *sc)
{
    /*
     * :hostmask TOPIC channel :topic
     */

    iac_client_conn_t *c = p_get_client(sc->smsg.nick);

    if (c) {
        IAC_SL_CHECK(c);
        c->msg = sc->smsg.msg;
        iac_irc_topic(c);
        c->msg = NULL;
    }

    /*
     * forward to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_part(iac_server_conn_t *sc)
{
    /*
     * :hostmask PART channel :quitmsg
     */

    iac_client_conn_t *c = p_get_client(sc->smsg.nick);

    if (c) {
        IAC_SL_CHECK(c);
        c->msg = sc->smsg.msg;
        iac_irc_part(c);
        c->msg = NULL;
    }

    /*
     * forward to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_quit(iac_server_conn_t *sc)
{
    /*
     * :hostmask QUIT quitmessage
     */

    iac_client_conn_t *c = p_get_client(sc->smsg.nick);

    if (c) {

        char *qs = strchr(sc->smsg.msg, ' ');

        if (*qs == ' ')
            qs = &qs[1];

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

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

        c->quitmsg = strdup(qs);

        if (c->flags & IAC_LOCAL) {

            /*
             * QUIT produced by another server affecting a local user.
             * Thus it's not a ping timeout or user requested quit. probably
             * a kill due another user or nick collision.
             */
            iac_net_close_client_conn(c);

        } else {

            /*
             * QUIT prodcued by another server affecting a non-local user.
             * The reason doesn't matter, just quit him.
             */
            iac_irc_real_quit(c);
        }
    }

    /*
     * forward the message to all servers
     */
    iac_server_forward_simple(sc);
}

void
iac_servlink_sync(iac_server_conn_t *sc)
{
    /*
     * SYNC
     */

    /*
     * limit sync to one per minute
     */
#if 0
    if (!iac_last_sync) {
        iac_last_sync = time(0);
    } else {
        if ( (time(0) - iac_last_sync) > 60 ) {
            iac_log(IAC_VERBOSE, "[www] s throttling sync\n");
            return;
        } else {
            iac_last_sync = time(0);
        }
    }
#endif


    iac_servlink_sync_mark();

    /*
     * forward to all servers
     */
    iac_server_forward_simple(sc);

    iac_servlink_do_sync();
}


void
iac_servlink_parse(iac_server_conn_t *sc)
{
    int i;
    rl_token_list_t *tl = NULL;

    rl_remove_eol(sc->msg);
    ti_trim(sc->msg);

    if ( sc->msg[0] == '\0' )
        return;

    iac_log(IAC_DEBUG, "[---] S %s\n", sc->msg);

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


    if ( rs_parse_match_fword(sc->msg, "PASS") &&
            !(sc->flags & IAC_OK) ) {

        iac_servlink_pass_peer(sc);

    } else if ( rs_parse_match_fword(sc->msg, "SERVER") &&
            !(sc->flags & IAC_OK) ) {
        
        iac_servlink_server_peer(sc);

    } else
    IACMP("PING", iac_servlink_ping_peer(sc)            )
    IACMP("PONG", iac_servlink_pong_peer(sc)            )
    {
        if ( sc->flags & IAC_OK ) {

            /*
             * If the server is using message id system:
             * <server numeric>:<message id>:<message>
             *
             * otherwise:
             * <message>
             *
             */

            if ( sc->flags & IAC_USEMSGID ) {

               long servn, msgid;
               char *msg;

                sc->omsg = strdup(sc->msg);

                tl = rl_token_split(sc->msg, "::");

                /*
                 * throw away invalid messages
                 */
                if ( tl->rtl_ntokens != 3 )
                    goto invalid_server_line;

#undef IACMP
#define IACMP(str, func) \
        if (rs_parse_match_fword(sc->smsg.msg, str)) { func; } else

                   servn = strtol(rl_token_get_next(tl), NULL, 10);
                   msgid = strtol(rl_token_get_next(tl), NULL, 10);

                   /*
                    * catch values out of range
                    */
                   if ( servn == LONG_MIN || servn == LONG_MAX ||
                        msgid == LONG_MAX || msgid == LONG_MAX ||
                        servn >= INT_MAX || msgid >= INT_MAX  ||
                        servn < 0 || msgid < 0)
                       goto invalid_server_line;

                   sc->smsg.numeric = (int) servn;
                   sc->smsg.msgid = (int) msgid;
                   msg = rl_token_get_next(tl);

                   if ( strlen(msg) > IAC_PARSELEN )
                       msg[IAC_PARSELEN] = '\0';

                   sc->smsg.msg = msg;

                   if (msg[0] == ':') {
                       sc->smsg.nick = msg;
                       for (i=0; msg[i] != '\0'; i++)
                           if ( msg[i] == ' ' )
                               break;

                       if (msg[i] != ' ')
                           goto invalid_server_line;

                       sc->smsg.msg[i] = '\0';
                       sc->smsg.msg = &msg[i+1];
                   }

                   iac_log(IAC_DEBUG, "[   ] S %d | %d | %s\n",
                        sc->smsg.numeric,
                        sc->smsg.msgid,
                        sc->smsg.msg);

                   /*
                    * throw away messages generated by myself
                    */
                   if (sc->smsg.numeric == iac_cfg.numeric) {
                       iac_log(IAC_DEBUG, "[XXX] S msg %d back again\n",
                            sc->smsg.msgid);
                       goto invalid_server_line;
                   }

                   if ( iac_msgcache_check(sc->smsg.numeric, sc->smsg.msgid) ) {
                       iac_log(IAC_DEBUG, "[XXX] S msg %d was already here\n",
                            sc->smsg.msgid);
                       goto invalid_server_line;
                   }

                   /*
                    * add message id of message to avoid the same
                    * message to be parsed multiple times
                    */
                   iac_msgcache_add(sc->smsg.numeric, sc->smsg.msgid);

                } else {

                    /*
                     * Message sent without message id system
                     */
                    char *msg;

                    sc->omsg = strdup(sc->msg);
                    
                    msg = sc->msg;

                    if ( strlen(msg) > IAC_PARSELEN )
                        msg[IAC_PARSELEN] = '\0';

                    sc->smsg.msg = msg;

                    if (msg[0] == ':') {
                        sc->smsg.nick = msg;
                        for (i=0; msg[i] != '\0'; i++)
                            if ( msg[i] == ' ' )
                                break;

                        if (msg[i] != ' ')
                            goto invalid_server_line;

                        sc->smsg.msg[i] = '\0';
                        sc->smsg.msg = &msg[i+1];
                    }
                }

                iac_log(IAC_VERBOSE, "[---] S [%s] %s\n",
                    (sc->smsg.nick) ? sc->smsg.nick : "none", 
                    sc->smsg.msg);


               IACMP("NICK", iac_servlink_nick(sc)         )
               IACMP("SERVICE", iac_servlink_service(sc)   )
               IACMP("SERVER", iac_servlink_server(sc)     )
               IACMP("SQUIT", iac_servlink_squit(sc)       )
               IACMP("JOIN", iac_servlink_join(sc)         )
               IACMP("NJOIN", iac_servlink_njoin(sc)        )
               IACMP("MODE", iac_servlink_mode(sc)         )
               IACMP("PRIVMSG", iac_servlink_privmsg(sc)   )
               IACMP("NOTICE", iac_servlink_privmsg(sc)    )
               IACMP("TOPIC", iac_servlink_topic(sc)       )
               IACMP("PART", iac_servlink_part(sc)         )
               IACMP("QUIT", iac_servlink_quit(sc)         )
               IACMP("SYNC", iac_servlink_sync(sc)         )
               {
                   iac_log(IAC_DEBUG, "[WWW] S Unknown server message: %s\n",
                        sc->smsg.msg);
               }

               memset(&(sc->smsg), 0, sizeof(iac_server_msg_t));

invalid_server_line:

            RL_FREE(sc->omsg);

            if (tl)
                rl_token_del_token_list(tl);
        }
    }

#undef IACMP
}


void
iac_server_send_simple(iac_server_conn_t *dst, char *msg)
{
    int len = strlen(msg);
    
    if ( len > IAC_SERVERMSGLEN ) {
        msg[IAC_SERVERMSGLEN] = '\0';
        len = strlen(msg);
    }

    if ( !(dst->flags & IAC_USEMSGID) ) {
        char *s;

        if ( (s = strchr(msg, ':')) ) {
            s++;
            if ( (s = strchr(s, ':')) ) {
                s++;
                msg = s;
                iac_log(IAC_DEBUG, "[   ] S Cutted server message\n");
            }
        }
    }

    iac_log(IAC_RAW, "[-->] S (%s:%d) %s",
            dst->ip ? dst->ip : "unknown",
            dst->port,
            msg);

    dst->bytes_sent += len;
    dst->msgs_sent++;

    send(dst->sock, msg, len, 0);
}


void
iac_server_send(iac_server_conn_t *dst, const char *fmt, ...)
{
    va_list args;
    char *msg;

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

    iac_server_send_simple(dst, msg);

    free(msg);
}

void
iac_server_bcast(const char *fmt, ...)
{
    static int msgid = 1;
    va_list args;
    char *msg, *real_msg;
    rl_list_item_t *li;

    if ( !iac_server_conns->rl_list_start )
    {
        return;
    }

    if ( msgid > IAC_MAX_MESSAGE_ID )
        msgid = 1;

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

    asprintf(&real_msg, "%d:%d:%s", iac_cfg.numeric, msgid, (msg) ? msg : "(null)");

    iac_log(IAC_RAW, "[**>] %s", real_msg);

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

        /*
         * Server must be fully initialized before getting server
         * messages. otherwise everyone could connect to the server
         * port and read all messages
         */
        if ( sc->flags & IAC_OK )
            iac_server_send_simple(sc, real_msg);

        li = li->rli_next;
    }

    free(msg);
    free(real_msg);

    msgid++;
}

void
iac_server_forward_simple(iac_server_conn_t *src)
{
    rl_list_item_t *li;

    li = iac_server_conns->rl_list_start;
    while (li) {
        iac_server_conn_t *dst = (iac_server_conn_t *) li->rli_data;
        if (src != dst && dst->flags & IAC_OK ) {
            iac_log(IAC_RAW, "[==>] S (%s:%d) %s", dst->ip, dst->port, src->omsg);
            iac_server_send(dst, "%s\r\n", src->omsg);
        }
        li = li->rli_next;
    }
}

void
iac_server_forward(iac_server_conn_t *src, const char *fmt, ...)
{
    rl_list_item_t *li;
    va_list args;
    char *msg;

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

    li = iac_server_conns->rl_list_start;
    while (li) {
        iac_server_conn_t *dst = (iac_server_conn_t *) li->rli_data;
        if (src != dst && dst->flags & IAC_OK) {
            iac_log(IAC_RAW, "[==>] S (%s:%d) %s", dst->ip, dst->port, msg);
            iac_server_send(dst, "%s\r\n", msg);
        }
        li = li->rli_next;
    }
}

int
iac_servlink_connect(iac_client_conn_t *src, iac_link_line_t *ll)
{
    struct timeval tv;

    if ( p_servlink_connect(src, ll->host, ll->port,
            ll->password, ll->options, &(ll->fd)) ) {

        ll->flags |= IAC_FAILED;
        ll->flags |= IAC_RETRY;
        ll->fd = 0;

        iac_log(IAC_VERBOSE, "[WWW] S Connection failed, I'll retry " \
            "in %d seconds\n", IAC_SERVLINK_RETRY_TIME);

        tv.tv_sec = IAC_SERVLINK_RETRY_TIME;
        tv.tv_usec = 0;

        ti_add_callback(&tv, &iac_servlink_retry, ll->host);

        return 1;
    } else {
        ll->flags |= IAC_CONNECTED;
        ll->flags |= IAC_MASTER;
        ll->flags &= ~(IAC_SLOWRETRY);
        ll->flags &= ~(IAC_FAILED);
        ll->flags &= ~(IAC_RETRY);

        return 0;
    }
}

int
p_servlink_connect(iac_client_conn_t *src, const char *server, int port,
        const char *password, const char *options, int *rfd)
{
    iac_server_conn_t *sc;

    if ( !(sc = (iac_server_conn_t *) malloc(sizeof(iac_server_conn_t))) )
        iac_log(IAC_ERR, "[EEE] S Unable to allocate memory for server handle\n");
    memset(sc, 0, sizeof(iac_server_conn_t));

    errno = 0;
    if ( (sc->sock = socket(PF_INET, SOCK_STREAM, 0)) < 0)
        iac_log(IAC_ERR, "[EEE] S Could not create socket: %s\n", strerror(errno));

    sc->addr.sin_family = AF_INET;
    sc->addr.sin_port = htons(port);

    errno = 0;
    if ( inet_pton(AF_INET, server, &(sc->addr.sin_addr)) < 0 ) {

        if ( src ) {
            iac_irc_notice_user(src, "While linking to (%s:%d): Could not " \
                "resolve %s: %s", server, port, server, strerror(errno));
        } else {
            iac_log(IAC_WARN, "[WWW] S Could not resolve %s: %s\n", server,
                strerror(errno));
        }

        free(sc);
        return 1;
    }

    sc->addrlen = sizeof(sc->addr);

    if ( connect(sc->sock, (struct sockaddr *) &(sc->addr),
            sc->addrlen) < 0 ) {

        if ( src ) {
            iac_irc_notice_user(src, "While linking to (%s:%d): Could not " \
                "connect to server %s: %s", server, port, server, strerror(errno));
        } else {
            iac_log(IAC_WARN, "[WWW] S Could not connect to server (%s:%d): %s\n",
                server, port, strerror(errno));
        }

        free(sc);
        return 1;
    }

    /*
     * get address of peer (the client)
     */
      errno = 0;
      if (getpeername(sc->sock, (struct sockaddr *) &sc->addr, &sc->addrlen) < 0) {

        if ( src ) {
            iac_irc_notice_user(src, "While linking to (%s:%d): getpeername() " \
                "failed: %s", server, port, strerror(errno));
        } else {
            iac_log(IAC_WARN, "[WWW] S getpeername() failed: %s\n",
                    strerror(errno));
        }

        close(sc->sock);
            free(sc);
            return 1;
      }

    /*
     * save ip address and cut it
     */
    sc->ip = strdup(inet_ntoa(sc->addr.sin_addr));
    if ( strlen(sc->ip) > IAC_HOSTLEN )
        sc->ip[IAC_HOSTLEN] = '\0';

    sc->port = ntohs(sc->addr.sin_port);

    sc->time_init = time(0);

    FD_SET(sc->sock, &iac_rfds);

    rl_list_add(iac_server_conns, (void *) sc, FALSE);

    iac_sbsock();

    sc->flags |= IAC_MASTER;

    if ( src ) {
        iac_irc_notice_user(src, "Now linked to %s:%d", server, port);
    }

    iac_irc_notice_ops("New server connection (%s:%d)\r\n",
        sc->ip, sc->port);

    iac_log(IAC_VERBOSE, "[***] S New server connection (%s:%d)\n",
        sc->ip, sc->port);

    if ( options )
        iac_server_send(sc, "PASS %s 0210 IRC| %s\r\n", password, options);
    else
        iac_server_send(sc, "PASS %s 0210 IRC|\r\n", password);

    iac_server_send(sc, "SERVER %s 0 %d %s\r\n",
        iac_cfg.servername, iac_cfg.numeric, iac_cfg.info);

    *rfd = sc->sock;

    return 0;
}


/* send a ping to a server */
void iac_server_send_ping(iac_server_conn_t *dst)
{
   dst->flags &= ~(IAC_PONG);
   iac_server_send(dst,"PING %ld\r\n", time(0) );
}   

   

/* check if there was a pong in time */
void iac_servlink_check_pong(const char *unused)
{
    iac_server_conn_t *src;
    rl_list_item_t *li;
    struct timeval tv;
    
    iac_log(IAC_DEBUG, "[   ] S Checking for PONG replies\n");
    
    li = iac_server_conns->rl_list_start;

    while(li) {

        src = (iac_server_conn_t *) li->rli_data;

        if ( src->flags & IAC_PONG ) {

            /* recived a pong, send new ping */
            iac_server_send_ping(src);

        } else {

            /* connection timed out */
            /* disconnect */

            /*
             * XXX
             * give reason
             */
            
            iac_net_close_server_conn(src);

            /*
             * li points to nirvana
             */
            li = iac_server_conns->rl_list_start;
            continue;

        }

        li = li->rli_next;
    }


    /* restart timer */

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

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

Generated by  Doxygen 1.6.0   Back to index