From: michael Date: Sat, 20 Jun 2015 15:51:09 +0000 (+0000) Subject: - irc.c: reformatting; move COMMAND_TABLE into irc_parse() X-Git-Tag: 1.0.8~17 X-Git-Url: http://git.serene-ircd.net/?a=commitdiff_plain;h=5efbee6c70f274a347ed889ef48af3a87b1fe968;p=hopm.git - irc.c: reformatting; move COMMAND_TABLE into irc_parse() git-svn-id: svn://svn.ircd-hybrid.org/svnroot/hopm/branches/1.0.x@6168 82007160-df01-0410-b94d-b575c5fd34c7 --- diff --git a/src/irc.c b/src/irc.c index 4c5ff8a..2cf51bd 100644 --- a/src/irc.c +++ b/src/irc.c @@ -53,28 +53,6 @@ #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"); + } }