muddy/
muddy/CVS/
muddy/area/
muddy/area/CVS/
muddy/clans/CVS/
muddy/classes/CVS/
muddy/doc/
muddy/doc/CVS/
muddy/etc/CVS/
muddy/etc/i3/
muddy/etc/i3/CVS/
muddy/imc/CVS/
muddy/lang/CVS/
muddy/licenses/CVS/
muddy/msgdb/CVS/
muddy/new/CVS/
muddy/notes/
muddy/player/
muddy/races/CVS/
muddy/religions/CVS/
muddy/src/CVS/
muddy/src/comm/CVS/
muddy/src/db/CVS/
muddy/src/intermud/
muddy/src/intermud/CVS/
muddy/src/irc/CVS/
muddy/src/olc/CVS/
/* $Id: comm_irc.c,v 1.666 2004/09/20 10:50:27 shrike Exp $ */
/*
 *  COMM_IRC patch v1.4
 *  Low-level IRC connecting module for SMAUG 1.4
 *  Copyright (C) 1998-2002 by Pedro J. Romero Duhau (Cronel)
 *  Based on original code by Arnold Hendriks (Unilynx) for SMAUG 1.0
 */

#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include "merc.h"
/* Socket and TCP/IP stuff.*/
#include <arpa/inet.h>
#include <netdb.h>
#include <errno.h>
#define closesocket close
#ifdef sun
int gethostname ( char *name, int namelen );
#endif
#ifndef MAXHOSTNAMELEN
#define MAXHOSTNAMELEN 255
#endif

/* 
 * Globals 
 */
#define		MESC			    '\x3'  /* mirc color escape */
#define		RECONNECT_TIMEOUT	(20)   /* seconds to wait before before trying to reconnect */

IRCDATA     ircdata;                   /* The irc settings */
unsigned    long login_timeout;        /* Time to wait for response */
bool        as_server;                 /* True; we're connected as a server, False; as client. */
int         irc_control;               /* The irc controlling socket. If -1, we're not connected */
bool        got_response = FALSE;      /* If true, we've gotten a response from the server */
bool        logged_in = FALSE;         /* If true, we're playable */
bool        should_reconnect = FALSE;  /* TRUE if we're down and have to attempt to reconnect */
unsigned long   reconnect_timeout;     /* time at wich we'll try to connect again */

NICK_DATA * first_nick;                /* list of known nicks */
NICK_DATA * last_nick;
/* 
 * In server mode, this list of nicks contains all the nicks in the network.
 * In client mode, only the nicks joined to the mud channel. In server mode
 * the user & host field are allways set; in client mode they're NULL until
 * the user tries to login. If the user logs in via DCC (!login), they are
 * never set in the nick in client mode. See msg_from_known_nick().
 */

/* DCC data */
bool        dcc_open = FALSE;   /* if true, we're waiting for a dcc connection */
unsigned    long dcc_timeout;   /* at this time we'll stop waiting */
NICK_DATA * dcc_nick;           /* nick we're waiting for */
int         dcc_control = -1;   /* the controlling socket for dcc connections */
unsigned long   dcc_host = -1;  /* our host number, used to send DCC chat requests */
/* 
 * Basicaly, DCC descriptors exist so that we can place the restriction that
 * players connected via DCC stay in the network while playing. So we need
 * to associate them with a nick, so that if this nick quits, we know what
 * connection to close. 
 */

char        vbuf  [ 8192 ];     /* general purpose buffers for */
char        vbuf2 [ 8192 ];     /* vsprintf */


/* 
 * Local functions 
 */
int         sock_printf(int fd, char *fmt, ...);
NICK_DATA * find_nick( NICK_DATA *list, char *nick );
void        new_irc_descriptor( NICK_DATA *n, char *user, char *host );
void        free_irc_desc( DESCRIPTOR_DATA *d );
void        free_dcc_desc( DESCRIPTOR_DATA *d );
bool        quit_nick( char *nick );
void        check_restrictions( DESCRIPTOR_DATA *d );
void        send_greeting( DESCRIPTOR_DATA *desc );

void        dcc_init( struct hostent *hp );
void        dcc_deinit( void );
void        dcc_close( void );
void        dcc_open_chat( NICK_DATA *n );
void        dcc_waiting( void );

void        ircstart( void );
void        disconnect_from_irc( char *quit_msg );
void        connect_to_irc( void );
void        set_reconnect( void );
int         connect_to_server( const char *server, const int port );
void        irc_game_loop( void );

void        process_one_line( char *cmdline );
void        process_error( char *source, char *parm );
void        process_names_reply( char *source, char *parm );
void        process_no_such_nick( char *source, char *parm );
void        process_kill( char *source, char *parm );
void        process_ping( char *source, char *parm );
void        process_quit( char *source, char *parm );
void        process_nick( char *source, char *parm );
void        process_join( char *source, char *parm );
void        process_part( char *source, char *parm );
void        process_privmsg( char *source, char *parm );
void        msg_from_playing_nick( NICK_DATA *n, char *txt );
void        msg_from_known_nick( NICK_DATA *n, char *source, char *txt );
void        flushing_nanny( DESCRIPTOR_DATA *desc, char *txt );
void        msg_from_unknown_nick( char *nick );
void        process_notice( char *source, char *parm );
void        process_ctcp( char *nick, char *parm );
void        process_forcedo_ctcp( char *nick, char *parm );

sh_int      b_check_color( char *p, int len );
bool        b_get_bleeding( char *buf );
void        b_advance_line( void );
bool        write_low_level( DESCRIPTOR_DATA *d, char *txt, int len, bool use_privmsg );
void        write_to_nick( char *nick, char *buf, bool use_privmsg );
void        nick_printf (char *nick, bool use_privmsg, char *fmt, ...);
void        write_to_channel( char *msg, bool use_privmsg );
bool        create_color_code( sh_int color, CHAR_DATA *ch, char *buf );
bool        create_special_code( char code, CHAR_DATA *ch, char *buf );
void        send_mirc_sound( CHAR_DATA *ch, char *sound_filename );

void        fread_ircdata( IRCDATA *ircd, FILE *fp );
bool        load_ircdata( IRCDATA *ircd );
void        save_ircdata( IRCDATA ircd );

bool        is_auth_nickserv( DESCRIPTOR_DATA *d );
void        msg_from_nickserv( char *cmd, char *txt );

/* 
 * Functions from comm.c
 */
void        save_sysdata( SYSTEM_DATA sys ); /* db.c */
void        free_desc( DESCRIPTOR_DATA *d ); /* comm.c */
int         init_socket( int port );
DESCRIPTOR_DATA * accept_new( int ctrl );
void        nanny( DESCRIPTOR_DATA *d, char *txt );
bool        pager_output( DESCRIPTOR_DATA *d );
bool        flush_buffer( DESCRIPTOR_DATA *d, bool fprompt );
bool        write_to_descriptor( int desc, char *txt, int len );

/*
 * IRC command table for processing 
 */
struct irc_cmd irc_cmd_table[] = 
{
    {"PING",    "",     process_ping},
    {"KILL",    "K",    process_kill},
    {"QUIT",    "Q",    process_quit}, 
    {"401",     "",     process_no_such_nick}, /* ERR_NOSUCHNICK */
    {"NICK",    "I",    process_nick}, 
    {"NOTICE",  "N",    process_notice},
    {"PRIVMSG", "P",    process_privmsg},
    {"353",     "",     process_names_reply}, /* RPL_NAMREPLY */
    {"JOIN",    "",     process_join}, 
    {"PART",    "",     process_part}, 
    {"ERROR",   "",     process_error},
    {NULL,      "",     NULL}
};




/**** Miscelaneous Functions *************************************************/

int sock_printf (int fd, char *fmt, ...)
/* simple socket output function */
{
    va_list Args;

    va_start (Args, fmt);
    vsprintf (vbuf, fmt, Args);
    write( fd, vbuf, strlen(vbuf) );
    va_end( Args );
    return strlen (vbuf);
}


NICK_DATA *find_nick( NICK_DATA *list, char *nick )
/* look for a nick in a list of nicks. return NULL if not found */
{
    NICK_DATA *n;

    for (n = list ; n ; n = n->next)
    {
        if (!str_cmp(n->nick, nick))
            break;
    }
    return n;
}


void new_irc_descriptor( NICK_DATA *n, char *user, char *host )
/* just a modified version of new_descriptor that uses a nick_data
   instead of an actual descriptor; creates IRC descriptors. */
{
    DESCRIPTOR_DATA *dnew;
    char buf[MAX_STRING_LENGTH];

    CREATE( dnew, DESCRIPTOR_DATA, 1 );
    dnew->next      = NULL;
    dnew->descriptor    = -1;
    dnew->connected = CON_GET_NAME;
    dnew->outsize   = 2000;
    dnew->idle      = 0;
    dnew->lines     = 0;
    dnew->scrlen    = 24;
    dnew->port      = 0;
    dnew->user      = STRALLOC("(unknown)");
    dnew->newstate  = 0;
    dnew->prevcolor = 0x07;

    CREATE( dnew->outbuf, char, dnew->outsize );

    n->user = STRALLOC( user );
    n->host = STRALLOC( host );
    dnew->host = STRALLOC( host );
    dnew->user = STRALLOC( user );
    sprintf( log_buf, "Incoming nick: %s!%s@%s.", n->nick, n->user, n->host );
    log_string_plus( log_buf, LOG_COMM, sysdata.log_level );

    /* interlink them */
    dnew->nick_data = n;
    n->desc = dnew;

    if (check_total_bans( dnew ))
    {
        nick_printf( n->nick, FALSE, "Your site has been banned from this Mud.\r" );
        free_irc_desc (dnew);
        return;
    }
    /*
     * Init descriptor data.
     */

    if (!last_descriptor && first_descriptor) /* cut & pasted */
    {
        DESCRIPTOR_DATA *d;

        ircbug( "New_irc_descriptor: last_desc is NULL, but first_desc is not! ...fixing" );
        for (d = first_descriptor; d; d = d->next)
            if (!d->next)
                last_descriptor = d;
    }

    LINK( dnew, first_descriptor, last_descriptor, next, prev );

    /*
     * Send the greeting.
     */
    send_greeting( dnew );

    if (++num_descriptors > sysdata.maxplayers)
        sysdata.maxplayers = num_descriptors;
    if (sysdata.maxplayers > sysdata.alltimemax)
    {
        if (sysdata.time_of_max)
            DISPOSE(sysdata.time_of_max);
        sprintf(buf, "%24.24s", ctime(&current_time));
        sysdata.time_of_max = str_dup(buf);
        sysdata.alltimemax = sysdata.maxplayers;
        sprintf( log_buf, "Broke all-time maximum player record: %d", sysdata.alltimemax );
        log_string_plus( log_buf, LOG_COMM, sysdata.log_level );
        to_channel( log_buf, CHANNEL_MONITOR, "Monitor", LEVEL_IMMORTAL );
        save_sysdata( sysdata );
    }
    return;
}

