// Client network command handlers for HaxServ // // Written by: Test_User // // This is free and unencumbered software released into the public // domain. // // Anyone is free to copy, modify, publish, use, compile, sell, or // distribute this software, either in source code form or as a compiled // binary, for any purpose, commercial or non-commercial, and by any // means. // // In jurisdictions that recognize copyright laws, the author or authors // of this software dedicate any and all copyright interest in the // software to the public domain. We make this dedication for the benefit // of the public at large and to the detriment of our heirs and // successors. We intend this dedication to be an overt act of // relinquishment in perpetuity of all present and future rights to this // software under copyright law. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. // IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR // OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, // ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR // OTHER DEALINGS IN THE SOFTWARE. #include #include #include #include #include #include #include #include #include #include "commands.h" #include "network.h" #include "config.h" #include "types.h" #include "table.h" #include "tls.h" struct table client_network_commands = {0}; struct string client_nick = {0}; uint8_t client_connected; int add_local_client(struct string uid, struct string nick_arg, struct string vhost_arg, struct string ident_arg, struct string realname_arg, time_t timestamp, char fake_cert) { if (has_table_index(user_list, uid)) return 1; struct user_info *user = malloc(sizeof(*user)); if (!user) goto add_local_client_fail_user; struct string server = { .data = malloc(3), .len = 3, }; if (!server.data) goto add_local_client_fail_server; memcpy(server.data, "1HC", 3); struct string hostname = { .data = malloc(vhost_arg.len), .len = vhost_arg.len, }; if (!hostname.data) goto add_local_client_fail_hostname; memcpy(hostname.data, vhost_arg.data, hostname.len); struct string vhost = { .data = malloc(vhost_arg.len), .len = vhost_arg.len, }; if (!vhost.data) goto add_local_client_fail_vhost; memcpy(vhost.data, vhost_arg.data, vhost.len); struct string ident = { .data = malloc(ident_arg.len), .len = ident_arg.len, }; if (!ident.data) goto add_local_client_fail_ident; memcpy(ident.data, ident_arg.data, ident.len); struct string ip = { .data = malloc(9), .len = 9, }; if (!ip.data) goto add_local_client_fail_ip; memcpy(ip.data, "/dev/null", 9); struct string realname = { .data = malloc(realname_arg.len), .len = realname_arg.len, }; if (!realname.data) goto add_local_client_fail_realname; memcpy(realname.data, realname_arg.data, realname.len); struct string nick = { .data = malloc(nick_arg.len), .len = nick_arg.len, }; if (!nick.data) goto add_local_client_fail_nick; memcpy(nick.data, nick_arg.data, nick.len); *user = (struct user_info){ .nick_ts = (uint64_t)timestamp, .user_ts = (uint64_t)timestamp, .server = server, .nick = nick, .hostname = hostname, .vhost = vhost, .ident = ident, .ip = ip, .realname = realname, .opertype = {.data = malloc(0), .len = 0}, .metadata = {.array = malloc(0), .len = 0}, }; set_table_index(&user_list, uid, user); SEND(STRING("GLOADMODULE m_servprotect\n")); // required for the +k we're about to use char string_time[21]; snprintf(string_time, 21, "%ld", timestamp); SEND(STRING("UID ")); SEND(uid); SEND(STRING(" ")); SEND(NULSTR(string_time)); SEND(STRING(" ")); SEND(nick); SEND(STRING(" ")); SEND(vhost); SEND(STRING(" ")); SEND(vhost); SEND(STRING(" ")); SEND(ident); SEND(STRING(" ")); SEND(ip); SEND(STRING(" ")); SEND(NULSTR(string_time)); SEND(STRING(" +k :")); SEND(realname); SEND(STRING("\n")); if (fake_cert) { SEND(STRING(":1HC METADATA ")); SEND(uid); SEND(STRING(" ssl_cert :vTrse ")); SEND(client_cert); SEND(STRING("\n")); } if (!STRING_EQ(uid, STRING("1HC000000"))) { // Don't oper haxserv, because echo is unprivileged SEND(STRING(":")); SEND(uid); SEND(STRING(" OPERTYPE ")); SEND(opertype); SEND(STRING("\n")); } return 0; add_local_client_fail_nick: free(realname.data); add_local_client_fail_realname: free(ip.data); add_local_client_fail_ip: free(ident.data); add_local_client_fail_ident: free(vhost.data); add_local_client_fail_vhost: free(hostname.data); add_local_client_fail_hostname: free(server.data); add_local_client_fail_server: free(user); add_local_client_fail_user: WRITES(2, STRING("OOM! (add_local_client)\n")); return 1; } int client_nick_handler(uint64_t argc, struct string *argv) { if (argc < 1) return 1; void *tmp = malloc(argv[0].len); if (!tmp) return 1; void *name_for_tables; if (client_connected) { name_for_tables = malloc(argv[0].len); if (!name_for_tables) { free(tmp); return 1; } memcpy(name_for_tables, argv[0].data, argv[0].len); } memcpy(tmp, argv[0].data, argv[0].len); if (client_connected) { SENDCLIENT(STRING(":")); SENDCLIENT(client_nick); SENDCLIENT(STRING("!e@e NICK :")); SENDCLIENT(argv[0]); SENDCLIENT(STRING("\r\n")); } free(client_nick.data); client_nick.data = tmp; client_nick.len = argv[0].len; if (client_connected) { struct user_info *client = get_table_index(user_list, STRING("1HC000001")); if (client) { free(client->nick.data); #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wmaybe-uninitialized" client->nick.data = name_for_tables; // Will not be used uninitialized, ignore the compiler's complaint here #pragma GCC diagnostic pop // Compiler sees global variable and assumes it may be changed in one of the functions called between checks client->nick.len = argv[0].len; } else { free(name_for_tables); WRITES(2, STRING("Client connected but client data missing!\n")); return 1; } SEND(STRING(":1HC000001 NICK ")); SEND(client_nick); SEND(STRING(" ")); char current_time[22]; snprintf(current_time, 22, "%ld", time(NULL)); SEND(NULSTR(current_time)); SEND(STRING("\n")); } return 0; } int client_user_handler(uint64_t argc, struct string *argv) { if (argc < 4) return 1; if (client_nick.len == 0) return 1; if (add_local_client(STRING("1HC000001"), client_nick, client_hostmask, argv[0], argv[3], time(NULL), 1) != 0) return 1; SENDCLIENT(STRING(":")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 001 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" :Welcome to the Rexnet IRC Network\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 002 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" :Your host is ")); SENDCLIENT(server_name); SENDCLIENT(STRING(", running a totally not sus IRCd\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 003 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" :This server was created 02:51:36 Apr 03 2023\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 004 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" ")); SENDCLIENT(server_name); SENDCLIENT(STRING(" InspIRCd-3 BDGHILNORSTWcdghikorswxz ABCDEFGHIJKLMNOPQRSTXYZabcdefghijklmnopqrstuvwz :BEFHIJLXYZabdefghjkloqvw\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 005 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" ACCEPT=100 AWAYLEN=200 BOT=B CALLERID=g CASEMAPPING=ascii CHANLIMIT=#:20 CHANMODES=IXZbegw,k,BEFHJLdfjl,ACDGKMNOPQRSTcimnprstuz CHANNELLEN=60 CHANTYPES=# ELIST=CMNTU ESILENCE=CcdiNnPpTtx EXCEPTS=e :are supported by this server\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 005 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" EXTBAN=,ACNOQRSTUacjmnprswz HOSTLEN=64 INVEX=I KEYLEN=32 KICKLEN=300 LINELEN=512 MAXLIST=I:1000,X:1000,b:1000,e:1000,g:1000,w:1000 MAXTARGETS=20 MODES=20 MONITOR=30 NAMELEN=130 NAMESX NETWORK=LibreIRC :are supported by this server\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 005 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" NICKLEN=30 OVERRIDE=O PREFIX=(Yqaohv)!~&@%+ REMOVE SAFELIST SECURELIST=60 SILENCE=100 STATUSMSG=!~&@%+ TOPICLEN=330 UHNAMES USERIP USERLEN=10 USERMODES=,,s,BDGHILNORSTWcdghikorwxz :are supported by this server\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 005 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" WATCH=32 WHOX :are supported by this server\r\n")); client_connected = 1; return 0; } int client_join_handler(uint64_t argc, struct string *argv) { if (argc < 1) return 1; time_t ctime = time(NULL); struct string channels = argv[0]; while (1) { char current_time_nulstr[22]; uint64_t current_time; if (ctime < 0) { WRITES(2, STRING("Please check your clock.\n")); return 1; } current_time = (uint64_t)ctime; uint64_t offset = 0; while (offset < channels.len && channels.data[offset] != ',') offset++; uint64_t oldlen = channels.len; channels.len = offset; struct channel_info *channel_info = get_table_index(channel_list, channels); if (!channel_info) { channel_info = malloc(sizeof(*channel_info)); if (!channel_info) { WRITES(2, STRING("OOM! (client_join)\n")); return 1; } *channel_info = (struct channel_info){ .ts = current_time, .topic = {.data = malloc(0), .len = 0}, .topic_ts = 0, .modes = {.array = malloc(0), .len = 0}, .user_list = {.array = malloc(0), .len = 0}, .metadata = {.array = malloc(0), .len = 0}, }; set_table_index(&channel_list, channels, channel_info); } if (channel_info->ts < current_time) current_time = channel_info->ts; snprintf(current_time_nulstr, 22, "%lu", current_time); SENDCLIENT(STRING(":")); SENDCLIENT(client_nick); SENDCLIENT(STRING("!e@e JOIN :")); SENDCLIENT(channels); for (uint64_t i = 0; i < channel_info->user_list.len; i++) { struct string user; struct user_info *info = channel_info->user_list.array[i].ptr; if (info) user = info->nick; else user = user_list.array[i].name; if (i%5 != 0) { SENDCLIENT(STRING(" ")); SENDCLIENT(user); } else { SENDCLIENT(STRING("\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 353 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" = ")); SENDCLIENT(channels); SENDCLIENT(STRING(" :")); SENDCLIENT(user); } } SENDCLIENT(STRING("\r\n:")); SENDCLIENT(server_name); SENDCLIENT(STRING(" 366 ")); SENDCLIENT(client_nick); SENDCLIENT(STRING(" ")); SENDCLIENT(channels); SENDCLIENT(STRING(" :End of /NAMES list.\r\n")); SEND(STRING(":1HC FJOIN ")); SEND(channels); SEND(STRING(" ")); SEND(NULSTR(current_time_nulstr)); SEND(STRING(" + :,1HC000001\n")); set_table_index(&(channel_info->user_list), STRING("1HC000001"), get_table_index(user_list, STRING("1HC000001"))); // TODO: Actually add local users to user_list channels.len = oldlen; if (channels.len <= offset+1) break; channels.data += offset + 1; channels.len -= offset + 1; } return 0; } int client_privmsg_handler(uint64_t argc, struct string *argv) { if (argc < 2) return 1; SEND(STRING(":1HC000001 PRIVMSG ")); SEND(argv[0]); SEND(STRING(" :")); SEND(argv[1]); SEND(STRING("\n")); uint64_t offset; if (argv[0].data[0] == '#') { if (argv[1].len < command_prefix.len || memcmp(argv[1].data, command_prefix.data, command_prefix.len) != 0) return 0; offset = command_prefix.len; } else { offset = 0; } if (offset >= argv[1].len || argv[1].data[offset] == ' ') return 0; uint64_t command_argc = 0; uint64_t old_offset = offset; while (offset < argv[1].len) { while (offset < argv[1].len && argv[1].data[offset] != ' ') offset++; command_argc++; while (offset < argv[1].len && argv[1].data[offset] == ' ') offset++; } offset = old_offset; struct string command_argv[command_argc]; // argv[0] in this case is the command itself, unlike network command handlers... might change one of these two later to match uint64_t i = 0; while (offset < argv[1].len) { command_argv[i].data = argv[1].data+offset; uint64_t start = offset; while (offset < argv[1].len && argv[1].data[offset] != ' ') offset++; command_argv[i].len = offset - start; while (offset < argv[1].len && argv[1].data[offset] == ' ') offset++; i++; } argv[1].data += old_offset; argv[1].len -= old_offset; struct command_def *cmd = get_table_index(user_commands, command_argv[0]); if (cmd) { struct string message[] = { STRING("Local user "), client_nick, STRING(" executes `"), argv[1], STRING("'\n"), }; privmsg(STRING("1HC000000"), log_channel, sizeof(message)/sizeof(*message), message); return cmd->func(STRING("1HC000001"), argv[1], argv[0], command_argc, command_argv, 1); } else { if (argv[0].data[0] == '#') { SEND(STRING(":1HC000000 NOTICE ")); SEND(argv[0]); SEND(STRING(" :Unknown command: ")); SEND(command_prefix); SEND(command_argv[0]); SEND(STRING("\n")); } return 0; } return 0; } int client_raw_handler(uint64_t argc, struct string *argv) { if (argc < 1) return 1; SEND(argv[0]); SEND(STRING("\n")); return 0; } int client_ping_handler(uint64_t argc, struct string *argv) { if (argc < 1) return 1; SENDCLIENT(STRING(":")); SENDCLIENT(server_name); SENDCLIENT(STRING(" PONG :")); SENDCLIENT(argv[0]); SENDCLIENT(STRING("\r\n")); return 0; } int client_mode_handler(uint64_t argc, struct string *argv) { if (argc < 2) return 0; // Mode querying not supported yet SEND(STRING(":1HC000001 MODE ")); for (uint64_t i = 0; i < argc - 1; i++) { SEND(argv[i]); SEND(STRING(" ")); } SEND(STRING(":")); SEND(argv[argc - 1]); SEND(STRING("\n")); return 0; } int client_fd = -1; int client_listen_fd; int initclientnetwork(void) { client_network_commands.array = malloc(0); set_table_index(&client_network_commands, STRING("NICK"), &client_nick_handler); set_table_index(&client_network_commands, STRING("USER"), &client_user_handler); set_table_index(&client_network_commands, STRING("JOIN"), &client_join_handler); set_table_index(&client_network_commands, STRING("PRIVMSG"), &client_privmsg_handler); set_table_index(&client_network_commands, STRING("RAW"), &client_raw_handler); set_table_index(&client_network_commands, STRING("PING"), &client_ping_handler); set_table_index(&client_network_commands, STRING("MODE"), &client_mode_handler); client_nick.data = malloc(0); client_listen_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (client_listen_fd < 0) return 1; int one = 1; setsockopt(client_listen_fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)); struct sockaddr_in localhost = { .sin_family = AF_INET, .sin_port = htons(6667), }; inet_pton(AF_INET, "127.0.0.1", &localhost.sin_addr); // this is indeed localhost for mine, and I have no intent to change this bind(client_listen_fd, (struct sockaddr*)&localhost, sizeof(localhost)); listen(client_listen_fd, 1); return 0; } #if LOGALL ssize_t SENDCLIENT(struct string msg) { if (msg.len == 0) return 0; static char printprefix = 1; if (printprefix) { #if COLORIZE WRITES(1, STRING("\x1b[31m[Us->Client] \x1b[32m")); #else WRITES(1, STRING("[Us->Client] ")); #endif printprefix = 0; } WRITES(1, msg); if (msg.data[msg.len - 1] == '\n') { printprefix = 1; #if COLORIZE WRITES(1, STRING("\x1b[0m\n")); #else WRITES(1, STRING("\n")); #endif } return WRITES(client_fd, msg); } #endif