/* vim: set shiftwidth=3 softtabstop=3 expandtab: */ 

/*
 * Copyright (C) 2002-2003  Erik Fears
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to
 *
 *       The Free Software Foundation, Inc.
 *       59 Temple Place - Suite 330
 *       Boston, MA  02111-1307, USA.
 *
 *
 */

#include "setup.h"

#include <stdio.h>
#include <unistd.h>

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#endif

#ifdef HAVE_STRINGS_H
# include <strings.h>
#endif

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#ifdef TIME_WITH_SYS_TIME
# include <sys/time.h>
# include <time.h>
#else
# ifdef HAVE_SYS_TIME_H
#  include <sys/time.h>
# else
#  include <time.h>
# endif
#endif

#include <errno.h>
#include <stdarg.h>
#include <regex.h>

#include "config.h"
#include "irc.h"
#include "log.h"
#include "opercmd.h"
#include "scan.h"
#include "dnsbl.h"
#include "stats.h"
#include "extern.h"
#include "options.h"
#include "match.h"
#include "compat.h"
#include "negcache.h"
#include "malloc.h"
#include "main.h"

RCSID("$Id: irc.c,v 1.27 2003/11/29 19:56:19 strtok Exp $");

static void irc_init(void);
static void irc_connect(void);
static void irc_reconnect(void);
static void irc_read(void);
static void irc_parse(void);

static struct ChannelConf *get_channel(const char *);

static struct UserInfo *userinfo_create(char *);
static void userinfo_free(struct UserInfo *source);

static void m_ping(char **, unsigned int, char *, struct UserInfo *);
static void m_invite(char **, unsigned int, char *, struct UserInfo *);
static void m_privmsg(char **, unsigned int, char *, struct UserInfo *);
static void m_ctcp(char **, unsigned int, char *, struct UserInfo *);
static void m_notice(char **, unsigned int, char *, struct UserInfo *);
static void m_perform(char **, unsigned int, char *, struct UserInfo *);
static void m_userhost(char **, unsigned int, char *, struct UserInfo *);
static void m_cannot_join(char **, unsigned int, char *, struct UserInfo *);
static void m_kill(char **, unsigned int, char *, struct UserInfo *);

extern struct cnode *nc_head;

/*
 * Certain variables we don't want to allocate memory for over and over
 * again so global scope is given.
 */

char                 IRC_RAW[MSGLENMAX];         /* Buffer to read data into              */
char                 IRC_SENDBUFF[MSGLENMAX];    /* Send buffer                           */
char                 IRC_CHANNELS[MSGLENMAX];    /* Stores comma delim list of channels   */
int                  IRC_RAW_LEN    = 0;         /* Position of IRC_RAW                   */

int                  IRC_FD         = 0;        /* File descriptor for IRC client        */

struct bopm_sockaddr IRC_SVR;                   /* Sock Address Struct for IRC server    */
struct bopm_ircaddr  IRC_LOCAL;                 /* Sock Address Struct for Bind          */

fd_set               IRC_READ_FDSET;             /* fd_set for IRC (read) data for select()*/
struct timeval       IRC_TIMEOUT;                /* timeval struct for select() timeout   */

time_t               IRC_LAST = 0;               /* Last full line of data from irc server*/
time_t               IRC_LASTRECONNECT = 0;      /* Time of last reconnection */

/* Table should be ordered with most occuring (or priority)
   commands at the top of the list. */

static struct CommandHash COMMAND_TABLE[] = {
         {"NOTICE",               m_notice         },
         {"PRIVMSG",              m_privmsg        },
         {"PING",                 m_ping           },
         {"INVITE",               m_invite         },
         {"001",                  m_perform        },
         {"302",                  m_userhost       },
         {"471",                  m_cannot_join    },
         {"473",                  m_cannot_join    },
         {"474",                  m_cannot_join    },
         {"475",                  m_cannot_join    },
         {"KILL",                 m_kill           }
      };