#define MAX_GREETING (60)
void send_greeting( DESCRIPTOR_DATA *desc )
/* New function to send random greeting screens. */
{
    HELP_DATA *pHelp, *pGreeting[ MAX_GREETING ];
    sh_int i, greeting_index;

    i = 0;
    for (pHelp = first_help ; pHelp ; pHelp = pHelp->next)
    {
        if (!strncasecmp( pHelp->keyword, "greeting", 8))
        {
            pGreeting[i] = pHelp;
            i++;
            if (i == MAX_GREETING)
                break;
        }
    }

    greeting_index = number_range( 0, i-1 );

    write_to_buffer( desc, pGreeting[greeting_index]->text, 0 );
    write_to_buffer( desc, "Enter your character name or type new: ", 0 );
}

void free_irc_desc( DESCRIPTOR_DATA *d )
/* a free_desc clone for IRC descs. if in client mode, it also frees
   the user@host data from the nick, thats not supposed to be there in
   that mode */
{
    if (!d->nick_data)
    {
        ircbug( "free_irc_desc: HORROR! no d->nick! " );
        return;
    }
    if (!as_server)
    {
        if (d->nick_data->host)
            STRFREE(d->nick_data->host);
        if (d->nick_data->user)
            STRFREE(d->nick_data->user);
    }
    d->nick_data->desc = NULL;

    STRFREE( d->host );
    DISPOSE( d->outbuf );
    STRFREE( d->user );
    if (d->pagebuf)
        DISPOSE( d->pagebuf );
    DISPOSE( d );
}


void free_dcc_desc( DESCRIPTOR_DATA *d )
/* another free_desc clone, for DCC descs */
{
    if (!d->nick_data)
    {
        ircbug( "free_dcc_desc: HORROR! no d->nick! " );
        return;
    }
    if (!as_server)
    {
        if (d->nick_data->host)
            STRFREE(d->nick_data->host);
        if (d->nick_data->user)
            STRFREE(d->nick_data->user);
    }
    d->nick_data->desc = NULL;
    free_desc( d );
}


void discard_nick_list( void )
{
    NICK_DATA *n, *n_next;

    for (n = first_nick ; n ; n = n_next)
    {
        n_next = n->next;
        quit_nick( n->nick );
    }
    first_nick = last_nick = NULL;  
}

bool quit_nick( char *nick )
/* a nick has quit from irc. remove him from the game and from the nick list */
{
    NICK_DATA *n;

    n = find_nick( first_nick, nick );
    if (!n)
        return FALSE;

#ifdef ALLOW_PURE_DCC
    if (n->desc && IS_DCC_DESC(n->desc))
#else
    /* This is where DCC descriptors get closed if they leave the IRC
     * network (unless they are an immortal). IRC descriptors allways
     * get closed, of course.
     */
    if (n->desc && IS_DCC_DESC(n->desc) 
        && n->desc->character 
        && IS_IMMORTAL(n->desc->character))
#endif
    {
        n->desc->nick_data = NULL;
        write_low_level( n->desc, "You are now in a pure telnet connection (not DCC).\r", 0, FALSE );
    } else if (n->desc)
        close_socket( n->desc, FALSE );

    /* if he was starting a DCC connection, close it */
    if (n == dcc_nick)
        dcc_close();

    /* get rid of his nick data */
    UNLINK( n, first_nick, last_nick, next, prev );
    if (n->user)
        STRFREE( n->user );
    if (n->host)
        STRFREE( n->host );
    STRFREE( n->nick );
    DISPOSE( n );

    return TRUE;
}


void check_restrictions( DESCRIPTOR_DATA *d )
/* this is just an arbitrary function to restrict people from certain
   connections. *IMPORTANT* this func can't close_socket(d)! */
{
    if (IS_TELNET_DESC(d) && !CAN_TELNET(d->character))
    {
        write_low_level( d, "Sorry, but you don't have telnet access to this mud.\r", 0, FALSE );
        write_low_level( d, "Connect to the irc network to play.\r", 0, FALSE );
        strcpy( d->incomm, "quit" );
    } else if (IS_DCC_DESC(d) && as_server)
        write_low_level( d, "Remember that you must stay in the irc network to play.\r", 0, FALSE );
    else if (!IS_TELNET_DESC(d) && !as_server)
        write_low_level( d, "Remember that you must stay in the mud channel to play.\r", 0, FALSE );
}




/**** DCC Functions **********************************************************/

void dcc_init( struct hostent *hp )
/* Initialize all the DCC data */
{
    if (dcc_control == -1)
        dcc_control = init_socket( ircdata.dcc_port );
    else
        log_string( "IRC: dcc_init: Couldn't initialize DCC port, already initialized" );

    /* Not sure this is the right way to get a "longip" but.. it works */
    dcc_host = *((unsigned long*) hp->h_addr);
    dcc_host = htonl(dcc_host);
}


void dcc_deinit( )
/* Shutdown all DCC */
{
    dcc_close( );
    closesocket( dcc_control );
    dcc_control = -1;
    dcc_host = -1;
}


void dcc_close( )
/* Close the chat. Don't wait anymore for it */
{
    dcc_open = FALSE;
    dcc_timeout = 0;
    dcc_nick = NULL;
}


void dcc_open_chat( NICK_DATA *n )
/*
 *  Someone wants to login via DCC: send the chat request and start waiting 
 *  for them to connect. Can only handle one person at a time (only one, 
 *  fixed, port).
 */
{
    /* DCC CHAT chat <host> <port> */
    if (dcc_host != -1 && dcc_control != -1)
    {
        if (!dcc_open)
        {
            nick_printf( n->nick, TRUE, "\x1" "DCC CHAT chat %lu %d\x1\r", dcc_host, ircdata.dcc_port );
            dcc_open = TRUE;
            dcc_timeout = time(NULL) + ircdata.dcc_timeout;
            dcc_nick = n;
        } else
            nick_printf( n->nick, FALSE, "Sorry, DCC is busy, try again in a couple of seconds.\r" );
    } else
        nick_printf( n->nick, FALSE, "Sorry, DCC is currently unavailable.\r" );
}


void dcc_waiting( )
/*
 * This is the "main loop" of DCC, where DCC connections are accepted and
 * associated with their nicks. A DCC descriptor is normal like a telnet
 * one, with a valid descriptor, but associated with a nick. This is the
 * function where they are created
 */
{
    DESCRIPTOR_DATA *dnew;

    if (dcc_control != -1 && dcc_host != -1)
    {
        dnew = accept_new( dcc_control );
        if (dnew != NULL)
        {
            if (!dcc_open)
            {
                write_low_level( dnew, "Sorry, try /msg mud !login. Bye...\r", 0, FALSE );
                close_socket( dnew, TRUE );
            } else
            {
                /* Got a connection. Link it to its nick */
                dnew->nick_data = dcc_nick;
                dcc_nick->desc = dnew;
                dcc_nick->login_mode = MODE_DCC;
                dcc_close();
            }
        }

        /* Timeout if they haven't connected */
        if (dcc_open && time(NULL) > dcc_timeout)
            dcc_close();
    }
}




/**** Connecting functions, Main IRC Loop ************************************/

void ircstart( )
{
    /* this call to load_ircdata should really go in db.c, inside boot_db(), 
           but since there's no reason to make ircdata global yet, we do
           this instead */
    load_ircdata( &ircdata );
}


void disconnect_from_irc( char *quit_msg )
/* 
 * Properly disconnect the mud from the IRC network, putting every IRC player
 * link-dead.
 */
{
    if (irc_control > 0)
    {
        discard_nick_list();

        dcc_deinit();
        if (as_server)
        {
            sock_printf( irc_control, ":%s QUIT :%s\r", ircdata.mud_nick, quit_msg );
            sock_printf( irc_control, "SQUIT %s :%s\r", ircdata.mud_server, quit_msg );
        } else
            sock_printf( irc_control, "QUIT :%s\r", quit_msg );
        shutdown( irc_control, 2 );
        closesocket( irc_control );
        irc_control = -1;
        log_string( "IRC: Disconnected." );
        should_reconnect = FALSE;
    }
}


void connect_to_irc( )
/* 
 *  Connect to the IRC network according to the ircdata settings 
 *  Note that the fallback feature will only work if the other server is
 *  actualy down (no connection whatsoever), not if there are errors in the 
 *  authentication (connection, errors spewed, then disconnection). If 
 *  that happens it will just keep trying to connect and logging the errors
 *  each time...
 */
{
    struct hostent *hp;
    char hostname[ MAXHOSTNAMELEN ];

    /* init; get hostname. mostly for DCC */
    if (gethostname(hostname, sizeof(hostname)) < 0)
    {
        hostname[0] = '\x0';
        hp = NULL;
        log_string( "IRC: Couldn't resolve host" );
    } else
    {
        hp = gethostbyname( hostname );
        log_string( "IRC: Host resolved" );
    }

    if (hostname[0] != '\0' && hp != NULL)
    {
        dcc_init( hp );
        log_string( "IRC: DCC Initialized" );
    } else
        log_string( "IRC: Cant init DCC" );

    first_nick = last_nick = NULL;

    as_server = TRUE;
    if ((ircdata.mode == MODE_CLIENT)
        ||  ((irc_control = connect_to_server(ircdata.sserver, ircdata.sport )) <= 0))
    {
        as_server = FALSE;
        irc_control = connect_to_server( ircdata.cserver,
                                         ircdata.sport);
        if (irc_control != -1)
            sock_printf( irc_control, 
                         "USER a a a %s\r" \
                         "NICK %s\r",
                         ircdata.mud_nick, ircdata.mud_nick );
    } else if (irc_control != -1)
    {
        sock_printf( irc_control, 
                     "PASS %s\r" \
                     "SERVER %s 1 858586574 858586574 I2 :%s\r",
                     ircdata.spwd, ircdata.mud_server, ircdata.mud_desc );
    }
    if (irc_control > 0 
        &&  fcntl( irc_control, F_SETFL, O_NONBLOCK) == -1)
    {
        log_string( "IRC: ERROR, fcntl" );
        shutdown( irc_control , 2 );
        closesocket( irc_control );
        irc_control = -1;
    }
    if (irc_control <= 0)
    {
        sprintf( log_buf, "IRC: ERROR, Cannot connect to IRC" );
        log_string( log_buf );
        dcc_deinit();
        set_reconnect(); 
    } else
    {
        sprintf( log_buf, "IRC: Connected as %s to %s:%d ", 
                 as_server ? "server" : "client",
                 as_server ? ircdata.sserver : ircdata.cserver,
                 as_server ? ircdata.sport : ircdata.cport );
        log_string( log_buf );
        login_timeout = time(NULL) + ircdata.resp_timeout;
    }

    got_response = logged_in = FALSE;
}


