396 lines
15 KiB
C
396 lines
15 KiB
C
/*
|
|
* 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 <https://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#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.h> // errno
|
|
#include <fcntl.h> // O_NONBLOCK
|
|
#include <stdio.h> // printf()
|
|
#include <stdlib.h> // srand()
|
|
#include <string.h> // strerror()
|
|
#include <sys/types.h> // time_t size_t
|
|
#include <time.h> // time()
|
|
#include <unistd.h> // getopt() chdir() close()
|
|
|
|
#define UIRC_IRCV3
|
|
#define UIRC_HELPERS
|
|
#include <uirc/uirc.h> // 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: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 '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", "20 seconds" },
|
|
{ 'V', "int", "Log level", "0 [LOG_FATAL]" },
|
|
{ '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 <host> [ <options> ... ]\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, fifo, log;
|
|
} 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.active) nanosleep(&sleep, NULL);
|
|
connection.info.active = false;
|
|
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.fifo.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.fifo.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 = -1;
|
|
buf.send.fd = -1;
|
|
buf.fifo.fd = -1;
|
|
connection.info.l_ping = 0;
|
|
connection.info.l_pong = 0;
|
|
connection.info.l_message = 0;
|
|
|
|
if ((buf.send.fd = init_connection(&connection)) > 0) {
|
|
buf.recv.fd = buf.send.fd;
|
|
add_socket_flags(buf.send.fd, O_NONBLOCK);
|
|
|
|
/* Registration process and CAP negotiation (TODO) */
|
|
signed long temp;
|
|
/*
|
|
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.reconinter = 0;
|
|
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.fifo.fd != -1) {
|
|
ssize_t brd, len;
|
|
/* Buffer writer */
|
|
if ((brd =
|
|
read(buf.fifo.fd, buf.fifo.buffer + buf.fifo.append_pos, sizeof(buf.fifo.buffer) - buf.fifo.append_pos - 1))
|
|
> 0) {
|
|
*(buf.fifo.buffer + (buf.fifo.append_pos += (size_t) brd)) = '\0';
|
|
LOG(LOG_DEBUG, "Read %li bytes from FIFO. Now appending at %li", brd, buf.fifo.append_pos);
|
|
connection.info.active = true;
|
|
} else if (brd == -1 && errno != EAGAIN && errno != EWOULDBLOCK && errno != EINTR) {
|
|
LOG(LOG_ERROR, "Failed to read FIFO fifo. " ERRNOFMT, strerror(errno), errno);
|
|
connection.info.state = CONN_RECONNECTING;
|
|
continue;
|
|
}
|
|
|
|
memset((void*) &buffer, '\0', sizeof(IRC_Message));
|
|
if ((len = get_buffer_line(buf.fifo.buffer)) > 0) {
|
|
LOG(LOG_DEBUG, "Got FIFO message: %s", buf.fifo.buffer);
|
|
if (Tok_mesg(buf.fifo.buffer, &buffer) == 1) {
|
|
LOG(LOG_DEBUG, "%s", "Tokenized FIFO message successfully.");
|
|
signed long temp;
|
|
if ((temp = Assm_mesg(buf.fifo.buffer, &buffer, sizeof(buf.fifo.buffer))) > 0) {
|
|
if (flush_buffer(buf.fifo.buffer, (size_t) temp, buf.send.fd) == -1) {
|
|
LOG(LOG_WARN,
|
|
"Couldn't send FIFO fifo to " ADDRFMT ". " ERRNOFMT,
|
|
connection.data.address,
|
|
connection.data.service,
|
|
strerror(errno),
|
|
errno);
|
|
connection.info.state = CONN_RECONNECTING;
|
|
continue;
|
|
}
|
|
}
|
|
for (long unsigned int x = 0; x < sizeof(buf.fifo.buffer) && *(buf.fifo.buffer + len + x); x++)
|
|
*(buf.fifo.buffer + x) = *(buf.fifo.buffer + x + len);
|
|
buf.fifo.append_pos -= (unsigned long) len;
|
|
*(buf.fifo.buffer + buf.fifo.append_pos) = '\0';
|
|
connection.info.active = true;
|
|
} else
|
|
LOG(LOG_WARN, "%s", "Received invalid IRC message (see RFC2812).");
|
|
} else if (len == -1) {
|
|
connection.info.state = CONN_RECONNECTING;
|
|
} else if (buf.fifo.append_pos == sizeof(buf.fifo.buffer) - 1) {
|
|
LOG(LOG_WARN, "%s. %s.", "FIFO buffer is full and no message could be parsed", "Discarding buffer");
|
|
buf.fifo.append_pos = 0;
|
|
*buf.fifo.buffer = '\0';
|
|
}
|
|
} else {
|
|
char pathbuf[UIRCD_LIMITS_PATH];
|
|
if (get_path(pathbuf, sizeof(pathbuf), &connection, NULL, true, true, false) >= 0) {
|
|
if (mkdir_bottomup(pathbuf)) buf.fifo.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.active = true;
|
|
} 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.active = true;
|
|
} 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;
|
|
}
|
|
|