- irc.c: reformatting; move COMMAND_TABLE into irc_parse()
authormichael <michael@82007160-df01-0410-b94d-b575c5fd34c7>
Sat, 20 Jun 2015 15:50:50 +0000 (15:50 +0000)
committermichael <michael@82007160-df01-0410-b94d-b575c5fd34c7>
Sat, 20 Jun 2015 15:50:50 +0000 (15:50 +0000)
git-svn-id: svn://svn.ircd-hybrid.org/svnroot/hopm/trunk@6167 82007160-df01-0410-b94d-b575c5fd34c7

src/irc.c

index 4c5ff8a72fbcd0aaac467fb9806a20395e9f3765..2cf51bd8945ceb96ec6eaf77bacab113afb6b7f9 100644 (file)
--- a/src/irc.c
+++ b/src/irc.c
 #include "serno.h"
 
 
-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 const struct ChannelConf *get_channel(const char *);
-
-static struct UserInfo *userinfo_create(const char *);
-static void userinfo_free(struct UserInfo *source);
-
-static void m_ping(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_invite(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_privmsg(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_ctcp(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_notice(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_perform(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_userhost(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_cannot_join(char *[], unsigned int, const char *, const struct UserInfo *);
-static void m_kill(char *[], unsigned int, const char *, const struct UserInfo *);
-
-
 /*
  * Certain variables we don't want to allocate memory for over and over
  * again so global scope is given.
@@ -88,491 +66,346 @@ static socklen_t svr_addrlen;
 static time_t IRC_LAST;                  /* Last full line of data from irc server */
 static time_t IRC_LASTRECONNECT;         /* Time of last reconnection              */
 
-/*
- * Table should be ordered with most occuring (or priority)
- * commands at the top of the list.
- */
-static const 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        },
-  { NULL,       NULL          }
-};
 
-/* irc_cycle
+/* get_channel
  *
- *    Pass control to the IRC portion of HOPM to handle any awaiting IRC events.
+ *    Check if a channel is defined in our conf. If so return
+ *    a pointer to it.
  *
  * Parameters:
- *    None
+ *    channel: channel to search conf for
  *
- * Return:
- *    None
+ * Return: Pointer to ChannelConf containing the channel
  */
-void
-irc_cycle(void)
+static const struct ChannelConf *
+get_channel(const char *channel)
 {
-  static struct pollfd pfd;
+  node_t *node;
 
-  if (IRC_FD <= 0)
+  LIST_FOREACH(node, IRCItem->channels->head)
   {
-    /* Initialise negative cache. */
-    if (OptionsItem->negcache)
-      nc_init(&nc_head);
-
-    /* Resolve remote host. */
-    irc_init();
+    struct ChannelConf *item = node->data;
 
-    /* Connect to remote host. */
-    irc_connect();
-    return;  /* In case connect() immediately failed */
+    if (strcasecmp(item->name, channel) == 0)
+      return item;
   }
 
-  pfd.fd = IRC_FD;
-  pfd.events = POLLIN;
-
-  /* Block .050 seconds to avoid excessive CPU use on poll(). */
-  switch (poll(&pfd, 1, 50))
-  {
-    case  0:
-    case -1:
-      break;
-    default:
-      /* Check if IRC data is available. */
-      if (pfd.revents & POLLIN)
-        irc_read();
-      else if (pfd.revents & (POLLERR | POLLHUP))
-        irc_reconnect();
-
-      break;
-  }
+  return NULL;
 }
 
-/* irc_init
+/* m_perform
  *
- *    Resolve IRC host and perform other initialization.
+ *    actions to perform on IRC connection
  *
  * Parameters:
- *    None
+ * parv[0]  = source
+ * parv[1]  = PING
+ * parv[2]  = PING TS/Package
  *
- * Return:
- *    None
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
 static void
-irc_init(void)
+m_perform(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *notused)
 {
-  const void *address = NULL;
-
-  if (IRC_FD)
-    close(IRC_FD);
+  node_t *node;
 
-  memset(&IRC_SVR, 0, sizeof(IRC_SVR));
+  log_printf("IRC -> Connected to %s/%d", IRCItem->server, IRCItem->port);
 
-  /* Resolve IRC host. */
-  if ((address = firedns_resolveip6(IRCItem->server)))
-  {
-    struct sockaddr_in6 *in = (struct sockaddr_in6 *)&IRC_SVR;
+  /* Identify to nickserv if needed */
+  if (!EmptyString(IRCItem->nickserv))
+    irc_send("%s", IRCItem->nickserv);
 
-    svr_addrlen = sizeof(struct sockaddr_in6);
-    IRC_SVR.ss_family = AF_INET6;
-    in->sin6_port = htons(IRCItem->port);
-    memcpy(&in->sin6_addr, address, sizeof(in->sin6_addr));
-  }
-  else if ((address = firedns_resolveip4(IRCItem->server)))
-  {
-    struct sockaddr_in *in = (struct sockaddr_in *)&IRC_SVR;
+  /* Oper */
+  irc_send("OPER %s", IRCItem->oper);
 
-    svr_addrlen = sizeof(struct sockaddr_in);
-    IRC_SVR.ss_family = AF_INET;
-    in->sin_port = htons(IRCItem->port);
-    memcpy(&in->sin_addr, address, sizeof(in->sin_addr));
-  }
-  else
-  {
-    log_printf("IRC -> firedns_resolveip(\"%s\"): %s", IRCItem->server,
-               firedns_strerror(firedns_errno));
-    exit(EXIT_FAILURE);
-  }
+  /* Set modes */
+  irc_send("MODE %s %s", IRCItem->nick, IRCItem->mode);
 
-  /* Request file desc for IRC client socket */
-  IRC_FD = socket(IRC_SVR.ss_family, SOCK_STREAM, 0);
+  /* Set Away */
+  if (!EmptyString(IRCItem->away))
+    irc_send("AWAY :%s", IRCItem->away);
 
-  if (IRC_FD == -1)
-  {
-    log_printf("IRC -> socket(): error creating socket: %s", strerror(errno));
-    exit(EXIT_FAILURE);
-  }
+  /* Perform */
+  LIST_FOREACH(node, IRCItem->performs->head)
+    irc_send("%s", node->data);
 
-  /* Bind */
-  if (!EmptyString(IRCItem->vhost))
+  /* Join all listed channels. */
+  LIST_FOREACH(node, IRCItem->channels->head)
   {
-    struct addrinfo hints, *res;
-
-    memset(&hints, 0, sizeof(hints));
-
-    hints.ai_family = AF_UNSPEC;
-    hints.ai_socktype = SOCK_STREAM;
-    hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+    const struct ChannelConf *channel = node->data;
 
-    if (getaddrinfo(IRCItem->vhost, NULL, &hints, &res))
-    {
-      log_printf("IRC -> bind(): %s is an invalid address", IRCItem->vhost);
-      exit(EXIT_FAILURE);
-    }
-    else if (bind(IRC_FD, res->ai_addr, res->ai_addrlen))
-    {
-      log_printf("IRC -> bind(): error binding to %s: %s", IRCItem->vhost, strerror(errno));
-      exit(EXIT_FAILURE);
-    }
+    if (EmptyString(channel->name))
+      continue;
 
-    freeaddrinfo(res);
+    if (!EmptyString(channel->key))
+      irc_send("JOIN %s %s", channel->name, channel->key);
+    else
+      irc_send("JOIN %s", channel->name);
   }
 }
 
-/* irc_send
- *
- *    Send data to remote IRC host.
+/* m_ping
  *
- * Parameters:
- *    data: Format of data to send
- *    ...: varargs to format with
+ * parv[0]  = source
+ * parv[1]  = PING
+ * parv[2]  = PING TS/Package
  *
- * Return: NONE
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
-void
-irc_send(const char *data, ...)
+static void
+m_ping(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  va_list arglist;
-  char buf[MSGLENMAX];
-  size_t len = 0;
-
-  va_start(arglist, data);
-  len = vsnprintf(buf, sizeof(buf), data, arglist);
-  va_end(arglist);
+  if (parc < 3)
+    return;
 
   if (OPT_DEBUG >= 2)
-    log_printf("IRC SEND -> %s", buf);
-
-  if (len > 510)
-    len = 510;
-
-  buf[len++] = '\r';
-  buf[len++] = '\n';
+    log_printf("IRC -> PING? PONG!");
 
-  if (send(IRC_FD, buf, len, 0) == -1)
-  {
-    /* Return of -1 indicates error sending data; we reconnect. */
-    log_printf("IRC -> Error sending data to server: %s", strerror(errno));
-    irc_reconnect();
-  }
+  irc_send("PONG %s", parv[2]);
 }
 
-/* irc_send
- *
- *    Send privmsg to all channels.
+/* m_invite
  *
- * Parameters:
- *    data: Format of data to send
- *    ...: varargs to format with
+ * parv[0]  = source
+ * parv[1]  = INVITE
+ * parv[2]  = target
+ * parv[3]  = channel
  *
- * Return: NONE
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
-void
-irc_send_channels(const char *data, ...)
+static void
+m_invite(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  const node_t *node;
-  va_list arglist;
-  char buf[MSGLENMAX];
+  const struct ChannelConf *channel = NULL;
 
-  va_start(arglist, data);
-  vsnprintf(buf, sizeof(buf), data, arglist);
-  va_end(arglist);
+  if (parc < 4)
+    return;
 
-  LIST_FOREACH(node, IRCItem->channels->head)
-  {
-    const struct ChannelConf *chan = node->data;
+  log_printf("IRC -> Invited to %s by %s", parv[3], parv[0]);
 
-    irc_send("PRIVMSG %s :%s", chan->name, buf);
-  }
+  if ((channel = get_channel(parv[3])) == NULL)
+    return;
+
+  irc_send("JOIN %s %s", channel->name, channel->key);
 }
 
-/* irc_connect
- *
- *    Connect to IRC server.
- *    XXX: FD allocation done here
+/* m_ctcp
+ * parv[0]  = source
+ * parv[1]  = PRIVMSG
+ * parv[2]  = target (channel or user)
+ * parv[3]  = message
  *
- * Parameters: NONE
- * Return: NONE
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
 static void
-irc_connect(void)
+m_ctcp(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  /* Connect to IRC server as client. */
-  if (connect(IRC_FD, (struct sockaddr *)&IRC_SVR, svr_addrlen) == -1)
-  {
-    log_printf("IRC -> connect(): error connecting to %s: %s",
-               IRCItem->server, strerror(errno));
-
-    if (errno == EISCONN /* Already connected */ || errno == EALREADY /* Previous attempt not complete */)
-      return;
+  if (strncasecmp(parv[3], "\001VERSION\001", 9) == 0)
+    irc_send("NOTICE %s :\001VERSION Hybrid Open Proxy Monitor %s(%s)\001",
+             source_p->irc_nick, VERSION, SERIALNUM);
+}
 
-    /* Try to connect again */
-    irc_reconnect();
-    return;
-  }
-
-  irc_send("NICK %s", IRCItem->nick);
-
-  if (!EmptyString(IRCItem->password))
-    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.
+/* m_privmsg
  *
- * Parameters: NONE
+ * parv[0]  = source
+ * parv[1]  = PRIVMSG
+ * parv[2]  = target (channel or user)
+ * parv[3]  = message
  *
- * Return: NONE
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
 static void
-irc_reconnect(void)
+m_privmsg(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  time_t present;
+  const struct ChannelConf *channel = NULL;
+  size_t nick_len;
 
-  time(&present);
+  if (source_p == NULL)
+    return;
 
-  /* Only try to reconnect every IRCItem->reconnectinterval seconds */
-  if ((present - IRC_LASTRECONNECT) < IRCItem->reconnectinterval)
+  if (parc < 4)
+    return;
+
+  /* CTCP */
+  if (*parv[3] == '\001')
   {
-    /* Sleep to avoid excessive CPU */
-    sleep(1);
+    m_ctcp(parv, parc, msg, source_p);
     return;
   }
 
-  time(&IRC_LASTRECONNECT);
+  /* Only interested in privmsg to channels */
+  if (*parv[2] != '#' && *parv[2] != '&')
+    return;
 
-  if (IRC_FD > 0)
-    close(IRC_FD);
+  /* Get a target */
+  if ((channel = get_channel(parv[2])) == NULL)
+    return;
 
-  /* Set IRC_FD 0 for reconnection on next irc_cycle(). */
-  IRC_FD = 0;
+  /* Find a suitable length to compare with */
+  nick_len = strcspn(parv[3], " :,");
 
-  log_printf("IRC -> Connection to (%s) failed, reconnecting.", IRCItem->server);
+  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], channel, source_p);
+  }
 }
 