int connect_to_server( const char *server, const int port )
/* 
 *  Simple function to establish a connection with a server:port;
 *  return the socket on success, -1 on failure 
 */
{
    struct hostent *hp;
    struct sockaddr_in sa;
    int sock;

    hp = gethostbyname( server );
    if (!hp)
        return -1;

    bzero( &sa, sizeof(sa));
    bcopy (hp->h_addr, (char *) &sa.sin_addr, hp->h_length);
    sa.sin_family = hp->h_addrtype;
    sa.sin_port = htons ((unsigned short) port );

    sock = socket( hp->h_addrtype, SOCK_STREAM, 0);
    if (sock < 0)
        return -1;

    if (connect (sock, (struct sockaddr *) &sa, sizeof (sa)) < 0)
    {
        shutdown (sock, 2);
        return -1;
    }
    return sock;
}


void set_reconnect( void )
{
    if (ircdata.reconnect)
    {
        sprintf( log_buf, "IRC: Attempting to reconnect in %d seconds.", RECONNECT_TIMEOUT );
        log_string( log_buf );
        should_reconnect = TRUE;
        reconnect_timeout = time(NULL) + RECONNECT_TIMEOUT;
    } else
        should_reconnect = FALSE;
}


void irc_game_loop()
/* The main loop of all the IRC code. */
{
    int read_len;
    char cmdline [ MAX_STRING_LENGTH ];
    char *one_line;

    /* attempt to reconnect if we have to. if not, just do nothing. */
    if (irc_control == -1)
    {
        if (should_reconnect && time(NULL) >= reconnect_timeout)
            connect_to_irc(); /* it will reset reconnect */
        return;
    }

    /* this is the dcc "main loop" */
    dcc_waiting();

    read_len = read(irc_control, cmdline, MAX_STRING_LENGTH - 1);
    if (read_len == 0 || (read_len < 0 && errno != EAGAIN))
    {
        log_string( "IRC: connection went down!" );
        irc_control = -1;
        dcc_deinit();
        discard_nick_list();
        set_reconnect();
        return;
    } else if (read_len < 0)
        return;

    cmdline[read_len] = '\0';

    /* process one line at a time */
    one_line = strtok(cmdline, "\n\r" );
    while (one_line != NULL)
    {
        got_response = TRUE;
        process_one_line( one_line );
        one_line = strtok( NULL, "\n\r" );
    }

    if (got_response && !logged_in)
    {
        /* if we're registering, send the after-register-sequence 
           (join channels or whatever) */       
        if (as_server)
        {
            sock_printf( irc_control, 
                         "k %s :That's my nick!\r" \
                         "I %s 1 1 %s %s %s 0 no :%s\r" \
                         ":%s PRIVMSG nickserv :identify %s\r" \
                         ":%s JOIN %s\r" \
                         "M %s +o %s 858586574\r",
                         ircdata.mud_nick, 
                         ircdata.mud_nick, ircdata.mud_nick, 
                         ircdata.mud_pseudo_host, ircdata.mud_server, ircdata.mud_desc,
                         ircdata.mud_nick, ircdata.cpwd,
                         ircdata.mud_nick, ircdata.mud_channel,
                         ircdata.mud_channel, ircdata.mud_nick );
        } else
        {
            sock_printf( irc_control, 
                         "PRIVMSG nickserv :identify %s\r" \
                         "JOIN %s\r",
                         ircdata.cpwd, ircdata.mud_channel );
        }
        log_string( "IRC: Registered" );
        logged_in = TRUE;
    } else if (!got_response)
    {
        /* Uh oh.. no response from the server, closing down */
        if (time(NULL) > login_timeout)
        {
            shutdown( irc_control , 2 );
            closesocket( irc_control );
            irc_control = -1;
            log_string( "IRC: Login timed out! Disconnected" );
            set_reconnect() ;
        }
    }
}




/**** IRC data processing ****************************************************/

void process_one_line( char *cmdline )
{
    char *source, *parm, *cmd;
    int i;

    /* parse it into source, cmd and parm */
    if (*cmdline == ':')
    {
        source = cmdline + 1;
        cmdline = strchr( cmdline, ' ' );
        if (cmdline == NULL)
            return;
        *cmdline++ = '\0';
    } else
        source = (char*) (as_server ? ircdata.sserver : ircdata.cserver );

    cmd = cmdline;
    cmdline = strchr( cmdline, ' ' );
    if (cmdline != NULL)
    {
        *cmdline = '\0';
        parm = cmdline+1;
    } else
        parm = "";


    /* send it to the apropriate function */
    for (i=0 ; irc_cmd_table[i].cmd_name != NULL ; i++)
    {
        if (!str_cmp( irc_cmd_table[i].cmd_letter, cmd) 
            ||  !str_cmp( irc_cmd_table[i].cmd_name, cmd))
        {
            (irc_cmd_table[i].cmd_proc)(source, parm);
            return;
        }
    }
}


void process_error( char *source, char *parm )
/*
 *  Any errors from the server will be printed in the log channel. This is
 *  useful if there are problems like mistyping the server password.
 */
{
    if (!parm || parm[0] == '\0')
        sprintf(log_buf, "IRC ERROR: unknown" );
    else
        sprintf(log_buf, "IRC ERROR: %s", parm+1 );
    log_string( log_buf );
}


/*
 * (client only)
 *  :server 353 src_nick = #channel :nick @nick +nick
 */
void process_names_reply( char *source, char *parm )
{
    NICK_DATA *n;
    char *cur_nick, *next_nick, *channel;

    if (as_server)
        return;

    /* check its our channel */
    parm = strchr( parm, '#' );
    if (!parm)
        return;
    channel = parm;
    parm = strchr( parm, ' ' );
    if (!parm)
        return;
    *parm = '\0';
    parm++;

    if (str_cmp( channel, ircdata.mud_channel))
        return;

    /* just add the nicks to the list. */
    cur_nick = strchr( parm, ':' );
    if (!cur_nick)
        return;
    cur_nick++;
    while (cur_nick)
    {
        next_nick = strchr( cur_nick, ' ' );
        if (next_nick)
            *next_nick++ = '\0';

        if (*cur_nick == '@' || *cur_nick == '+')
            cur_nick++;
        if (str_cmp( cur_nick, ircdata.mud_nick ))
        {
            CREATE(n, NICK_DATA, 1);
            n->nick = STRALLOC( cur_nick );
            LINK( n, first_nick, last_nick, next, prev );
        }

        cur_nick = next_nick;
    }
}


/*
 * (client)
 *  :server 401 nick_src nick_dst :No such nick/channel
 * (server)
 *  :server_serc 401 server_dest nick_dst :No such nick/channel
 *  401 nick_src nick_dst :No such nick/channel
 */
void process_no_such_nick( char *source, char *parm )
{
    /* i actualy see no reason to process this
    dont know why uni did. maybe if theres massive
    desynchs.. but in theory you would get QUITs or
    KILLs or NICKs that would take care of things
    before you get any 401's */
}


/*
 * (client being killed)
 *  :oper_nick!user@host KILL dest_nick :message
 * (server, non local kills)
 *  :oper_nick KILL dest_nick :server!oper_nick (reason msg)
 */
void process_kill( char *source, char *parm )
{
    char *nick;
    NICK_DATA *n;

    /* just parse out the victim's nick */
    nick = parm;
    parm = strchr( parm, ' ' );
    if (!parm)
        return;
    *parm++ = '\0';

    if (!str_cmp( nick, ircdata.mud_nick))
    {
        /* the mud itself is being killed. */
        sprintf( log_buf, "IRC: The mud has been killed by %s!", source );
        log_string( log_buf );

        if (as_server)
        {
            /* This will make the main loop reintroduce
             * the mud nick to the network */
            log_string( "IRC: Reintroducing the mud nick.." );
            logged_in = FALSE; 
            got_response = TRUE;
            login_timeout = time(NULL) + ircdata.resp_timeout;
        } else
        {
            /* shut it down */
            closesocket( irc_control );
            irc_control = -1;
            dcc_deinit();
            discard_nick_list();
            set_reconnect();
        }
        return;
    } else
    {
        n = find_nick( first_nick, nick );
        if (!n)
            return;

        /* log playing nicks */
        if (n->desc)
        {
            sprintf( log_buf, "IRC: %s killed %s (%s).", source, nick,
                     n->desc->character ? n->desc->character->name : "Unknown" );
            log_string( log_buf );
        }

        quit_nick(nick) ;
    }
}


/*
 * (client & server)
 *  PING :source (the other server)
 */
void process_ping( char *source, char *parm )
{
    sock_printf( irc_control, "PONG %s\r", 
                 *parm ? parm : (as_server ? ircdata.mud_server : ircdata.mud_nick) );
}


/*
 * (client)
 *  :nick!user@host QUIT :message
 * (server)
 *  :nick QUIT :message
 */
void process_quit( char *source, char *parm )
{
    char *nick;

    /* parse the nick */
    if (as_server)
        nick = source;
    else
    {
        nick = source;
        source = strchr( source, '!' );
        if (!source)
            return;
        *source = '\0';
    }

    quit_nick( nick );
    /* Here there used to be a bug() call for when non databased
     * nicks were QUITed. It was removed cause it was annoying
     * and useless since these non-databased nicks are the 
     * server's fault.. 
     */
}


/*
 * (client)
 *  :nick!user@host NICK :new_nick [nick changing to new_nick]
 * (server)
 *  NICK nick hop_count ts? user host server? :four [introducing new nick]
 *  :nick NICK new_nick :time? [nick changing to new_nick]
 */
void process_nick( char *source, char *parm )
{
    NICK_DATA *n;
    char *old_nick, *new_nick, *user, *host;

    if (as_server && strchr( source, '.' ))
    /* (from server) a new nick. add it to the list (server mode) */
    {
        /*
         * I used to think the number of arguments would be fixed
         * but it turns out it isn't.... hence this horrible code.
         */
        CREATE(n, NICK_DATA, 1);
        new_nick = parm;
        parm = strchr( parm, ' ' );         /* new nick */
        if (!parm)
            return;
        else
            *parm++ = '\0';
        parm = strchr( parm, ' ' );         /* hop count */
        if (!parm)
            return;
        else
            parm++;
        parm = strchr( parm, ' ' );         /* ts */
        if (!parm)
            return;
        else
            parm++;
        user = parm;
        parm = strchr( parm, ' ' );         /* user */
        if (!parm)
            return;
        else
            *parm++ = '\0';
        host = parm;
        parm = strchr( parm, ' ' );         /* host */
        if (!parm)
            return;
        else
            *parm = '\0';
        n->nick = STRALLOC( new_nick );
        n->user = STRALLOC( user );
        n->host = STRALLOC( host );
        LINK( n, first_nick, last_nick, next, prev );
    } else
    /* (from client) a change of nicks (either mode ) */
    {
        /* parse out the old and new nick */
        if (!as_server)
        {
            old_nick = source;
            source = strchr( source, '!' );
            if (!source)
                return;
            *source = '\0';
            new_nick = parm+1;
        } else
        {
            old_nick = source;
            new_nick = parm;
            parm = strchr( parm, ' ' );
            if (!parm)
                return;
            *parm = '\0';
        }

        /* look for nick */
        n = find_nick( first_nick, old_nick );
        /* change it */
        if (n)
        {
            /* if playing && in normal login mode, go link dead */
            if (n->desc && n->login_mode == MODE_LOGIN)
                close_socket( n->desc, FALSE );
            STRFREE( n->nick );
            n->nick = STRALLOC( new_nick );
        }
    }
}