/* irc_cycle
 *
 *    Pass control to the IRC portion of BOPM to handle any awaiting IRC events.
 *
 * Parameters:
 *    None
 *
 * Return:
 *    None
 *
 */

void irc_cycle(void)
{
   if (IRC_FD <= 0)
   {
      /* Initialise negative cache. */
      if (OptionsItem->negcache > 0)
         nc_init(&nc_head);

      /* Resolve remote host. */
      irc_init();

      /* Connect to remote host. */
      irc_connect();

      return;      /* In case connect() immediately failed */ 
  }

   IRC_TIMEOUT.tv_sec  = 0;
   /* Block .025 seconds to avoid excessive CPU use on select(). */
   IRC_TIMEOUT.tv_usec = 25000;

   FD_ZERO(&IRC_READ_FDSET);
   FD_SET(IRC_FD, &IRC_READ_FDSET);

   switch (select((IRC_FD + 1), &IRC_READ_FDSET, 0, 0, &IRC_TIMEOUT))
   {
      case -1:
         return;
         break;
      case 0:
         break;
      default:
         /* Check if IRC data is available. */
         if (FD_ISSET(IRC_FD, &IRC_READ_FDSET))
            irc_read();
         break;
   }
}




/* irc_init
 *
 *    Resolve IRC host and perform other initialization.
 *
 * Parameters: 
 *    None
 * 
 * Return:
 *    None
 *
 */

static void irc_init(void)
{
   node_t *node;
   struct ChannelConf *chan;
   struct bopm_sockaddr bsaddr;
   struct in_addr *irc_host;


   if (IRC_FD)
      close(IRC_FD);

   memset(&IRC_SVR, 0, sizeof(IRC_SVR));
   memset(&IRC_LOCAL, 0, sizeof(IRC_LOCAL));
   memset(&bsaddr, 0, sizeof(struct bopm_sockaddr));

   /* Resolve IRC host. */
   if ((irc_host = firedns_resolveip4(IRCItem->server)) == NULL)
   {
      log_printf("IRC -> firedns_resolveip4(\"%s\"): %s", IRCItem->server,
            firedns_strerror(fdns_errno));
      exit(EXIT_FAILURE);
   }

   IRC_SVR.sa4.sin_family = AF_INET;
   IRC_SVR.sa4.sin_port = htons(IRCItem->port);
   IRC_SVR.sa4.sin_addr = *irc_host;

   if (IRC_SVR.sa4.sin_addr.s_addr == INADDR_NONE)
   {
      log_printf("IRC -> Unknown error resolving remote host (%s)",
          IRCItem->server);
      exit(EXIT_FAILURE);
   }

   /* Request file desc for IRC client socket */
   IRC_FD = socket(AF_INET, SOCK_STREAM, 0);

   if (IRC_FD == -1)
   {
      switch(errno)
      {
         case EINVAL:
         case EPROTONOSUPPORT:
            log_printf("IRC -> socket(): SOCK_STREAM is not "
                "supported on this domain");
            break;
         case ENFILE:
            log_printf("IRC -> socket(): Not enough free file "
                "descriptors to allocate IRC socket");
            break;
         case EMFILE:
            log_printf("IRC -> socket(): Process table overflow when "
                "requesting file descriptor");
            break;
         case EACCES:
            log_printf("IRC -> socket(): Permission denied to create "
                "socket of type SOCK_STREAM");
            break;
         case ENOMEM:
            log_printf("IRC -> socket(): Insufficient memory to "
                "allocate socket");
            break;
         default:
            log_printf("IRC -> socket(): Unknown error allocating "
                "socket");
            break;
      }
      exit(EXIT_FAILURE);
   }

   /* Bind */
   if (strlen(IRCItem->vhost) > 0)
   {
      int bindret = 0;
      if (!inet_pton(AF_INET, IRCItem->vhost, &(IRC_LOCAL.in4.s_addr)))
      {
         log_printf("IRC -> bind(): %s is an invalid address", IRCItem->vhost);
         exit(EXIT_FAILURE);
      }
      bsaddr.sa4.sin_addr.s_addr = IRC_LOCAL.in4.s_addr;
      bsaddr.sa4.sin_family = AF_INET;
      bsaddr.sa4.sin_port = htons(0);

      bindret = bind(IRC_FD, (struct sockaddr *) &(bsaddr.sa4), sizeof(bsaddr.sa4));

      if (bindret)
      {
         switch(errno)
         {
            case EACCES:
               log_printf("IRC -> bind(): No access to bind to %s",
                   IRCItem->vhost);
               break;
            default:
               log_printf("IRC -> bind(): Error binding to %s (%d)",
                   IRCItem->vhost, errno);
               break;
         }
         exit(EXIT_FAILURE);
      }

   }

   /* Setup target list for irc_send_channels */
   IRC_CHANNELS[0] = '\0';
   LIST_FOREACH(node, IRCItem->channels->head)
   {
      chan = (struct ChannelConf *) node->data;
      strncat(IRC_CHANNELS, chan->name, MSGLENMAX);

      if(node->next)
         strncat(IRC_CHANNELS, ",", MSGLENMAX);
   }
   IRC_CHANNELS[MSGLENMAX] = '\0';


}


