#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.
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
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");
+ }
}