/* * Copyright (c) 2002-2003 Erik Fears * Copyright (c) 2014-2020 ircd-hybrid development team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 * USA */ #include "setup.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "config.h" #include "irc.h" #include "log.h" #include "opercmd.h" #include "scan.h" #include "dnsbl.h" #include "stats.h" #include "options.h" #include "match.h" #include "compat.h" #include "negcache.h" #include "memory.h" #include "main.h" #include "serno.h" /* * Certain variables we don't want to allocate memory for over and over * again so global scope is given. */ static char IRC_RAW[MSGLENMAX]; /* Buffer to read data into */ static unsigned int IRC_RAW_LEN; /* Position of IRC_RAW */ static int IRC_FD = -1; /* File descriptor for IRC client */ static struct sockaddr_storage IRC_SVR; /* Sock Address Struct for IRC server */ 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 */ /* get_channel * * Check if a channel is defined in our conf. If so return * a pointer to it. * * Parameters: * channel: channel to search conf for * * Return: Pointer to ChannelConf containing the channel */ static const struct ChannelConf * get_channel(const char *name) { node_t *node; LIST_FOREACH(node, IRCItem.channels.head) { struct ChannelConf *item = node->data; if (strcasecmp(item->name, name) == 0) return item; } return NULL; } /* m_perform * * actions to perform on IRC connection * * Parameters: * parv[0] = source * parv[1] = PING * parv[2] = PING TS/Package * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_perform(char *parv[], unsigned int parc, const char *msg, const char *source_p) { node_t *node; log_printf("IRC -> Connected to %s/%d", IRCItem.server, IRCItem.port); /* Identify to nickserv if needed */ if (!EmptyString(IRCItem.nickserv)) irc_send("%s", IRCItem.nickserv); /* Oper */ irc_send("OPER %s", IRCItem.oper); /* Set modes */ irc_send("MODE %s %s", IRCItem.nick, IRCItem.mode); /* Set Away */ if (!EmptyString(IRCItem.away)) irc_send("AWAY :%s", IRCItem.away); /* Perform */ LIST_FOREACH(node, IRCItem.performs.head) irc_send("%s", node->data); /* Join all listed channels. */ LIST_FOREACH(node, IRCItem.channels.head) { const struct ChannelConf *channel = node->data; if (EmptyString(channel->name)) continue; if (!EmptyString(channel->key)) irc_send("JOIN %s %s", channel->name, channel->key); else irc_send("JOIN %s", channel->name); } } /* m_ping * * parv[0] = source * parv[1] = PING * parv[2] = PING TS/Package * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_ping(char *parv[], unsigned int parc, const char *msg, const char *source_p) { if (parc < 3) return; if (OPT_DEBUG >= 2) log_printf("IRC -> PING? PONG!"); irc_send("PONG %s", parv[2]); } /* m_invite * * parv[0] = source * parv[1] = INVITE * parv[2] = target * parv[3] = channel * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_invite(char *parv[], unsigned int parc, const char *msg, const char *source_p) { if (parc < 4) return; log_printf("IRC -> Invited to %s by %s", parv[3], parv[0]); const struct ChannelConf *channel = get_channel(parv[3]); if (channel == NULL) return; irc_send("JOIN %s %s", channel->name, channel->key); } /* 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 char *source_p) { if (strncasecmp(parv[3], "\001VERSION\001", 9) == 0) irc_send("NOTICE %s :\001VERSION Hybrid Open Proxy Monitor %s(%s)\001", source_p, VERSION, SERIALNUM); } /* m_privmsg * * parv[0] = source * parv[1] = PRIVMSG * parv[2] = target (channel or user) * parv[3] = message * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_privmsg(char *parv[], unsigned int parc, const char *msg, const char *source_p) { if (source_p == NULL) return; if (parc < 4) return; /* CTCP */ if (*parv[3] == '\001') { m_ctcp(parv, parc, msg, source_p); return; } /* Only interested in privmsg to channels */ if (*parv[2] != '#' && *parv[2] != '&') return; /* Get a target */ const struct ChannelConf *channel = get_channel(parv[2]); if (channel == NULL) return; int hit = strncasecmp(parv[3], "!all ", 5) == 0; if (hit == 0) { size_t nick_len = strlen(IRCItem.nick); if (strncasecmp(parv[3], IRCItem.nick, nick_len) == 0) hit = *(parv[3] + nick_len) == ' ' || *(parv[3] + nick_len) == ',' || *(parv[3] + nick_len) == ':'; } if (hit) /* XXX command_parse will alter parv[3]. */ command_parse(parv[3], channel->name, source_p); } /* m_notice * * parv[0] = source * parv[1] = NOTICE * parv[2] = target * parv[3] = message * * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_notice(char *parv[], unsigned int parc, const char *msg, const char *source_p) { 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; /* Compile the regular expression if it has not been already */ if (preg == NULL) { preg = xcalloc(sizeof(*preg)); if ((errnum = regcomp(preg, IRCItem.connregex, REG_ICASE | REG_EXTENDED))) { char errmsg[256]; regerror(errnum, preg, errmsg, sizeof(errmsg)); log_printf("IRC REGEX -> Error when compiling regular expression: %s", errmsg); xfree(preg); preg = NULL; return; } } /* 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 (pmatch[4].rm_so == -1) { log_printf("IRC REGEX -> pmatch[4].rm_so is -1 while parsing??? Aborting."); return; } /* * Offsets for data in the connection notice: * * NICKNAME: pmatch[1].rm_so TO pmatch[1].rm_eo * USERNAME: pmatch[2].rm_so TO pmatch[2].rm_eo * HOSTNAME: pmatch[3].rm_so TO pmatch[3].rm_eo * IP : pmatch[4].rm_so TO pmatch[4].rm_eo */ for (unsigned int i = 0; i < 4; ++i) { user[i] = (parv[3] + pmatch[i + 1].rm_so); *(parv[3] + pmatch[i + 1].rm_eo) = '\0'; } if (OPT_DEBUG > 0) log_printf("IRC REGEX -> Parsed %s!%s@%s [%s] from connection notice.", user[0], user[1], user[2], user[3]); LIST_FOREACH(node, IRCItem.notices.head) irc_send("NOTICE %s :%s", user[0], node->data); /* Pass this information off to scan.c */ scan_connect(user, msg); /* Record the connect for stats purposes */ stats_connect(); } /* m_userhost * * parv[0] = source * parv[1] = USERHOST * parv[2] = target (hopm) * parv[3] = :nick=(flags)user@host * * * source_p: UserInfo struct of the source user, or NULL if * the source (parv[0]) is a server. */ static void m_userhost(char *parv[], unsigned int parc, const char *msg, const char *source_p) { if (parc < 4) return; command_userhost(parv[3]); } /* m_cannot_join * * parv[0] = source * parv[1] = numeric * parv[2] = target (hopm) * parv[3] = channel * parv[4] = error text */ static void m_cannot_join(char *parv[], unsigned int parc, const char *msg, const char *source_p) { if (parc < 5) return; /* Is it one of our channels? */ const struct ChannelConf *channel = get_channel(parv[3]); if (channel == NULL) return; 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 char *source_p) { /* Restart hopm to rehash */ main_restart(); } /* userinfo_create * * Parse a nick!user@host into a UserInfo struct * and return a pointer to the new struct. * * Parameters: * source: nick!user@host to parse * * Return: * pointer to new UserInfo struct, or NULL if parsing failed */ static const char * userinfo_create(const char *source) { static char name[MSGLENMAX]; unsigned int has_user = 0; unsigned int has_host = 0; strlcpy(name, source, sizeof(name)); for (char *p = name; *p; ++p) { if (*p == '!') { *p = '\0'; ++has_user; continue; } if (*p == '@' && has_user) { ++has_host; continue; } } if (has_user == 1 && has_host == 1) return name; return NULL; }; /* irc_init * * Resolve IRC host and perform other initialization. * * Parameters: * None * * Return: * None */ static void irc_init(void) { const void *address; assert(IRC_FD == -1); memset(&IRC_SVR, 0, sizeof(IRC_SVR)); /* Resolve IRC host. */ if ((address = firedns_resolveip6(IRCItem.server))) { struct sockaddr_in6 *in = (struct sockaddr_in6 *)&IRC_SVR; svr_addrlen = sizeof(*in); 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; svr_addrlen = sizeof(*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); } /* Request file desc for IRC client socket */ IRC_FD = socket(IRC_SVR.ss_family, SOCK_STREAM, 0); if (IRC_FD == -1) { log_printf("IRC -> socket(): error creating socket: %s", strerror(errno)); exit(EXIT_FAILURE); } /* Bind */ if (!EmptyString(IRCItem.vhost)) { struct addrinfo hints, *res; memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_NUMERICHOST; int n = getaddrinfo(IRCItem.vhost, NULL, &hints, &res); if (n) { log_printf("IRC -> error binding to %s: %s", IRCItem.vhost, gai_strerror(n)); exit(EXIT_FAILURE); } else if (bind(IRC_FD, res->ai_addr, res->ai_addrlen)) { log_printf("IRC -> error binding to %s: %s", IRCItem.vhost, strerror(errno)); exit(EXIT_FAILURE); } freeaddrinfo(res); } } /* irc_reconnect * * Close connection to IRC server. * * Parameters: NONE * * Return: NONE */ static void irc_reconnect(void) { time_t present; time(&present); /* Only try to reconnect every IRCItem.reconnectinterval seconds */ if ((present - IRC_LASTRECONNECT) < IRCItem.reconnectinterval) { /* Sleep to avoid excessive CPU */ sleep(1); return; } time(&IRC_LASTRECONNECT); if (IRC_FD > -1) { close(IRC_FD); IRC_FD = -1; /* Set IRC_FD -1 for reconnection on next irc_cycle(). */ } log_printf("IRC -> Connection to (%s) failed, reconnecting.", IRCItem.server); } /* irc_connect * * Connect to IRC server. * XXX: FD allocation done here * * Parameters: NONE * Return: NONE */ static void irc_connect(void) { /* Connect to IRC server as client. */ if (connect(IRC_FD, (struct sockaddr *)&IRC_SVR, 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; /* 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_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 * * 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 */ struct CommandHash { const char *command; void (*handler)(char *[], unsigned int, const char *, const char *); }; /* * Table should be ordered with most occuring (or priority) * commands at the top of the list. */ static const struct CommandHash COMMAND_TABLE[] = { { .command = "NOTICE", .handler = m_notice }, { .command = "PRIVMSG", .handler = m_privmsg }, { .command = "PING", .handler = m_ping }, { .command = "INVITE", .handler = m_invite }, { .command = "001", .handler = m_perform }, { .command = "302", .handler = m_userhost }, { .command = "471", .handler = m_cannot_join }, { .command = "473", .handler = m_cannot_join }, { .command = "474", .handler = m_cannot_join }, { .command = "475", .handler = m_cannot_join }, { .command = "KILL", .handler = m_kill }, { .command = NULL } }; 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 { parv[0] = IRCItem.server; parv[parc++] = IRC_RAW; } pos = IRC_RAW; while ((pos = strchr(pos, ' ')) && parc <= 17) { /* Avoid excessive spaces and end of IRC_RAW */ if (*(pos + 1) == ' ' || *(pos + 1) == '\0') { pos++; continue; } /* Anything after a : is considered the final string of the message */ if (*(pos + 1) == ':') { parv[parc++] = pos + 2; *pos = '\0'; break; } /* * Set the next parv at this position and replace the space with a * \0 for the previous parv */ parv[parc++] = pos + 1; *pos = '\0'; pos++; } /* * 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) { cmd->handler(parv, parc, msg, userinfo_create(parv[0])); break; } } } /* irc_read * * 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 irc_read(void) { ssize_t len; char c; while ((len = read(IRC_FD, &c, 1)) > 0) { if (c == '\r') continue; if (c == '\n') { /* Null string. */ IRC_RAW[IRC_RAW_LEN] = '\0'; /* Parse line. */ irc_parse(); /* Reset counter. */ IRC_RAW_LEN = 0; break; } if (c != '\0') IRC_RAW[IRC_RAW_LEN++] = c; } if ((len <= 0) && (errno != EAGAIN)) { if (errno != EINTR) log_printf("IRC -> Error reading data from server: %s", strerror(errno)); irc_reconnect(); IRC_RAW_LEN = 0; return; } } /* 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 == -1) { /* Initialize negative cache. */ if (OptionsItem.negcache) negcache_init(); /* Resolve remote host. */ irc_init(); /* Connect to remote host. */ irc_connect(); return; /* In case connect() immediately failed */ } 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; } } /* irc_send * * Send data to remote IRC host. * * Parameters: * data: Format of data to send * ...: varargs to format with * * Return: NONE */ void irc_send(const char *data, ...) { va_list arglist; char buf[MSGLENMAX]; va_start(arglist, data); size_t 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(); } } /* irc_send_channels * * Send privmsg to all channels. * * Parameters: * data: Format of data to send * ...: varargs to format with * * Return: NONE */ void irc_send_channels(const char *data, ...) { const node_t *node; va_list arglist; char buf[MSGLENMAX]; va_start(arglist, data); vsnprintf(buf, sizeof(buf), data, arglist); va_end(arglist); LIST_FOREACH(node, IRCItem.channels.head) { const struct ChannelConf *chan = node->data; irc_send("PRIVMSG %s :%s", chan->name, buf); } } /* irc_timer * * Functions to be performed every ~seconds. * * Parameters: NONE * Return: NONE */ void irc_timer(void) { time_t present, delta; time(&present); delta = present - IRC_LAST; /* No data in 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"); } }