/* irc_send
 *
 *    Send data to remote IRC host.
 *
 * Parameters:
 *    data: Format of data to send
 *    ...: varargs to format with
 * 
 * Return: NONE
 */


void irc_send(char *data, ...)
{
   va_list arglist;
   char    data2[MSGLENMAX];
   char    tosend[MSGLENMAX];

   va_start(arglist, data);
   vsnprintf(data2, MSGLENMAX, data, arglist);
   va_end(arglist);

   if (OPT_DEBUG >= 2)
      log_printf("IRC SEND -> %s", data2);

   snprintf(tosend, MSGLENMAX, "%s\n", data2);

   if (send(IRC_FD, tosend, strlen(tosend), 0) == -1)
   {
      /* Return of -1 indicates error sending data; we reconnect. */
      log_printf("IRC -> Error sending data to server\n");
      irc_reconnect();
   }
}

/* irc_send
 *
 *    Send privmsg to all channels.
 *
 * Parameters:
 *    data: Format of data to send
 *    ...: varargs to format with
 *
 * Return: NONE
 */

void irc_send_channels(char *data, ...)
{
   va_list arglist;
   char    data2[MSGLENMAX];
   char    tosend[MSGLENMAX];

   va_start(arglist, data);
   vsnprintf(data2, MSGLENMAX, data, arglist);
   va_end(arglist);

   snprintf(tosend, MSGLENMAX, "PRIVMSG %s :%s", IRC_CHANNELS, data2);

   irc_send("%s", tosend);
}




/* irc_connect
 *
 *    Connect to IRC server.
 *    XXX: FD allocation done here
 *
 * Parameters: NONE
 * Return: NONE
 *
 */

static void irc_connect(void)
{
   /* Connect to IRC server as client. */
   if (connect(IRC_FD, (struct sockaddr *) &IRC_SVR,
               sizeof(IRC_SVR)) == -1)
   {
      switch(errno)
      {
         case EISCONN:
            /* Already connected */
            return;
         case ECONNREFUSED:
            log_printf("IRC -> connect(): Connection refused by (%s)",
                IRCItem->server);
            break;
         case ETIMEDOUT:
            log_printf("IRC -> connect(): Timed out connecting to (%s)",
                IRCItem->server);
            break;
         case ENETUNREACH:
            log_printf("IRC -> connect(): Network unreachable");
            break;
         case EALREADY:
            /* Previous attempt not complete */
            return;
         default:
            log_printf("IRC -> connect(): Unknown error connecting to (%s)",
                IRCItem->server);

            if (OPT_DEBUG >= 1)
               log_printf("%s", strerror(errno));
      }
      /* Try to connect again */
      irc_reconnect();
      return;
   }

   irc_send("NICK %s", IRCItem->nick);

   if(strlen(IRCItem->password) > 0)
      irc_send("PASS %s", IRCItem->password);

   irc_send("USER %s %s %s :%s",
            IRCItem->username, IRCItem->username, IRCItem->username,
            IRCItem->realname);

   time(&IRC_LAST);
}