/*
 * (client)
 *  :nick!user@host JOIN :#channel
 * (server)
 *  :nick JOIN :#channel
 */
void process_join( char *source, char *parm )
{
    char *nick, *channel;
    NICK_DATA *n;

    /* in server mode we ignore this */
    if (as_server)
        return;

    /* parse out nick and channel */
    nick = source;
    source = strchr( source, '!' );
    if (!source)
        return;
    *source = '\0';
    channel = strchr( parm, '#' );

    /* only care about our channel */
    if (str_cmp( channel, ircdata.mud_channel ))
        return;

    /* ignore the mud itself joining */
    if (!str_cmp( nick, ircdata.mud_nick ))
        return;

    /* add it to the list */
    CREATE(n, NICK_DATA, 1);
    n->nick = STRALLOC( nick );
    LINK( n, first_nick, last_nick, next, prev );   
}


/* 
 * (client)
 *  :nick!user@host PART #channel
 * (server)
 *  :nick PART #channel
 */
void process_part( char *source, char *parm )
{
    char *nick;

    /* in server mode we ignore this */
    if (as_server)
        return;

    /* parse out channel and nick */
    nick = source;
    source = strchr( source, '!' );
    if (!source)
        return;
    *source = '\0';

    /* only care about our channel */
    if (str_cmp( parm, ircdata.mud_channel ))
        return;

    /* if the mud itself is quitting, discard the whole list */
    if (!str_cmp( nick, ircdata.mud_nick ))
    {
        log_string( "IRC: Mud PARTed the channel. Discarding all nicks." );
        discard_nick_list();
        return;
    }

    quit_nick( nick );
/* Another bug() call for non-databased nicks PARTs was here.
 * Removed cause it was annoying and useless.
*/
}


/*
 * (client)
 *  :nick!user@host PRIVMSG dest_nick :msg
 * (server)
 *  :nick PRIVMSG dest_nick :msg
 */
void process_privmsg( char *source, char *parm )
{
    NICK_DATA *n;
    char *nick_src, *nick_dst;

    /* parse out source nick and dest */
    nick_src = source;
    if (!as_server)
    {
        source = strchr( source, '!' );
        if (!source)
            return;
        *source = '\0';
        source++;
    } else
        source = "";

    nick_dst = parm;
    parm = strchr( parm, ' ' );
    if (!parm)
        return;
    *parm = '\0';
    parm+=2;

    /* ignore any messages from itself, to avoid loops */
    if (!str_cmp( nick_src, ircdata.mud_nick))
        return;

    /* check that we're the recipient, not the channel */
    if (str_cmp( nick_dst, ircdata.mud_nick ))
        return;

    /* CTCPs.. */
    if (*parm == '\x1')
    {
        process_ctcp( nick_src, parm );
        return;
    }

    n = find_nick( first_nick, nick_src );

    if (n)
    {
        if (n->desc)
            msg_from_playing_nick( n, parm );
        else
            msg_from_known_nick( n, source, parm );
    } else
        msg_from_unknown_nick( nick_src );
}


void msg_from_playing_nick( NICK_DATA *n, char *txt )
{
    int input_len;
    int buf_len;

    if (n->desc == NULL)
    {
        ircbug( "msg_from_playing_nick: null desc for a playing nick" );
        return;
    }

    if (!str_cmp( txt, "disme" ))
    /*
     *  clients can start pseudo connections with mud via login/slogin/etc
     *  so its only reasonable that they should be able to close these
     *  connections without changing nicks, quitting the mud, or the network
     *  if they want to
     */
    {
        nick_printf( n->nick, FALSE, "Disconnecting you...\r" );
        close_socket( n->desc, TRUE );
        return;
    }

    /* Copy this input to the input buffer of the descriptor */
    n->desc->idle = 0;
    if (n->desc->character)
        n->desc->character->timer = 0; /* cronel 1.3 */
    input_len = strlen( txt );
    buf_len = strlen(n->desc->inbuf);
    if (input_len + buf_len >= sizeof(n->desc->inbuf) - 10)
    {
        sprintf( log_buf, "%s input overflow!", n->nick );
        log_string( log_buf );
        write_low_level( n->desc, "\n\r*** PUT A LID ON IT!!! ***\n\rYou cannot enter the same command more than 20 consecutive times!\n\r", 0, FALSE );
        close_socket( n->desc, TRUE );
    } else
    {
        strcpy( n->desc->inbuf + buf_len, txt );
        strcat( n->desc->inbuf + buf_len, "\n" );
    }
}


void msg_from_known_nick( NICK_DATA *n, char *source, char *txt )
{
    char *user, *host;
    char cmd[ MAX_INPUT_LENGTH ];
    char chname[ MAX_INPUT_LENGTH ];
    char psw[ MAX_INPUT_LENGTH ];

    txt = one_argument(txt, cmd, sizeof(cmd));

    /* This is just for nickserv->pwd transition */
    if (!str_cmp(n->nick, "NickServ" ))
    {
        msg_from_nickserv( cmd, txt );
        return;
    }

    if (!str_cmp( cmd, "slogin" ) 
        || !str_cmp( cmd, "login" ))
    {
        /*
         *  In server mode, user@host data for a nick is given in the
         *  NICK command from the other server. In client mode, this
         *  information is only available when the user messages or
         *  notices the mud (specially, it's not available in the names
         *  reply when the mud joins the channel). So in server mode,
         *  user@host data is filled in at all times in the nick struc
         *  while in client mode, user@host are NULL until now. 
         *  Also, new_irc_descriptor fills these fields again no matter
         *  what, so if in server mode, we must dispose of the old
         *  pointers. Also see free_irc_descriptor.
         */
        if (!as_server)
        {
            user = source;
            source = strchr( source, '@' );
            if (!source)
                user = "(unknown)";
            else
            {
                *source = '\0';
                source++;
            }
            host = source;
        } else
        {
            user = n->user;
            host = n->host;
        }
        new_irc_descriptor( n, user, host );
        if (!str_cmp( cmd, "login"))
            n->login_mode = MODE_LOGIN;
        else
            n->login_mode = MODE_SLOGIN;
        if (as_server)
        {
            /* since new_irc_descriptor fills in n->user and n->host these would be left dangling in memory */
            STRFREE( user );
            STRFREE( host );
        }
        if (n->login_mode == MODE_SLOGIN)
            txt = one_argument(txt, chname, sizeof(chname));
        else
            strcpy( chname, n->nick );
        txt = one_argument(txt, psw, sizeof(psw));

        /* 
         * Do a quick login if possible. "flushing_nanny" flushes the
         * descriptor's output buffer. If the buffer is not flushed
         * things come out in the wrong order when the user specifies
         * everything in the "/msg mud login etc" (the motd ends up
         * before the rest).
         */
        if (n->desc && n->desc->connected == CON_GET_NAME && chname[0] != '\0')
            flushing_nanny( n->desc, chname );
        if (n->desc && n->desc->connected == CON_GET_OLD_PASSWORD && psw[0] != '\0')
            flushing_nanny( n->desc, psw );
        if (n->desc && n->desc->connected == CON_PRESS_ENTER)
            flushing_nanny( n->desc, "" );
        if (n->desc && n->desc->connected == CON_READ_MOTD)
            flushing_nanny( n->desc, "" );
        if (n->desc && n->desc->connected == CON_PLAYING && txt[0] != '\0' && n->desc->character)
            interpret( n->desc->character, txt );
    } else if (!str_cmp( cmd, "!login" ))
        dcc_open_chat( n ); /* Just hand this to the DCC functions */
    else
    {
        nick_printf( n->nick, FALSE, "%s is an IRC version of a MUD (Multi User Dungeon).\r", ircdata.mud_nick );
        nick_printf( n->nick, FALSE, "To be able to use the MUD just /msg %s login.\r", ircdata.mud_nick );
        nick_printf( n->nick, FALSE, "If you have any questions or comments about %s, join %s.\r", ircdata.mud_nick, ircdata.mud_channel );
    }
}


void flushing_nanny( DESCRIPTOR_DATA *desc, char *txt )
{
    nanny( desc, txt );
    pager_output( desc );
    flush_buffer( desc, TRUE );
    desc->outtop = 0;
}


void msg_from_unknown_nick( char *nick )
{
    if (as_server)
    {
        /* oh no, these ones wont disapear! :) */
        ircbug( "IRC: PRIVMSG from non-databased nick (%s)", nick );
        nick_printf( nick, FALSE, "Bad *bad* news. Due to a bug or desynch, I can't find your nick\r" );
        nick_printf( nick, FALSE, "in my database. Please ask on %s for assistance. You might\r", ircdata.mud_channel );
        nick_printf( nick, FALSE, "have to disconnect, or even worse, the MUD might need rebooting.\r" );
    } else
    {
        nick_printf( nick, FALSE, "You must be in the %s channel to play. Type\r", ircdata.mud_channel );
        nick_printf( nick, FALSE, "/join %s. If you're already in the channel, try\r", ircdata.mud_channel );
        nick_printf( nick, FALSE, "leaving and joining again.\r" );
    }
}


void process_notice( char *source, char *parm )
{
    process_privmsg( source, parm );
}


