/* * This file is part of uIRCd. (https://git.redxen.eu/caskd/uIRCd) * Copyright (c) 2019, 2020 Alex-David Denes * * uIRCd 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 3 of the License, or * any later version. * * uIRCd 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 uIRCd. If not, see . */ #include "main.h" #include "buffer.h" #include "configuration.h" #include "connection.h" #include "filesystem.h" #include "logging.h" #include "memory.h" #include "signal.h" #include // errno #include // O_NONBLOCK #include // printf() #include // srand() #include // strerror() #include // time_t size_t #include // time() #include // getopt() chdir() close() #define UIRC_IRCV3 #define UIRC_HELPERS #include // IRC_Message int parse_args(int argc, char** argv, Connection* conn) { int c; unsigned long chanindex = 0; while ((c = getopt(argc, argv, "V:a:p:d:t:N:U:R:P:q:c:k:" #ifdef UIRCD_FEATURE_LIBCONFIG "C:" #endif /* UIRCD_FEATURE_LIBCONFIG */ "vh")) != -1) { switch (c) { case 'V': loglevel = atoi(optarg); break; case 'a': allocate_copy(&conn->data.address, optarg); break; case 'p': allocate_copy(&conn->data.service, optarg); break; case 'd': allocate_copy(&conn->data.path, optarg); break; case 't': conn->data.timeout = (unsigned int) atoi(optarg); break; case 'N': allocate_copy(&conn->user.nickname, optarg); break; case 'U': allocate_copy(&conn->user.username, optarg); break; case 'R': allocate_copy(&conn->user.realname, optarg); break; case 'P': allocate_copy(&conn->user.password, optarg); break; case 'q': allocate_copy(&conn->data.quitmsg, optarg); break; case 'c': { resize_chanarray(&conn->info.channels); chanindex = get_channelindex(optarg, conn->info.channels); if (!set_channel(&conn->info.channels[chanindex], optarg, NULL, true)) LOG(LOG_WARN, "Couldn't set channel %s as chan #%lu.", optarg, chanindex); break; } case 'k': { if (!set_channel(&conn->info.channels[chanindex], NULL, optarg, true)) LOG(LOG_WARN, "Couldn't set key for channel #%lu.", chanindex); break; } case 'v': printf("uIRCd version " UIRCD_VERSION); return 0; case 'h': print_help(); return 0; #ifdef UIRCD_FEATURE_LIBCONFIG case 'C': parse_configfile(optarg, conn); break; #endif /* UIRCD_FEATURE_LIBCONFIG */ } } return 1; } void print_help(void) { struct help { char arg; char* type; char* desc; char* def; } arg_list[] = { { 'a', "host", "Host to connect to (IP/Hostname)", UIRCD_DEFAULT_ADDR }, { 'p', "serv", "Service (port for TCP/UDP)", UIRCD_DEFAULT_PORT }, { 'N', "str", "Nickname for registration", UIRCD_DEFAULT_NICK }, { 'U', "str", "Username for registration", UIRCD_DEFAULT_USER }, { 'R', "str", "Realname for registration", UIRCD_DEFAULT_REAL }, { 'P', "str", "Password for registration", NULL }, { 'd', "str", "Directory for logs", "current dir" }, { 'q', "str", "Quit message", UIRCD_DEFAULT_QUITMSG }, { 't', "uint", "Timeout duration", NULL }, { 'V', "int", "Log level", "3 [LOG_INFO]" }, { 'c', "str", "Channel", NULL }, { 'k', "str", "Channel key", NULL }, #ifdef UIRCD_FEATURE_LIBCONFIG { 'C', "path", "Configuration path", NULL }, #endif /* UIRCD_FEATURE_LIBCONFIG */ { 'v', NULL, "Print version information", NULL }, { 'h', NULL, "Print this help message", NULL } }; printf("usage: uircd -a [ ... ]\n"); for (size_t i = 0; i < sizeof(arg_list) / sizeof(*arg_list); i++) { printf(" -%c", arg_list[i].arg); if (arg_list[i].type != NULL) printf(" \t%s", arg_list[i].type); if (arg_list[i].desc != NULL) printf(" \t%s", arg_list[i].desc); if (arg_list[i].def != NULL) printf(" (default: %s)", arg_list[i].def); putchar('\n'); } } int main(int argc, char* argv[]) { IRC_Message buffer; Connection connection = { 0 }; time_t ctime; struct timespec sleep = { 0, 50000000L }; struct { Buffer_Info recv, send, // Network receive and send (automatic) fiforecv, fifosend, // FIFO receive and send (manual) log; // Log writing buffer (automatic) } buf; init_chanarray(&connection.info.channels); srand((unsigned int) time(NULL)); if (!set_config_defaults(&connection)) { LOG(LOG_FATAL, "Couldn't allocate memory for configuration variables. " ERRNOFMT, strerror(errno), errno); return EXIT_FAILURE; } switch (parse_args(argc, argv, &connection)) { case 0: return EXIT_SUCCESS; case -1: return EXIT_FAILURE; } if (connection.data.path != NULL) { int tmpres; // TODO: Add chroot()/jail support by default where supported if ((tmpres = chdir(connection.data.path)) != 0) { LOG(LOG_ERROR, "Couldn't change log directory to %s. " ERRNOFMT, connection.data.path, strerror(errno), errno); return EXIT_FAILURE; } LOG(LOG_VERBOSE, "Changed directory to %s.", connection.data.path); } setup_signals(); for (;;) { ctime = time(NULL); if (connection.info.state == CONN_IDLE) nanosleep(&sleep, NULL); if (!run || connection.info.state == CONN_CLOSING) { signed long temp; if ((temp = Assm_mesg(buf.send.buffer, Assm_cmd_QUIT(connection.data.quitmsg), sizeof(buf.send.buffer))) > 0) { buf.send.append_pos = (unsigned long) temp; LOG(LOG_VERBOSE, "Sending a QUIT message to " ADDRFMT " containing \"%s\".", connection.data.address, connection.data.service, connection.data.quitmsg); if (flush_buffer(buf.send.buffer, buf.send.append_pos, buf.send.fd) == -1) LOG(LOG_WARN, "Couldn't flush send buffer to " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); } close(buf.send.fd); close(buf.fiforecv.fd); connection.info.state = CONN_CLOSED; LOG(LOG_VERBOSE, "Connection to " ADDRFMT " was closed.", connection.data.address, connection.data.service); break; } else if (connection.info.state == CONN_CLOSED) { break; } else if (connection.info.state == CONN_RECONNECTING) { close(buf.send.fd); close(buf.fiforecv.fd); connection.info.state = CONN_PENDING; if (connection.info.reconinter <= 300) connection.info.reconinter += 5; continue; } else if (connection.info.state == CONN_PENDING) { if (ctime - connection.info.l_connect < connection.info.reconinter) continue; connection.info.l_connect = ctime; /* Reset all state-dependent values to empty */ memset(&buf, '\0', sizeof(buf)); buf.recv.fd = buf.send.fd = buf.fiforecv.fd = buf.fifosend.fd = -1; connection.info.l_message = 0; if ((buf.send.fd = init_connection(&connection)) > 0) { buf.recv.fd = buf.fifosend.fd = buf.send.fd; add_socket_flags(buf.send.fd, O_NONBLOCK); signed long temp; /* Registration process and CAP negotiation (TODO) if ((temp = Assm_mesg(buf.send.buffer, Assm_cmd_CAP_LS("302"), sizeof(buf.send.buffer))) > 0) { buf.send.append_pos = (size_t) temp; LOG(LOG_VERBOSE, "Sending a CAP LS to " ADDRFMT ".", connection.data.address, connection.data.service); if (flush_buffer(buf.send.buffer, buf.send.append_pos, buf.send.fd) == -1) { LOG(LOG_WARN, "Couldn't send CAP LS to " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } } */ if (connection.user.password != NULL && (temp = Assm_mesg(buf.send.buffer, Assm_cmd_PASS(connection.user.password), sizeof(buf.send.buffer))) > 0) { buf.send.append_pos = (size_t) temp; LOG(LOG_VERBOSE, "Sending PASS authentication to " ADDRFMT, connection.data.address, connection.data.service); if (flush_buffer(buf.send.buffer, buf.send.append_pos, buf.send.fd) == -1) { LOG(LOG_WARN, "Couldn't send PASS authentication on " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } } if ((temp = Assm_mesg(buf.send.buffer, Assm_cmd_NICK(connection.user.nickname), sizeof(buf.send.buffer))) > 0) { buf.send.append_pos = (size_t) temp; LOG(LOG_VERBOSE, "Sending a NICK registration to " ADDRFMT " containing \"%s\".", connection.data.address, connection.data.service, connection.user.nickname); if (flush_buffer(buf.send.buffer, buf.send.append_pos, buf.send.fd) == -1) { LOG(LOG_WARN, "Couldn't register nickname on " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } } if ((temp = Assm_mesg(buf.send.buffer, Assm_cmd_USER(connection.user.username, connection.user.realname, 0), sizeof(buf.send.buffer))) > 0) { buf.send.append_pos = (size_t) temp; LOG(LOG_VERBOSE, "Sending a USER registration to " ADDRFMT " containing \"%s\".", connection.data.address, connection.data.service, connection.user.realname); if (flush_buffer(buf.send.buffer, buf.send.append_pos, buf.send.fd) == -1) { LOG(LOG_WARN, "Couldn't register user and real name on " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } } connection.info.state = CONN_REGISTERED; } else if (buf.send.fd == INIT_SOFTFAIL) { connection.info.state = CONN_RECONNECTING; continue; } else if (buf.send.fd == INIT_HARDFAIL) { connection.info.state = CONN_CLOSED; continue; } } else if (connection.info.state == CONN_ACTIVE || connection.info.state == CONN_IDLE) { connection.info.reconinter = 0; connection.info.state = CONN_IDLE; if (connection.data.timeout > 0 && connection.info.l_message < ctime - connection.data.timeout) { LOG(LOG_WARN, "Timed out because no message was received since %lu.", connection.info.l_message); connection.info.state = CONN_RECONNECTING; continue; } if (buf.fiforecv.fd != -1) { ssize_t brd, len; /* FIFO reader */ if ((brd = read(buf.fiforecv.fd, buf.fiforecv.buffer + buf.fiforecv.append_pos, sizeof(buf.fiforecv.buffer) - buf.fiforecv.append_pos - 1)) > 0) { *(buf.fiforecv.buffer + (buf.fiforecv.append_pos += (size_t) brd)) = '\0'; LOG(LOG_DEBUG, "Read %li bytes from FIFO buffer. Now appending at %li with \"%s\" so far.", brd, buf.fiforecv.append_pos, buf.fiforecv.buffer); connection.info.state = CONN_ACTIVE; } else if (brd == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { LOG(LOG_ERROR, "Failed to read FIFO buffer. " ERRNOFMT, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } memset((void*) &buffer, '\0', sizeof(IRC_Message)); if ((len = get_buffer_line(buf.fiforecv.buffer)) > 0) { LOG(LOG_DEBUG, "Got IRC message: %s", buf.fiforecv.buffer); if (Tok_mesg(buf.fiforecv.buffer, &buffer) == 1) { signed long temp; if ((temp = Assm_mesg(buf.fifosend.buffer, &buffer, sizeof(buf.fifosend.buffer))) > 0) { if (flush_buffer(buf.fifosend.buffer, (size_t) temp, buf.fifosend.fd) == -1) { LOG(LOG_WARN, "Couldn't send FIFO contents to " ADDRFMT ". " ERRNOFMT, connection.data.address, connection.data.service, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } } } else { LOG(LOG_WARN, "%s", "Received invalid IRC message on FIFO (see RFC2812)."); } for (long unsigned int x = 0; x < sizeof(buf.fiforecv.buffer) && *(buf.fiforecv.buffer + len + x); x++) *(buf.fiforecv.buffer + x) = *(buf.fiforecv.buffer + x + len); buf.fiforecv.append_pos -= (unsigned long) len; *(buf.fiforecv.buffer + buf.fiforecv.append_pos) = '\0'; } else if (buf.fiforecv.append_pos == sizeof(buf.fiforecv.fd) - 1) { LOG(LOG_WARN, "%s.", "FIFO buffer is full and no message could be parsed. Cleared buffer."); memset(buf.fiforecv.buffer, '\0', sizeof(buf.fiforecv)); buf.fiforecv.append_pos = 0; } } else { char pathbuf[UIRCD_LIMITS_PATH]; if (get_path(pathbuf, sizeof(pathbuf), &connection, NULL, true, true, false) >= 0) { if (mkdir_bottomup(pathbuf)) buf.fiforecv.fd = makeinput(pathbuf); } } } ssize_t brd, len; /* Buffer reader */ if ((brd = read(buf.recv.fd, buf.recv.buffer + buf.recv.append_pos, sizeof(buf.recv.buffer) - buf.recv.append_pos - 1)) > 0) { *(buf.recv.buffer + (buf.recv.append_pos += (size_t) brd)) = '\0'; LOG(LOG_DEBUG, "Read %li bytes from socket buffer. Now appending at %li with \"%s\" so far.", brd, buf.recv.append_pos, buf.recv.buffer); connection.info.state = CONN_ACTIVE; } else if (brd == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) { LOG(LOG_ERROR, "Failed to read inbound traffic. " ERRNOFMT, strerror(errno), errno); connection.info.state = CONN_RECONNECTING; continue; } memset((void*) &buffer, '\0', sizeof(IRC_Message)); if ((len = get_buffer_line(buf.recv.buffer)) > 0) { LOG(LOG_DEBUG, "Got IRC message: %s", buf.recv.buffer); if (Tok_mesg(buf.recv.buffer, &buffer) == 1) { connection.info.l_message = ctime; char datebuf[25]; if (buffer.tags.time.value == NULL) { Assm_tag_timestamp(datebuf, sizeof(datebuf), ctime); buffer.tags.time.value = datebuf; } char logpath[UIRCD_LIMITS_PATH]; if (Assm_mesg(buf.log.buffer, &buffer, sizeof(buf.log.buffer)) > 0) { printf("%s\r\n", buf.log.buffer); bool match[][3] = { { false, false, false }, { true, false, false }, { true, false, true } }; for (unsigned long i = 0; i < sizeof(match) / sizeof(*match); i++) { if (get_path(logpath, sizeof(logpath), &connection, &buffer, match[i][0], match[i][1], match[i][2]) >= 0) { if (mkdir_bottomup(logpath)) write_log(logpath, buf.log.buffer); } } } if (!auto_msg_actions(&buffer, &connection, &buf.send)) continue; for (long unsigned int x = 0; x < sizeof(buf.recv.buffer) && *(buf.recv.buffer + len + x); x++) *(buf.recv.buffer + x) = *(buf.recv.buffer + x + len); buf.recv.append_pos -= (unsigned long) len; *(buf.recv.buffer + buf.recv.append_pos) = '\0'; connection.info.state = CONN_ACTIVE; } else LOG(LOG_WARN, "%s", "Received invalid IRC message (see RFC2812)."); } else if (len == -1) { connection.info.state = CONN_RECONNECTING; } else if (buf.recv.append_pos == sizeof(buf.recv.fd) - 1) { LOG(LOG_WARN, "%s.", "Read buffer is full and no message could be parsed"); connection.info.state = CONN_RECONNECTING; } } LOG(LOG_VERBOSE, "%s", "Exiting gracefully."); return EXIT_SUCCESS; }