-/* irc_read
+/* m_notice
  *
- *    irc_read is called by irc_cycle when new data is ready to be
- *    read from the irc server.
+ * parv[0]  = source
+ * parv[1]  = NOTICE
+ * parv[2]  = target
+ * parv[3]  = message
  *
- * Parameters: NONE
- * Return: NONE
+ *
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
 static void
-irc_read(void)
+m_notice(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  int len;
-  char c;
+  static regex_t *preg = NULL;
+  regmatch_t pmatch[5];
+  int errnum;
+  const char *user[4];
+  const node_t *node;
 
-  while ((len = read(IRC_FD, &c, 1)) > 0)
+  /* Not interested in notices from users */
+  if (source_p)
+    return;
+
+  if (parc < 4)
+    return;
+
+  /* Compile the regular expression if it has not been already */
+  if (preg == NULL)
   {
-    if (c == '\r')
-      continue;
+    preg = xcalloc(sizeof(*preg));
 
-    if (c == '\n')
+    if ((errnum = regcomp(preg, IRCItem->connregex, REG_ICASE | REG_EXTENDED)))
     {
-      /* Null string. */
-      IRC_RAW[IRC_RAW_LEN] = '\0';
+      char errmsg[256];
 
-      /* Parse line. */
-      irc_parse();
+      regerror(errnum, preg, errmsg, sizeof(errmsg));
+      log_printf("IRC REGEX -> Error when compiling regular expression: %s", errmsg);
 
-      /* Reset counter. */
-      IRC_RAW_LEN = 0;
-      break;
+      xfree(preg);
+      preg = NULL;
+      return;
     }
-
-    if (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);
+  /* Match the expression against the possible connection notice */
+  if (regexec(preg, parv[3], 5, pmatch, 0))
+    return;
 
-    irc_reconnect();
-    IRC_RAW_LEN = 0;
+  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;
   }