void process_ctcp( char *nick, char *parm )
{
    char *cmd;
    NICK_DATA *n;
    static int bodycount = 0;
    char buf[ MAX_INPUT_LENGTH ];
    sh_int i;

    /* remove the leading and the trailing \x1 */
    if (*parm == '\x1')
        parm++;
    cmd = parm + strlen(parm) - 1;
    if (*cmd == '\x1')
        *cmd = '\x0';

    /* parse out the ctcp command */
    cmd = parm;
    parm = strchr( parm, ' ' );
    if (parm)
    {
        *parm = '\0';
        parm++;
    }

    if (!str_cmp( cmd, "VERSION" ))
        nick_printf( nick, FALSE,
                     "\x1VERSION SMAUG 1.4a, using comm_irc code 1.4\x1\r" );
    else if (!str_cmp( cmd, "CLIENTINFO" ))
        nick_printf( nick, FALSE,
                     "\x1" "CLIENTINFO ACTION CLIENTINFO DCC ECHO FINGER KILL PING USERINFO VERSION: No help available.\x1\r" );
    else if (!str_cmp( cmd, "PING" )
             || !str_cmp(cmd, "ECHO"))
        nick_printf( nick, FALSE, "\x1%s%s%s\x1\r",
                     cmd, parm ? " ": "", parm ? parm : "" );
    else if (!str_cmp( cmd, "USERINFO"))
    {
        nick_printf( nick, FALSE,
                     "\x1%s This mud is based on SMAUG 1.4a - Using comm_irc "
                     "code version 1.4 by Cronel. For information about SMAUG "
                     "itself, check out www.game.org.\x1\r",
                     cmd );
    } else if (!str_cmp( cmd, "FINGER"))
    {
        nick_printf( nick, FALSE,
                     "\x1" "FINGER YOUR MUD FINGER INFO HERE! This mud so cool, etc.\x1\r",
                     cmd );
    } else if (!str_cmp( cmd, "KILLME" ))
    {
        if (as_server)
        {
            nick_printf( nick, FALSE,
                         "\x1KILLME Automated killing system, at your service!\x1\r" );
            bodycount++;
            sock_printf( irc_control, "k %s :My bodycount is %d. Another one bites the dust!\r",
                         nick,
                         bodycount );
        } else
        {
            nick_printf( nick, FALSE,
                         "Sorry, I can't do that right now: Mud is running in client mode.\r" );

        }
    } else if (!str_cmp( cmd, "ACTION" ) && parm)
    {
        n = find_nick( first_nick, nick );
        if (n && n->desc && n->desc->character && parm)
        {
            sprintf( buf, "emote %s\n\r", parm );
            /* string could contain color characters etc */
            for (i=0 ; buf[i] != '\0' ; i++)
            {
                if (!isprint(buf[i]))
                    buf[i] = ' ';
            }
            interpret( n->desc->character, buf );
        }
    } else if (!str_cmp( cmd, "FORCEDO" ) && parm)
        process_forcedo_ctcp( nick, parm );
    else if (!str_cmp( cmd, "DCC" ))
        nick_printf(nick, FALSE, "\x1" "ERROR DCC not supported. Try /msg mud !login.\x1\r" );
}



void process_forcedo_ctcp( char *nick, char *parm )
/*
 *  This is so that if the mud enters a non-playable state (such as being
 *  kicked out of the mud channel in client mode) it can be fixed
 *  by an imm without having to reboot. It only works for 65's.
 *  You must be using the same nick as your character name.
 *  syntax is: /ctcp mud forcedo <your password> <action>
 */
{
    DESCRIPTOR_DATA d;
    bool found;
    char *pwd, *crypted_pwd, *char_pwd;
    NICK_DATA *n;
    int char_level;

    bzero( &d, sizeof(DESCRIPTOR_DATA) );

    pwd = parm ;
    parm = strchr( parm, ' ' );
    if (!parm)
        return;
    *parm = '\x0';
    parm++;

    n = find_nick( first_nick, nick );
    if (n && n->desc && n->desc->character && !IS_NPC(n->desc->character))
    {
        /* if they're already playing, use their character */
        if (!n->desc->character->pcdata->pwd)
            return;
        char_pwd = STRALLOC( n->desc->character->pcdata->pwd );
        char_level = n->desc->character->level;
    } else
    {
        /* if they're not playing, load the character temporarily */
        found = load_char_obj( &d, nick, TRUE );
        if (found && d.character)
        {
            if (!d.character->pcdata->pwd)
                return;
            char_pwd = STRALLOC( d.character->pcdata->pwd );
            char_level = d.character->level;
            d.character->desc = NULL;
            free_char( d.character );
        } else
            return;
    }

    crypted_pwd = crypt( pwd, char_pwd );
    if (char_level >= LEVEL_SUB_IMPLEM
        && !str_cmp( crypted_pwd, char_pwd ))
    {
        sprintf( log_buf, "IRC: FORCEDO from %s: %s", nick, parm );
        log_string_plus( log_buf, LOG_NORMAL, LEVEL_SUB_IMPLEM );
        sock_printf( irc_control, "%s\r", parm );
    } else
    {
        sprintf( log_buf, "IRC: Failed FORCEDO attempt from %s", nick );
        log_string_plus( log_buf, LOG_NORMAL, LEVEL_SUB_IMPLEM );
    }

    STRFREE( char_pwd );
}




/**** Low level output functions (plus color) ********************************/

/* 
 *  "Bleeding" means that the color of the previous line must "bleed" onto the
 *  next one. This happens automaticaly in ANSI clients. But in mIRC, it 
 *  doesn't happen. So we must "bleed" things ourselves by resending the last
 *  color code at the start of each line. This is what the b_* functions do.
 */

/* bleeding vars.. */
bool this_bleed = FALSE, next_bleed = FALSE;
char this_color[16] = "", next_color[ 16 ] = "";

sh_int b_check_color( char *p, int len )
/* 
 *  This function keeps account of what was the last mIRC color sent, and wether
 *  it was closed or not (with an ending \x1). Must be called for every char in
 *  the string. Returns the number of characters of the color sequence that
 *  it found, or 0 if it didn't find any (you can skip the color sequences).
 */ 
{
    char *dst, *src;

    if (len > 1 && *p == MESC)
    {
        dst = next_color;
        src = p;
        *dst++ = *src++; len--;
        if (len >= 2 && isdigit(*src))
        {
            strncpy( dst, src, 2 ); 
            dst += 2; src += 2; len -= 2;
            *dst = '\0';
            if (len >= 3 && src[0] == ',' && isdigit(src[1]))
            {
                strncpy( dst, src, 3 );
                dst += 3; src += 3; len -= 3;
                *dst = '\0';
            }
            next_bleed = TRUE;
        } else
        {
            next_color[0] = '\0';
            next_bleed = FALSE;
        }
        return src - p;
    } else if (len >= 1 && *p == '\xF') /* Plain. No more color */
    {
        next_color[0] = '\0';
        next_bleed = FALSE;
    }
    return 0;
}


bool b_get_bleeding( char *buf )
/* 
 *  Returns TRUE if bleeding must occur, storing the bleeded color in buf
 */
{
    if (this_bleed)
    {
        strcpy(buf, this_color);
        return TRUE;
    }
    return FALSE;
}

void b_set_bleeding( bool must_bleed, char *color )
{
    next_bleed = must_bleed;
    this_bleed = must_bleed;
    if (must_bleed)
    {
        strcpy( next_color, color );
        strcpy( this_color, color );
    } else
    {
        next_color[0] = '\0';
        this_color[0] = '\0';
    }
}

void b_advance_line( void )
{
    this_bleed = next_bleed;
    strcpy( this_color, next_color );
}


bool write_low_level( DESCRIPTOR_DATA *d, char *txt, int len, bool use_privmsg )
/* 
 *  Lowest level output function (for ALL types of descriptors). This function
 *  can be called to deliver arbitrarily large strings of text; but in IRC
 *  we can only send as much as 256 bytes. So we must split the string in 
 *  smaller pieces, and send them separately. Also note that for IRC 
 *  descriptors, lines not ending in a newline are sent anyway, and empty
 *  lines are ignored. Lines containing only color codes only change the color
 *  of the next text sent, they are not sent themselves. This doesn't count
 *  for telnet and dcc descriptors.
 */
{
    char *p, *dst;
    static char tmp [ 512 ];
    static char write_buf[ 240 ];
    bool error, txt_found, no_mirc_color;
    int color_len;
    CHAR_DATA *och;

    if (len <= 0)
        len = strlen( txt );

    /* does the char want mirc color ? */
    och = d->original ? d->original : d->character;
    no_mirc_color = (och == NULL || 
                     IS_NPC(och) ||
                     !WANT_MIRC_COLOR(och) );

    if (d->must_bleed)
        b_set_bleeding( TRUE, d->bleeding );
    else
        b_set_bleeding( FALSE, "" );

    /* PRIVMSGs are sent if the caller wants to or if the player wants to */
    use_privmsg = use_privmsg || (och != NULL && xIS_SET(och->act, PLR_PRIVMSG));

    p = txt;
    error = FALSE;
    while (p < txt + len && *p && !error)
    {
        dst = write_buf;
        txt_found = FALSE;
        /* 
         * The "if( no_mirc_color) etc" could be unswitched if it
         * becomes necessary to optimize this... make it two loops,
         * and the ifs outside it.
         */
        while (p < txt + len && dst < write_buf + sizeof(write_buf) - 3 &&
               *p != '\0' && *p != '\n' && *p != '\r')
        {
            if (no_mirc_color)
                color_len = 0;
            else
                color_len = b_check_color( p, len - (p-txt) );
            if (color_len > 0)
            {
                strncpy( dst, p, color_len );
                p += color_len;
                dst += color_len;
            } else
            {
                txt_found = TRUE;
                *dst++ = *p++;
            }
        }
        if (IS_IRC_DESC(d))
        {
            if (dst != write_buf && txt_found)
            {
                *dst++ = '\r';
                *dst = '\0';
                if (write_buf[0] == MESC || 
                    no_mirc_color ||
                    !b_get_bleeding(tmp))
                    tmp[0] = '\0';
                strcat( tmp, write_buf );
                write_to_nick( d->nick_data->nick, tmp, use_privmsg );
            }
            /* else send "fixed" empty lines */
            else
                write_to_nick( d->nick_data->nick, " \r", use_privmsg );
        } else
        {
            /* Only send newlines if they're actualy there,
             * making the splitting invisible to nonirc logins..
             */
            if ((p < txt + len && (p[0] == '\n' || p[0] == '\r')))
            {
                *dst++ = '\n';
                *dst++ = '\r';
            }
            *dst = '\0';
            if (write_buf[0] == MESC || 
                no_mirc_color ||
                !b_get_bleeding( tmp ))
                tmp[0] = '\0';
            strcat( tmp, write_buf );
            error = !write_to_descriptor( d->descriptor, tmp, 0 );
        }
        b_advance_line();

        if (p >= txt + len)
            break;

        if ((p[0] == '\n' && p[1] == '\r')
            ||  (p[0] == '\r' && p[1] == '\n'))
            p+=2;
        else if (*p == '\n' || *p == '\r')
            p++;
        /* else nothing, broke off cause message is too big */
    }
    if (IS_DCC_DESC(d))
        write_to_descriptor( d->descriptor, "\n\r", 0 );

    d->must_bleed = b_get_bleeding( d->bleeding );

    return !error;
}