/* irc_reconnect
 *
 *    Close connection to IRC server.
 *
 * Parameters: NONE
 *
 * Return: NONE
 */

static void irc_reconnect(void)
{

   time_t present;
   
   time(&present);
  
   /* Only try to reconnect every RECONNECT_INTERVAL seconds */ 
   if((present - IRC_LASTRECONNECT) < RECONNECTINTERVAL)
   {
      /* Sleep to avoid excessive CPU */
      sleep(1);
      return;
   }

   time(&IRC_LASTRECONNECT);

   if(IRC_FD > 0)
      close(IRC_FD);

   /* Set IRC_FD 0 for reconnection on next irc_cycle(). */
   IRC_FD = 0;

   log_printf("IRC -> Connection to (%s) failed, reconnecting.", IRCItem->server);
}



/* irc_read
 *
 *    irc_read is called my irc_cycle when new data is ready to be
 *    read from the irc server. 
 *
 * Parameters: NONE
 * Return: NONE
 *
 */

static void irc_read(void)
{
   int len;
   char c;

   while ((len = read(IRC_FD, &c, 1)) > 0)
   {
      if (c == '\r')
         continue;

      if (c == '\n')
      {
         /* Null string. */
         IRC_RAW[IRC_RAW_LEN] = '\0';
         /* Parse line. */
         irc_parse();
         /* Reset counter. */
         IRC_RAW_LEN = 0;
         break;
      }

      if (c != '\r' && c != '\n' && c != '\0')
         IRC_RAW[IRC_RAW_LEN++] = c;
   }

   if((len <= 0) && (errno != EAGAIN))
   {
      if(OPT_DEBUG >= 2)
         log_printf("irc_read -> errno=%d len=%d", errno, len);
      irc_reconnect();
      IRC_RAW_LEN = 0;
      return;
   }
}


/* irc_parse
 *
 *    irc_parse is called by irc_read when a full line of data
 *    is ready to be parsed.
 *
 * Parameters: NONE
 * Return: NONE
 *
 */


static void irc_parse(void)
{
   struct UserInfo *source_p;
   char *pos;
   unsigned int i;

   /*
      parv stores the parsed token, parc is the count of the parsed 
      tokens
     
      parv[0] is ALWAYS the source, and is the server name of the source
      did not exist 
   */

   static char            *parv[17];
   static unsigned int     parc;
   static char             msg[MSGLENMAX];    /* Temporarily stores IRC msg to pass to handlers */

   parc = 1;

   if(IRC_RAW_LEN <= 0)
      return;

   if (OPT_DEBUG >= 2)
      log_printf("IRC READ -> %s", IRC_RAW);

   time(&IRC_LAST);

   /* Store a copy of IRC_RAW for the handlers (for functions that need PROOF) */
   strcpy(msg, IRC_RAW);

   /* parv[0] is always the source */
   if(IRC_RAW[0] == ':')
      parv[0] = IRC_RAW + 1;
   else
   {
      parv[0] = IRCItem->server;
      parv[parc++] = IRC_RAW;
   }

   pos = IRC_RAW;

   while((pos = strchr(pos, ' ')) && parc <= 17)
   {

      /* Avoid excessive spaces and end of IRC_RAW */
      if(*(pos + 1) == ' ' && *(pos + 1) == '\0')
      {
         pos++;
         continue;
      }

      /* Anything after a : is considered the final string of the
            message */
      if(*(pos + 1) == ':')
      {
         parv[parc++] = pos + 2;
         *pos = '\0';
         break;
      }

      /* Set the next parv at this position and replace the space with a
         \0 for the previous parv */
      parv[parc++] = pos + 1;
      *pos = '\0';
      pos++;
   }

   /* Generate a UserInfo struct from the source */

   source_p = userinfo_create(parv[0]);

   /* Determine which command this is from the command table
      and let the handler for that command take control */

   for(i = 0; i < (sizeof(COMMAND_TABLE) / sizeof(struct CommandHash)); i++)
      if(strcasecmp(COMMAND_TABLE[i].command, parv[1]) == 0)
      {
         (*COMMAND_TABLE[i].handler)(parv, parc, msg, source_p);
         break;
      }

   userinfo_free(source_p);
}