-}
-
-/* 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)
-{
-  char *pos;
 
   /*
-   * parv stores the parsed token, parc is the count of the parsed
-   * tokens
+   *   Offsets for data in the connection notice:
    *
-   * parv[0] is ALWAYS the source, and is the server name of the source
-   * did not exist
+   *   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
    */
-  char            *parv[17];
-  unsigned int     parc = 1;
-  char             msg[MSGLENMAX];    /* Temporarily stores IRC msg to pass to handlers */
-
-  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) */
-  strlcpy(msg, IRC_RAW, sizeof(msg));
-
-  /* parv[0] is always the source */
-  if (IRC_RAW[0] == ':')
-    parv[0] = IRC_RAW + 1;
-  else
+  for (unsigned int i = 0; i < 4; ++i)
   {
-    parv[0] = IRCItem->server;
-    parv[parc++] = IRC_RAW;
+    user[i] = (parv[3] + pmatch[i + 1].rm_so);
+    *(parv[3] + pmatch[i + 1].rm_eo) = '\0';
   }
 
-  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;
-    }
+  if (OPT_DEBUG > 0)
+    log_printf("IRC REGEX -> Parsed %s!%s@%s [%s] from connection notice.",
+               user[0], user[1], user[2], user[3]);
 
-    /*
-     * 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++;
-  }
+  LIST_FOREACH(node, IRCItem->notices->head)
+    irc_send("NOTICE %s :%s", user[0], node->data);
 
-  /*
-   * Determine which command this is from the command table
-   * and let the handler for that command take control
-   */
-  for (const struct CommandHash *cmd = COMMAND_TABLE; cmd->command; ++cmd)
-  {
-    if (strcasecmp(cmd->command, parv[1]) == 0)
-    {
-      /* Generate a UserInfo struct from the source */
-      struct UserInfo *source_p = userinfo_create(parv[0]);
+  /* Pass this information off to scan.c */
+  scan_connect(user, msg);
 
-      cmd->handler(parv, parc, msg, source_p);
-      userinfo_free(source_p);
-      break;
-    }
-  }
+  /* Record the connect for stats purposes */
+  stats_connect();
 }
 