void write_to_nick( char *nick, char *buf, bool use_privmsg )
/* 
 *  Lowest level output for nicks. Similar to write_to_descriptor. "buf" is
 *  assumed to end \n or \r and terminated by \0. It assumes that irc_control
 *  is valid, wich should be true as long as it's not called from outside
 *  comm_irc.c
 */
{
    if (as_server)
    {
        sock_printf( irc_control, ":%s %c %s :%s",
                     ircdata.mud_nick,
                     use_privmsg ? 'P' : 'N',
                     nick,
                     buf );
    } else
    {
        sock_printf( irc_control, "%s %s :%s",
                     use_privmsg ? "PRIVMSG" : "NOTICE",
                     nick, 
                     buf );
    }
}


void nick_printf (char *nick, bool use_privmsg, char *fmt, ...)
{
    va_list Args;

    va_start (Args, fmt);
    vsprintf (vbuf2, fmt, Args);
    write_to_nick( nick, vbuf2, use_privmsg );
    va_end( Args );
}


void write_to_channel( char *msg, bool use_privmsg )
/*
 *  Just a nice thing, for the echoes. Assumes \r at the end of the string.
 */
{
    if (irc_control == -1 || !logged_in)
        return;
    if (as_server)
    {
        sock_printf( irc_control, ":%s %c %s :%s",
                     ircdata.mud_nick,
                     use_privmsg ? 'P' : 'N',
                     ircdata.mud_channel,
                     msg );
    } else
    {
        sock_printf( irc_control, "%s %s :%s",
                     use_privmsg ? "PRIVMSG" : "NOTICE",
                     ircdata.mud_channel, 
                     msg );
    }
}


/* 
 * These ones are here just so that the translation tables look meaningful 
 */
typedef enum 
{
    ANSI_BLACK, ANSI_RED, ANSI_GREEN, ANSI_YELLOW,
    ANSI_BLUE, ANSI_MAGENTA, ANSI_CYAN, ANSI_WHITE,
    ANSI_HIGH_INTENSITY = 0x08, ANSI_BLINK = 0x80
} ansi_colors;
/* 
 * ANSI colors, as sent to create_color_code, are one byte, where the three
 * lower bits of the higher nibble define the background color, and the three
 * lower bits of the lower nibble define the foreground color. The highest
 * bit of the lower nibble gives "high intensity" to the foreground color,
 * and the highest bit of the higher nibble makes the foreground color blink.
 */

typedef enum
{
    MIRC_WHITE, MIRC_BLACK, MIRC_BLUE, MIRC_GREEN,
    MIRC_RED, MIRC_BROWN, MIRC_PURPLE, MIRC_ORANGE,
    MIRC_YELLOW, MIRC_LGREEN, MIRC_CYAN, MIRC_LCYAN,
    MIRC_LBLUE, MIRC_PINK, MIRC_GRAY, MIRC_LGRAY
} mirc_color;
/* 
 * The color byte is interpreted as a simple color index for mIRC colors;
 * the index is used in the table to map it to something close to its
 * ANSI equivalent and that looks good on mIRC.
 */

static sh_int mircb_tbl [16] =
{
    /* PCOLORS equivalents
     x (Black)	r (Red)		g (Green)	o (Orange)
     b (Dark Blue)	p (Purple)	c (Cyan)	w (Gray)
     z (Dark Grey)	R (Light Red)	G (Light Green)	Y (Yellow)
     B (Blue)	P (Pink)	C (Light Blue)	W (White)
   
     ansi equivalents 
     ANSI_BLACK,	ANSI_RED,	ANSI_GREEN,	ANSI_YELLOW,
     ANSI_BLUE,	ANSI_MAGENTA,	ANSI_CYAN,	ANSI_WHITE
   */
    MIRC_BLACK,   MIRC_RED,   MIRC_GREEN, MIRC_ORANGE,    /* low */
    MIRC_LBLUE,   MIRC_PURPLE,    MIRC_CYAN,  MIRC_LGRAY,

    MIRC_GRAY,    MIRC_RED,   MIRC_LGREEN,    MIRC_YELLOW,    /* high */
    MIRC_LBLUE,   MIRC_PINK,  MIRC_LCYAN, MIRC_WHITE
};
/* BLUE (replaced by LBLUE) barely visible on black background */

static sh_int mircw_tbl [16] =
{
/* PCOLORS equivalents
  x (Black)	r (Red)		g (Green)	o (Orange)
  b (Dark Blue)	p (Purple)	c (Cyan)	w (Gray)
  z (Dark Grey)	R (Light Red)	G (Light Green)	Y (Yellow)
  B (Blue)	P (Pink)	C (Light Blue)	W (White)

  ansi equivalents 
  ANSI_BLACK,	ANSI_RED,	ANSI_GREEN,	ANSI_YELLOW,
  ANSI_BLUE,	ANSI_MAGENTA,	ANSI_CYAN,	ANSI_WHITE
*/
    MIRC_WHITE,   MIRC_RED,   MIRC_GREEN, MIRC_ORANGE,    /* low */
    MIRC_BLUE,    MIRC_PURPLE,    MIRC_CYAN,  MIRC_GRAY,

    MIRC_LGRAY,   MIRC_RED,   MIRC_GREEN, MIRC_ORANGE,    /* high */
    MIRC_LBLUE,   MIRC_PINK,  MIRC_CYAN,  MIRC_BLACK
};
/* In white background, the black-white colors are inverted
 * (white=black, grey=grey, dark grey=light grey, black=white)
 * These colors look like hell on white background: 
 * YELLOW (replaced by ORANGE) LGREEN (by GREEN) LCYAN (by CYAN) 
 */



bool create_color_code( sh_int color, CHAR_DATA *ch, char *buf )
/* 
 * Create a color sequence of that color, for that character, and
 * store it in buf. Returns FALSE if nothing was done, TRUE otherwise.
 * Careful, this function is called with ch pointing to the original
 * character if they're switched, so ch->desc can be NULL. look for
 * ch->original->desc instead.
 */
/* NOTE: This function may *seem* to send inefficient color codes.
 * It was modified once to send more efficient codes, but weird 
 * things started to happen with ANSI colors... So I changed it back
 * and now it sends exactly the same codes as stock SMAUG. No
 * problems seen so far... The Gods of ANSI work in misterious ways...
 */
{
    char tmp[ MAX_INPUT_LENGTH ];
    int len;
    DESCRIPTOR_DATA *d;

    if (ch->switched)
        d = ch->switched->desc;
    else
        d = ch->desc;

    if (color == d->prevcolor)
        return FALSE;

    /*RIP is actualy RIP+ANSI..*/
    if (ch->color == COLOR_ANSI || ch->color == COLOR_RIP)
    {
        strcpy( buf, "\x1B[" );
        if ((color & 0x88) != (d->prevcolor & 0x88))
        /* if there's any change in the high bits */
        {
            strcat( buf, "m\x1B[" );
            if (color & 0x80)
                strcat( buf, "5;" );
            if (color & 0x08)
                strcat( buf, "1;" );
        }
        if ((color & 0x70) != (d->prevcolor & 0x70))
        {
            sprintf( tmp, "4%d;", (color & 0x70) >> 4 );
            strcat( buf, tmp );
        }
        if ((color & 0x07) != (d->prevcolor & 0x07))
        {
            sprintf( tmp, "3%d;", (color & 0x07) );
            strcat( buf, tmp );
        }
        len = strlen( buf );
        if (buf[len-1] == ';')
            buf[len-1] = 'm';
        else
            strcat( buf, "m" );
        d->prevcolor = color;
        return TRUE;
    } else if (ch->color == COLOR_MIRCW || ch->color == COLOR_MIRCB)
    {
        buf[0] = '\x0';
        /* Its VERY IMPORTANT that mirc colors sent have two digits! */
        if ((color & 0x0F) != (d->prevcolor & 0x0F) 
            || (color & 0xF0) != (d->prevcolor & 0xF0))
        {
            sprintf( tmp, "%c%02d", MESC, 
                     ch->color == COLOR_MIRCB ?
                     mircb_tbl[(color & 0x0F)] :
                     mircw_tbl[(color & 0x0F)] );
            strcat( buf, tmp );
            if ((color & 0xF0) != (d->prevcolor & 0xF0))
            {
                sprintf( tmp, ",%02d", 
                         ch->color == COLOR_MIRCB ?
                         mircb_tbl[((color & 0xF0) >> 4)] :
                         mircw_tbl[((color & 0xF0) >> 4)] );
                strcat( buf, tmp );
            }
        }
        d->prevcolor = color;
        return TRUE;
    }
    return FALSE;
}


bool create_special_code( char code, CHAR_DATA *ch, char *buf )
/* 
 * Create a special mIRC sequence (such as bold or underline).
 * These are escaped via S: &S<CODE>, i.e. &SB = bold. See
 * make_color_sequence in comm.c. None of this will work for
 * people who want ANSI, RIP, or no color.
 * Warning: this one is also called with ch pointing to och.
 */
{
    if (ch->color != COLOR_MIRCW && ch->color != COLOR_MIRCB)
        return FALSE;

    switch (code)
    {
        case 'B':           /* Bold */
            strcpy( buf, "\x2" );
            return TRUE;
        case 'U':           /* Underlined */
            strcpy( buf, "\x1F" );
            return TRUE;
        case 'R':           /* Reverse */
            strcpy( buf, "\x16" );
            return TRUE;
        case 'P':           /* Plain */
            strcpy( buf, "\xF" );
            return TRUE;
        case 'C':           /* Color escape */
            /* Useful if you want to close a color sequence without
             * having to set it plain (keeping bold, for instance).
             * You can also do things like "&SC04" to set red 
             * color manualy. It will look bad to non-mirc-color 
             * players, tho, since they will see the numbers 
             * ("04"). No way around that.
             */
            strcpy( buf, "\x3" );
            return TRUE;
        case 'S':           /* CTCP escape (and Sound) */
            strcpy( buf, "\x1");
            return TRUE;
    }
    return FALSE;
}

sh_int atype_to_color( sh_int AType, CHAR_DATA *ch )
/* Converts from "AType" (the colors defined in mud.h) 
 * to a color value as accepted by create_color_code
 * (same than in d->prevcolor).
 * These "ATypes" are just colors from 0-15, with the
 * 16 bit set if blinking. No background colors. 
 * In mIRC instead of blinking we make it red background.
 * Basicaly, a workaround so that create_color_code can be used
 * in set_char_color and set_pager_color
 */
{
    sh_int color;

    color = 0;
    if (ch->color == COLOR_ANSI || ch->color == COLOR_RIP)
        color = (AType & 0xF) | (AType > 15 ? 0x80 : 0x0 );
    else if (ch->color == COLOR_MIRCB || ch->color == COLOR_MIRCW)
        color = (AType & 0xF) | (AType > 15 ? (ANSI_RED << 4) : 0x0);

    return color;
}


