diff --git a/src/bnetd/irc.cpp b/src/bnetd/irc.cpp index b52b633..f8f5783 100644 --- a/src/bnetd/irc.cpp +++ b/src/bnetd/irc.cpp @@ -28,6 +28,8 @@ #include <cstring> #include <ctime> +#include <fmt/format.h> + #include "compat/strcasecmp.h" #include "common/irc_protocol.h" @@ -66,76 +68,92 @@ namespace pvpgn { typedef struct { - char const * nick; - char const * user; - char const * host; + char const* nick; + char const* user; + char const* host; } t_irc_message_from; - static char ** irc_split_elems(char * list, int separator, int ignoreblank); - static int irc_unget_elems(char ** elems); - static char * irc_message_preformat(t_irc_message_from const * from, char const * command, char const * dest, char const * text); + static char** irc_split_elems(char* list, int separator, int ignoreblank); + static int irc_unget_elems(char** elems); + static std::string irc_message_preformat(const t_irc_message_from* from, const char* command, const char* dest, const char* text); - extern int irc_send_cmd(t_connection * conn, char const * command, char const * params) + static std::size_t irc_send_get_max_params_length(t_connection* conn) { - t_packet * p; - char data[MAX_IRC_MESSAGE_LEN + 1]; - char const * ircname = server_get_hostname(); - char const * nick; + const char* const ircname = server_get_hostname(); + const char* const nick = conn_get_loggeduser(conn); + const int example_numeric = 0; - if (!conn) { + // fmt::format(":{} {:03d} {} {}\r\n", ircname, numeric, nick ? nick : "UserName", params); + auto overhead = fmt::formatted_size(":{} {:03d} {} {}\r\n", ircname, example_numeric, nick ? nick : "UserName", ""); + return MAX_IRC_MESSAGE_LEN - overhead; + } + + extern int irc_send(t_connection* conn, int numeric, char const* params) + { + if (!conn) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - if (!command) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL command"); - return -1; - } - if (!(p = packet_create(packet_class_raw))) { - eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); + + if ((numeric > 999) || (numeric < 0)) + { + /* more than 3 digits or negative */ + eventlog(eventlog_level_error, __FUNCTION__, "invalid irc message numeric ({})", numeric); return -1; } - nick = conn_get_loggeduser(conn); - if (!nick) - nick = "UserName"; + try + { + const char* hostname = server_get_hostname(); + std::string nick; + { + const char* tempnick = conn_get_loggeduser(conn); + nick = tempnick ? tempnick : "UserName"; + } - if (params) - std::snprintf(data, sizeof(data), ":%s %s %s %s", ircname, command, nick, params); - else - std::snprintf(data, sizeof(data), ":%s %s %s", ircname, command, nick); + std::string data = fmt::format(":{} {:03d} {}", hostname, numeric, nick); + if (params) + { + fmt::format_to(std::back_inserter(data), " {}", params); + } + fmt::format_to(std::back_inserter(data), "\r\n"); - DEBUG2("[{}] sent \"{}\"", conn_get_socket(conn), data); - std::strcat(data, "\r\n"); - packet_set_size(p, 0); - packet_append_data(p, data, std::strlen(data)); - conn_push_outqueue(conn, p); - packet_del_ref(p); - return 0; + if (data.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", data.length(), MAX_IRC_MESSAGE_LEN); + eventlog(eventlog_level_debug, __FUNCTION__, "hostname: {}, numeric: {}, nick: {}, params: \"{}\"", hostname, numeric, nick, params); + return -1; + } + + t_packet* packet = packet_create(packet_class_raw); + if (packet == nullptr) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); + return -1; + } + packet_set_size(packet, 0); + packet_append_data(packet, data.c_str(), data.length()); + conn_push_outqueue(conn, packet); + packet_del_ref(packet); + + data.erase(data.end() - 2); + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] sent \"{}\"", conn_get_socket(conn), data); + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC command ({})", conn_get_socket(conn), e.what()); + return -1; + } } - extern int irc_send(t_connection * conn, int code, char const * params) + extern int irc_send_ping(t_connection* conn) { - char temp[4]; /* '000\0' */ - - if (!conn) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); - return -1; - } - if ((code > 999) || (code < 0)) { /* more than 3 digits or negative */ - eventlog(eventlog_level_error, __FUNCTION__, "invalid message code ({})", code); - return -1; - } - std::sprintf(temp, "%03u", code); - return irc_send_cmd(conn, temp, params); - } - - extern int irc_send_ping(t_connection * conn) - { - t_packet * p; - char data[MAX_IRC_MESSAGE_LEN]; - - if (!conn) { + if (!conn) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } @@ -143,69 +161,112 @@ namespace pvpgn if ((conn_get_class(conn) == conn_class_wserv) || (conn_get_class(conn) == conn_class_wgameres) || (conn_get_class(conn) == conn_class_wladder)) + { return 0; - - if (!(p = packet_create(packet_class_raw))) { - eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); - return -1; } - conn_set_ircping(conn, get_ticks()); - if (conn_get_state(conn) == conn_state_bot_username) - std::sprintf(data, "PING :%u", conn_get_ircping(conn)); /* Undernet doesn't reveal the servername yet ... neither do we */ - else if ((6 + std::strlen(server_get_hostname()) + 2 + 1) <= MAX_IRC_MESSAGE_LEN) - std::sprintf(data, "PING :%s", server_get_hostname()); - else - eventlog(eventlog_level_error, __FUNCTION__, "maximum message length exceeded"); - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] sent \"{}\"", conn_get_socket(conn), data); - std::strcat(data, "\r\n"); - packet_set_size(p, 0); - packet_append_data(p, data, std::strlen(data)); - conn_push_outqueue(conn, p); - packet_del_ref(p); - return 0; + try + { + conn_set_ircping(conn, get_ticks()); + + // MAX_IRC_MESSAGE_LEN + std::string data; + + if (conn_get_state(conn) == conn_state_bot_username) + { + data = fmt::format("PING :{}\r\n", conn_get_ircping(conn)); // Undernet doesn't reveal the servername yet ... neither do we + } + else + { + data = fmt::format("PING :{}\r\n", server_get_hostname()); + } + + if (data.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", data.length(), MAX_IRC_MESSAGE_LEN); + return -1; + } + + t_packet* p = packet_create(packet_class_raw); + if (!p) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); + return -1; + } + packet_set_size(p, 0); + packet_append_data(p, data.c_str(), data.length()); + conn_push_outqueue(conn, p); + packet_del_ref(p); + + data.erase(data.end() - 2); + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] sent \"{}\"", conn_get_socket(conn), data); + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC command ({})", conn_get_socket(conn), e.what()); + return -1; + } } - extern int irc_send_pong(t_connection * conn, char const * params) + extern int irc_send_pong(t_connection* conn, char const* params) { - t_packet * p; - char data[MAX_IRC_MESSAGE_LEN]; - - if (!conn) { + if (!conn) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - if ((1 + std::strlen(server_get_hostname()) + 1 + 4 + 1 + std::strlen(server_get_hostname()) + ((params) ? (2 + std::strlen(params)) : (0)) + 2 + 1) > MAX_IRC_MESSAGE_LEN) { - eventlog(eventlog_level_error, __FUNCTION__, "max message length exceeded"); - return -1; - } - if (!(p = packet_create(packet_class_raw))) { - eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); - return -1; - } - if (params) - std::sprintf(data, ":%s PONG %s :%s", server_get_hostname(), server_get_hostname(), params); - else - std::sprintf(data, ":%s PONG %s", server_get_hostname(), server_get_hostname()); - eventlog(eventlog_level_debug, __FUNCTION__, "[{}] sent \"{}\"", conn_get_socket(conn), data); - std::strcat(data, "\r\n"); - packet_set_size(p, 0); - packet_append_data(p, data, std::strlen(data)); - conn_push_outqueue(conn, p); - packet_del_ref(p); - return 0; + try + { + std::string data; + if (params) + { + data = fmt::format(":{} PONG {} :{}\r\n", server_get_hostname(), server_get_hostname(), params); + } + else + { + data = fmt::format(":{} PONG {}\r\n", server_get_hostname(), server_get_hostname()); + } + + if (data.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", data.length(), MAX_IRC_MESSAGE_LEN); + return -1; + } + + t_packet* p = packet_create(packet_class_raw); + if (!p) + { + eventlog(eventlog_level_error, __FUNCTION__, "could not create packet"); + return -1; + } + packet_set_size(p, 0); + packet_append_data(p, data.c_str(), data.length()); + conn_push_outqueue(conn, p); + packet_del_ref(p); + + data.erase(data.end() - 2); + eventlog(eventlog_level_debug, __FUNCTION__, "[{}] sent \"{}\"", conn_get_socket(conn), data); + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC command ({})", conn_get_socket(conn), e.what()); + return -1; + } } - extern int irc_authenticate(t_connection * conn, char const * passhash) + extern int irc_authenticate(t_connection* conn, char const* passhash) { /* FIXME: Move this function to handle_irc.* file! */ - char temp[MAX_IRC_MESSAGE_LEN]; t_hash h1; t_hash h2; - t_account * a; - char const * temphash; - char const * username; + t_account* a; + char const* temphash; + char const* username; if (!conn) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); @@ -228,8 +289,8 @@ namespace pvpgn } if (connlist_find_connection_by_account(a) && prefs_get_kick_old_login() == 0) { - std::snprintf(temp, sizeof(temp), "%s :Account is already in use!", conn_get_loggeduser(conn)); - irc_send(conn, ERR_NICKNAMEINUSE, temp); + std::string temp = fmt::format("{} :Account is already in use!", conn_get_loggeduser(conn)); + irc_send(conn, ERR_NICKNAMEINUSE, temp.c_str()); } else if (account_get_auth_lock(a) == 1) { message_send_text(conn, message_type_notice, NULL, "Authentication rejected (account is locked) "); @@ -265,9 +326,9 @@ namespace pvpgn /* ':' -> '%=' */ /* ',' -> '%-' */ /* In IRC a channel can be specified by '#'+channelname or '!'+channelid */ - extern char const * irc_convert_channel(t_channel const * channel, t_connection * c) + extern char const* irc_convert_channel(t_channel const* channel, t_connection* c) { - char const * bname; + char const* bname; static char out[MAX_CHANNELNAME_LEN]; unsigned int outpos; int i; @@ -319,21 +380,23 @@ namespace pvpgn else { out[outpos++] = bname[i]; } - if ((outpos + 2) >= (sizeof(out))) { - std::sprintf(out, "!%u", channel_get_channelid(channel)); + + if ((outpos + 2) >= (sizeof(out))) + { + std::snprintf(out, sizeof(out), "!%u", channel_get_channelid(channel)); return out; } } return out; } - extern char const * irc_convert_ircname(char const * pircname) + extern char const* irc_convert_ircname(char const* pircname) { static char out[MAX_CHANNELNAME_LEN]; unsigned int outpos; int special; int i; - char const * ircname = pircname + 1; + char const* ircname = pircname + 1; if (!ircname) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL ircname"); @@ -344,7 +407,7 @@ namespace pvpgn std::memset(out, 0, sizeof(out)); special = 0; if (pircname[0] == '!') { - t_channel * channel; + t_channel* channel; channel = channellist_find_channel_bychannelid(std::atoi(ircname)); if (channel) @@ -405,11 +468,11 @@ namespace pvpgn /* splits an string list into its elements */ /* (list will be modified) */ - static char ** irc_split_elems(char * list, int separator, int ignoreblank) + static char** irc_split_elems(char* list, int separator, int ignoreblank) { int i; int count; - char ** out; + char** out; if (!list) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL list"); @@ -427,7 +490,7 @@ namespace pvpgn } count++; /* count separators -> we have one more element ... */ /* we also need a terminating element */ - out = (char**)xmalloc((count + 1)*sizeof(char *)); + out = (char**)xmalloc((count + 1) * sizeof(char*)); out[0] = list; if (count > 1) { @@ -439,7 +502,7 @@ namespace pvpgn return NULL; } if (ignoreblank) - while ((*out[i] + 1) == separator) out[i]++; + while ((*out[i] + 1) == separator) out[i]++; *out[i]++ = '\0'; } if ((ignoreblank) && (out[count - 1]) && (*out[count - 1] == '\0')) { @@ -453,7 +516,7 @@ namespace pvpgn return out; } - static int irc_unget_elems(char ** elems) + static int irc_unget_elems(char** elems) { if (!elems) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL elems"); @@ -463,88 +526,74 @@ namespace pvpgn return 0; } - extern char ** irc_get_listelems(char * list) + extern char** irc_get_listelems(char* list) { return irc_split_elems(list, ',', 0); } - extern int irc_unget_listelems(char ** elems) + extern int irc_unget_listelems(char** elems) { return irc_unget_elems(elems); } - extern char ** irc_get_paramelems(char * list) + extern char** irc_get_paramelems(char* list) { return irc_split_elems(list, ' ', 1); } - extern int irc_unget_paramelems(char ** elems) + extern int irc_unget_paramelems(char** elems) { return irc_unget_elems(elems); } - extern char ** irc_get_ladderelems(char * list) + extern char** irc_get_ladderelems(char* list) { return irc_split_elems(list, ':', 1); } - extern int irc_unget_ladderelems(char ** elems) + extern int irc_unget_ladderelems(char** elems) { return irc_unget_elems(elems); } - static char * irc_message_preformat(t_irc_message_from const * from, char const * command, char const * dest, char const * text) + static std::string irc_message_preformat(const t_irc_message_from* from, const char* command, const char* dest, const char* text) { - char * myfrom; - char const * mydest = ""; - char const * mytext = ""; - int len; - char * msg; - - if (!command) { + if (!command) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL command"); return NULL; } - if (from) { - if ((!from->nick) || (!from->user) || (!from->host)) { + + std::string myfrom; + if (from) + { + if ((!from->nick) || (!from->user) || (!from->host)) + { eventlog(eventlog_level_error, __FUNCTION__, "got malformed from"); return NULL; } - myfrom = (char*)xmalloc(std::strlen(from->nick) + 1 + std::strlen(from->user) + 1 + std::strlen(from->host) + 1); /* nick + "!" + user + "@" + host + "\0" */ - std::sprintf(myfrom, "%s!%s@%s", from->nick, from->user, from->host); + + myfrom = fmt::format("{}!{}@{}", from->nick, from->user, from->host); } else - myfrom = xstrdup(server_get_hostname()); - if (dest) - mydest = dest; + { + myfrom = server_get_hostname(); + } - if (text) - mytext = text; - - len = 1 + std::strlen(myfrom) + 1 + - std::strlen(command) + 1 + - std::strlen(mydest) + 1 + - 1 + std::strlen(mytext) + 1; - - - msg = (char*)xmalloc(len); - - std::sprintf(msg, ":%s\n%s\n%s\n%s", myfrom, command, mydest, mytext); - xfree(myfrom); - return msg; + return fmt::format(":{}\n{}\n{}\n{}", myfrom, command, dest ? dest : "", text ? text : ""); } - extern int irc_message_postformat(t_packet * packet, t_connection const * dest) + extern int irc_message_postformat(t_packet* packet, t_connection const* dest) { /* the four elements */ - char * e1; - char * e1_2; - char * e2; - char * e3; - char * e4; - char const * tname = NULL; - char const * toname = "AUTH"; /* fallback name */ - const char * temp; + char* e1; + char* e1_2; + char* e2; + char* e3; + char* e4; + char const* tname = NULL; + char const* toname = "AUTH"; /* fallback name */ + const char* temp; if (!packet) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL packet"); @@ -600,703 +649,988 @@ namespace pvpgn else temp = " "; - if (std::strlen(e1) + 1 + std::strlen(e2) + 1 + std::strlen(toname) + std::strlen(temp) + std::strlen(e4) + 2 + 1 <= MAX_IRC_MESSAGE_LEN) { - char msg[MAX_IRC_MESSAGE_LEN + 1]; - + try + { + std::string msg; if (e1_2) - std::sprintf(msg, "%s@hidden %s %s%s%s", e1, e2, toname, temp, e4); + { + msg = fmt::format("{}@hidden {} {}{}{}\r\n", e1, e2, toname, temp, e4); + } else - std::sprintf(msg, "%s %s %s%s%s", e1, e2, toname, temp, e4); + { + msg = fmt::format("{} {} {}{}{}\r\n", e1, e2, toname, temp, e4); + } - DEBUG2("[{}] sent \"{}\"", conn_get_socket(dest), msg); - std::strcat(msg, "\r\n"); + if (msg.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", msg.length(), MAX_IRC_MESSAGE_LEN); + + if (tname) + { + conn_unget_chatname(dest, tname); + } + + return -1; + } packet_set_size(packet, 0); - packet_append_data(packet, msg, std::strlen(msg)); + packet_append_data(packet, msg.c_str(), msg.length()); + if (tname) + { conn_unget_chatname(dest, tname); + } + return 0; } - else { - /* FIXME: split up message? */ - eventlog(eventlog_level_warn, __FUNCTION__, "maximum IRC message length exceeded"); - if (tname) - conn_unget_chatname(dest, tname); + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[?] failed to post format IRC message ({})", e.what()); + return -1; } } - extern int irc_message_format(t_packet * packet, t_message_type type, t_connection * me, t_connection * dst, char const * text, unsigned int dstflags) + extern int irc_message_format(t_packet* packet, t_message_type type, t_connection* me, t_connection* dst, char const* text, unsigned int dstflags) { - char * msg; - char const * ctag; - t_irc_message_from from; - if (!packet) { eventlog(eventlog_level_error, __FUNCTION__, "got NULL packet"); return -1; } - msg = NULL; - if (me) - ctag = clienttag_uint_to_str(conn_get_clienttag(me)); - else - ctag = clienttag_uint_to_str(CLIENTTAG_IIRC_UINT); - - switch (type) + try { - /* case message_type_adduser: this is sent manually in handle_irc */ - case message_type_adduser: - /* when we do it somewhere else, then we can also make sure to not get our logs spammed */ - break; - case message_type_join: - { - t_channel * channel; - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - - channel = conn_get_channel(me); - - if (tag_check_wolv2(conn_get_clienttag(dst))) { - char temp[MAX_IRC_MESSAGE_LEN]; - t_clan * clan; - unsigned int clanid = 0; - std::memset(temp, 0, sizeof(temp)); - - /** - * For WOLv2 the channel JOIN output must be like the following: - * user!WWOL@hostname JOIN :clanID,longIP channelName - */ - - clan = account_get_clan(conn_get_account(me)); - - if (clan) - clanid = clan_get_clanid(clan); - - std::sprintf(temp, ":%u,%u", clanid, conn_get_addr(me)); - msg = irc_message_preformat(&from, "JOIN", temp, irc_convert_channel(channel, dst)); - } - else { - msg = irc_message_preformat(&from, "JOIN", "\r", irc_convert_channel(channel, dst)); - } - conn_unget_chatname(me, from.nick); - } - break; - case message_type_part: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "PART", "\r", irc_convert_channel(conn_get_channel(me), dst)); - conn_unget_chatname(me, from.nick); - break; - case message_type_talk: - case message_type_whisper: - { - char const * dest; - char temp[MAX_IRC_MESSAGE_LEN]; - - if (me) { - from.nick = conn_get_chatname(me); - from.host = addr_num_to_ip_str(conn_get_addr(me)); - } - else { - from.nick = server_get_hostname(); - from.host = server_get_hostname(); - } - from.user = ctag; - - if (type == message_type_talk) - dest = irc_convert_channel(conn_get_channel(me), dst); /* FIXME: support more channels and choose right one! */ - else - dest = ""; /* will be replaced with username in postformat */ - - std::sprintf(temp, ":%s", text); - - if ((conn_get_wol(me) != 1) && (conn_get_wol(dst) == 1) && (conn_wol_get_pageme(dst)) - && (type == message_type_whisper) && (conn_get_channel(me) != (conn_get_channel(dst)))) - msg = irc_message_preformat(&from, "PAGE", NULL, temp); - else - msg = irc_message_preformat(&from, "PRIVMSG", dest, temp); - - if (me) - conn_unget_chatname(me, from.nick); - } - break; - case message_type_emote: - { - char const * dest; - char temp[MAX_IRC_MESSAGE_LEN]; - - /* "\001ACTION " + text + "\001" + \0 */ - - /* PELISH: WOLv1, DUNE2000 and RENEGADE shows emotes automaticaly to self */ - if ((me == dst) && ((tag_check_wolv1(conn_get_clienttag(dst))) || - (conn_get_clienttag(dst) == CLIENTTAG_DUNE2000_UINT) || - (conn_get_clienttag(dst) == CLIENTTAG_RENEGADE_UINT) || - (conn_get_clienttag(dst) == CLIENTTAG_RENGDFDS_UINT))) - break; - - if ((8 + std::strlen(text) + 1 + 1) <= MAX_IRC_MESSAGE_LEN) { - std::sprintf(temp, ":\001ACTION %s\001", text); - } - else { - std::sprintf(temp, ":\001ACTION (maximum message length exceeded)\001"); - } - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - /* FIXME: also supports whisper emotes? */ - dest = irc_convert_channel(conn_get_channel(me), dst); /* FIXME: support more channels and choose right one! */ - msg = irc_message_preformat(&from, "PRIVMSG", dest, temp); - conn_unget_chatname(me, from.nick); - } - break; - case message_type_broadcast: - case message_type_info: - case message_type_error: - { - char temp[MAX_IRC_MESSAGE_LEN]; - std::sprintf(temp, ":%s", text); - if (conn_get_wol(dst) == 1) { - if ((type == message_type_info) || (type == message_type_error)) - msg = irc_message_preformat(NULL, "PAGE", NULL, temp); - else - msg = irc_message_preformat(NULL, "NOTICE", NULL, temp); - } - else - msg = irc_message_preformat(NULL, "NOTICE", NULL, temp); - } - break; - case message_type_channel: - /* ignore it */ - break; - case message_type_userflags: - /* ignore it but maybe will be used for set MODE +o if user is operator - * but at this time is this command sended only once when user join - * first channel */ - break; - case message_type_mode: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "MODE", "\r", text); - conn_unget_chatname(me, from.nick); - break; - case message_type_nick: - { - from.nick = conn_get_loggeduser(me); - from.host = addr_num_to_ip_str(conn_get_addr(me)); - from.user = ctag; - msg = irc_message_preformat(&from, "NICK", "\r", text); - } - break; - case message_type_notice: - { - char temp[MAX_IRC_MESSAGE_LEN]; - std::sprintf(temp, ":%s", text); - - if (me && conn_get_chatname(me)) - { - from.nick = conn_get_chatname(me); - from.host = addr_num_to_ip_str(conn_get_addr(me)); - from.user = ctag; - msg = irc_message_preformat(&from, "NOTICE", "", temp); - } - else - { - msg = irc_message_preformat(NULL, "NOTICE", "", temp); - } - } - break; - case message_type_namreply: - { - t_channel * channel; - - channel = conn_get_channel(me); - - irc_send_rpl_namreply(dst, channel); - } - break; - case message_type_topic: - { - t_channel * channel; - channel = conn_get_channel(me); - irc_send_topic(dst, channel); - } - break; - case message_type_kick: - { - char temp[MAX_IRC_MESSAGE_LEN]; - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - if (text) - std::sprintf(temp, "%s :%s", conn_get_chatname(me), text); - else - std::sprintf(temp, "%s :", conn_get_chatname(me)); - msg = irc_message_preformat(&from, "KICK", irc_convert_channel(conn_get_channel(me), dst), temp); - conn_unget_chatname(me, from.nick); - } - break; - case message_type_quit: - if (conn_get_clienttag(dst) == CLIENTTAG_IIRC_UINT) { - char temp[MAX_IRC_MESSAGE_LEN]; - if (text) - sprintf(temp, ":%s", text); - else - sprintf(temp, ":"); - - from.nick = conn_get_chatname(me); - from.host = addr_num_to_ip_str(conn_get_addr(me)); - from.user = ctag; - msg = irc_message_preformat(&from, "QUIT", "\r", temp); + const char* ctag; + if (me) + { + ctag = clienttag_uint_to_str(conn_get_clienttag(me)); } - else { + else + { + ctag = clienttag_uint_to_str(CLIENTTAG_IIRC_UINT); + } + + std::string msg; + + switch (type) + { + /* case message_type_adduser: this is sent manually in handle_irc */ + case message_type_adduser: + /* when we do it somewhere else, then we can also make sure to not get our logs spammed */ + break; + case message_type_join: + { + t_irc_message_from from; from.nick = conn_get_chatname(me); from.user = ctag; from.host = addr_num_to_ip_str(conn_get_addr(me)); + + t_channel* channel = conn_get_channel(me); + + if (tag_check_wolv2(conn_get_clienttag(dst))) + { + /** + * For WOLv2 the channel JOIN output must be like the following: + * user!WWOL@hostname JOIN :clanID,longIP channelName + */ + + t_clan* clan = account_get_clan(conn_get_account(me)); + + unsigned int clanid = 0; + if (clan) + { + clanid = clan_get_clanid(clan); + } + + std::string temp = fmt::format(":{},{}", clanid, conn_get_addr(me)); + msg = irc_message_preformat(&from, "JOIN", temp.c_str(), irc_convert_channel(channel, dst)); + } + else + { + msg = irc_message_preformat(&from, "JOIN", "\r", irc_convert_channel(channel, dst)); + } + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_part: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + msg = irc_message_preformat(&from, "PART", "\r", irc_convert_channel(conn_get_channel(me), dst)); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_talk: + case message_type_whisper: + { + t_irc_message_from from; + if (me) + { + from.nick = conn_get_chatname(me); + from.host = addr_num_to_ip_str(conn_get_addr(me)); + } + else + { + from.nick = server_get_hostname(); + from.host = server_get_hostname(); + } + from.user = ctag; + + const char* dest; + if (type == message_type_talk) + { + dest = irc_convert_channel(conn_get_channel(me), dst); // FIXME: support more channels and choose right one! + } + else + { + dest = ""; // will be replaced with username in postformat + } + + std::string temp = fmt::format(":{}", text); + + if ((conn_get_wol(me) != 1) + && (conn_get_wol(dst) == 1) + && (conn_wol_get_pageme(dst)) + && (type == message_type_whisper) + && (conn_get_channel(me) != (conn_get_channel(dst)))) + { + msg = irc_message_preformat(&from, "PAGE", nullptr, temp.c_str()); + } + else + { + msg = irc_message_preformat(&from, "PRIVMSG", dest, temp.c_str()); + } + + if (me) + { + conn_unget_chatname(me, from.nick); + } + + break; + } + case message_type_emote: + { + // PELISH: WOLv1, DUNE2000 and RENEGADE shows emotes automaticaly to self + if ((me == dst) && ((tag_check_wolv1(conn_get_clienttag(dst))) || + (conn_get_clienttag(dst) == CLIENTTAG_DUNE2000_UINT) || + (conn_get_clienttag(dst) == CLIENTTAG_RENEGADE_UINT) || + (conn_get_clienttag(dst) == CLIENTTAG_RENGDFDS_UINT))) + { + break; + } + + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + std::string temp; + + if (fmt::formatted_size(":\001ACTION {}\001", text) <= MAX_IRC_MESSAGE_LEN) + { + temp = fmt::format(":\001ACTION {}\001", text); + } + else + { + temp = ":\001ACTION (maximum message length exceeded)\001"; + } + + + // FIXME: also supports whisper emotes? + const char* dest = irc_convert_channel(conn_get_channel(me), dst); // FIXME: support more channels and choose right one! + msg = irc_message_preformat(&from, "PRIVMSG", dest, temp.c_str()); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_broadcast: + case message_type_info: + case message_type_error: + { + std::string temp = fmt::format(":{}", text); + + if (conn_get_wol(dst) == 1) + { + if ((type == message_type_info) || (type == message_type_error)) + { + msg = irc_message_preformat(nullptr, "PAGE", nullptr, temp.c_str()); + } + else + { + msg = irc_message_preformat(nullptr, "NOTICE", nullptr, temp.c_str()); + } + } + else + { + msg = irc_message_preformat(nullptr, "NOTICE", nullptr, temp.c_str()); + } + + break; + } + case message_type_channel: + /* ignore it */ + break; + case message_type_userflags: + /* ignore it but maybe will be used for set MODE +o if user is operator + * but at this time is this command sended only once when user join + * first channel */ + break; + case message_type_mode: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "MODE", "\r", text); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_nick: + { + t_irc_message_from from; + from.nick = conn_get_loggeduser(me); + from.host = addr_num_to_ip_str(conn_get_addr(me)); + from.user = ctag; + + msg = irc_message_preformat(&from, "NICK", "\r", text); + + break; + } + case message_type_notice: + { + std::string temp = fmt::format(":{}", text); + + if (me && conn_get_chatname(me)) + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.host = addr_num_to_ip_str(conn_get_addr(me)); + from.user = ctag; + + msg = irc_message_preformat(&from, "NOTICE", nullptr, temp.c_str()); + } + else + { + msg = irc_message_preformat(nullptr, "NOTICE", nullptr, temp.c_str()); + } + + break; + } + case message_type_namreply: + { + t_channel* channel = conn_get_channel(me); + + irc_send_rpl_namreply(dst, channel); + + break; + } + case message_type_topic: + { + t_channel* channel = conn_get_channel(me); + + irc_send_topic(dst, channel); + + break; + } + case message_type_kick: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + std::string temp; + if (text) + { + temp = fmt::format("{} :{}", conn_get_chatname(me), text); + } + else + { + temp = fmt::format("{} :", conn_get_chatname(me)); + } + + msg = irc_message_preformat(&from, "KICK", irc_convert_channel(conn_get_channel(me), dst), temp.c_str()); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_quit: + if (conn_get_clienttag(dst) == CLIENTTAG_IIRC_UINT) + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.host = addr_num_to_ip_str(conn_get_addr(me)); + from.user = ctag; + + std::string temp; + if (text) + { + temp = fmt::format(":{}", text); + } + else + { + temp = ":"; + } + + msg = irc_message_preformat(&from, "QUIT", "\r", temp.c_str()); + } + else + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "PART", "\r", irc_convert_channel(conn_get_channel(me), dst)); + + conn_unget_chatname(me, from.nick); + } + break; + + /** + * Westwood Online Extensions + */ + case message_type_host: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "HOST", "\r", text); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_invmsg: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "INVMSG", nullptr, text); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_type_page: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + std::string temp = fmt::format(":{}", text); + + msg = irc_message_preformat(&from, "PAGE", nullptr, temp.c_str()); + conn_unget_chatname(me, from.nick); } break; + case message_wol_joingame: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); - /** - * Westwood Online Extensions - */ - case message_type_host: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "HOST", "\r", text); - conn_unget_chatname(me, from.nick); - break; - case message_type_invmsg: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "INVMSG", NULL, text); - conn_unget_chatname(me, from.nick); - break; - case message_type_page: - { - char temp[MAX_IRC_MESSAGE_LEN]; - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - std::sprintf(temp, ":%s", text); - msg = irc_message_preformat(&from, "PAGE", NULL, temp); - conn_unget_chatname(me, from.nick); - } - break; - case message_wol_joingame: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "JOINGAME", "\r", text); - conn_unget_chatname(me, from.nick); - break; - case message_type_gameopt_talk: - case message_type_gameopt_whisper: - { - char const * dest; - char temp[MAX_IRC_MESSAGE_LEN]; + msg = irc_message_preformat(&from, "JOINGAME", "\r", text); - if (me) { - from.nick = conn_get_chatname(me); - from.host = addr_num_to_ip_str(conn_get_addr(me)); - } - else { - from.nick = server_get_hostname(); - from.host = server_get_hostname(); - } - from.user = ctag; + conn_unget_chatname(me, from.nick); - if (type == message_type_gameopt_talk) - dest = irc_convert_channel(conn_get_channel(me), dst); /* FIXME: support more channels and choose right one! */ - else - dest = ""; /* will be replaced with username in postformat */ + break; + } + case message_type_gameopt_talk: + case message_type_gameopt_whisper: + { + t_irc_message_from from; + if (me) + { + from.nick = conn_get_chatname(me); + from.host = addr_num_to_ip_str(conn_get_addr(me)); + } + else + { + from.nick = server_get_hostname(); + from.host = server_get_hostname(); + } + from.user = ctag; - std::sprintf(temp, ":%s", text); + char const* dest; + if (type == message_type_gameopt_talk) + { + dest = irc_convert_channel(conn_get_channel(me), dst); // FIXME: support more channels and choose right one! + } + else + { + dest = nullptr; // will be replaced with username in postformat + } - msg = irc_message_preformat(&from, "GAMEOPT", dest, temp); + std::string temp = fmt::format(":{}", text); + + msg = irc_message_preformat(&from, "GAMEOPT", dest, temp.c_str()); + + if (me) + { + conn_unget_chatname(me, from.nick); + } + + break; + } + case message_wol_start_game: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "STARTG", nullptr, text); + + conn_unget_chatname(me, from.nick); + + break; + } + case message_wol_advertr: + msg = irc_message_preformat(nullptr, "ADVERTR", "\r", text); + break; + case message_wol_chanchk: + msg = irc_message_preformat(nullptr, "CHANCHK", "\r", text); + break; + case message_wol_userip: + { + t_irc_message_from from; + from.nick = conn_get_chatname(me); + from.user = ctag; + from.host = addr_num_to_ip_str(conn_get_addr(me)); + + msg = irc_message_preformat(&from, "USERIP", "\r", text); + + conn_unget_chatname(me, from.nick); + + break; + } + default: + eventlog(eventlog_level_warn, __FUNCTION__, "unknown message type"); + return -1; + } + + if (!msg.empty()) + { + if (msg.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", msg.length(), MAX_IRC_MESSAGE_LEN); + } + else + { + packet_append_string(packet, msg.c_str()); + return 0; + } + } - if (me) - conn_unget_chatname(me, from.nick); - } - break; - case message_wol_start_game: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "STARTG", NULL, text); - conn_unget_chatname(me, from.nick); - break; - case message_wol_advertr: - msg = irc_message_preformat(NULL, "ADVERTR", "\r", text); - break; - case message_wol_chanchk: - msg = irc_message_preformat(NULL, "CHANCHK", "\r", text); - break; - case message_wol_userip: - from.nick = conn_get_chatname(me); - from.user = ctag; - from.host = addr_num_to_ip_str(conn_get_addr(me)); - msg = irc_message_preformat(&from, "USERIP", "\r", text); - conn_unget_chatname(me, from.nick); - break; - default: - eventlog(eventlog_level_warn, __FUNCTION__, "{} not yet implemented", type); return -1; } - - if (msg) { - packet_append_string(packet, msg); - xfree(msg); - return 0; + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(me), e.what()); + return -1; } - return -1; } - int irc_send_rpl_namreply_internal(t_connection * c, t_channel const * channel){ - char temp[MAX_IRC_MESSAGE_LEN]; - char const * ircname; - int first = 1; - t_connection * m; - - if (!channel) { + int irc_send_rpl_namreply_internal(t_connection* c, t_channel const* channel) + { + if (!channel) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel"); return -1; } - std::memset(temp, 0, sizeof(temp)); - ircname = irc_convert_channel(channel, c); - if (!ircname) { + const char* channel_irc_name = irc_convert_channel(channel, c); + if (!channel_irc_name) + { eventlog(eventlog_level_error, __FUNCTION__, "channel has NULL ircname"); return -1; } - /* '@' = secret; '*' = private; '=' = public */ - if ((1 + 1 + std::strlen(ircname) + 2 + 1) <= MAX_IRC_MESSAGE_LEN) { - std::sprintf(temp, "%c %s :", ((channel_get_permanent(channel)) ? ('=') : ('*')), ircname); - } - else { - eventlog(eventlog_level_warn, __FUNCTION__, "maximum message length exceeded"); - return -1; - } + + // '@' = secret; '*' = private; '=' = public + const std::string beginning_params = fmt::format("{} {} :", channel_get_permanent(channel) ? '=' : '*', channel_irc_name); + std::string params = beginning_params; + const std::size_t max_params_length = irc_send_get_max_params_length(c); + params.reserve(max_params_length); if (!(channel_get_flags(channel) & channel_flags_thevoid)) - for (m = channel_get_first(channel); m; m = channel_get_next()) { - char const * name = conn_get_chatname(m); - char flg[5] = ""; - unsigned int flags; + { + bool is_first_user = true; // for determining whether to prepend leading whitespace to current user - if (!name) - continue; - flags = conn_get_flags(m); - if (flags & MF_BLIZZARD) - std::strcat(flg, "@"); - else if ((flags & MF_BNET) || (flags & MF_GAVEL)) - std::strcat(flg, "%"); - else if (flags & MF_VOICE) - std::strcat(flg, "+"); - if ((std::strlen(temp) + ((!first) ? (1) : (0)) + std::strlen(flg) + std::strlen(name) + 1) <= sizeof(temp)) { - if (!first) std::strcat(temp, " "); - if (conn_get_wol(c) == 1) { - char _temp[MAX_IRC_MESSAGE_LEN]; - t_game * game = conn_get_game(m); - if ((first) && ((std::strcmp(ircname, "#Lob_38_0") == 0) || - (std::strcmp(ircname, "#Lob_39_0") == 0) || - (std::strcmp(ircname, "#Lob_40_0") == 0))) { + // manually add 'matchbot' to the beginning of the user list if this message is being sent to a WOL client and + // the channel is #Lob_38_0, #Lob_39_0, or #Lob_40_0. + if (conn_get_wol(c) == 1 && ((std::strcmp(channel_irc_name, "#Lob_38_0") == 0) || + (std::strcmp(channel_irc_name, "#Lob_39_0") == 0) || + (std::strcmp(channel_irc_name, "#Lob_40_0") == 0))) + { + // FIXME: shouldn't we check if the t_connection pointed to by 'c' is a WOLv2 client or not + params += "@matchbot,0,0"; + is_first_user = false; + } - std::sprintf(_temp, "@matchbot,0,0 "); - std::strcat(temp, _temp); - first = 0; + for (t_connection* m = channel_get_first(channel); m; m = channel_get_next()) + { + char const* name = conn_get_chatname(m); + if (!name) + { + continue; + } + char flg[2] = {}; + unsigned int flags = conn_get_flags(m); + if (flags & MF_BLIZZARD) + { + flg[0] = '@'; + } + else if ((flags & MF_BNET) || (flags & MF_GAVEL)) + { + flg[0] = '%'; + } + else if (flags & MF_VOICE) + { + flg[0] = '+'; + } + + // specially handle 'flg' for all WOL clients + if (conn_get_wol(c) == 1) + { + t_game* game = conn_get_game(m); + if ((game) && (game_get_channel(game) == channel) && (game_get_owner(game) == m)) + { + /* PELISH: Only game owners will have OP flag (this prevent official OP to be normal player) */ + flg[0] = '@'; } - - if ((game) && (game_get_channel(game) == channel)) { - if (game_get_owner(game) == m) { - /* PELISH: Only game owners will have OP flag (this prevent official OP to be normal player) */ - std::strcat(temp, "@"); + else + { + if ((flags & MF_BNET) || (flags & MF_GAVEL)) + { + /* PELISH: WOL does not understand to '%' char so we using '@' for TempOP too */ + flg[0] = '@'; } } - else { - if ((flags & MF_BNET) || (flags & MF_GAVEL)) - std::sprintf(flg, "@"); /* PELISH: WOL does not understand to '%' char so we using '@' for TempOP too */ - std::strcat(temp, flg); - } - if (tag_check_wolv2(conn_get_clienttag(c))) { - /* BATTLECLAN Support */ - std::memset(_temp, 0, sizeof(_temp)); - t_clan * clan = account_get_clan(conn_get_account(m)); - unsigned int clanid = 0; - - if (clan) - clanid = clan_get_clanid(clan); - - std::sprintf(_temp, "%s,%u,%u", name, clanid, conn_get_addr(m)); - std::strcat(temp, _temp); - } - else { - std::strcat(temp, name); - } - } - else { - std::strcat(temp, flg); - std::strcat(temp, name); } - first = 0; + if (tag_check_wolv2(conn_get_clienttag(c))) + { + // send IRC user list in custom format for WOLv2 clients + + /* BATTLECLAN Support */ + t_clan* clan = account_get_clan(conn_get_account(m)); + unsigned int clanid = 0; + + if (clan) + { + clanid = clan_get_clanid(clan); + } + + if ((params.length() + fmt::formatted_size("{}{}{},{},{}", is_first_user ? "" : " ", flg, name, clanid, conn_get_addr(m))) > max_params_length) + { + // can't append next user to user list, send current user list out + irc_send(c, RPL_NAMREPLY, params.c_str()); + params = beginning_params; + is_first_user = true; + } + + params += fmt::format("{}{}{},{},{}", is_first_user ? "" : " ", flg, name, clanid, conn_get_addr(m)); + } + else + { + // send IRC user list in standard format for non-WOLv2 clients + + if ((params.length() + fmt::formatted_size("{}{}{}", is_first_user ? "" : " ", flg, name)) > max_params_length) + { + // can't append next user to user list, send current user list out + irc_send(c, RPL_NAMREPLY, params.c_str()); + params = beginning_params; + is_first_user = true; + } + + params += fmt::format("{}{}{}", is_first_user ? "" : " ", flg, name); + } + + conn_unget_chatname(m, name); } - conn_unget_chatname(m, name); } - irc_send(c, RPL_NAMREPLY, temp); + + irc_send(c, RPL_NAMREPLY, params.c_str()); return 0; } - extern int irc_send_rpl_namreply(t_connection * c, t_channel const * channel) + extern int irc_send_rpl_namreply(t_connection* c, t_channel const* channel) { - char temp[MAX_IRC_MESSAGE_LEN]; - char const * ircname; - - if (!c) { + if (!c) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - if (channel) { - ircname = irc_convert_channel(channel, c); - if (!ircname) { + std::string temp; + if (channel) + { + const char* ircname = irc_convert_channel(channel, c); + if (!ircname) + { eventlog(eventlog_level_error, __FUNCTION__, "channel has NULL ircname"); return -1; } + irc_send_rpl_namreply_internal(c, channel); - std::sprintf(temp, "%.32s :End of NAMES list", ircname); + + temp = fmt::format("{} :End of NAMES list", ircname); } - else { - t_elem const * curr; + else + { + t_elem const* curr; LIST_TRAVERSE_CONST(channellist(), curr) { - channel = (t_channel*)elem_get_data(curr); + t_channel* curr_channel = static_cast<t_channel*>(elem_get_data(curr)); irc_send_rpl_namreply_internal(c, channel); } - std::sprintf(temp, "* :End of NAMES list"); + + temp = "* :End of NAMES list"; } - irc_send(c, RPL_ENDOFNAMES, temp); + irc_send(c, RPL_ENDOFNAMES, temp.c_str()); + return 0; } - static int irc_who_connection(t_connection * dest, t_connection * c) + static int irc_who_connection(t_connection* dest, t_connection* c) { - t_account * a; - char const * tempuser; - char const * tempowner; - char const * tempname; - char const * tempip; - char const * tempflags = "@"; /* FIXME: that's dumb */ - char temp[MAX_IRC_MESSAGE_LEN]; - char const * tempchannel; - - if (!dest) { + if (!dest) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL destination"); return -1; } - if (!c) { + + if (!c) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - a = conn_get_account(c); - if (!(tempuser = clienttag_uint_to_str(conn_get_clienttag(c)))) + + try { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL clienttag (tempuser)"); - return -1; + t_account* a = conn_get_account(c); + char const* tempuser; + if (!(tempuser = clienttag_uint_to_str(conn_get_clienttag(c)))) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL clienttag (tempuser)"); + return -1; + } + + char const* tempowner; + if (!(tempowner = account_get_ll_owner(a))) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL ll_owner (tempowner)"); + return -1; + } + + char const* tempname; + if (!(tempname = conn_get_username(c))) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL username (tempname)"); + return -1; + } + + char const* tempip; + if (!(tempip = addr_num_to_ip_str(conn_get_addr(c)))) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL addr (tempip)"); + return -1; + } + + char const* tempchannel; + if (!(tempchannel = irc_convert_channel(conn_get_channel(c), c))) + { + eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel (tempchannel)"); + return -1; + } + + char const* tempflags = "@"; // FIXME: that's dumb + std::string temp = fmt::format("{} {} {} {} {} {}{} :0 {}", tempchannel, tempuser, tempip, server_get_hostname(), tempname, 'H', tempflags, tempowner); + if (temp.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", temp.length(), MAX_IRC_MESSAGE_LEN); + return -1; + } + + irc_send(dest, RPL_WHOREPLY, temp.c_str()); + + return 0; } - if (!(tempowner = account_get_ll_owner(a))) + catch (const std::exception& e) { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL ll_owner (tempowner)"); + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(c), e.what()); return -1; } - if (!(tempname = conn_get_username(c))) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL username (tempname)"); - return -1; - } - if (!(tempip = addr_num_to_ip_str(conn_get_addr(c)))) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL addr (tempip)"); - return -1; - } - if (!(tempchannel = irc_convert_channel(conn_get_channel(c), c))) - { - eventlog(eventlog_level_error, __FUNCTION__, "got NULL channel (tempchannel)"); - return -1; - } - if ((std::strlen(tempchannel) + 1 + std::strlen(tempuser) + 1 + std::strlen(tempip) + 1 + std::strlen(server_get_hostname()) + 1 + std::strlen(tempname) + 1 + 1 + std::strlen(tempflags) + 4 + std::strlen(tempowner) + 1) > MAX_IRC_MESSAGE_LEN) { - eventlog(eventlog_level_info, __FUNCTION__, "WHO reply too long - skip"); - return -1; - } - else - std::sprintf(temp, "%s %s %s %s %s %c%s :0 %s", tempchannel, tempuser, tempip, server_get_hostname(), tempname, 'H', tempflags, tempowner); - irc_send(dest, RPL_WHOREPLY, temp); - return 0; } - extern int irc_who(t_connection * c, char const * name) + extern int irc_who(t_connection* c, char const* name) { - /* FIXME: support wildcards! */ + // FIXME: support wildcards! - if (!c) { + if (!c) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - if (!name) { + + if (!name) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL name"); return -1; } - if ((name[0] == '#') || (name[0] == '&') || (name[0] == '!')) { - /* it's a channel */ - t_connection * info; - t_channel * channel; - char const * ircname; - ircname = irc_convert_ircname(name); - channel = channellist_find_channel_by_name(ircname, NULL, NULL); - if (!channel) { - char temp[MAX_IRC_MESSAGE_LEN]; + try + { + if ((name[0] == '#') || (name[0] == '&') || (name[0] == '!')) + { + // it's a channel + const char* ircname = irc_convert_ircname(name); + t_channel* channel = channellist_find_channel_by_name(ircname, nullptr, nullptr); - if ((std::strlen(":No such channel") + 1 + std::strlen(name) + 1) <= MAX_IRC_MESSAGE_LEN) { - std::sprintf(temp, ":No such channel %s", name); - irc_send(c, ERR_NOSUCHCHANNEL, temp); + if (!channel) + { + std::string temp = fmt::format(":No such channel {}", name); + if (temp.length() > MAX_IRC_MESSAGE_LEN) + { + eventlog(eventlog_level_error, __FUNCTION__, "irc message length too long (got {} bytes, max {} bytes)", temp.length(), MAX_IRC_MESSAGE_LEN); + irc_send(c, ERR_NOSUCHCHANNEL, ":No such channel"); + } + else + { + irc_send(c, ERR_NOSUCHCHANNEL, temp.c_str()); + } + + return 0; } - else { - irc_send(c, ERR_NOSUCHCHANNEL, ":No such channel"); - } - return 0; - } - for (info = channel_get_first(channel); info; info = channel_get_next()) { - irc_who_connection(c, info); - } - } - else { - /* it's just one user */ - t_connection * info; - if ((info = connlist_find_connection_by_accountname(name))) - return irc_who_connection(c, info); + for (t_connection* info = channel_get_first(channel); info; info = channel_get_next()) + { + irc_who_connection(c, info); + } + } + else + { + // it's just one user + t_connection* info; + if ((info = connlist_find_connection_by_accountname(name))) + { + return irc_who_connection(c, info); + } + } + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(c), e.what()); + return -1; } - return 0; } - extern int irc_send_motd(t_connection * conn) + extern int irc_send_motd(t_connection* conn) { - char const * tempname; - char const * filename; - std::FILE *fp; - char * line, *formatted_line; - char send_line[MAX_IRC_MESSAGE_LEN]; - char motd_failed = 0; - bool first = true; - - if (!conn) { + if (!conn) + { eventlog(eventlog_level_error, __FUNCTION__, "got NULL connection"); return -1; } - tempname = conn_get_loggeduser(conn); + std::FILE* fp = nullptr; + char* formatted_line = nullptr; + try + { + bool send_failed_motd = false; - if ((filename = prefs_get_motdfile())) { - if ((fp = std::fopen(filename, "r"))) { - while ((line = file_get_line(fp))) { - if ((formatted_line = message_format_line(conn, line))) { - formatted_line[0] = ' '; - std::sprintf(send_line, ":-%s", formatted_line); - if (first) { - irc_send(conn, RPL_MOTDSTART, send_line); - first = false; + const char* filename; + if ((filename = prefs_get_motdfile())) + { + fp = std::fopen(filename, "r"); + if (fp) + { + bool first = true; + const char* line; + while ((line = file_get_line(fp))) + { + if ((formatted_line = message_format_line(conn, line))) + { + formatted_line[0] = ' '; + std::string send_line = fmt::format(":-{}", formatted_line); + + if (first) + { + irc_send(conn, RPL_MOTDSTART, send_line.c_str()); + first = false; + } + else + { + irc_send(conn, RPL_MOTD, send_line.c_str()); + } + + xfree(formatted_line); + formatted_line = nullptr; } - else - irc_send(conn, RPL_MOTD, send_line); - xfree(formatted_line); } - } - file_get_line(NULL); // clear file_get_line buffer + file_get_line(nullptr); // clear file_get_line buffer + std::fclose(fp); + fp = nullptr; + } + else + { + send_failed_motd = true; + } + } + else + { + send_failed_motd = true; + } + + if (true) + { + irc_send(conn, RPL_MOTDSTART, ":- Failed to load motd, sending default motd "); + irc_send(conn, RPL_MOTD, ":- ====================================================== "); + irc_send(conn, RPL_MOTD, ":- http://www.pvpgn.pro "); + irc_send(conn, RPL_MOTD, ":- ====================================================== "); + } + + irc_send(conn, RPL_ENDOFMOTD, ":End of /MOTD command"); + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(conn), e.what()); + + if (fp) + { std::fclose(fp); } - else - motd_failed = 1; - } - else - motd_failed = 1; - if (motd_failed) { - irc_send(conn, RPL_MOTDSTART, ":- Failed to load motd, sending default motd "); - irc_send(conn, RPL_MOTD, ":- ====================================================== "); - irc_send(conn, RPL_MOTD, ":- http://www.pvpgn.pro "); - irc_send(conn, RPL_MOTD, ":- ====================================================== "); + if (formatted_line) + { + xfree(formatted_line); + } + + file_get_line(nullptr); // clear file_get_line buffer + + return -1; } - irc_send(conn, RPL_ENDOFMOTD, ":End of /MOTD command"); - return 0; } - int irc_welcome(t_connection * conn){ - if (conn_get_wol(conn)) - handle_wol_welcome(conn); - else - handle_irc_welcome(conn); - - return 0; - } - - extern int _handle_nick_command(t_connection * conn, int numparams, char ** params, char * text) + int irc_welcome(t_connection* conn) + { + if (conn_get_wol(conn)) + { + return handle_wol_welcome(conn); + } + else + { + return handle_irc_welcome(conn); + } + } + + extern int _handle_nick_command(t_connection* conn, int numparams, char** params, char* text) { - char temp[MAX_IRC_MESSAGE_LEN]; /* FIXME: more strict param checking */ - if ((conn_get_loggeduser(conn)) && - (conn_get_state(conn) != conn_state_bot_password && - conn_get_state(conn) != conn_state_bot_username)) { - irc_send(conn, ERR_RESTRICTED, ":You can't change your nick after login"); - } - else { - if ((params) && (params[0])) { - if (conn_get_loggeduser(conn)){ - std::snprintf(temp, sizeof(temp), ":%s", params[0]); - message_send_text(conn, message_type_nick, conn, temp); - } - conn_set_loggeduser(conn, params[0]); - } - else if (text) { - if (conn_get_loggeduser(conn)){ - std::snprintf(temp, sizeof(temp), ":%s", text); - message_send_text(conn, message_type_nick, conn, temp); - } - conn_set_loggeduser(conn, text); + try + { + if ((conn_get_loggeduser(conn)) && + (conn_get_state(conn) != conn_state_bot_password && + conn_get_state(conn) != conn_state_bot_username)) + { + irc_send(conn, ERR_RESTRICTED, ":You can't change your nick after login"); } else - irc_send(conn, ERR_NEEDMOREPARAMS, "NICK :Not enough parameters"); + { + if ((params) && (params[0])) + { + // truncate to (MAX_USERNAME_LEN - 1) + std::string nickname = fmt::format("{:." + std::to_string(MAX_USERNAME_LEN - 1) + "}", params[0]); - if ((conn_get_user(conn)) && (conn_get_loggeduser(conn))) - irc_welcome(conn); /* only send the welcome if we have USER and NICK */ + if (conn_get_loggeduser(conn)) + { + std::string temp = fmt::format(":{}", nickname); + message_send_text(conn, message_type_nick, conn, temp); + } + + conn_set_loggeduser(conn, nickname.c_str()); + } + else if (text) + { + // truncate to (MAX_USERNAME_LEN - 1) + std::string nickname = fmt::format("{:." + std::to_string(MAX_USERNAME_LEN - 1) + "}", text); + + if (conn_get_loggeduser(conn)) + { + std::string temp = fmt::format(":{}", nickname); + message_send_text(conn, message_type_nick, conn, temp); + } + + conn_set_loggeduser(conn, nickname.c_str()); + } + else + { + irc_send(conn, ERR_NEEDMOREPARAMS, "NICK :Not enough parameters"); + } + + if ((conn_get_user(conn)) && (conn_get_loggeduser(conn))) + { + irc_welcome(conn); // only send the welcome if we have USER and NICK + } + } + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(conn), e.what()); + + return -1; } - return 0; } - extern int _handle_ping_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_ping_command(t_connection* conn, int numparams, char** params, char* text) { /* Dizzy: just ignore this because RFC says we should not reply client PINGs * NOTE: RFC2812 doesn't seem to be very expressive about this ... */ if (numparams) + { irc_send_pong(conn, params[0]); + } else + { irc_send_pong(conn, text); + } + return 0; } - extern int _handle_pong_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_pong_command(t_connection* conn, int numparams, char** params, char* text) { /* NOTE: RFC2812 doesn't seem to be very expressive about this ... */ if (conn_get_ircping(conn) == 0) { @@ -1304,7 +1638,7 @@ namespace pvpgn } else { unsigned int val = 0; - char * sname; + char* sname; if (numparams >= 1) { val = std::strtoul(params[0], NULL, 10); @@ -1333,89 +1667,110 @@ namespace pvpgn return 0; } - extern int _handle_join_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_join_command(t_connection* conn, int numparams, char** params, char* text) { - if (numparams >= 1) { - char ** e; + if (numparams >= 1) + { + char** e; /* According to RFC2812 - if channelname is "0" we need to PART all channels */ - if (std::strcmp(params[0], "0") == 0) { + if (std::strcmp(params[0], "0") == 0) + { conn_part_channel(conn); return 0; } e = irc_get_listelems(params[0]); - if ((e) && (e[0])) { + if ((e) && (e[0])) + { char temp[MAX_IRC_MESSAGE_LEN]; - char const * ircname = irc_convert_ircname(e[0]); - t_channel * old_channel = conn_get_channel(conn); - t_channel * channel; - t_account * acc = conn_get_account(conn); + char const* ircname = irc_convert_ircname(e[0]); + t_channel* old_channel = conn_get_channel(conn); + t_channel* channel; + t_account* acc = conn_get_account(conn); - if ((ircname) && (channel = channellist_find_channel_by_name(ircname, NULL, NULL))) { - if (channel_check_banning(channel, conn)) { + if ((ircname) && (channel = channellist_find_channel_by_name(ircname, NULL, NULL))) + { + if (channel_check_banning(channel, conn)) + { std::snprintf(temp, sizeof(temp), "%s :You are banned from that channel.", e[0]); irc_send(conn, ERR_BANNEDFROMCHAN, temp); if (e) + { irc_unget_listelems(e); + } + return 0; } if ((account_get_auth_admin(acc, NULL) != 1) && (account_get_auth_admin(acc, ircname) != 1) && (account_get_auth_operator(acc, NULL) != 1) && (account_get_auth_operator(acc, ircname) != 1) && - (channel_get_max(channel) != -1) && (channel_get_curr(channel) >= channel_get_max(channel))) { + (channel_get_max(channel) != -1) && (channel_get_curr(channel) >= channel_get_max(channel))) + { std::snprintf(temp, sizeof(temp), "%s :The channel is currently full.", e[0]); irc_send(conn, ERR_CHANNELISFULL, temp); if (e) + { irc_unget_listelems(e); + } return 0; } } - if ((!(ircname)) || (conn_set_channel(conn, ircname) < 0)) { + if ((!(ircname)) || (conn_set_channel(conn, ircname) < 0)) + { std::snprintf(temp, sizeof(temp), "%s :JOIN failed", e[0]); irc_send(conn, ERR_NOSUCHCHANNEL, temp); /* Anything is still bad */ } } + if (e) + { irc_unget_listelems(e); + } } else + { irc_send(conn, ERR_NEEDMOREPARAMS, "JOIN :Not enough parameters"); + } + return 0; } - extern int irc_send_topic(t_connection * c, t_channel const * channel) + extern int irc_send_topic(t_connection* c, t_channel const* channel) { class_topic Topic; std::string topicstr = Topic.get(channel_get_name(channel)); char temp[MAX_IRC_MESSAGE_LEN]; - if (topicstr.empty() == false) { + if (topicstr.empty() == false) + { std::snprintf(temp, sizeof(temp), "%s :%s", irc_convert_channel(channel, c), topicstr.c_str()); irc_send(c, RPL_TOPIC, temp); } - else { + else + { std::snprintf(temp, sizeof(temp), "%s :", irc_convert_channel(channel, c)); irc_send(c, RPL_TOPIC, temp); /* PELISH: This is according to RFC2812 but brakes WOLv1/WOLv2 */ //snprintf(temp, sizeof(temp), "%s :No topic is set", irc_convert_channel(channel,c)); //irc_send(c, RPL_NOTOPIC, temp); } + return 0; } - extern int _handle_topic_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_topic_command(t_connection* conn, int numparams, char** params, char* text) { - char ** e = NULL; + char** e = NULL; char temp[MAX_IRC_MESSAGE_LEN]; class_topic Topic; if (params && params[0]) { if (conn_get_wol(conn) == 1) { - t_channel * channel = conn_get_channel(conn); + t_channel* channel = conn_get_channel(conn); if (channel) Topic.set(std::string(channel_get_name(channel)), std::string(text), false); else { @@ -1425,12 +1780,12 @@ namespace pvpgn } e = irc_get_listelems(params[0]); } - if ((e) && (e[0])) + if ((e) && (e[0])) { - t_channel *channel = conn_get_channel(conn); + t_channel* channel = conn_get_channel(conn); if (channel) { - char const * ircname = irc_convert_ircname(e[0]); + char const* ircname = irc_convert_ircname(e[0]); if ((ircname) && (strcasecmp(channel_get_name(channel), ircname) == 0)) { irc_send_topic(conn, channel); @@ -1456,10 +1811,8 @@ namespace pvpgn return 0; } - extern int _handle_kick_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_kick_command(t_connection* conn, int numparams, char** params, char* text) { - char temp[MAX_IRC_MESSAGE_LEN]; - char ** e; /** * Heres the imput expected * KICK [channel] [kicked_user],[kicked_user2] @@ -1470,57 +1823,85 @@ namespace pvpgn * WOL in [text] sends Admin name */ - if ((numparams != 2) || !(params[1])) { + if ((numparams != 2) || !(params[1])) + { irc_send(conn, ERR_NEEDMOREPARAMS, "KICK :Not enough parameters"); return 0; } - if (e = irc_get_listelems(params[1])) + try { - /* Make standart PvPGN KICK from RFC2812 KICK */ - if (text) - std::snprintf(temp, sizeof(temp), "/kick %s %s", e[0], text); - else - std::snprintf(temp, sizeof(temp), "/kick %s", e[0]); + char** e; + if (e = irc_get_listelems(params[1])) + { + std::string temp; - handle_command(conn, temp); + /* Make standard PvPGN KICK from RFC2812 KICK */ + if (text) + { + temp = fmt::format("/kick {} {}", e[0], text); + } + else + { + temp = fmt::format("/kick {}", e[0]); + } - irc_unget_listelems(e); + handle_command(conn, temp.c_str()); + + irc_unget_listelems(e); + } + + return 0; + } + catch (const std::exception& e) + { + eventlog(eventlog_level_error, __FUNCTION__, "[{}] failed to send IRC message ({})", conn_get_socket(conn), e.what()); + + return -1; } - return 0; } - static int irc_send_banlist(t_connection * conn, t_channel * channel) + static int irc_send_banlist(t_connection* conn, t_channel* channel) { - t_elem const * curr; - char const * banned; - char const * ircname = server_get_hostname(); - char temp[MAX_IRC_MESSAGE_LEN]; - - if (!conn) { + if (!conn) + { ERROR0("got NULL conn"); return -1; } - if (!channel) { + if (!channel) + { ERROR0("got NULL channel"); return -1; } - LIST_TRAVERSE_CONST(channel_get_banlist(channel), curr) { - banned = (char*)elem_get_data(curr); + const char* server_hostname = server_get_hostname(); + const char* irc_channelname = irc_convert_channel(channel, conn); - //FIXME: right now we lie about who have gives ban and also about bantime - std::snprintf(temp, sizeof(temp), "%s %s!*@* %s 1208297879", irc_convert_channel(channel, conn), banned, ircname); - irc_send(conn, RPL_BANLIST, temp); + const t_elem* curr; + LIST_TRAVERSE_CONST(channel_get_banlist(channel), curr) + { + try + { + const char* banned_username = static_cast<char*>(elem_get_data(curr)); + + //FIXME: right now we lie about who have gives ban and also about bantime + std::string temp = fmt::format("{} {}!*@* {} 1208297879", irc_channelname, banned_username, server_hostname); + irc_send(conn, RPL_BANLIST, temp.c_str()); + } + catch (...) + { + continue; + } } + return 0; } - extern int _handle_mode_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_mode_command(t_connection* conn, int numparams, char** params, char* text) { char temp[MAX_IRC_MESSAGE_LEN]; - t_account * acc = conn_get_account(conn); + t_account* acc = conn_get_account(conn); std::memset(temp, 0, sizeof(temp)); @@ -1531,8 +1912,8 @@ namespace pvpgn if (params[0][0] == '#') { /* Channel mode */ - t_channel * channel; - char const * ircname = irc_convert_ircname(params[0]); + t_channel* channel; + char const* ircname = irc_convert_ircname(params[0]); /* FIXME: Supports more than one channel in MODE command */ if (!(channel = conn_get_channel(conn))) { @@ -1632,11 +2013,11 @@ namespace pvpgn return 0; } - extern int _handle_time_command(t_connection * conn, int numparams, char ** params, char * text) + extern int _handle_time_command(t_connection* conn, int numparams, char** params, char* text) { char temp[MAX_IRC_MESSAGE_LEN] = {}; // PELISH: According to RFC2812 std::time_t now; - char const * ircname = server_get_hostname(); + char const* ircname = server_get_hostname(); if ((numparams >= 1) && (params[0])) { @@ -1646,17 +2027,20 @@ namespace pvpgn std::snprintf(temp, sizeof(temp), "%s :%" PRId64, ircname, static_cast<std::int64_t>(now)); irc_send(conn, RPL_TIME, temp); } - else { + else + { std::snprintf(temp, sizeof(temp), "%s :No such server", params[0]); irc_send(conn, ERR_NOSUCHSERVER, temp); } } - else { + else + { /* RPL_TIME contains time and name of this server */ now = std::time(NULL); std::snprintf(temp, sizeof(temp), "%s :%" PRId64, ircname, static_cast<std::int64_t>(now)); irc_send(conn, RPL_TIME, temp); } + return 0; } diff --git a/src/bnetd/irc.h b/src/bnetd/irc.h index b0168cd..12784a0 100644 --- a/src/bnetd/irc.h +++ b/src/bnetd/irc.h @@ -34,8 +34,7 @@ namespace pvpgn namespace bnetd { - extern int irc_send_cmd(t_connection * conn, char const * command, char const * params); - extern int irc_send(t_connection * conn, int code, char const * params); + extern int irc_send(t_connection * conn, int numeric, char const * params); extern int irc_send_ping(t_connection * conn); extern int irc_send_pong(t_connection * conn, char const * params); extern int irc_authenticate(t_connection * conn, char const * passhash);