/* irc_timer
 *
 *    Functions to be performed every ~seconds.
 *
 * Parameters: NONE
 * Return: NONE
 *
 */

void irc_timer(void)
{
   time_t present, delta;

   time(&present);

   delta = present - IRC_LAST;

   /* No data in NODATA_TIMEOUT minutes (set in options.h). */
   if (delta >= NODATA_TIMEOUT)
   {
      log_printf("IRC -> Timeout awaiting data from server.");
      irc_reconnect();
      /* Make sure we dont do this again for a while */
      time(&IRC_LAST);
   }
   else if (delta >= NODATA_TIMEOUT / 2)
   {
      /*
       * Generate some data so high ping times or bugs in certain
       * ircds (*cough* unreal *cough*) don't cause uneeded
       * reconnections
       */
      irc_send("PING :BOPM");
   }

}




/* get_channel
 *
 *    Check if a channel is defined in our conf. If so return
 *    a pointer to it.
 *
 * Parameters:
 *    channel: channel to search conf for
 *
 * Return: Pointer to ChannelConf containing the channel
 */

static struct ChannelConf *get_channel(const char *channel)
{
   node_t *node;
   struct ChannelConf *item;

   LIST_FOREACH(node, IRCItem->channels->head)
   {
      item = node->data;

      if(strcasecmp(item->name, channel) == 0)
         return item;
   }

   return NULL;
}


/* userinfo_create
 *
 *    Parse a nick!user@host into a UserInfo struct
 *    and return a pointer to the new struct.
 *
 * Parameters:
 *    source: nick!user@host to parse
 *
 * Return:
 *    pointer to new UserInfo struct, or NULL if parsing failed
 *
 */

static struct UserInfo *userinfo_create(char *source)
{
   struct UserInfo *ret;

   char *nick;
   char *username;
   char *hostname;
   char *tmp;

   int i, len;

   nick = username = hostname = NULL;
   tmp = DupString(source);
   len = strlen(tmp);

   nick = tmp;

   for(i = 0; i < len; i++)
   {
      if(tmp[i] == '!')
      {
         tmp[i] = '\0';
         username = tmp + i + 1;
      }
      if(tmp[i] == '@')
      {
         tmp[i] = '\0';
         hostname = tmp + i + 1;
      }
   }

   if(nick == NULL || username == NULL || hostname == NULL)
   {
      MyFree(tmp);
      return NULL;
   }

   ret = MyMalloc(sizeof *ret);

   ret->irc_nick     = DupString(nick);
   ret->irc_username = DupString(username);
   ret->irc_hostname = DupString(hostname);

   MyFree(tmp);

   return ret;
};



/* userinfo_free
 *
 *    Free a UserInfo struct created with userinfo_create.
 *
 * Parameters:
 *    source: struct to free
 * 
 * Return: None
 *
 */

static void userinfo_free(struct UserInfo *source_p)
{
   if(source_p == NULL)
      return;

   MyFree(source_p->irc_nick);
   MyFree(source_p->irc_username);
   MyFree(source_p->irc_hostname);
   MyFree(source_p);
}



/* m_perform
 *
 *    actions to perform on IRC connection
 *
 * Parameters:
 * parv[0]  = source
 * parv[1]  = PING
 * parv[2]  = PING TS/Package
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 */