void send_mirc_sound( CHAR_DATA *ch, char *sound_filename )
{
    if (ch->desc != NULL && IS_IRC_DESC(ch->desc))
    {
        nick_printf( ch->desc->nick_data->nick, TRUE,
                     "\x1" "SOUND %s\x1\r", sound_filename );
    }
}




/**** Functions to load/save the ircdata *************************************/
/* (Should really go in db.c) */

#if defined(KEY)
#undef KEY
#endif
#define KEY( literal, field, value ) 	\
	if( !str_cmp( word, literal) )	\
	{				\
		field = value;		\
		fMatch = TRUE;		\
		break;			\
	}

void fread_ircdata( IRCDATA *ircd, FILE *fp )
{
    char *word;
    bool fMatch;

    for (; ;)
    {
        word   = feof( fp ) ? "End" : fread_word( fp );
        fMatch = FALSE;

        switch (UPPER(word[0]))
        {
            case '*':
                fMatch = TRUE;
                fread_to_eol( fp );
                break;
            case 'C':
                KEY( "CServer", ircd->cserver,  STRALLOC(fread_word(fp)) );
                KEY( "CPassword",   ircd->cpwd, STRALLOC(fread_word(fp)) );
                KEY( "CPort",   ircd->cport,    fread_number(fp) );
                break;
            case 'D':
                KEY( "DccPort", ircd->dcc_port, fread_number(fp) );
                KEY( "DccTimeout",  ircd->dcc_timeout, fread_number(fp) );
                KEY( "Desc",    ircd->mud_desc, fread_string(fp) );
                break;
            case 'E':
                if (!str_cmp( "End", word ))
                    return;
                break;
            case 'M':
                KEY( "MudServer",   ircd->mud_server, STRALLOC(fread_word(fp)) );
                KEY( "MudNick", ircd->mud_nick, STRALLOC(fread_word(fp)) );
                KEY( "MudChannel",  ircd->mud_channel, STRALLOC(fread_word(fp)) );
                if (!str_cmp( word, "Mode" ))
                {
                    fMatch = TRUE;
                    word = fread_word( fp );
                    if (!str_cmp( word, "server" ))
                        ircd->mode = MODE_SERVER;
                    else if (!str_cmp( word, "client" ))
                        ircd->mode = MODE_CLIENT;
                    else
                    {
                        ircd->mode = MODE_CLIENT;
                        ircbug("fread_ircdata: wrong keyword for mode.\n\r" );
                    }
                }
                break;
            case 'P':
                KEY( "PseudoHost",  ircd->mud_pseudo_host, STRALLOC(fread_word(fp)) );
                break;
            case 'R':
                KEY( "Reconnect",   ircd->reconnect, fread_number(fp) );
                break;
            case 'S':
                KEY( "SServer", ircd->sserver,  STRALLOC(fread_word(fp)) );
                KEY( "SPassword",   ircd->spwd, STRALLOC(fread_word(fp)) );
                KEY( "SPort",   ircd->sport,    fread_number(fp) );
                break;
            case 'T':
                KEY( "Timeout", ircd->resp_timeout, fread_number(fp) );
                break;
        }

        if (!fMatch)
        {
            ircbug( "Fread_ircdata: no match: %s", word );
        }
    }
}


bool load_ircdata( IRCDATA *ircd )
{
    char filename[MAX_INPUT_LENGTH];
    FILE *fp;
    bool found;

    found = FALSE;
    sprintf( filename, "%sircdata.dat", SYSTEM_DIR );

    if (( fp = fopen( filename, "r" ) ) != NULL)
    {
        found = TRUE;
        for (; ;)
        {
            char letter;
            char *word;

            letter = fread_letter( fp );
            if (letter == '*')
            {
                fread_to_eol( fp );
                continue;
            }

            if (letter != '#')
            {
                ircbug( "Load_ircdata: # not found." );
                break;
            }

            word = fread_word( fp );
            if (!str_cmp( word, "IRCDATA" ))
            {
                fread_ircdata( ircd, fp );
                break;
            } else
                if (!str_cmp( word, "END"  ))
                break;
            else
            {
                ircbug( "Load_ircdata: bad section." );
                break;
            }
        }
        fclose( fp );
    }
    return found;
}


void save_ircdata( IRCDATA ircd )
{
    FILE *fp;
    char filename[MAX_INPUT_LENGTH];

    sprintf( filename, "%sircdata.dat", SYSTEM_DIR );

    fclose( fpReserve );
    if (( fp = fopen( filename, "w" ) ) == NULL)
    {
        ircbug( "save_ircdata: fopen" );
        perror( filename );
    } else
    {
        fprintf( fp, "#IRCDATA\n" );
        fprintf( fp, "Mode          %s\n", ircd.mode == MODE_SERVER ? "server" : "client" );
        fprintf( fp, "Timeout       %d\n", ircd.resp_timeout );
        fprintf( fp, "Reconnect     %d\n", ircd.reconnect );
        fprintf( fp, "SServer       %s\n", ircd.sserver );
        fprintf( fp, "SPort         %d\n", ircd.sport );
        fprintf( fp, "SPassword     %s\n", ircd.spwd );
        fprintf( fp, "CServer       %s\n", ircd.cserver );
        fprintf( fp, "CPort         %d\n", ircd.cport );
        fprintf( fp, "CPassword     %s\n", ircd.cpwd );
        fprintf( fp, "PseudoHost    %s\n", ircd.mud_pseudo_host );
        fprintf( fp, "MudServer     %s\n", ircd.mud_server );
        fprintf( fp, "MudNick       %s\n", ircd.mud_nick );
        fprintf( fp, "MudChannel    %s\n", ircd.mud_channel );
        fprintf( fp, "DccPort       %d\n", ircd.dcc_port );
        fprintf( fp, "DccTimeout    %d\n", ircd.dcc_timeout );
        fprintf( fp, "Desc          %s~\n", ircd.mud_desc );
        fprintf( fp, "End\n\n" );
        fprintf( fp, "#END\n" );
    }
    fclose( fp );
    fpReserve = fopen( NULL_FILE, "r" );
    return;
}




/** Functions that implement IRC related commands ****************************/


void do_ircconnect( CHAR_DATA *ch, char *argument )
{
    if (IS_NPC(ch) || ch->level < LEVEL_SUB_IMPLEM)
    {
        send_to_char( "You can't ircconnect.\n\r", ch );
        return;
    }

    if (irc_control != -1)
    {
        ch_printf(ch, "Already connected as %s to %s %d.\n\r", 
                  as_server ? "server" : "client",
                  as_server ? ircdata.sserver : ircdata.cserver,
                  as_server ? ircdata.sport : ircdata.cport );
    } else
        connect_to_irc( );
}


void do_ircdisconnect( CHAR_DATA *ch, char *argument )
{
    char buf[ MAX_STRING_LENGTH ];

    if (IS_NPC(ch) || ch->level < LEVEL_SUB_IMPLEM)
    {
        send_to_char( "You can't ircdisconnect.\n\r", ch );
        return;
    }

    if (IS_IRC_DESC(ch->desc))
    {
        if (argument[0] == '!')
            argument = one_argument(argument, buf, sizeof(buf));
        else
        {
            send_to_char( "You are playing from IRC. You would go link-dead if the mud disconnects.\n\r" \
                          "Type \"ircdisconnect ! <text>\" if you still want to do it.\n\r", ch );
            return;
        }
    }

    if (irc_control == -1)
        send_to_char( "Not connected to IRC.\n\r", ch );
    else
    {
        if (argument[0] == '\0')
            sprintf(buf, "(%s) Disconnecting.", ch->name );
        else
            sprintf(buf, "(%s) %s", ch->name, argument );
        disconnect_from_irc( buf );
    }
}