-/* irc_timer
+/* m_userhost
  *
- *    Functions to be performed every ~seconds.
+ * parv[0]  = source
+ * parv[1]  = USERHOST
+ * parv[2]  = target (hopm)
+ * parv[3]  = :nick=(flags)user@host
  *
- * Parameters: NONE
- * Return: NONE
+ *
+ * source_p: UserInfo struct of the source user, or NULL if
+ * the source (parv[0]) is a server.
  */
-void
-irc_timer(void)
+static void
+m_userhost(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  time_t present, delta;
-
-  time(&present);
+  if (parc < 4)
+    return;
 
-  delta = present - IRC_LAST;
+  command_userhost(parv[3]);
+}
 
-  /* No data in IRCItem->readtimeout seconds */
-  if (delta >= IRCItem->readtimeout)
-  {
-    log_printf("IRC -> Timeout awaiting data from server.");
-    irc_reconnect();
-
-    /* Make sure we don't do this again for a while */
-    time(&IRC_LAST);
-  }
-  else if (delta >= IRCItem->readtimeout / 2)
-  {
-    /*
-     * Generate some data so high ping times don't cause uneeded
-     * reconnections
-     */
-    irc_send("PING :HOPM");
-  }
-}
-
-/* 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
+/* m_cannot_join
  *
- * Return: Pointer to ChannelConf containing the channel
+ * parv[0]  = source
+ * parv[1]  = numeric
+ * parv[2]  = target (hopm)
+ * parv[3]  = channel
+ * parv[4]  = error text
  */