static void m_perform(char **parv, unsigned int parc, char *msg, struct UserInfo *notused)
{
   node_t *node;
   struct ChannelConf *channel;

   USE_VAR(parv);
   USE_VAR(parc);
   USE_VAR(msg);
   USE_VAR(notused);

   log_printf("IRC -> Connected to %s:%d", IRCItem->server, IRCItem->port);

   /* Identify to nickserv if needed */
   if(strlen(IRCItem->nickserv))
      irc_send("%s", IRCItem->nickserv);

   /* Oper */
   irc_send("OPER %s", IRCItem->oper);

   /* Set modes */
   irc_send("MODE %s %s", IRCItem->nick, IRCItem->mode);

   /* Set Away */
   irc_send("AWAY :%s", IRCItem->away);

   /* Perform */
   LIST_FOREACH(node, IRCItem->performs->head)
      irc_send("%s", (char *) node->data);

   /* Join all listed channels. */
   LIST_FOREACH(node, IRCItem->channels->head)
   {
      channel = (struct ChannelConf *) node->data;

      if(strlen(channel->name) == 0)
         continue;

      if(strlen(channel->key) > 0)
         irc_send("JOIN %s %s", channel->name, channel->key);
      else
         irc_send("JOIN %s", channel->name);
   }
}


/* m_ping
 *
 * parv[0]  = source
 * parv[1]  = PING
 * parv[2]  = PING TS/Package
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 */
static void m_ping(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{
   USE_VAR(msg);
   USE_VAR(source_p);

   if(parc < 3)
      return;

   if(OPT_DEBUG >= 2)
      log_printf("IRC -> PING? PONG!");

   irc_send("PONG %s", parv[2]);
}



/* m_invite
 *
 * parv[0]  = source
 * parv[1]  = INVITE
 * parv[2]  = target
 * parv[3]  = channel
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 *
 */

static void m_invite(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{
   struct ChannelConf *channel;
   
   USE_VAR(msg);
   USE_VAR(source_p);

   if(parc < 4)
      return;

   log_printf("IRC -> Invited to %s by %s", parv[3], parv[0]);

   if((channel = get_channel(parv[3])) == NULL)
      return;

   irc_send("JOIN %s %s", channel->name, channel->key);
}




/* m_privmsg
 *
 * parv[0]  = source
 * parv[1]  = PRIVMSG
 * parv[2]  = target (channel or user)
 * parv[3]  = message
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 *
 */

static void m_privmsg(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{
   struct ChannelConf *channel;
   size_t nick_len;

   if(source_p == NULL)
      return;

   if(parc < 4)
      return;

   /* CTCP */
   if(parv[3][0] == '\001')
      m_ctcp(parv, parc, msg, source_p);

   /* Only interested in privmsg to channels */
   if(parv[2][0] != '#' && parv[2][0] != '&')
      return;

   /* Get a target */
   if((channel = get_channel(parv[2])) == NULL)
      return;

   /* Find a suitable length to compare with */
   nick_len = strcspn(parv[3], " :,");
   if(nick_len < 3 && strlen(IRCItem->nick) >= 3)
      nick_len = 3;
   
   /* message is a command */
   if(strncasecmp(parv[3], IRCItem->nick, nick_len) == 0  ||
         strncasecmp(parv[3], "!all", 4) == 0)
   {
      /* XXX command_parse will alter parv[3]. */
      command_parse(parv[3], msg, channel, source_p);
   }
}





/* m_ctcp
 * parv[0]  = source
 * parv[1]  = PRIVMSG
 * parv[2]  = target (channel or user)
 * parv[3]  = message
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 *
 */

static void m_ctcp(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{
   USE_VAR(parc);
   USE_VAR(msg);

   if(strncasecmp(parv[3], "\001VERSION\001", 9) == 0)
   {
      irc_send("NOTICE %s :\001VERSION Blitzed Open Proxy Monitor %s\001",
            source_p->irc_nick, VERSION);
   }
}





/* m_notice
 *
 * parv[0]  = source
 * parv[1]  = NOTICE
 * parv[2]  = target
 * parv[3]  = message
 *
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 *
 */

static void m_notice(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{


   static regex_t *preg = NULL;
   regmatch_t pmatch[5];

   static char errmsg[256];
   int errnum, i;

   char *user[4];

   if(parc < 4)
      return;

   /* Not interested in notices from users */
   if(source_p != NULL)
      return;

   /* Compile the regular expression if it has not been already */
   if(preg == NULL)
   {
      preg = MyMalloc(sizeof *preg);

      if((errnum = regcomp(preg, IRCItem->connregex, REG_ICASE | REG_EXTENDED)) != 0)
      {

         regerror(errnum, preg, errmsg, 256);
         log_printf("IRC REGEX -> Error when compiling regular expression");
         log_printf("IRC REGEX -> %s", errmsg);

         MyFree(preg);
         preg = NULL;
         return;
      }
   }

   /* Match the expression against the possible connection notice */
   if(regexec(preg, parv[3], 5, pmatch, 0) != 0)
      return;

   if(OPT_DEBUG > 0)
      log_printf("IRC REGEX -> Regular expression caught connection notice. Parsing.");

   if(pmatch[4].rm_so == -1)
   {
      log_printf("IRC REGEX -> pmatch[4].rm_so is -1 while parsing??? Aborting.");
      return;
   }

   /*
       Offsets for data in the connection notice:

       NICKNAME: pmatch[1].rm_so  TO  pmatch[1].rm_eo 
       USERNAME: pmatch[2].rm_so  TO  pmatch[2].rm_eo
       HOSTNAME: pmatch[3].rm_so  TO  pmatch[3].rm_eo
       IP      : pmatch[4].rm_so  TO  pmatch[4].rm_eo

    */ 

   for(i = 0; i < 4; i++)
   {
      user[i] = (parv[3] + pmatch[i + 1].rm_so);
      *(parv[3] + pmatch[i + 1].rm_eo) = '\0';
   }

   if(OPT_DEBUG > 0)
      log_printf("IRC REGEX -> Parsed %s!%s@%s [%s] from connection notice.",
          user[0], user[1], user[2], user[3]);

   /*FIXME (reminder) In the case of any rehash to the regex, preg MUST be freed first.
       regfree(preg);
   */

   /* Pass this information off to scan.c */
   scan_connect(user, msg);
   /* Record the connect for stats purposes */
   stats_connect();
}

/* m_userhost
 *
 * parv[0]  = source
 * parv[1]  = USERHOST
 * parv[2]  = target (bopm)
 * parv[3]  = :nick=(flags)user@host
 *
 *
 * source_p: UserInfo struct of the source user, or NULL if
 * the source (parv[0]) is a server.
 *
 */

static void m_userhost(char **parv, unsigned int parc, char *msg,
      struct UserInfo *source_p)
{
   USE_VAR(msg);
   USE_VAR(source_p);

   if(parc < 4)
      return;

   command_userhost(parv[3]);
}

/* m_cannot_join
 *
 * parv[0]  = source
 * parv[1]  = numeric
 * parv[2]  = target (bopm)
 * parv[3]  = channel
 * parv[4]  = error text
 *
 */

static void m_cannot_join(char **parv, unsigned int parc, char *msg,
      struct UserInfo *source_p)
{
   struct ChannelConf *channel;

   USE_VAR(msg);
   USE_VAR(source_p);

   if(parc < 5)
      return;

   /* Is it one of our channels? */
   if((channel = get_channel(parv[3])) == NULL)
      return;

   if(strlen(channel->invite) == 0)
      return;

   irc_send("%s", channel->invite);
}


/* m_kill
 *
 * parv[0]  = source
 * parv[1]  = numeric
 * parv[2]  = target (bopm)
 * parv[3]  = channel
 * parv[4]  = error text
 *
 */

static void m_kill(char **parv, unsigned int parc, char *msg, struct UserInfo *source_p)
{
   USE_VAR(parv);
   USE_VAR(parc);
   USE_VAR(msg);
   USE_VAR(source_p);

   /* Restart bopm to rehash */
   main_restart();
}


syntax highlighted by Code2HTML, v. 0.9.1