void do_ircset( CHAR_DATA *ch, char *argument )
{
    char arg[ MAX_INPUT_LENGTH];
    sh_int num;
    bool is_num, found;

    if (IS_NPC(ch) || ch->level < LEVEL_SUB_IMPLEM)
    {
        send_to_char( "You can't ircset.\n\r", ch );
        return;
    }

    if (argument[0] == '\0')
    {
        pager_printf(ch, "\n\r" );
        pager_printf(ch, "Mode: %s   Login timeout: %d seconds   Reconnect: %d\n\r", 
                     ircdata.mode == MODE_CLIENT ? "client" : "server",
                     ircdata.resp_timeout,
                     ircdata.reconnect );
        pager_printf(ch, "Sserver:  %-16s   Sport: %d   Spwd: <not shown>\n\r",
                     ircdata.sserver, ircdata.sport );
        pager_printf(ch, "Cserver:  %-16s   Cport: %d   Cpwd: <not shown>\n\r",
                     ircdata.cserver, ircdata.cport );
        pager_printf(ch, "Mud host: %-16s   Mud Server: %s\n\r",
                     ircdata.mud_pseudo_host, ircdata.mud_server );
        pager_printf(ch, "Mud nick: %s   Mud channel: %s\n\r",
                     ircdata.mud_nick, ircdata.mud_channel );
        pager_printf(ch, "DCC Port: %d  DCC timeout: %d\n\r",
                     ircdata.dcc_port, ircdata.dcc_timeout );
        pager_printf(ch, "Description: %s\n\r",
                     ircdata.mud_desc );
        pager_printf(ch, "State: %s\n\r",
                     irc_control == -1 ? "Not connected." :
                     (!logged_in ? "Connected. Registering..." : "Conected and registered.") );
        return;
    }

    argument = one_argument(argument, arg, sizeof(arg));
    smash_tilde( argument );

    if (!str_cmp( arg, "help" ))
    {
        do_help( ch, "ircset" );
        return;
    } else if (!str_cmp( arg, "save" ))
    {
        save_ircdata( ircdata );
        send_to_char( "Saved ircdata.\n\r", ch );
        return;
    } else if (!str_cmp( arg, "mode"))
    {
        if (!str_cmp( argument, "server" ))
            ircdata.mode = MODE_SERVER;
        else if (!str_cmp( argument, "client" ))
            ircdata.mode = MODE_CLIENT;
        else
        {
            send_to_char( "Mode is either \"server\" or \"client\".", ch );
            return;
        }
    } else if (!str_cmp( arg, "sserver"))
    {
        if (!strchr( argument, '.' ))
        {
            send_to_char( "Invalid value for sserver.\n\r", ch );
            return;
        }
        if (ircdata.sserver)
            STRFREE( ircdata.sserver );
        ircdata.sserver = STRALLOC(argument);
    } else if (!str_cmp( arg, "spwd"))
    {
        if (ircdata.spwd)
            STRFREE( ircdata.spwd );
        ircdata.spwd = STRALLOC(argument);
    } else if (!str_cmp( arg, "cserver"))
    {
        if (ircdata.cserver)
            STRFREE( ircdata.cserver );
        ircdata.cserver = STRALLOC(argument);
    } else if (!str_cmp( arg, "cpwd"))
    {
        if (ircdata.cpwd)
            STRFREE( ircdata.cpwd );
        ircdata.cpwd = STRALLOC(argument);
    } else if (!str_cmp( arg, "mud_nick" ))
    {
        if (irc_control != -1)
        {
            log_string( "IRC: We're connected. Changing the mud's nick." );
            if (as_server)
                sock_printf( irc_control, ":%s NICK %s\r", ircdata.mud_nick, argument );
            else
                sock_printf( irc_control, "NICK %s\r", argument );
        }
        if (ircdata.mud_nick)
            STRFREE( ircdata.mud_nick );
        ircdata.mud_nick = STRALLOC(argument);
    } else if (!str_cmp( arg, "mud_channel" ))
    {
        if (irc_control != -1 && !as_server)
        {
            log_string( "IRC: We're connected. Changing the mud's channel." );
            if (ircdata.mud_channel)
                sock_printf( irc_control, "PART %s\r", ircdata.mud_channel );
            sock_printf( irc_control, "JOIN %s\r", argument );
        }
        if (ircdata.mud_channel)
            STRFREE( ircdata.mud_channel );
        ircdata.mud_channel = STRALLOC(argument);
    } else if (!str_cmp( arg, "mud_host" ))
    {
        if (ircdata.mud_pseudo_host)
            STRFREE( ircdata.mud_pseudo_host );
        ircdata.mud_pseudo_host = STRALLOC(argument);
    } else if (!str_cmp( arg, "mud_server" ))
    {
        if (ircdata.mud_server)
            STRFREE( ircdata.mud_server );
        ircdata.mud_server = STRALLOC(argument);
    }

    else if (!str_cmp( arg, "desc" ))
    {
        if (ircdata.mud_desc)
            STRFREE( ircdata.mud_desc );
        ircdata.mud_desc = STRALLOC(argument);
    } else
    {
        num = -1; /* just to get rid of warning */
        if (argument[0] != '\0' && is_number(argument))
        {
            is_num = TRUE;
            num = atoi( argument );
        } else
            is_num = FALSE;

        found = FALSE;
        if (!str_cmp( arg, "sport") && (found=TRUE) && is_num)
        {
            if (num <= 1024)
            {
                send_to_char( "Port must be above 1024.\n\r", ch );
                return;
            }
            ircdata.sport = num;
        } else if (!str_cmp( arg, "cport") && (found=TRUE) && is_num)
        {
            if (num <= 1024)
            {
                send_to_char( "Port must be above 1024.\n\r", ch );
                return;
            }
            ircdata.cport = num;
        } else if (!str_cmp( arg, "login_timeout") && (found=TRUE) && is_num)
        {
            if (num <= 10)
            {
                send_to_char( "You must give it at least 10 seconds.\n\r", ch );
                return;
            }
            ircdata.resp_timeout = num;
        } else if (!str_cmp( arg, "dcc_port" ) && (found=TRUE) && is_num)
        {
            if (num <= 1024)
            {
                send_to_char( "Port must be above 1024.\n\r", ch );
                return;
            }
            ircdata.dcc_port = num;
            if (irc_control && dcc_control)
            {
                log_string( "IRC: Reinitializing the DCC port" );
                closesocket( dcc_control );
                dcc_control = init_socket( ircdata.dcc_port );
            }
        } else if (!str_cmp( arg, "dcc_timeout" ) && (found=TRUE) && is_num)
        {
            ircdata.dcc_timeout = num;
        } else if (!str_cmp( arg, "reconnect" ) && (found=TRUE) && is_num)
        {
            ircdata.reconnect = (num != 0);
        } else if (found && !is_num)
        {
            ch_printf( ch, "The \"%s\" option takes a number as argument.\n\r", arg );
            return;
        } else
        {
            send_to_char( "Ircset what?\n\r", ch );
            return;
        }
    }

    send_to_char( "Ok.\n\r", ch );
}


void do_color( CHAR_DATA *ch, char *argument )
{
    char *kwd[] = {"none", "ansi", "rip", "mircb", "mircw", NULL};
    char *cdesc[] = {"no", "ANSI", "RIP + ANSI", "mIRC (Black background)", "mIRC (White background)"};
    sh_int i;

    if (argument[0] == '\0')
    {
        ch_printf( ch, "What color setting do you want? ANSI, RIP, MIRCW, MIRCB?\n\r" );
        ch_printf( ch, "Currently sending %s colors.\n\r",
                   cdesc[ ch->color ] );
        return;
    }

    for (i=0 ; kwd[i] != NULL ; i++)
    {
        if (!str_cmp( argument, kwd[i] ))
        {
            ch->color = i;
            sprintf( log_buf, "&BOk. You will now recieve &g%s&B colors.\n\r", 
                     cdesc[i] );
            if (ch->color == COLOR_ANSI)
                send_to_char( "\033[0;37;40m", ch );
            send_to_char_color( log_buf, ch );
            return;
        }
    }
    ch_printf( ch, "What color setting do you want? ANSI, RIP, MIRCW, MIRCB?\n\r" );
}

/*
irclink <descriptor number> [nick]
This command will link a descriptor to an IRC nickname, thus making it
a DCC login instead of a Telnet login, or viceversa, unlink the nick
from its descriptor, making a Telnet login out of a DCC login.
Examples:
    irclink 14 pepto
      Will link descriptor 14 (currently a telnet login) with the IRC 
      nick pepto, making it a DCC login.
    irclink 14
*/
void do_irclink( CHAR_DATA *ch, char *argument )
{
    char desc_num_str[ MAX_INPUT_LENGTH ];
    DESCRIPTOR_DATA *d;
    int desc_num;
    char nick[ MAX_INPUT_LENGTH ];
    NICK_DATA *n;
    CHAR_DATA *victim;

    n = find_nick( first_nick, nick );
    argument = one_argument(argument, desc_num_str, sizeof(desc_num_str));
    one_argument(argument, nick, sizeof(nick));

    if (desc_num_str[0] == '\0')
    {
        send_to_char( "Syntax: irclink <descriptor> [nick]\n\r", ch );
        return;
    }
    if (!is_number( desc_num_str ))
    {
        send_to_char( "Descriptor argument must be numeric.\n\r", ch );
        return;
    }
    desc_num = atoi( desc_num_str );

    for (d = first_descriptor ; d ; d = d->next)
    {
        if (d->descriptor == desc_num)
            break;
    }

    if (d == NULL)
    {
        send_to_char( "No such descriptor.\n\r", ch );
        return;
    }

    victim = d->original ? d->original : d->character;
    if (victim && victim->level >= ch->level && str_cmp(victim->name, ch->name))
    {
        send_to_char( "Go link someone else!\n\r", ch );
        return;
    }

    if (nick[0] != '\0')
    /* link */
    {
        if (d->nick_data != NULL)
        {
            ch_printf( ch, "That descriptor is already associated with nick %s. Unlink it first.\n\r",
                       d->nick_data->nick );
            return;
        }

        n = find_nick( first_nick, nick );
        if (n == NULL)
        {
            send_to_char( "No such nick in the nick database.\n\r", ch );
            return;
        } else if (n->desc)
        {
            send_to_char( "That nick is already associated with a descriptor.\n\r", ch );
            return;
        }
        n->desc = d;
        d->nick_data = n;
        n->login_mode = MODE_DCC;
        ch_printf( ch, "Ok. Descriptor %d is now associated with nick %s (A DCC login).\n\r",
                   desc_num,
                   n->nick );
    } else
    /* unlink */
    {
        if (d->nick_data == NULL)
        {
            send_to_char( "That descriptor is not associated with a nick (a Telnet login).\n\r", ch );
            return;
        }
        ch_printf( ch, "Ok. Descriptor %d is no longer associated with nick %s (A telnet login).\n\r", 
                   desc_num, 
                   d->nick_data->nick );
        d->nick_data->desc = NULL;
        d->nick_data = NULL;
    }
}


/** Functions to deal with nickserv ******************************************/

bool is_auth_nickserv( DESCRIPTOR_DATA *d )
/* 
 * The descriptor can be of any type; we only know it doesn't have a pwd.
 */
{
    char *nick;

    if (!IS_IRC_DESC(d))
    {
        write_to_buffer( d, "You don't have a password. Please connect via IRC.\n\r", 0 );
        d->character->desc = NULL;
        close_socket( d, FALSE );
    } else
    {
        nick = d->nick_data->nick;
        switch (d->nick_data->nickserv)
        {
            case NICKSERV_NONE:
                nick_printf( nick, FALSE, "You don't have a mud password. Mud passwords are not required,\r" );
                nick_printf( nick, FALSE, "but encouraged. Nickserv authentication will never be switched\r" );
                nick_printf( nick, FALSE, "off anymore, *except* if you have a mud password.\r" );
                nick_printf( nick, FALSE, "Standby for nickserv verification...\r" );
                nick_printf( "NickServ", TRUE, "VERIFY %s\r", d->nick_data->nick );
                d->nick_data->nickserv = NICKSERV_START;
                break;
            case NICKSERV_START:
                nick_printf( nick, FALSE, "Standby for nickserv verification...\r" );
                nick_printf( "NickServ", TRUE, "VERIFY %s\r", d->nick_data->nick );
                break;
            case NICKSERV_UNREG:
                nick_printf( nick, FALSE, "Your nick is not registered. /msg NickServ help register.\r" );
                d->nick_data->nickserv = NICKSERV_NONE;
                close_socket( d, TRUE );
                break;
            case NICKSERV_UNREC:
                nick_printf( nick, FALSE, "You are not identified to nickserv. /msg NickServ identify <pwd>.\r" );
                d->nick_data->nickserv = NICKSERV_NONE;
                close_socket( d, TRUE );
                break;
            case NICKSERV_REC:
                nick_printf( nick, FALSE, "Verified to nickserv. Type \"help password\" once in the game.\r" );
                d->nick_data->nickserv = NICKSERV_NONE;
                return TRUE;
        }
    }
    return FALSE;
}

void msg_from_nickserv( char *cmd, char *txt )
/* We got a msg from nickserv. See what he says about one of our nicks.. */
{
    NICK_DATA *tn;

    tn = find_nick( first_nick, txt );
    if (!tn || !tn->desc || tn->nickserv != NICKSERV_START)
        return;

    if (!str_cmp( cmd, "UNREG"))
        tn->nickserv = NICKSERV_UNREG;
    else if (!str_cmp( cmd, "UNREC"))
        tn->nickserv = NICKSERV_UNREC;
    else if (!str_cmp( cmd, "REC" ))
        tn->nickserv = NICKSERV_REC;

    nanny( tn->desc, "" );
    return;
}