-static const struct ChannelConf *
-get_channel(const char *channel)
+static void
+m_cannot_join(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
 {
-  node_t *node;
+  const struct ChannelConf *channel = NULL;
 
-  LIST_FOREACH(node, IRCItem->channels->head)
-  {
-    struct ChannelConf *item = node->data;
+  if (parc < 5)
+    return;
 
-    if (strcasecmp(item->name, channel) == 0)
-      return item;
-  }
+  /* Is it one of our channels? */
+  if ((channel = get_channel(parv[3])) == NULL)
+    return;
 
-  return NULL;
+  if (EmptyString(channel->invite))
+    return;
+
+  irc_send("%s", channel->invite);
+}
+
+/* m_kill
+ *
+ * parv[0]  = source
+ * parv[1]  = numeric
+ * parv[2]  = target (hopm)
+ * parv[3]  = channel
+ * parv[4]  = error text
+ */
+static void
+m_kill(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+{
+  /* Restart hopm to rehash */
+  main_restart();
 }
 
 /* userinfo_create
@@ -646,322 +479,463 @@ userinfo_free(struct UserInfo *source_p)
   xfree(source_p);
 }
 
-/* m_perform
+/* irc_init
  *
- *    actions to perform on IRC connection
+ *    Resolve IRC host and perform other initialization.
  *
  * Parameters:
- * parv[0]  = source
- * parv[1]  = PING
- * parv[2]  = PING TS/Package
+ *    None
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ * Return:
+ *    None
  */
 static void
-m_perform(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *notused)
+irc_init(void)
 {
-  node_t *node;
+  const void *address = NULL;
 
-  log_printf("IRC -> Connected to %s/%d", IRCItem->server, IRCItem->port);
+  if (IRC_FD)
+    close(IRC_FD);
 
-  /* Identify to nickserv if needed */
-  if (!EmptyString(IRCItem->nickserv))
-    irc_send("%s", IRCItem->nickserv);
+  memset(&IRC_SVR, 0, sizeof(IRC_SVR));
 
-  /* Oper */
-  irc_send("OPER %s", IRCItem->oper);
+  /* Resolve IRC host. */
+  if ((address = firedns_resolveip6(IRCItem->server)))
+  {
+    struct sockaddr_in6 *in = (struct sockaddr_in6 *)&IRC_SVR;
 
-  /* Set modes */
-  irc_send("MODE %s %s", IRCItem->nick, IRCItem->mode);
+    svr_addrlen = sizeof(struct sockaddr_in6);
+    IRC_SVR.ss_family = AF_INET6;
+    in->sin6_port = htons(IRCItem->port);
+    memcpy(&in->sin6_addr, address, sizeof(in->sin6_addr));
+  }
+  else if ((address = firedns_resolveip4(IRCItem->server)))
+  {
+    struct sockaddr_in *in = (struct sockaddr_in *)&IRC_SVR;
 
-  /* Set Away */
-  if (!EmptyString(IRCItem->away))
-    irc_send("AWAY :%s", IRCItem->away);
+    svr_addrlen = sizeof(struct sockaddr_in);
+    IRC_SVR.ss_family = AF_INET;
+    in->sin_port = htons(IRCItem->port);
+    memcpy(&in->sin_addr, address, sizeof(in->sin_addr));
+  }
+  else
+  {
+    log_printf("IRC -> firedns_resolveip(\"%s\"): %s", IRCItem->server,
+               firedns_strerror(firedns_errno));
+    exit(EXIT_FAILURE);
+  }
 
-  /* Perform */
-  LIST_FOREACH(node, IRCItem->performs->head)
-    irc_send("%s", node->data);
+  /* Request file desc for IRC client socket */
+  IRC_FD = socket(IRC_SVR.ss_family, SOCK_STREAM, 0);
 
-  /* Join all listed channels. */
-  LIST_FOREACH(node, IRCItem->channels->head)
+  if (IRC_FD == -1)
   {
-    const struct ChannelConf *channel = node->data;
+    log_printf("IRC -> socket(): error creating socket: %s", strerror(errno));
+    exit(EXIT_FAILURE);
+  }
 
-    if (EmptyString(channel->name))
-      continue;
+  /* Bind */
+  if (!EmptyString(IRCItem->vhost))
+  {
+    struct addrinfo hints, *res;
 
-    if (!EmptyString(channel->key))
-      irc_send("JOIN %s %s", channel->name, channel->key);
-    else
-      irc_send("JOIN %s", channel->name);
+    memset(&hints, 0, sizeof(hints));
+
+    hints.ai_family = AF_UNSPEC;
+    hints.ai_socktype = SOCK_STREAM;
+    hints.ai_flags = AI_PASSIVE | AI_NUMERICHOST;
+
+    if (getaddrinfo(IRCItem->vhost, NULL, &hints, &res))
+    {
+      log_printf("IRC -> bind(): %s is an invalid address", IRCItem->vhost);
+      exit(EXIT_FAILURE);
+    }
+    else if (bind(IRC_FD, res->ai_addr, res->ai_addrlen))
+    {
+      log_printf("IRC -> bind(): error binding to %s: %s", IRCItem->vhost, strerror(errno));
+      exit(EXIT_FAILURE);
+    }
+
+    freeaddrinfo(res);
   }
 }
 
-/* m_ping
+/* irc_reconnect
  *
- * parv[0]  = source
- * parv[1]  = PING
- * parv[2]  = PING TS/Package
+ *    Close connection to IRC server.
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ * Parameters: NONE
+ *
+ * Return: NONE
  */
 static void
-m_ping(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+irc_reconnect(void)
 {
-  if (parc < 3)
+  time_t present;
+
+  time(&present);
+
+  /* Only try to reconnect every IRCItem->reconnectinterval seconds */
+  if ((present - IRC_LASTRECONNECT) < IRCItem->reconnectinterval)
+  {
+    /* Sleep to avoid excessive CPU */
+    sleep(1);
     return;
+  }
 
-  if (OPT_DEBUG >= 2)
-    log_printf("IRC -> PING? PONG!");
+  time(&IRC_LASTRECONNECT);
 
-  irc_send("PONG %s", parv[2]);
+  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);
 }
 
-/* m_invite
+/* irc_connect
  *
- * parv[0]  = source
- * parv[1]  = INVITE
- * parv[2]  = target
- * parv[3]  = channel
+ *    Connect to IRC server.
+ *    XXX: FD allocation done here
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ * Parameters: NONE
+ * Return: NONE
  */
 static void
-m_invite(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+irc_connect(void)
 {
-  const struct ChannelConf *channel = NULL;
+  /* Connect to IRC server as client. */
+  if (connect(IRC_FD, (struct sockaddr *)&IRC_SVR, svr_addrlen) == -1)
+  {
+    log_printf("IRC -> connect(): error connecting to %s: %s",
+               IRCItem->server, strerror(errno));
 
-  if (parc < 4)
+    if (errno == EISCONN /* Already connected */ || errno == EALREADY /* Previous attempt not complete */)
+      return;
+
+    /* Try to connect again */
+    irc_reconnect();
     return;
+  }
 
-  log_printf("IRC -> Invited to %s by %s", parv[3], parv[0]);
+  irc_send("NICK %s", IRCItem->nick);
 
-  if ((channel = get_channel(parv[3])) == NULL)
-    return;
+  if (!EmptyString(IRCItem->password))
+    irc_send("PASS %s", IRCItem->password);
 
-  irc_send("JOIN %s %s", channel->name, channel->key);
+  irc_send("USER %s %s %s :%s",
+           IRCItem->username,
+           IRCItem->username,
+           IRCItem->username,
+           IRCItem->realname);
+  time(&IRC_LAST);
 }
 
-/* m_privmsg
+/* irc_parse
  *
- * parv[0]  = source
- * parv[1]  = PRIVMSG
- * parv[2]  = target (channel or user)
- * parv[3]  = message
+ *    irc_parse is called by irc_read when a full line of data
+ *    is ready to be parsed.
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ * Parameters: NONE
+ * Return: NONE
  */
 static void
-m_privmsg(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+irc_parse(void)
 {
-  const struct ChannelConf *channel = NULL;
-  size_t nick_len;
-
-  if (source_p == NULL)
-    return;
+  char *pos;
 
-  if (parc < 4)
-    return;
+  /*
+   * 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
+   */
+  char        *parv[17];
+  unsigned int parc = 1;
+  char         msg[MSGLENMAX];  /* Temporarily stores IRC msg to pass to handlers */
 
-  /* CTCP */
-  if (*parv[3] == '\001')
+  /*
+   * Table should be ordered with most occuring (or priority)
+   * commands at the top of the list.
+   */
+  static const struct CommandHash COMMAND_TABLE[] =
   {
-    m_ctcp(parv, parc, msg, source_p);
-    return;
-  }
+    { "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        },
+    { NULL,      NULL          }
+  };
 
-  /* Only interested in privmsg to channels */
-  if (*parv[2] != '#' && *parv[2] != '&')
+  if (IRC_RAW_LEN == 0)
     return;
 
-  /* Get a target */
-  if ((channel = get_channel(parv[2])) == NULL)
-    return;
+  if (OPT_DEBUG >= 2)
+    log_printf("IRC READ -> %s", IRC_RAW);
 
-  /* Find a suitable length to compare with */
-  nick_len = strcspn(parv[3], " :,");
+  time(&IRC_LAST);
 
-  if (nick_len < 3 && strlen(IRCItem->nick) >= 3)
-    nick_len = 3;
+  /* Store a copy of IRC_RAW for the handlers (for functions that need PROOF) */
+  strlcpy(msg, IRC_RAW, sizeof(msg));
 
-  /* message is a command */
-  if (strncasecmp(parv[3], IRCItem->nick, nick_len) == 0  ||
-      strncasecmp(parv[3], "!all", 4) == 0)
+  /* parv[0] is always the source */
+  if (IRC_RAW[0] == ':')
+    parv[0] = IRC_RAW + 1;
+  else
   {
-    /* XXX command_parse will alter parv[3]. */
-    command_parse(parv[3], channel, source_p);
+    parv[0] = IRCItem->server;
+    parv[parc++] = IRC_RAW;
   }
-}
 
-/* 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, const char *msg, const struct UserInfo *source_p)
-{
-  if (strncasecmp(parv[3], "\001VERSION\001", 9) == 0)
-    irc_send("NOTICE %s :\001VERSION Hybrid Open Proxy Monitor %s(%s)\001",
-             source_p->irc_nick, VERSION, SERIALNUM);
+  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++;
+  }
+
+  /*
+   * Determine which command this is from the command table
+   * and let the handler for that command take control
+   */
+  for (const struct CommandHash *cmd = COMMAND_TABLE; cmd->command; ++cmd)
+  {
+    if (strcasecmp(cmd->command, parv[1]) == 0)
+    {
+      /* Generate a UserInfo struct from the source */
+      struct UserInfo *source_p = userinfo_create(parv[0]);
+
+      cmd->handler(parv, parc, msg, source_p);
+      userinfo_free(source_p);
+      break;
+    }
+  }
 }
 
-/* m_notice
- *
- * parv[0]  = source
- * parv[1]  = NOTICE
- * parv[2]  = target
- * parv[3]  = message
- *
+/* irc_read
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ *    irc_read is called by irc_cycle when new data is ready to be
+ *    read from the irc server.
  *
+ * Parameters: NONE
+ * Return: NONE
  */
 static void
-m_notice(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+irc_read(void)
 {
-  static regex_t *preg = NULL;
-  regmatch_t pmatch[5];
-  int errnum;
-  const char *user[4];
-  const node_t *node;
-
-  /* Not interested in notices from users */
-  if (source_p)
-    return;
-
-  if (parc < 4)
-    return;
+  int len;
+  char c;
 
-  /* Compile the regular expression if it has not been already */
-  if (preg == NULL)
+  while ((len = read(IRC_FD, &c, 1)) > 0)
   {
-    preg = xcalloc(sizeof(*preg));
+    if (c == '\r')
+      continue;
 
-    if ((errnum = regcomp(preg, IRCItem->connregex, REG_ICASE | REG_EXTENDED)))
+    if (c == '\n')
     {
-      char errmsg[256];
+      /* Null string. */
+      IRC_RAW[IRC_RAW_LEN] = '\0';
 
-      regerror(errnum, preg, errmsg, sizeof(errmsg));
-      log_printf("IRC REGEX -> Error when compiling regular expression: %s", errmsg);
+      /* Parse line. */
+      irc_parse();
 
-      xfree(preg);
-      preg = NULL;
-      return;
+      /* Reset counter. */
+      IRC_RAW_LEN = 0;
+      break;
     }
-  }
-
-  /* Match the expression against the possible connection notice */
-  if (regexec(preg, parv[3], 5, pmatch, 0))
-    return;
 
-  if (OPT_DEBUG > 0)
-    log_printf("IRC REGEX -> Regular expression caught connection notice. Parsing.");
+    if (c != '\0')
+      IRC_RAW[IRC_RAW_LEN++] = c;
+  }
 
-  if (pmatch[4].rm_so == -1)
+  if ((len <= 0) && (errno != EAGAIN))
   {
-    log_printf("IRC REGEX -> pmatch[4].rm_so is -1 while parsing??? Aborting.");
+    if (OPT_DEBUG >= 2)
+      log_printf("irc_read -> errno=%d len=%d", errno, len);
+
+    irc_reconnect();
+    IRC_RAW_LEN = 0;
     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 (unsigned int i = 0; i < 4; ++i)
+/* irc_cycle
+ *
+ *    Pass control to the IRC portion of HOPM to handle any awaiting IRC events.
+ *
+ * Parameters:
+ *    None
+ *
+ * Return:
+ *    None
+ */
+void
+irc_cycle(void)
+{
+  static struct pollfd pfd;
+
+  if (IRC_FD <= 0)
   {
-    user[i] = (parv[3] + pmatch[i + 1].rm_so);
-    *(parv[3] + pmatch[i + 1].rm_eo) = '\0';
-  }
+    /* Initialise negative cache. */
+    if (OptionsItem->negcache)
+      nc_init(&nc_head);
 
-  if (OPT_DEBUG > 0)
-    log_printf("IRC REGEX -> Parsed %s!%s@%s [%s] from connection notice.",
-               user[0], user[1], user[2], user[3]);
+    /* Resolve remote host. */
+    irc_init();
 
-  LIST_FOREACH(node, IRCItem->notices->head)
-    irc_send("NOTICE %s :%s", user[0], node->data);
+    /* Connect to remote host. */
+    irc_connect();
+    return;  /* In case connect() immediately failed */
+  }
 
-  /* Pass this information off to scan.c */
-  scan_connect(user, msg);
+  pfd.fd = IRC_FD;
+  pfd.events = POLLIN;
 
-  /* Record the connect for stats purposes */
-  stats_connect();
+  /* Block .050 seconds to avoid excessive CPU use on poll(). */
+  switch (poll(&pfd, 1, 50))
+  {
+    case  0:
+    case -1:
+      break;
+    default:
+      /* Check if IRC data is available. */
+      if (pfd.revents & POLLIN)
+        irc_read();
+      else if (pfd.revents & (POLLERR | POLLHUP))
+        irc_reconnect();
+
+      break;
+  }
 }
 
-/* m_userhost
- *
- * parv[0]  = source
- * parv[1]  = USERHOST
- * parv[2]  = target (hopm)
- * parv[3]  = :nick=(flags)user@host
+/* irc_send
  *
+ *    Send data to remote IRC host.
  *
- * source_p: UserInfo struct of the source user, or NULL if
- * the source (parv[0]) is a server.
+ * Parameters:
+ *    data: Format of data to send
+ *    ...: varargs to format with
  *
+ * Return: NONE
  */
-static void
-m_userhost(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+void
+irc_send(const char *data, ...)
 {
-  if (parc < 4)
-    return;
+  va_list arglist;
+  char buf[MSGLENMAX];
+  size_t len = 0;
 
-  command_userhost(parv[3]);
+  va_start(arglist, data);
+  len = vsnprintf(buf, sizeof(buf), data, arglist);
+  va_end(arglist);
+
+  if (OPT_DEBUG >= 2)
+    log_printf("IRC SEND -> %s", buf);
+
+  if (len > 510)
+    len = 510;
+
+  buf[len++] = '\r';
+  buf[len++] = '\n';
+
+  if (send(IRC_FD, buf, len, 0) == -1)
+  {
+    /* Return of -1 indicates error sending data; we reconnect. */
+    log_printf("IRC -> Error sending data to server: %s", strerror(errno));
+    irc_reconnect();
+  }
 }
 
-/* m_cannot_join
+/* irc_send
  *
- * parv[0]  = source
- * parv[1]  = numeric
- * parv[2]  = target (hopm)
- * parv[3]  = channel
- * parv[4]  = error text
+ *    Send privmsg to all channels.
+ *
+ * Parameters:
+ *    data: Format of data to send
+ *    ...: varargs to format with
  *
+ * Return: NONE
  */
-static void
-m_cannot_join(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+void
+irc_send_channels(const char *data, ...)
 {
-  const struct ChannelConf *channel = NULL;
-
-  if (parc < 5)
-    return;
+  const node_t *node;
+  va_list arglist;
+  char buf[MSGLENMAX];
 
-  /* Is it one of our channels? */
-  if ((channel = get_channel(parv[3])) == NULL)
-    return;
+  va_start(arglist, data);
+  vsnprintf(buf, sizeof(buf), data, arglist);
+  va_end(arglist);
 
-  if (EmptyString(channel->invite))
-    return;
+  LIST_FOREACH(node, IRCItem->channels->head)
+  {
+    const struct ChannelConf *chan = node->data;
 
-  irc_send("%s", channel->invite);
+    irc_send("PRIVMSG %s :%s", chan->name, buf);
+  }
 }
 
-/* m_kill
+/* irc_timer
  *
- * parv[0]  = source
- * parv[1]  = numeric
- * parv[2]  = target (hopm)
- * parv[3]  = channel
- * parv[4]  = error text
+ *    Functions to be performed every ~seconds.
  *
+ * Parameters: NONE
+ * Return: NONE
  */
-static void
-m_kill(char *parv[], unsigned int parc, const char *msg, const struct UserInfo *source_p)
+void
+irc_timer(void)
 {
-  /* Restart hopm to rehash */
-  main_restart();
+  time_t present, delta;
+
+  time(&present);
+
+  delta = present - IRC_LAST;
+
+  /* No data in IRCItem->readtimeout seconds */
+  if (delta >= IRCItem->readtimeout)
+  {
+    log_printf("IRC -> Timeout awaiting data from server.");
+    irc_reconnect();
+
+    /* Make sure we don't do this again for a while */
+    time(&IRC_LAST);
+  }
+  else if (delta >= IRCItem->readtimeout / 2)
+  {
+    /*
+     * Generate some data so high ping times don't cause uneeded
+     * reconnections
+     */
+    irc_send("PING :HOPM");
+  }
 }