/* * kike.c: the experimental IRC daemon * * Copyright (c) 2014 - 2015, Přemysl Janouch * * Permission to use, copy, modify, and/or distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. * */ #include "config.h" #define PROGRAM_NAME "kike" #define WANT_SYSLOG_LOGGING #include "common.c" #include "kike-replies.c" #include enum { PIPE_READ, PIPE_WRITE }; // FIXME: don't use time_t to compute time deltas // --- Configuration (application-specific) ------------------------------------ // Just get rid of the crappiest ciphers available by default #define DEFAULT_CIPHERS "DEFAULT:!MEDIUM:!LOW" static struct simple_config_item g_config_table[] = { { "pid_file", NULL, "Path or name of the PID file" }, { "server_name", NULL, "Server name" }, { "server_info", "My server", "Brief server description" }, { "motd", NULL, "MOTD filename" }, { "catalog", NULL, "catgets localization catalog" }, { "bind_host", NULL, "Address of the IRC server" }, { "bind_port", "6667", "Port of the IRC server" }, { "tls_cert", NULL, "Server TLS certificate (PEM)" }, { "tls_key", NULL, "Server TLS private key (PEM)" }, { "tls_ciphers", DEFAULT_CIPHERS, "OpenSSL cipher list" }, { "operators", NULL, "IRCop TLS cert. fingerprints" }, { "max_connections", "0", "Global connection limit" }, { "ping_interval", "180", "Interval between PING's (sec)" }, { NULL, NULL, NULL } }; // --- Signals ----------------------------------------------------------------- static int g_signal_pipe[2]; ///< A pipe used to signal... signals /// Program termination has been requested by a signal static volatile sig_atomic_t g_termination_requested; static void sigterm_handler (int signum) { (void) signum; g_termination_requested = true; int original_errno = errno; if (write (g_signal_pipe[1], "t", 1) == -1) soft_assert (errno == EAGAIN); errno = original_errno; } static void setup_signal_handlers (void) { if (pipe (g_signal_pipe) == -1) exit_fatal ("%s: %s", "pipe", strerror (errno)); set_cloexec (g_signal_pipe[0]); set_cloexec (g_signal_pipe[1]); // So that the pipe cannot overflow; it would make write() block within // the signal handler, which is something we really don't want to happen. // The same holds true for read(). set_blocking (g_signal_pipe[0], false); set_blocking (g_signal_pipe[1], false); signal (SIGPIPE, SIG_IGN); struct sigaction sa; sa.sa_flags = SA_RESTART; sigemptyset (&sa.sa_mask); sa.sa_handler = sigterm_handler; if (sigaction (SIGINT, &sa, NULL) == -1 || sigaction (SIGTERM, &sa, NULL) == -1) exit_fatal ("%s: %s", "sigaction", strerror (errno)); } // --- Rate limiter ------------------------------------------------------------ struct flood_detector { unsigned interval; ///< Interval for the limit unsigned limit; ///< Maximum number of events allowed time_t *timestamps; ///< Timestamps of last events unsigned pos; ///< Index of the oldest event }; static void flood_detector_init (struct flood_detector *self, unsigned interval, unsigned limit) { self->interval = interval; self->limit = limit; self->timestamps = xcalloc (limit + 1, sizeof *self->timestamps); self->pos = 0; } static void flood_detector_free (struct flood_detector *self) { free (self->timestamps); } static bool flood_detector_check (struct flood_detector *self) { time_t now = time (NULL); self->timestamps[self->pos++] = now; if (self->pos > self->limit) self->pos = 0; time_t begin = now - self->interval; size_t count = 0; for (size_t i = 0; i <= self->limit; i++) if (self->timestamps[i] >= begin) count++; return count <= self->limit; } // --- IRC token validation ---------------------------------------------------- // Use the enum only if applicable and a simple boolean isn't sufficient. enum validation_result { VALIDATION_OK, VALIDATION_ERROR_EMPTY, VALIDATION_ERROR_TOO_LONG, VALIDATION_ERROR_INVALID }; // Everything as per RFC 2812 #define IRC_MAX_NICKNAME 9 #define IRC_MAX_HOSTNAME 63 #define IRC_MAX_CHANNEL_NAME 50 #define IRC_MAX_MESSAGE_LENGTH 510 static bool irc_regex_match (const char *regex, const char *s) { static struct str_map cache; static bool initialized; if (!initialized) { regex_cache_init (&cache); initialized = true; } struct error *e = NULL; bool result = regex_cache_match (&cache, regex, REG_EXTENDED | REG_NOSUB, s, &e); hard_assert (!e); return result; } static const char * irc_validate_to_str (enum validation_result result) { switch (result) { case VALIDATION_OK: return "success"; case VALIDATION_ERROR_EMPTY: return "the value is empty"; case VALIDATION_ERROR_INVALID: return "invalid format"; case VALIDATION_ERROR_TOO_LONG: return "the value is too long"; default: abort (); } } // Anything to keep it as short as possible #define SN "[0-9A-Za-z][-0-9A-Za-z]*[0-9A-Za-z]*" #define N4 "[0-9]{1,3}" #define N6 "[0-9ABCDEFabcdef]{1,}" #define LE "A-Za-z" #define SP "][\\\\`_^{|}" static enum validation_result irc_validate_hostname (const char *hostname) { if (!*hostname) return VALIDATION_ERROR_EMPTY; if (!irc_regex_match ("^" SN "(\\." SN ")*$", hostname)) return VALIDATION_ERROR_INVALID; if (strlen (hostname) > IRC_MAX_HOSTNAME) return VALIDATION_ERROR_TOO_LONG; return VALIDATION_OK; } static bool irc_is_valid_hostaddr (const char *hostaddr) { if (irc_regex_match ("^" N4 "\\." N4 "\\." N4 "\\." N4 "$", hostaddr) || irc_regex_match ("^" N6 ":" N6 ":" N6 ":" N6 ":" N6 ":" N6 ":" N6 ":" N6 "$", hostaddr) || irc_regex_match ("^0:0:0:0:0:(0|[Ff]{4}):" N4 "\\." N4 "\\." N4 "\\." N4 "$", hostaddr)) return true; return false; } static bool irc_is_valid_host (const char *host) { return irc_validate_hostname (host) == VALIDATION_OK || irc_is_valid_hostaddr (host); } static bool irc_is_valid_user (const char *user) { return irc_regex_match ("^[^\r\n @]+$", user); } static bool irc_validate_nickname (const char *nickname) { if (!*nickname) return VALIDATION_ERROR_EMPTY; if (!irc_regex_match ("^[" SP LE "][" SP LE "0-9-]*$", nickname)) return VALIDATION_ERROR_INVALID; if (strlen (nickname) > IRC_MAX_NICKNAME) return VALIDATION_ERROR_TOO_LONG; return VALIDATION_OK; } static enum validation_result irc_validate_channel_name (const char *channel_name) { if (!*channel_name) return VALIDATION_ERROR_EMPTY; if (*channel_name != '#' || strpbrk (channel_name, "\7\r\n ,:")) return VALIDATION_ERROR_INVALID; if (strlen (channel_name) > IRC_MAX_CHANNEL_NAME) return VALIDATION_ERROR_TOO_LONG; return VALIDATION_OK; } static bool irc_is_valid_key (const char *key) { // XXX: should be 7-bit as well but whatever return irc_regex_match ("^[^\r\n\f\t\v ]{1,23}$", key); } #undef SN #undef N4 #undef N6 #undef LE #undef SP static bool irc_is_valid_user_mask (const char *mask) { return irc_regex_match ("^[^!@]+![^!@]+@[^@!]+$", mask); } static bool irc_is_valid_fingerprint (const char *fp) { return irc_regex_match ("^[a-fA-F0-9]{40}$", fp); } // --- Clients (equals users) -------------------------------------------------- #define IRC_SUPPORTED_USER_MODES "aiwros" enum { IRC_USER_MODE_INVISIBLE = (1 << 0), IRC_USER_MODE_RX_WALLOPS = (1 << 1), IRC_USER_MODE_RESTRICTED = (1 << 2), IRC_USER_MODE_OPERATOR = (1 << 3), IRC_USER_MODE_RX_SERVER_NOTICES = (1 << 4) }; enum { IRC_CAP_MULTI_PREFIX = (1 << 0), IRC_CAP_INVITE_NOTIFY = (1 << 1), IRC_CAP_ECHO_MESSAGE = (1 << 2), IRC_CAP_USERHOST_IN_NAMES = (1 << 3) }; struct client { LIST_HEADER (struct client) struct server_context *ctx; ///< Server context time_t opened; ///< When the connection was opened size_t n_sent_messages; ///< Number of sent messages total size_t sent_bytes; ///< Number of sent bytes total size_t n_received_messages; ///< Number of received messages total size_t received_bytes; ///< Number of received bytes total int socket_fd; ///< The TCP socket struct str read_buffer; ///< Unprocessed input struct str write_buffer; ///< Output yet to be sent out struct poller_fd socket_event; ///< The socket can be read/written to struct poller_timer ping_timer; ///< We should send a ping struct poller_timer timeout_timer; ///< Connection seems to be dead struct poller_timer kill_timer; ///< Hard kill timeout bool initialized; ///< Has any data been received yet? bool cap_negotiating; ///< Negotiating capabilities bool registered; ///< The user has registered bool closing_link; ///< Closing link bool half_closed; ///< Closing link: conn. is half-closed unsigned long cap_version; ///< CAP protocol version unsigned caps_enabled; ///< Enabled capabilities bool ssl_rx_want_tx; ///< SSL_read() wants to write bool ssl_tx_want_rx; ///< SSL_write() wants to read SSL *ssl; ///< SSL connection char *ssl_cert_fingerprint; ///< Client certificate fingerprint char *nickname; ///< IRC nickname (main identifier) char *username; ///< IRC username char *realname; ///< IRC realname (e-mail) char *hostname; ///< Hostname shown to the network char *address; ///< Full address including port unsigned mode; ///< User's mode char *away_message; ///< Away message time_t last_active; ///< Last PRIVMSG, to get idle time struct str_map invites; ///< Channel invitations by operators struct flood_detector antiflood; ///< Flood detector }; static void client_init (struct client *self) { memset (self, 0, sizeof *self); self->socket_fd = -1; str_init (&self->read_buffer); str_init (&self->write_buffer); self->cap_version = 301; // TODO: make this configurable and more fine-grained flood_detector_init (&self->antiflood, 10, 20); str_map_init (&self->invites); self->invites.key_xfrm = irc_strxfrm; } static void client_free (struct client *self) { if (!soft_assert (self->socket_fd == -1)) xclose (self->socket_fd); if (self->ssl) SSL_free (self->ssl); str_free (&self->read_buffer); str_free (&self->write_buffer); free (self->nickname); free (self->username); free (self->realname); free (self->hostname); free (self->address); free (self->away_message); flood_detector_free (&self->antiflood); str_map_free (&self->invites); } static void client_close_link (struct client *c, const char *reason); static void client_kill (struct client *c, const char *reason); static void client_send (struct client *, const char *, ...) ATTRIBUTE_PRINTF (2, 3); static void client_cancel_timers (struct client *); static void client_set_kill_timer (struct client *); static void client_update_poller (struct client *, const struct pollfd *); // --- Channels ---------------------------------------------------------------- #define IRC_SUPPORTED_CHAN_MODES "ov" "beI" "imnqpst" "kl" enum { IRC_CHAN_MODE_INVITE_ONLY = (1 << 0), IRC_CHAN_MODE_MODERATED = (1 << 1), IRC_CHAN_MODE_NO_OUTSIDE_MSGS = (1 << 2), IRC_CHAN_MODE_QUIET = (1 << 3), IRC_CHAN_MODE_PRIVATE = (1 << 4), IRC_CHAN_MODE_SECRET = (1 << 5), IRC_CHAN_MODE_PROTECTED_TOPIC = (1 << 6), IRC_CHAN_MODE_OPERATOR = (1 << 7), IRC_CHAN_MODE_VOICE = (1 << 8) }; struct channel_user { LIST_HEADER (struct channel_user) unsigned modes; struct client *c; }; struct channel { struct server_context *ctx; ///< Server context char *name; ///< Channel name unsigned modes; ///< Channel modes char *key; ///< Channel key long user_limit; ///< User limit or -1 time_t created; ///< Creation time char *topic; ///< Channel topic char *topic_who; ///< Who set the topic time_t topic_time; ///< When the topic was set struct channel_user *users; ///< Channel users struct str_vector ban_list; ///< Ban list struct str_vector exception_list; ///< Exceptions from bans struct str_vector invite_list; ///< Exceptions from +I }; static struct channel * channel_new (void) { struct channel *self = xcalloc (1, sizeof *self); self->user_limit = -1; self->topic = xstrdup (""); str_vector_init (&self->ban_list); str_vector_init (&self->exception_list); str_vector_init (&self->invite_list); return self; } static void channel_delete (struct channel *self) { free (self->name); free (self->key); free (self->topic); free (self->topic_who); struct channel_user *link, *tmp; for (link = self->users; link; link = tmp) { tmp = link->next; free (link); } str_vector_free (&self->ban_list); str_vector_free (&self->exception_list); str_vector_free (&self->invite_list); free (self); } static char * channel_get_mode (struct channel *self, bool disclose_secrets) { struct str mode; str_init (&mode); unsigned m = self->modes; if (m & IRC_CHAN_MODE_INVITE_ONLY) str_append_c (&mode, 'i'); if (m & IRC_CHAN_MODE_MODERATED) str_append_c (&mode, 'm'); if (m & IRC_CHAN_MODE_NO_OUTSIDE_MSGS) str_append_c (&mode, 'n'); if (m & IRC_CHAN_MODE_QUIET) str_append_c (&mode, 'q'); if (m & IRC_CHAN_MODE_PRIVATE) str_append_c (&mode, 'p'); if (m & IRC_CHAN_MODE_SECRET) str_append_c (&mode, 's'); if (m & IRC_CHAN_MODE_PROTECTED_TOPIC) str_append_c (&mode, 't'); if (self->user_limit != -1) str_append_c (&mode, 'l'); if (self->key) str_append_c (&mode, 'k'); // XXX: is it correct to split it? Try it on an existing implementation. if (disclose_secrets) { if (self->user_limit != -1) str_append_printf (&mode, " %ld", self->user_limit); if (self->key) str_append_printf (&mode, " %s", self->key); } return str_steal (&mode); } static struct channel_user * channel_get_user (const struct channel *chan, const struct client *c) { for (struct channel_user *iter = chan->users; iter; iter = iter->next) if (iter->c == c) return iter; return NULL; } static struct channel_user * channel_add_user (struct channel *chan, struct client *c) { struct channel_user *link = xcalloc (1, sizeof *link); link->c = c; LIST_PREPEND (chan->users, link); return link; } static void channel_remove_user (struct channel *chan, struct channel_user *user) { LIST_UNLINK (chan->users, user); free (user); } static size_t channel_user_count (const struct channel *chan) { size_t result = 0; for (struct channel_user *iter = chan->users; iter; iter = iter->next) result++; return result; } // --- IRC server context ------------------------------------------------------ struct whowas_info { char *nickname; ///< IRC nickname char *username; ///< IRC username char *realname; ///< IRC realname char *hostname; ///< Hostname shown to the network }; struct whowas_info * whowas_info_new (struct client *c) { struct whowas_info *self = xmalloc (sizeof *self); self->nickname = xstrdup (c->nickname); self->username = xstrdup (c->username); self->realname = xstrdup (c->realname); self->hostname = xstrdup (c->hostname); return self; } static void whowas_info_destroy (struct whowas_info *self) { free (self->nickname); free (self->username); free (self->realname); free (self->hostname); free (self); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct irc_command { const char *name; bool requires_registration; void (*handler) (const struct irc_message *, struct client *); size_t n_received; ///< Number of commands received size_t bytes_received; ///< Number of bytes received total }; struct server_context { int *listen_fds; ///< Listening socket FD's struct poller_fd *listen_events; ///< New connections available size_t n_listen_fds; ///< Number of listening sockets time_t started; ///< When has the server been started SSL_CTX *ssl_ctx; ///< SSL context struct client *clients; ///< Clients unsigned n_clients; ///< Current number of connections struct str_map users; ///< Maps nicknames to clients struct str_map channels; ///< Maps channel names to data struct str_map handlers; ///< Message handlers struct str_map cap_handlers; ///< CAP message handlers struct str_map whowas; ///< WHOWAS registry struct poller poller; ///< Manages polled description struct poller_timer quit_timer; ///< Quit timeout timer bool quitting; ///< User requested quitting bool polling; ///< The event loop is running struct poller_fd signal_event; ///< Got a signal struct str_map config; ///< Server configuration char *server_name; ///< Our server name unsigned ping_interval; ///< Ping interval in seconds unsigned max_connections; ///< Max. connections allowed or 0 struct str_vector motd; ///< MOTD (none if empty) nl_catd catalog; ///< Message catalog for server msgs struct str_map operators; ///< TLS cert. fingerprints for IRCops }; static void on_irc_quit_timeout (void *user_data) { struct server_context *ctx = user_data; struct client *iter, *next; for (iter = ctx->clients; iter; iter = next) { next = iter->next; // irc_initiate_quit() has already unregistered the client client_kill (iter, "Shutting down"); } } static void server_context_init (struct server_context *self) { memset (self, 0, sizeof *self); str_map_init (&self->users); self->users.key_xfrm = irc_strxfrm; str_map_init (&self->channels); self->channels.key_xfrm = irc_strxfrm; self->channels.free = (void (*) (void *)) channel_delete; str_map_init (&self->handlers); self->handlers.key_xfrm = irc_strxfrm; str_map_init (&self->cap_handlers); self->cap_handlers.key_xfrm = irc_strxfrm; str_map_init (&self->whowas); self->whowas.key_xfrm = irc_strxfrm; self->whowas.free = (void (*) (void *)) whowas_info_destroy; poller_init (&self->poller); poller_timer_init (&self->quit_timer, &self->poller); self->quit_timer.dispatcher = on_irc_quit_timeout; self->quit_timer.user_data = self; str_map_init (&self->config); self->config.free = free; simple_config_load_defaults (&self->config, g_config_table); str_vector_init (&self->motd); self->catalog = (nl_catd) -1; str_map_init (&self->operators); // The regular irc_strxfrm() is sufficient for fingerprints self->operators.key_xfrm = irc_strxfrm; } static void server_context_free (struct server_context *self) { str_map_free (&self->config); for (size_t i = 0; i < self->n_listen_fds; i++) { xclose (self->listen_fds[i]); self->listen_events[i].closed = true; poller_fd_reset (&self->listen_events[i]); } free (self->listen_fds); free (self->listen_events); hard_assert (!self->clients); if (self->ssl_ctx) SSL_CTX_free (self->ssl_ctx); free (self->server_name); str_map_free (&self->users); str_map_free (&self->channels); str_map_free (&self->handlers); str_map_free (&self->cap_handlers); str_map_free (&self->whowas); poller_free (&self->poller); str_vector_free (&self->motd); if (self->catalog != (nl_catd) -1) catclose (self->catalog); str_map_free (&self->operators); } static const char * irc_get_text (struct server_context *ctx, int id, const char *def) { if (!soft_assert (def != NULL)) def = ""; if (ctx->catalog == (nl_catd) -1) return def; return catgets (ctx->catalog, 1, id, def); } static void irc_try_finish_quit (struct server_context *ctx) { if (!ctx->n_clients && ctx->quitting) { poller_timer_reset (&ctx->quit_timer); ctx->polling = false; } } static void irc_initiate_quit (struct server_context *ctx) { print_status ("shutting down"); for (size_t i = 0; i < ctx->n_listen_fds; i++) { xclose (ctx->listen_fds[i]); ctx->listen_events[i].closed = true; poller_fd_reset (&ctx->listen_events[i]); } ctx->n_listen_fds = 0; for (struct client *iter = ctx->clients; iter; iter = iter->next) if (!iter->closing_link) client_close_link (iter, "Shutting down"); ctx->quitting = true; poller_timer_set (&ctx->quit_timer, 5000); irc_try_finish_quit (ctx); } static struct channel * irc_channel_create (struct server_context *ctx, const char *name) { struct channel *chan = channel_new (); chan->ctx = ctx; chan->name = xstrdup (name); chan->created = time (NULL); str_map_set (&ctx->channels, name, chan); return chan; } static void irc_channel_destroy_if_empty (struct server_context *ctx, struct channel *chan) { if (!chan->users) str_map_set (&ctx->channels, chan->name, NULL); } static void irc_send_to_roommates (struct client *c, const char *message) { struct str_map targets; str_map_init (&targets); targets.key_xfrm = irc_strxfrm; struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); struct channel *chan; while ((chan = str_map_iter_next (&iter))) { if (chan->modes & IRC_CHAN_MODE_QUIET || !channel_get_user (chan, c)) continue; for (struct channel_user *iter = chan->users; iter; iter = iter->next) str_map_set (&targets, iter->c->nickname, iter->c); } str_map_iter_init (&iter, &targets); struct client *target; while ((target = str_map_iter_next (&iter))) if (target != c) client_send (target, "%s", message); } // --- Clients (continued) ----------------------------------------------------- static void client_mode_to_str (unsigned m, struct str *out) { if (m & IRC_USER_MODE_INVISIBLE) str_append_c (out, 'i'); if (m & IRC_USER_MODE_RX_WALLOPS) str_append_c (out, 'w'); if (m & IRC_USER_MODE_RESTRICTED) str_append_c (out, 'r'); if (m & IRC_USER_MODE_OPERATOR) str_append_c (out, 'o'); if (m & IRC_USER_MODE_RX_SERVER_NOTICES) str_append_c (out, 's'); } static char * client_get_mode (struct client *self) { struct str mode; str_init (&mode); if (self->away_message) str_append_c (&mode, 'a'); client_mode_to_str (self->mode, &mode); return str_steal (&mode); } static void client_send_str (struct client *c, const struct str *s) { hard_assert (c->initialized && !c->closing_link); size_t old_sendq = c->write_buffer.len; // TODO: kill the connection above some "SendQ" threshold (careful!) str_append_data (&c->write_buffer, s->str, s->len > IRC_MAX_MESSAGE_LENGTH ? IRC_MAX_MESSAGE_LENGTH : s->len); str_append (&c->write_buffer, "\r\n"); // XXX: we might want to move this elsewhere, so that it doesn't get called // as often; it's going to cause a lot of syscalls with epoll. client_update_poller (c, NULL); // Technically we haven't sent it yet but that's a minor detail c->n_sent_messages++; c->sent_bytes += c->write_buffer.len - old_sendq; } static void client_send (struct client *c, const char *format, ...) { struct str tmp; str_init (&tmp); va_list ap; va_start (ap, format); str_append_vprintf (&tmp, format, ap); va_end (ap); client_send_str (c, &tmp); str_free (&tmp); } static void client_add_to_whowas (struct client *c) { // Only keeping one entry for each nickname // TODO: make sure this list doesn't get too long, for example by // putting them in a linked list ordered by time str_map_set (&c->ctx->whowas, c->nickname, whowas_info_new (c)); } static void client_unregister (struct client *c, const char *reason) { if (!c->registered) return; char *message = xstrdup_printf (":%s!%s@%s QUIT :%s", c->nickname, c->username, c->hostname, reason); irc_send_to_roommates (c, message); free (message); struct str_map_unset_iter iter; str_map_unset_iter_init (&iter, &c->ctx->channels); struct channel *chan; while ((chan = str_map_unset_iter_next (&iter))) { struct channel_user *user; if (!(user = channel_get_user (chan, c))) continue; channel_remove_user (chan, user); irc_channel_destroy_if_empty (c->ctx, chan); } str_map_unset_iter_free (&iter); client_add_to_whowas (c); str_map_set (&c->ctx->users, c->nickname, NULL); free (c->nickname); c->nickname = NULL; c->registered = false; } static void client_close_link (struct client *c, const char *reason) { if (!soft_assert (!c->closing_link)) return; // We push an `ERROR' message to the write buffer and let the poller send // it, with some arbitrary timeout. The `closing_link' state makes sure // that a/ we ignore any successive messages, and b/ that the connection // is killed after the write buffer is transferred and emptied. client_send (c, "ERROR :Closing Link: %s[%s] (%s)", c->nickname ? c->nickname : "*", c->hostname /* TODO host IP? */, reason); c->closing_link = true; client_unregister (c, reason); client_set_kill_timer (c); } static void client_kill (struct client *c, const char *reason) { client_unregister (c, reason ? reason : "Client exited"); struct server_context *ctx = c->ctx; if (c->ssl) // Note that we might have already called this once, but that is fine (void) SSL_shutdown (c->ssl); xclose (c->socket_fd); c->socket_event.closed = true; poller_fd_reset (&c->socket_event); client_cancel_timers (c); print_debug ("closed connection to %s (%s)", c->address, reason ? reason : ""); c->socket_fd = -1; client_free (c); LIST_UNLINK (ctx->clients, c); ctx->n_clients--; free (c); irc_try_finish_quit (ctx); } static bool client_in_mask_list (const struct client *c, const struct str_vector *mask) { char *client = xstrdup_printf ("%s!%s@%s", c->nickname, c->username, c->hostname); bool result = false; for (size_t i = 0; i < mask->len; i++) if (!irc_fnmatch (mask->vector[i], client)) { result = true; break; } free (client); return result; } static char * client_get_ssl_cert_fingerprint (struct client *c) { if (!c->ssl) return NULL; X509 *peer_cert = SSL_get_peer_certificate (c->ssl); if (!peer_cert) return NULL; int cert_len = i2d_X509 (peer_cert, NULL); if (cert_len < 0) return NULL; unsigned char cert[cert_len], *p = cert; if (i2d_X509 (peer_cert, &p) < 0) return NULL; unsigned char hash[SHA_DIGEST_LENGTH]; SHA1 (cert, cert_len, hash); struct str fingerprint; str_init (&fingerprint); for (size_t i = 0; i < sizeof hash; i++) str_append_printf (&fingerprint, "%02x", hash[i]); return str_steal (&fingerprint); } // --- Timers ------------------------------------------------------------------ static void client_cancel_timers (struct client *c) { poller_timer_reset (&c->kill_timer); poller_timer_reset (&c->timeout_timer); poller_timer_reset (&c->ping_timer); } static void client_set_timer (struct client *c, struct poller_timer *timer, unsigned interval) { client_cancel_timers (c); poller_timer_set (timer, interval * 1000); } static void on_client_kill_timer (void *user_data) { struct client *c = user_data; hard_assert (!c->initialized || c->closing_link); client_kill (c, NULL); } static void client_set_kill_timer (struct client *c) { client_set_timer (c, &c->kill_timer, c->ctx->ping_interval); } static void on_client_timeout_timer (void *user_data) { struct client *c = user_data; char *reason = xstrdup_printf ("Ping timeout: >%u seconds", c->ctx->ping_interval); client_close_link (c, reason); free (reason); } static void on_client_ping_timer (void *user_data) { struct client *c = user_data; hard_assert (!c->closing_link); client_send (c, "PING :%s", c->ctx->server_name); client_set_timer (c, &c->timeout_timer, c->ctx->ping_interval); } static void client_set_ping_timer (struct client *c) { client_set_timer (c, &c->ping_timer, c->ctx->ping_interval); } // --- IRC command handling ---------------------------------------------------- static void irc_make_reply (struct client *c, int id, va_list ap, struct str *output) { str_append_printf (output, ":%s %03d %s ", c->ctx->server_name, id, c->nickname ? c->nickname : "*"); str_append_vprintf (output, irc_get_text (c->ctx, id, g_default_replies[id]), ap); } // XXX: this way we cannot typecheck the arguments, so we must be careful static void irc_send_reply (struct client *c, int id, ...) { struct str reply; str_init (&reply); va_list ap; va_start (ap, id); irc_make_reply (c, id, ap, &reply); va_end (ap); client_send_str (c, &reply); str_free (&reply); } /// Send a space-separated list of words across as many replies as needed static void irc_send_reply_vector (struct client *c, int id, char **items, ...) { struct str common; str_init (&common); va_list ap; va_start (ap, items); irc_make_reply (c, id, ap, &common); va_end (ap); // We always send at least one message (there might be a client that // expects us to send this message at least once) do { struct str reply; str_init (&reply); str_append_str (&reply, &common); // If not even a single item fits in the limit (which may happen, // in theory) it just gets cropped. We could also skip it. if (*items) str_append (&reply, *items++); // Append as many items as fits in a single message while (*items && reply.len + 1 + strlen (*items) <= IRC_MAX_MESSAGE_LENGTH) str_append_printf (&reply, " %s", *items++); client_send_str (c, &reply); str_free (&reply); } while (*items); str_free (&common); } #define RETURN_WITH_REPLY(c, ...) \ BLOCK_START \ irc_send_reply ((c), __VA_ARGS__); \ return; \ BLOCK_END static void irc_send_motd (struct client *c) { struct server_context *ctx = c->ctx; if (!ctx->motd.len) RETURN_WITH_REPLY (c, IRC_ERR_NOMOTD); irc_send_reply (c, IRC_RPL_MOTDSTART, ctx->server_name); for (size_t i = 0; i < ctx->motd.len; i++) irc_send_reply (c, IRC_RPL_MOTD, ctx->motd.vector[i]); irc_send_reply (c, IRC_RPL_ENDOFMOTD); } static void irc_send_lusers (struct client *c) { int n_users = 0, n_services = 0, n_opers = 0, n_unknown = 0; for (struct client *link = c->ctx->clients; link; link = link->next) { if (link->registered) n_users++; else n_unknown++; if (link->mode & IRC_USER_MODE_OPERATOR) n_opers++; } int n_channels = 0; struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); struct channel *chan; while ((chan = str_map_iter_next (&iter))) if (!(chan->modes & IRC_CHAN_MODE_SECRET) || channel_get_user (chan, c)) n_channels++; irc_send_reply (c, IRC_RPL_LUSERCLIENT, n_users, n_services, 1 /* servers total */); if (n_opers) irc_send_reply (c, IRC_RPL_LUSEROP, n_opers); if (n_unknown) irc_send_reply (c, IRC_RPL_LUSERUNKNOWN, n_unknown); if (n_channels) irc_send_reply (c, IRC_RPL_LUSERCHANNELS, n_channels); irc_send_reply (c, IRC_RPL_LUSERME, n_users + n_services + n_unknown, 0 /* peer servers */); } static bool irc_is_this_me (struct server_context *ctx, const char *target) { // Target servers can also be matched by their users return !irc_fnmatch (target, ctx->server_name) || str_map_find (&ctx->users, target); } static void irc_send_isupport (struct client *c) { // Only # channels, +e supported, +I supported, unlimited arguments to MODE irc_send_reply (c, IRC_RPL_ISUPPORT, "CHANTYPES=# EXCEPTS INVEX MODES" " TARGMAX=WHOIS:,LIST:,NAMES:,PRIVMSG:1,NOTICE:1,KICK:" " NICKLEN=" XSTRINGIFY (IRC_MAX_NICKNAME) " CHANNELLEN=" XSTRINGIFY (IRC_MAX_CHANNEL_NAME)); } static void irc_try_finish_registration (struct client *c) { struct server_context *ctx = c->ctx; if (!c->nickname || !c->username || !c->realname) return; if (c->registered || c->cap_negotiating) return; c->registered = true; irc_send_reply (c, IRC_RPL_WELCOME, c->nickname, c->username, c->hostname); irc_send_reply (c, IRC_RPL_YOURHOST, ctx->server_name, PROGRAM_VERSION); // The purpose of this message eludes me irc_send_reply (c, IRC_RPL_CREATED, __DATE__); irc_send_reply (c, IRC_RPL_MYINFO, ctx->server_name, PROGRAM_VERSION, IRC_SUPPORTED_USER_MODES, IRC_SUPPORTED_CHAN_MODES); irc_send_isupport (c); irc_send_lusers (c); irc_send_motd (c); char *mode = client_get_mode (c); if (*mode) client_send (c, ":%s MODE %s :+%s", c->nickname, c->nickname, mode); free (mode); hard_assert (c->ssl_cert_fingerprint == NULL); if ((c->ssl_cert_fingerprint = client_get_ssl_cert_fingerprint (c))) client_send (c, ":%s NOTICE %s :" "Your TLS client certificate fingerprint is %s", ctx->server_name, c->nickname, c->ssl_cert_fingerprint); str_map_set (&ctx->whowas, c->nickname, NULL); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // IRCv3 capability negotiation. See http://ircv3.org for details. struct irc_cap_args { const char *subcommand; ///< The subcommand being processed const char *full_params; ///< Whole parameter string struct str_vector params; ///< Split parameters const char *target; ///< Target parameter for replies }; static struct { unsigned flag; ///< Flag const char *name; ///< Name of the capability } irc_cap_table[] = { { IRC_CAP_MULTI_PREFIX, "multi-prefix" }, { IRC_CAP_INVITE_NOTIFY, "invite-notify" }, { IRC_CAP_ECHO_MESSAGE, "echo-message" }, { IRC_CAP_USERHOST_IN_NAMES, "userhost-in-names" }, }; static void irc_handle_cap_ls (struct client *c, struct irc_cap_args *a) { if (a->params.len == 1 && !xstrtoul (&c->cap_version, a->params.vector[0], 10)) irc_send_reply (c, IRC_ERR_INVALIDCAPCMD, a->subcommand, "Ignoring invalid protocol version number"); c->cap_negotiating = true; client_send (c, ":%s CAP %s LS :multi-prefix invite-notify echo-message" " userhost-in-names", c->ctx->server_name, a->target); } static void irc_handle_cap_list (struct client *c, struct irc_cap_args *a) { struct str_vector caps; str_vector_init (&caps); for (size_t i = 0; i < N_ELEMENTS (irc_cap_table); i++) if (c->caps_enabled & irc_cap_table[i].flag) str_vector_add (&caps, irc_cap_table[i].name); char *caps_str = join_str_vector (&caps, ' '); str_vector_free (&caps); client_send (c, ":%s CAP %s LIST :%s", c->ctx->server_name, a->target, caps_str); free (caps_str); } static unsigned irc_decode_capability (const char *name) { for (size_t i = 0; i < N_ELEMENTS (irc_cap_table); i++) if (!strcmp (irc_cap_table[i].name, name)) return irc_cap_table[i].flag; return 0; } static void irc_handle_cap_req (struct client *c, struct irc_cap_args *a) { c->cap_negotiating = true; unsigned new_caps = c->caps_enabled; bool success = true; for (size_t i = 0; i < a->params.len; i++) { bool removing = false; const char *name = a->params.vector[i]; if (*name == '-') { removing = true; name++; } unsigned cap; if (!(cap = irc_decode_capability (name))) success = false; else if (removing) new_caps &= ~cap; else new_caps |= cap; } if (success) { c->caps_enabled = new_caps; client_send (c, ":%s CAP %s ACK :%s", c->ctx->server_name, a->target, a->full_params); } else client_send (c, ":%s CAP %s NAK :%s", c->ctx->server_name, a->target, a->full_params); } static void irc_handle_cap_ack (struct client *c, struct irc_cap_args *a) { if (a->params.len) irc_send_reply (c, IRC_ERR_INVALIDCAPCMD, a->subcommand, "No acknowledgable capabilities supported"); } static void irc_handle_cap_end (struct client *c, struct irc_cap_args *a) { (void) a; c->cap_negotiating = false; irc_try_finish_registration (c); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - struct irc_cap_command { const char *name; void (*handler) (struct client *, struct irc_cap_args *); }; static void irc_register_cap_handlers (struct server_context *ctx) { static const struct irc_cap_command cap_handlers[] = { { "LS", irc_handle_cap_ls }, { "LIST", irc_handle_cap_list }, { "REQ", irc_handle_cap_req }, { "ACK", irc_handle_cap_ack }, { "END", irc_handle_cap_end }, }; for (size_t i = 0; i < N_ELEMENTS (cap_handlers); i++) { const struct irc_cap_command *cmd = &cap_handlers[i]; str_map_set (&ctx->cap_handlers, cmd->name, (void *) cmd); } } static void irc_handle_cap (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); struct irc_cap_args args; args.target = c->nickname ? c->nickname : "*"; args.subcommand = msg->params.vector[0]; args.full_params = ""; str_vector_init (&args.params); if (msg->params.len > 1) { args.full_params = msg->params.vector[1]; cstr_split_ignore_empty (args.full_params, ' ', &args.params); } struct irc_cap_command *cmd = str_map_find (&c->ctx->cap_handlers, args.subcommand); if (!cmd) irc_send_reply (c, IRC_ERR_INVALIDCAPCMD, args.subcommand, "Invalid CAP subcommand"); else cmd->handler (c, &args); str_vector_free (&args.params); } static void irc_handle_pass (const struct irc_message *msg, struct client *c) { if (c->registered) irc_send_reply (c, IRC_ERR_ALREADYREGISTERED); else if (msg->params.len < 1) irc_send_reply (c, IRC_ERR_NEEDMOREPARAMS, msg->command); // We have TLS client certificates for this purpose; ignoring } static void irc_handle_nick (const struct irc_message *msg, struct client *c) { struct server_context *ctx = c->ctx; if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NONICKNAMEGIVEN); const char *nickname = msg->params.vector[0]; if (irc_validate_nickname (nickname) != VALIDATION_OK) RETURN_WITH_REPLY (c, IRC_ERR_ERRONEOUSNICKNAME, nickname); struct client *client = str_map_find (&ctx->users, nickname); if (client && client != c) RETURN_WITH_REPLY (c, IRC_ERR_NICKNAMEINUSE, nickname); if (c->registered) { client_add_to_whowas (c); char *message = xstrdup_printf (":%s!%s@%s NICK :%s", c->nickname, c->username, c->hostname, nickname); irc_send_to_roommates (c, message); client_send (c, "%s", message); free (message); } // Release the old nickname and allocate a new one if (c->nickname) { str_map_set (&ctx->users, c->nickname, NULL); free (c->nickname); } c->nickname = xstrdup (nickname); str_map_set (&ctx->users, nickname, c); if (!c->registered) irc_try_finish_registration (c); } static void irc_handle_user (const struct irc_message *msg, struct client *c) { if (c->registered) RETURN_WITH_REPLY (c, IRC_ERR_ALREADYREGISTERED); if (msg->params.len < 4) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *username = msg->params.vector[0]; const char *mode = msg->params.vector[1]; const char *realname = msg->params.vector[3]; // Unfortunately the protocol doesn't give us any means of rejecting it if (!irc_is_valid_user (username)) username = "xxx"; free (c->username); c->username = xstrdup (username); free (c->realname); c->realname = xstrdup (realname); unsigned long m; if (xstrtoul (&m, mode, 10)) { if (m & 4) c->mode |= IRC_USER_MODE_RX_WALLOPS; if (m & 8) c->mode |= IRC_USER_MODE_INVISIBLE; } irc_try_finish_registration (c); } static void irc_handle_userhost (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); struct str reply; str_init (&reply); for (size_t i = 0; i < 5 && i < msg->params.len; i++) { const char *nick = msg->params.vector[i]; struct client *target = str_map_find (&c->ctx->users, nick); if (!target) continue; if (i) str_append_c (&reply, ' '); str_append (&reply, nick); if (target->mode & IRC_USER_MODE_OPERATOR) str_append_c (&reply, '*'); str_append_printf (&reply, "=%c%s@%s", target->away_message ? '-' : '+', target->username, target->hostname); } irc_send_reply (c, IRC_RPL_USERHOST, reply.str); str_free (&reply); } static void irc_handle_lusers (const struct irc_message *msg, struct client *c) { if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1])) irc_send_reply (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]); else irc_send_lusers (c); } static void irc_handle_motd (const struct irc_message *msg, struct client *c) { if (msg->params.len > 0 && !irc_is_this_me (c->ctx, msg->params.vector[0])) irc_send_reply (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); else irc_send_motd (c); } static void irc_handle_ping (const struct irc_message *msg, struct client *c) { // XXX: the RFC is pretty incomprehensible about the exact usage if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1])) irc_send_reply (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]); else if (msg->params.len < 1) irc_send_reply (c, IRC_ERR_NOORIGIN); else client_send (c, ":%s PONG :%s", c->ctx->server_name, msg->params.vector[0]); } static void irc_handle_pong (const struct irc_message *msg, struct client *c) { // We are the only server, so we don't have to care too much if (msg->params.len < 1) irc_send_reply (c, IRC_ERR_NOORIGIN); else // Set a new timer to send another PING client_set_ping_timer (c); } static void irc_handle_quit (const struct irc_message *msg, struct client *c) { char *reason = xstrdup_printf ("Quit: %s", msg->params.len > 0 ? msg->params.vector[0] : c->nickname); client_close_link (c, reason); free (reason); } static void irc_handle_time (const struct irc_message *msg, struct client *c) { if (msg->params.len > 0 && !irc_is_this_me (c->ctx, msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); char buf[32]; time_t now = time (NULL); struct tm tm; strftime (buf, sizeof buf, "%a %b %d %Y %T", localtime_r (&now, &tm)); irc_send_reply (c, IRC_RPL_TIME, c->ctx->server_name, buf); } static void irc_handle_version (const struct irc_message *msg, struct client *c) { if (msg->params.len > 0 && !irc_is_this_me (c->ctx, msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); irc_send_reply (c, IRC_RPL_VERSION, PROGRAM_VERSION, g_debug_mode, c->ctx->server_name, PROGRAM_NAME " " PROGRAM_VERSION); irc_send_isupport (c); } static void irc_channel_multicast (struct channel *chan, const char *message, struct client *except) { for (struct channel_user *iter = chan->users; iter; iter = iter->next) if (iter->c != except) client_send (iter->c, "%s", message); } static bool irc_modify_mode (unsigned *mask, unsigned mode, bool add) { unsigned orig = *mask; if (add) *mask |= mode; else *mask &= ~mode; return *mask != orig; } static void irc_update_user_mode (struct client *c, unsigned new_mode) { unsigned old_mode = c->mode; c->mode = new_mode; unsigned added = new_mode & ~old_mode; unsigned removed = old_mode & ~new_mode; struct str diff; str_init (&diff); if (added) { str_append_c (&diff, '+'); client_mode_to_str (added, &diff); } if (removed) { str_append_c (&diff, '-'); client_mode_to_str (removed, &diff); } if (diff.len) client_send (c, ":%s MODE %s :%s", c->nickname, c->nickname, diff.str); str_free (&diff); } static void irc_handle_user_mode_change (struct client *c, const char *mode_string) { unsigned new_mode = c->mode; bool adding = true; while (*mode_string) switch (*mode_string++) { case '+': adding = true; break; case '-': adding = false; break; case 'a': // Ignore, the client should use AWAY break; case 'i': irc_modify_mode (&new_mode, IRC_USER_MODE_INVISIBLE, adding); break; case 'w': irc_modify_mode (&new_mode, IRC_USER_MODE_RX_WALLOPS, adding); break; case 'r': // It's not possible to un-restrict yourself if (adding) new_mode |= IRC_USER_MODE_RESTRICTED; break; case 'o': if (!adding) new_mode &= ~IRC_USER_MODE_OPERATOR; else if (c->ssl_cert_fingerprint && str_map_find (&c->ctx->operators, c->ssl_cert_fingerprint)) new_mode |= IRC_USER_MODE_OPERATOR; else client_send (c, ":%s NOTICE %s :Either you're not using an TLS" " client certificate, or the fingerprint doesn't match", c->ctx->server_name, c->nickname); break; case 's': irc_modify_mode (&new_mode, IRC_USER_MODE_RX_SERVER_NOTICES, adding); break; default: RETURN_WITH_REPLY (c, IRC_ERR_UMODEUNKNOWNFLAG); } irc_update_user_mode (c, new_mode); } static void irc_send_channel_list (struct client *c, const char *channel_name, const struct str_vector *list, int reply, int end_reply) { for (size_t i = 0; i < list->len; i++) irc_send_reply (c, reply, channel_name, list->vector[i]); irc_send_reply (c, end_reply, channel_name); } static char * irc_check_expand_user_mask (const char *mask) { struct str result; str_init (&result); str_append (&result, mask); // Make sure it is a complete mask if (!strchr (result.str, '!')) str_append (&result, "!*"); if (!strchr (result.str, '@')) str_append (&result, "@*"); // And validate whatever the result is if (!irc_is_valid_user_mask (result.str)) { str_free (&result); return NULL; } return str_steal (&result); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // Channel MODE command handling. This is by far the worst command to implement // from the whole RFC; don't blame me if it doesn't work exactly as expected. struct mode_processor { // Inputs to set after initialization: char **params; ///< Mode string parameters struct client *c; ///< Who does the changes struct channel *channel; ///< The channel we're modifying struct channel_user *user; ///< Presence of the client in the chan // Internals: bool adding; ///< Currently adding modes char mode_char; ///< Currently processed mode char struct str added; ///< Added modes struct str removed; ///< Removed modes struct str_vector added_params; ///< Params for added modes struct str_vector removed_params; ///< Params for removed modes struct str *output; ///< "added" or "removed" struct str_vector *output_params; ///< Similarly for "*_params" }; static void mode_processor_init (struct mode_processor *self) { memset (self, 0, sizeof *self); str_init (&self->added); str_init (&self->removed); str_vector_init (&self->added_params); str_vector_init (&self->removed_params); } static void mode_processor_free (struct mode_processor *self) { str_free (&self->added); str_free (&self->removed); str_vector_free (&self->added_params); str_vector_free (&self->removed_params); } static const char * mode_processor_next_param (struct mode_processor *self) { if (!*self->params) return NULL; return *self->params++; } static bool mode_processor_check_operator (struct mode_processor *self) { if ((self->user && (self->user->modes & IRC_CHAN_MODE_OPERATOR)) || (self->c->mode & IRC_USER_MODE_OPERATOR)) return true; irc_send_reply (self->c, IRC_ERR_CHANOPRIVSNEEDED, self->channel->name); return false; } static void mode_processor_do_user (struct mode_processor *self, int mode) { const char *target = mode_processor_next_param (self); if (!mode_processor_check_operator (self) || !target) return; struct client *client; struct channel_user *target_user; if (!(client = str_map_find (&self->c->ctx->users, target))) irc_send_reply (self->c, IRC_ERR_NOSUCHNICK, target); else if (!(target_user = channel_get_user (self->channel, client))) irc_send_reply (self->c, IRC_ERR_USERNOTINCHANNEL, target, self->channel->name); else if (irc_modify_mode (&target_user->modes, mode, self->adding)) { str_append_c (self->output, self->mode_char); \ str_vector_add (self->output_params, client->nickname); } } static bool mode_processor_do_chan (struct mode_processor *self, int mode) { if (!mode_processor_check_operator (self) || !irc_modify_mode (&self->channel->modes, mode, self->adding)) return false; str_append_c (self->output, self->mode_char); return true; } static void mode_processor_do_chan_remove (struct mode_processor *self, char mode_char, int mode) { if (self->adding && irc_modify_mode (&self->channel->modes, mode, false)) str_append_c (&self->removed, mode_char); } static void mode_processor_do_list (struct mode_processor *self, struct str_vector *list, int list_msg, int end_msg) { const char *target = mode_processor_next_param (self); if (!target) { if (self->adding) irc_send_channel_list (self->c, self->channel->name, list, list_msg, end_msg); return; } if (!mode_processor_check_operator (self)) return; char *mask = irc_check_expand_user_mask (target); if (!mask) return; size_t i; for (i = 0; i < list->len; i++) if (!irc_strcmp (list->vector[i], mask)) break; bool found = i != list->len; if ((found ^ self->adding)) { if (self->adding) str_vector_add (list, mask); else str_vector_remove (list, i); str_append_c (self->output, self->mode_char); str_vector_add (self->output_params, mask); } free (mask); } static void mode_processor_do_key (struct mode_processor *self) { const char *target = mode_processor_next_param (self); if (!mode_processor_check_operator (self) || !target) return; if (!self->adding) { if (!self->channel->key || irc_strcmp (target, self->channel->key)) return; str_append_c (&self->removed, self->mode_char); str_vector_add (&self->removed_params, self->channel->key); free (self->channel->key); self->channel->key = NULL; } else if (!irc_is_valid_key (target)) // TODO: we should notify the user somehow return; else if (self->channel->key) irc_send_reply (self->c, IRC_ERR_KEYSET, self->channel->name); else { self->channel->key = xstrdup (target); str_append_c (&self->added, self->mode_char); str_vector_add (&self->added_params, self->channel->key); } } static void mode_processor_do_limit (struct mode_processor *self) { if (!mode_processor_check_operator (self)) return; const char *target; if (!self->adding) { if (self->channel->user_limit == -1) return; self->channel->user_limit = -1; str_append_c (&self->removed, self->mode_char); } else if ((target = mode_processor_next_param (self))) { unsigned long x; if (xstrtoul (&x, target, 10) && x > 0 && x <= LONG_MAX) { self->channel->user_limit = x; str_append_c (&self->added, self->mode_char); str_vector_add (&self->added_params, target); } } } static bool mode_processor_step (struct mode_processor *self, char mode_char) { switch ((self->mode_char = mode_char)) { case '+': self->adding = true; self->output = &self->added; self->output_params = &self->added_params; break; case '-': self->adding = false; self->output = &self->removed; self->output_params = &self->removed_params; break; #define USER(mode) mode_processor_do_user (self, (mode)) #define CHAN(mode) mode_processor_do_chan (self, (mode)) case 'o': USER (IRC_CHAN_MODE_OPERATOR); break; case 'v': USER (IRC_CHAN_MODE_VOICE); break; case 'i': CHAN (IRC_CHAN_MODE_INVITE_ONLY); break; case 'm': CHAN (IRC_CHAN_MODE_MODERATED); break; case 'n': CHAN (IRC_CHAN_MODE_NO_OUTSIDE_MSGS); break; case 'q': CHAN (IRC_CHAN_MODE_QUIET); break; case 't': CHAN (IRC_CHAN_MODE_PROTECTED_TOPIC); break; case 'p': if (CHAN (IRC_CHAN_MODE_PRIVATE)) mode_processor_do_chan_remove (self, 's', IRC_CHAN_MODE_SECRET); break; case 's': if (CHAN (IRC_CHAN_MODE_SECRET)) mode_processor_do_chan_remove (self, 'p', IRC_CHAN_MODE_PRIVATE); break; #undef USER #undef CHAN case 'b': mode_processor_do_list (self, &self->channel->ban_list, IRC_RPL_BANLIST, IRC_RPL_ENDOFBANLIST); break; case 'e': mode_processor_do_list (self, &self->channel->exception_list, IRC_RPL_EXCEPTLIST, IRC_RPL_ENDOFEXCEPTLIST); break; case 'I': mode_processor_do_list (self, &self->channel->invite_list, IRC_RPL_INVITELIST, IRC_RPL_ENDOFINVITELIST); break; case 'k': mode_processor_do_key (self); break; case 'l': mode_processor_do_limit (self); break; default: // It's not safe to continue, results could be undesired irc_send_reply (self->c, IRC_ERR_UNKNOWNMODE, mode_char, self->channel->name); return false; } return true; } static void irc_handle_chan_mode_change (struct client *c, struct channel *chan, char *params[]) { struct mode_processor p; mode_processor_init (&p); p.params = params; p.channel = chan; p.c = c; p.user = channel_get_user (chan, c); const char *mode_string; while ((mode_string = mode_processor_next_param (&p))) { mode_processor_step (&p, '+'); while (*mode_string) if (!mode_processor_step (&p, *mode_string++)) break; } // TODO: limit to three changes with parameter per command if (p.added.len || p.removed.len) { struct str message; str_init (&message); str_append_printf (&message, ":%s!%s@%s MODE %s ", p.c->nickname, p.c->username, p.c->hostname, p.channel->name); if (p.added.len) str_append_printf (&message, "+%s", p.added.str); if (p.removed.len) str_append_printf (&message, "-%s", p.removed.str); for (size_t i = 0; i < p.added_params.len; i++) str_append_printf (&message, " %s", p.added_params.vector[i]); for (size_t i = 0; i < p.removed_params.len; i++) str_append_printf (&message, " %s", p.removed_params.vector[i]); irc_channel_multicast (p.channel, message.str, NULL); str_free (&message); } mode_processor_free (&p); } // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static void irc_handle_mode (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *target = msg->params.vector[0]; struct client *client = str_map_find (&c->ctx->users, target); struct channel *chan = str_map_find (&c->ctx->channels, target); if (client) { if (irc_strcmp (target, c->nickname)) RETURN_WITH_REPLY (c, IRC_ERR_USERSDONTMATCH); if (msg->params.len < 2) { char *mode = client_get_mode (client); irc_send_reply (c, IRC_RPL_UMODEIS, mode); free (mode); } else irc_handle_user_mode_change (c, msg->params.vector[1]); } else if (chan) { if (msg->params.len < 2) { char *mode = channel_get_mode (chan, channel_get_user (chan, c)); irc_send_reply (c, IRC_RPL_CHANNELMODEIS, target, mode); irc_send_reply (c, IRC_RPL_CREATIONTIME, target, (long long) chan->created); free (mode); } else irc_handle_chan_mode_change (c, chan, &msg->params.vector[1]); } else irc_send_reply (c, IRC_ERR_NOSUCHNICK, target); } static void irc_handle_user_message (const struct irc_message *msg, struct client *c, const char *command, bool allow_away_reply) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NORECIPIENT, msg->command); if (msg->params.len < 2 || !*msg->params.vector[1]) RETURN_WITH_REPLY (c, IRC_ERR_NOTEXTTOSEND); const char *target = msg->params.vector[0]; const char *text = msg->params.vector[1]; struct client *client = str_map_find (&c->ctx->users, target); if (client) { client_send (client, ":%s!%s@%s %s %s :%s", c->nickname, c->username, c->hostname, command, target, text); if (allow_away_reply && client->away_message) irc_send_reply (c, IRC_RPL_AWAY, target, client->away_message); // Acknowledging a message from the client to itself would be silly if (client != c && (c->caps_enabled & IRC_CAP_ECHO_MESSAGE)) client_send (c, ":%s!%s@%s %s %s :%s", c->nickname, c->username, c->hostname, command, target, text); return; } struct channel *chan = str_map_find (&c->ctx->channels, target); if (chan) { struct channel_user *user = channel_get_user (chan, c); if ((chan->modes & IRC_CHAN_MODE_NO_OUTSIDE_MSGS) && !user) RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target); if ((chan->modes & IRC_CHAN_MODE_MODERATED) && (!user || !(user->modes & (IRC_CHAN_MODE_VOICE | IRC_CHAN_MODE_OPERATOR)))) RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target); if (client_in_mask_list (c, &chan->ban_list) && !client_in_mask_list (c, &chan->exception_list)) RETURN_WITH_REPLY (c, IRC_ERR_CANNOTSENDTOCHAN, target); char *message = xstrdup_printf (":%s!%s@%s %s %s :%s", c->nickname, c->username, c->hostname, command, target, text); irc_channel_multicast (chan, message, (c->caps_enabled & IRC_CAP_ECHO_MESSAGE) ? NULL : c); free (message); return; } irc_send_reply (c, IRC_ERR_NOSUCHNICK, target); } static void irc_handle_privmsg (const struct irc_message *msg, struct client *c) { irc_handle_user_message (msg, c, "PRIVMSG", true); // Let's not care too much about success or failure c->last_active = time (NULL); } static void irc_handle_notice (const struct irc_message *msg, struct client *c) { irc_handle_user_message (msg, c, "NOTICE", false); } static void irc_send_rpl_list (struct client *c, const struct channel *chan) { int visible = 0; for (struct channel_user *user = chan->users; user; user = user->next) visible++; irc_send_reply (c, IRC_RPL_LIST, chan->name, visible, chan->topic); } static void irc_handle_list (const struct irc_message *msg, struct client *c) { if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]); struct channel *chan; if (msg->params.len == 0) { struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); while ((chan = str_map_iter_next (&iter))) if (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET)) || channel_get_user (chan, c)) irc_send_rpl_list (c, chan); } else { struct str_vector channels; str_vector_init (&channels); cstr_split_ignore_empty (msg->params.vector[0], ',', &channels); for (size_t i = 0; i < channels.len; i++) if ((chan = str_map_find (&c->ctx->channels, channels.vector[i])) && (!(chan->modes & IRC_CHAN_MODE_SECRET) || channel_get_user (chan, c))) irc_send_rpl_list (c, chan); str_vector_free (&channels); } irc_send_reply (c, IRC_RPL_LISTEND); } static void irc_append_prefixes (struct client *c, struct channel_user *user, struct str *output) { struct str prefixes; str_init (&prefixes); if (user->modes & IRC_CHAN_MODE_OPERATOR) str_append_c (&prefixes, '@'); if (user->modes & IRC_CHAN_MODE_VOICE) str_append_c (&prefixes, '+'); if (prefixes.len) { if (c->caps_enabled & IRC_CAP_MULTI_PREFIX) str_append (output, prefixes.str); else str_append_c (output, prefixes.str[0]); } str_free (&prefixes); } static char * irc_make_rpl_namreply_item (struct client *c, struct client *target, struct channel_user *user) { struct str result; str_init (&result); if (user) irc_append_prefixes (c, user, &result); str_append (&result, target->nickname); if (c->caps_enabled & IRC_CAP_USERHOST_IN_NAMES) str_append_printf (&result, "!%s@%s", target->username, target->hostname); return str_steal (&result); } static void irc_send_rpl_namreply (struct client *c, const struct channel *chan, struct str_map *used_nicks) { struct str_vector nicks; str_vector_init (&nicks); char type = '='; if (chan->modes & IRC_CHAN_MODE_SECRET) type = '@'; else if (chan->modes & IRC_CHAN_MODE_PRIVATE) type = '*'; bool on_channel = channel_get_user (chan, c); for (struct channel_user *iter = chan->users; iter; iter = iter->next) { if (!on_channel && (iter->c->mode & IRC_USER_MODE_INVISIBLE)) continue; if (used_nicks) str_map_set (used_nicks, iter->c->nickname, (void *) 1); str_vector_add_owned (&nicks, irc_make_rpl_namreply_item (c, iter->c, iter)); } irc_send_reply_vector (c, IRC_RPL_NAMREPLY, nicks.vector, type, chan->name, ""); str_vector_free (&nicks); } static void irc_send_disassociated_names (struct client *c, struct str_map *used) { struct str_vector nicks; str_vector_init (&nicks); struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->users); struct client *target; while ((target = str_map_iter_next (&iter))) { if ((target->mode & IRC_USER_MODE_INVISIBLE) || str_map_find (used, target->nickname)) continue; str_vector_add_owned (&nicks, irc_make_rpl_namreply_item (c, target, NULL)); } if (nicks.len) irc_send_reply_vector (c, IRC_RPL_NAMREPLY, nicks.vector, '*', "*", ""); str_vector_free (&nicks); } static void irc_handle_names (const struct irc_message *msg, struct client *c) { if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]); struct channel *chan; if (msg->params.len == 0) { struct str_map used; str_map_init (&used); used.key_xfrm = irc_strxfrm; struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); while ((chan = str_map_iter_next (&iter))) if (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET)) || channel_get_user (chan, c)) irc_send_rpl_namreply (c, chan, &used); // Also send all visible users we haven't listed yet irc_send_disassociated_names (c, &used); str_map_free (&used); irc_send_reply (c, IRC_RPL_ENDOFNAMES, "*"); } else { struct str_vector channels; str_vector_init (&channels); cstr_split_ignore_empty (msg->params.vector[0], ',', &channels); for (size_t i = 0; i < channels.len; i++) if ((chan = str_map_find (&c->ctx->channels, channels.vector[i])) && (!(chan->modes & IRC_CHAN_MODE_SECRET) || channel_get_user (chan, c))) { irc_send_rpl_namreply (c, chan, NULL); irc_send_reply (c, IRC_RPL_ENDOFNAMES, channels.vector[i]); } str_vector_free (&channels); } } static void irc_send_rpl_whoreply (struct client *c, const struct channel *chan, const struct client *target) { struct str chars; str_init (&chars); str_append_c (&chars, target->away_message ? 'G' : 'H'); if (target->mode & IRC_USER_MODE_OPERATOR) str_append_c (&chars, '*'); struct channel_user *user; if (chan && (user = channel_get_user (chan, target))) irc_append_prefixes (c, user, &chars); irc_send_reply (c, IRC_RPL_WHOREPLY, chan ? chan->name : "*", target->username, target->hostname, target->ctx->server_name, target->nickname, chars.str, 0 /* hop count */, target->realname); str_free (&chars); } static void irc_match_send_rpl_whoreply (struct client *c, struct client *target, const char *mask) { bool is_roommate = false; struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); struct channel *chan; while ((chan = str_map_iter_next (&iter))) if (channel_get_user (chan, target) && channel_get_user (chan, c)) { is_roommate = true; break; } if ((target->mode & IRC_USER_MODE_INVISIBLE) && !is_roommate) return; if (irc_fnmatch (mask, target->hostname) && irc_fnmatch (mask, target->nickname) && irc_fnmatch (mask, target->realname) && irc_fnmatch (mask, c->ctx->server_name)) return; // Try to find a channel they're on that's visible to us struct channel *user_chan = NULL; str_map_iter_init (&iter, &c->ctx->channels); while ((chan = str_map_iter_next (&iter))) if (channel_get_user (chan, target) && (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET)) || channel_get_user (chan, c))) { user_chan = chan; break; } irc_send_rpl_whoreply (c, user_chan, target); } static void irc_handle_who (const struct irc_message *msg, struct client *c) { bool only_ops = msg->params.len > 1 && !strcmp (msg->params.vector[1], "o"); const char *shown_mask = msg->params.vector[0], *used_mask; if (!shown_mask) used_mask = shown_mask = "*"; else if (!strcmp (shown_mask, "0")) used_mask = "*"; else used_mask = shown_mask; struct channel *chan; if ((chan = str_map_find (&c->ctx->channels, used_mask))) { bool on_chan = !!channel_get_user (chan, c); if (on_chan || !(chan->modes & IRC_CHAN_MODE_SECRET)) for (struct channel_user *iter = chan->users; iter; iter = iter->next) { if ((on_chan || !(iter->c->mode & IRC_USER_MODE_INVISIBLE)) && (!only_ops || (iter->c->mode & IRC_USER_MODE_OPERATOR))) irc_send_rpl_whoreply (c, chan, iter->c); } } else { struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->users); struct client *target; while ((target = str_map_iter_next (&iter))) if (!only_ops || (target->mode & IRC_USER_MODE_OPERATOR)) irc_match_send_rpl_whoreply (c, target, used_mask); } irc_send_reply (c, IRC_RPL_ENDOFWHO, shown_mask); } static void irc_send_whois_reply (struct client *c, const struct client *target) { const char *nick = target->nickname; irc_send_reply (c, IRC_RPL_WHOISUSER, nick, target->username, target->hostname, target->realname); irc_send_reply (c, IRC_RPL_WHOISSERVER, nick, target->ctx->server_name, str_map_find (&c->ctx->config, "server_info")); if (target->mode & IRC_USER_MODE_OPERATOR) irc_send_reply (c, IRC_RPL_WHOISOPERATOR, nick); irc_send_reply (c, IRC_RPL_WHOISIDLE, nick, (int) (time (NULL) - target->last_active)); if (target->away_message) irc_send_reply (c, IRC_RPL_AWAY, nick, target->away_message); struct str_vector channels; str_vector_init (&channels); struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->channels); struct channel *chan; struct channel_user *channel_user; while ((chan = str_map_iter_next (&iter))) if ((channel_user = channel_get_user (chan, target)) && (!(chan->modes & (IRC_CHAN_MODE_PRIVATE | IRC_CHAN_MODE_SECRET)) || channel_get_user (chan, c))) { struct str item; str_init (&item); if (channel_user->modes & IRC_CHAN_MODE_OPERATOR) str_append_c (&item, '@'); else if (channel_user->modes & IRC_CHAN_MODE_VOICE) str_append_c (&item, '+'); str_append (&item, chan->name); str_vector_add_owned (&channels, str_steal (&item)); } irc_send_reply_vector (c, IRC_RPL_WHOISCHANNELS, channels.vector, nick, ""); str_vector_free (&channels); irc_send_reply (c, IRC_RPL_ENDOFWHOIS, nick); } static void irc_handle_whois (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); struct str_vector masks; str_vector_init (&masks); const char *masks_str = msg->params.vector[msg->params.len > 1]; cstr_split_ignore_empty (masks_str, ',', &masks); for (size_t i = 0; i < masks.len; i++) { const char *mask = masks.vector[i]; struct client *target; if (!strpbrk (mask, "*?")) { if (!(target = str_map_find (&c->ctx->users, mask))) irc_send_reply (c, IRC_ERR_NOSUCHNICK, mask); else irc_send_whois_reply (c, target); } else { struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->users); bool found = false; while ((target = str_map_iter_next (&iter)) && !irc_fnmatch (mask, target->nickname)) { irc_send_whois_reply (c, target); found = true; } if (!found) irc_send_reply (c, IRC_ERR_NOSUCHNICK, mask); } } str_vector_free (&masks); } static void irc_handle_whowas (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); if (msg->params.len > 2 && !irc_is_this_me (c->ctx, msg->params.vector[2])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[2]); // The "count" parameter is ignored, we only store one entry for a nick struct str_vector nicks; str_vector_init (&nicks); cstr_split_ignore_empty (msg->params.vector[0], ',', &nicks); for (size_t i = 0; i < nicks.len; i++) { const char *nick = nicks.vector[i]; struct whowas_info *info = str_map_find (&c->ctx->whowas, nick); if (!info) irc_send_reply (c, IRC_ERR_WASNOSUCHNICK, nick); else { irc_send_reply (c, IRC_RPL_WHOWASUSER, nick, info->username, info->hostname, info->realname); irc_send_reply (c, IRC_RPL_WHOISSERVER, nick, c->ctx->server_name, str_map_find (&c->ctx->config, "server_info")); } irc_send_reply (c, IRC_RPL_ENDOFWHOWAS, nick); } str_vector_free (&nicks); } static void irc_send_rpl_topic (struct client *c, struct channel *chan) { if (!*chan->topic) irc_send_reply (c, IRC_RPL_NOTOPIC, chan->name); else { irc_send_reply (c, IRC_RPL_TOPIC, chan->name, chan->topic); irc_send_reply (c, IRC_RPL_TOPICWHOTIME, chan->name, chan->topic_who, (long long) chan->topic_time); } } static void irc_handle_topic (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *target = msg->params.vector[0]; struct channel *chan = str_map_find (&c->ctx->channels, target); if (!chan) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHCHANNEL, target); if (msg->params.len < 2) { irc_send_rpl_topic (c, chan); return; } struct channel_user *user = channel_get_user (chan, c); if (!user) RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, target); if ((chan->modes & IRC_CHAN_MODE_PROTECTED_TOPIC) && !(user->modes & IRC_CHAN_MODE_OPERATOR)) RETURN_WITH_REPLY (c, IRC_ERR_CHANOPRIVSNEEDED, target); free (chan->topic); free (chan->topic_who); chan->topic = xstrdup (msg->params.vector[1]); chan->topic_who = xstrdup_printf ("%s!%s@%s", c->nickname, c->username, c->hostname); chan->topic_time = time (NULL); char *message = xstrdup_printf (":%s!%s@%s TOPIC %s :%s", c->nickname, c->username, c->hostname, target, chan->topic); irc_channel_multicast (chan, message, NULL); free (message); } static void irc_try_part (struct client *c, const char *channel_name, const char *reason) { if (!reason) reason = c->nickname; struct channel *chan; if (!(chan = str_map_find (&c->ctx->channels, channel_name))) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHCHANNEL, channel_name); struct channel_user *user; if (!(user = channel_get_user (chan, c))) RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, channel_name); char *message = xstrdup_printf (":%s!%s@%s PART %s :%s", c->nickname, c->username, c->hostname, channel_name, reason); if (!(chan->modes & IRC_CHAN_MODE_QUIET)) irc_channel_multicast (chan, message, NULL); else client_send (c, "%s", message); free (message); channel_remove_user (chan, user); irc_channel_destroy_if_empty (c->ctx, chan); } static void irc_part_all_channels (struct client *c) { // We have to be careful here, the channel might get destroyed struct str_map_unset_iter iter; str_map_unset_iter_init (&iter, &c->ctx->channels); struct channel *chan; while ((chan = str_map_unset_iter_next (&iter))) if (channel_get_user (chan, c)) irc_try_part (c, chan->name, NULL); str_map_unset_iter_free (&iter); } static void irc_handle_part (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *reason = msg->params.len > 1 ? msg->params.vector[1] : NULL; struct str_vector channels; str_vector_init (&channels); cstr_split_ignore_empty (msg->params.vector[0], ',', &channels); for (size_t i = 0; i < channels.len; i++) irc_try_part (c, channels.vector[i], reason); str_vector_free (&channels); } static void irc_try_kick (struct client *c, const char *channel_name, const char *nick, const char *reason) { struct channel *chan; if (!(chan = str_map_find (&c->ctx->channels, channel_name))) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHCHANNEL, channel_name); struct channel_user *user; if (!(user = channel_get_user (chan, c))) RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, channel_name); if (!(user->modes & IRC_CHAN_MODE_OPERATOR)) RETURN_WITH_REPLY (c, IRC_ERR_CHANOPRIVSNEEDED, channel_name); struct client *client; if (!(client = str_map_find (&c->ctx->users, nick)) || !(user = channel_get_user (chan, client))) RETURN_WITH_REPLY (c, IRC_ERR_USERNOTINCHANNEL, nick, channel_name); char *message = xstrdup_printf (":%s!%s@%s KICK %s %s :%s", c->nickname, c->username, c->hostname, channel_name, nick, reason); if (!(chan->modes & IRC_CHAN_MODE_QUIET)) irc_channel_multicast (chan, message, NULL); else client_send (c, "%s", message); free (message); channel_remove_user (chan, user); irc_channel_destroy_if_empty (c->ctx, chan); } static void irc_handle_kick (const struct irc_message *msg, struct client *c) { if (msg->params.len < 2) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *reason = c->nickname; if (msg->params.len > 2) reason = msg->params.vector[2]; struct str_vector channels; struct str_vector users; str_vector_init (&channels); str_vector_init (&users); cstr_split_ignore_empty (msg->params.vector[0], ',', &channels); cstr_split_ignore_empty (msg->params.vector[1], ',', &users); if (channels.len == 1) for (size_t i = 0; i < users.len; i++) irc_try_kick (c, channels.vector[0], users.vector[i], reason); else for (size_t i = 0; i < channels.len && i < users.len; i++) irc_try_kick (c, channels.vector[i], users.vector[i], reason); str_vector_free (&channels); str_vector_free (&users); } static void irc_send_invite_notifications (struct channel *chan, struct client *c, struct client *target) { for (struct channel_user *iter = chan->users; iter; iter = iter->next) if (iter->c != target && iter->c->caps_enabled & IRC_CAP_INVITE_NOTIFY) client_send (iter->c, ":%s!%s@%s INVITE %s %s", c->nickname, c->username, c->hostname, target->nickname, chan->name); } static void irc_handle_invite (const struct irc_message *msg, struct client *c) { if (msg->params.len < 2) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); const char *target = msg->params.vector[0]; const char *channel_name = msg->params.vector[1]; struct client *client = str_map_find (&c->ctx->users, target); if (!client) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHNICK, target); struct channel *chan = str_map_find (&c->ctx->channels, channel_name); if (chan) { struct channel_user *inviting_user; if (!(inviting_user = channel_get_user (chan, c))) RETURN_WITH_REPLY (c, IRC_ERR_NOTONCHANNEL, channel_name); if (channel_get_user (chan, client)) RETURN_WITH_REPLY (c, IRC_ERR_USERONCHANNEL, target, channel_name); if ((inviting_user->modes & IRC_CHAN_MODE_OPERATOR)) str_map_set (&client->invites, channel_name, (void *) 1); else if ((chan->modes & IRC_CHAN_MODE_INVITE_ONLY)) RETURN_WITH_REPLY (c, IRC_ERR_CHANOPRIVSNEEDED, channel_name); // It's not specified when and how we should send out invite-notify if (chan->modes & IRC_CHAN_MODE_INVITE_ONLY) irc_send_invite_notifications (chan, c, client); } client_send (client, ":%s!%s@%s INVITE %s %s", c->nickname, c->username, c->hostname, client->nickname, channel_name); if (client->away_message) irc_send_reply (c, IRC_RPL_AWAY, client->nickname, client->away_message); irc_send_reply (c, IRC_RPL_INVITING, client->nickname, channel_name); } static void irc_try_join (struct client *c, const char *channel_name, const char *key) { struct channel *chan = str_map_find (&c->ctx->channels, channel_name); unsigned user_mode = 0; if (!chan) { if (irc_validate_channel_name (channel_name) != VALIDATION_OK) RETURN_WITH_REPLY (c, IRC_ERR_BADCHANMASK, channel_name); chan = irc_channel_create (c->ctx, channel_name); user_mode = IRC_CHAN_MODE_OPERATOR; } else if (channel_get_user (chan, c)) return; bool invited_by_chanop = str_map_find (&c->invites, channel_name); if ((chan->modes & IRC_CHAN_MODE_INVITE_ONLY) && !client_in_mask_list (c, &chan->invite_list) && !invited_by_chanop) RETURN_WITH_REPLY (c, IRC_ERR_INVITEONLYCHAN, channel_name); if (chan->key && (!key || strcmp (key, chan->key))) RETURN_WITH_REPLY (c, IRC_ERR_BADCHANNELKEY, channel_name); if (chan->user_limit != -1 && channel_user_count (chan) >= (size_t) chan->user_limit) RETURN_WITH_REPLY (c, IRC_ERR_CHANNELISFULL, channel_name); if (client_in_mask_list (c, &chan->ban_list) && !client_in_mask_list (c, &chan->exception_list) && !invited_by_chanop) RETURN_WITH_REPLY (c, IRC_ERR_BANNEDFROMCHAN, channel_name); // Destroy any invitation as there's no other way to get rid of it str_map_set (&c->invites, channel_name, NULL); channel_add_user (chan, c)->modes = user_mode; char *message = xstrdup_printf (":%s!%s@%s JOIN %s", c->nickname, c->username, c->hostname, channel_name); if (!(chan->modes & IRC_CHAN_MODE_QUIET)) irc_channel_multicast (chan, message, NULL); else client_send (c, "%s", message); free (message); irc_send_rpl_topic (c, chan); irc_send_rpl_namreply (c, chan, NULL); irc_send_reply (c, IRC_RPL_ENDOFNAMES, chan->name); } static void irc_handle_join (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); if (!strcmp (msg->params.vector[0], "0")) { irc_part_all_channels (c); return; } struct str_vector channels; struct str_vector keys; str_vector_init (&channels); str_vector_init (&keys); cstr_split_ignore_empty (msg->params.vector[0], ',', &channels); if (msg->params.len > 1) cstr_split_ignore_empty (msg->params.vector[1], ',', &keys); for (size_t i = 0; i < channels.len; i++) irc_try_join (c, channels.vector[i], i < keys.len ? keys.vector[i] : NULL); str_vector_free (&channels); str_vector_free (&keys); } static void irc_handle_summon (const struct irc_message *msg, struct client *c) { (void) msg; irc_send_reply (c, IRC_ERR_SUMMONDISABLED); } static void irc_handle_users (const struct irc_message *msg, struct client *c) { (void) msg; irc_send_reply (c, IRC_ERR_USERSDISABLED); } static void irc_handle_away (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) { free (c->away_message); c->away_message = NULL; irc_send_reply (c, IRC_RPL_UNAWAY); } else { free (c->away_message); c->away_message = xstrdup (msg->params.vector[0]); irc_send_reply (c, IRC_RPL_NOWAWAY); } } static void irc_handle_ison (const struct irc_message *msg, struct client *c) { if (msg->params.len < 1) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); struct str result; str_init (&result); const char *nick; if (str_map_find (&c->ctx->users, (nick = msg->params.vector[0]))) str_append (&result, nick); for (size_t i = 1; i < msg->params.len; i++) if (str_map_find (&c->ctx->users, (nick = msg->params.vector[i]))) str_append_printf (&result, " %s", nick); irc_send_reply (c, IRC_RPL_ISON, result.str); str_free (&result); } static void irc_handle_admin (const struct irc_message *msg, struct client *c) { if (msg->params.len > 0 && !irc_is_this_me (c->ctx, msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); irc_send_reply (c, IRC_ERR_NOADMININFO, c->ctx->server_name); } static void irc_handle_stats_links (struct client *c, const struct irc_message *msg) { // There is only an "l" query in RFC 2812 but we cannot link, // so instead we provide the "L" query giving information for all users const char *filter = NULL; if (msg->params.len > 1) filter = msg->params.vector[1]; for (struct client *iter = c->ctx->clients; iter; iter = iter->next) { if (filter && irc_strcmp (iter->nickname, filter)) continue; irc_send_reply (c, IRC_RPL_STATSLINKINFO, iter->address, // linkname iter->write_buffer.len, // sendq iter->n_sent_messages, iter->sent_bytes / 1024, iter->n_received_messages, iter->received_bytes / 1024, (long long) (time (NULL) - iter->opened)); } } static void irc_handle_stats_commands (struct client *c) { struct str_map_iter iter; str_map_iter_init (&iter, &c->ctx->handlers); struct irc_command *handler; while ((handler = str_map_iter_next (&iter))) { if (!handler->n_received) continue; irc_send_reply (c, IRC_RPL_STATSCOMMANDS, handler->name, handler->n_received, handler->bytes_received, (size_t) 0); } } static void irc_handle_stats_uptime (struct client *c) { time_t uptime = time (NULL) - c->ctx->started; int days = uptime / 60 / 60 / 24; int hours = (uptime % (60 * 60 * 24)) / 60 / 60; int mins = (uptime % (60 * 60)) / 60; int secs = uptime % 60; irc_send_reply (c, IRC_RPL_STATSUPTIME, days, hours, mins, secs); } static void irc_handle_stats (const struct irc_message *msg, struct client *c) { char query; if (msg->params.len < 1 || !(query = *msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[1])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[1]); if (!(c->mode & IRC_USER_MODE_OPERATOR)) RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES); switch (query) { case 'L': irc_handle_stats_links (c, msg); break; case 'm': irc_handle_stats_commands (c); break; case 'u': irc_handle_stats_uptime (c); break; } irc_send_reply (c, IRC_RPL_ENDOFSTATS, query); } static void irc_handle_links (const struct irc_message *msg, struct client *c) { if (msg->params.len > 1 && !irc_is_this_me (c->ctx, msg->params.vector[0])) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHSERVER, msg->params.vector[0]); const char *mask = "*"; if (msg->params.len > 0) mask = msg->params.vector[msg->params.len > 1]; if (!irc_fnmatch (mask, c->ctx->server_name)) irc_send_reply (c, IRC_RPL_LINKS, mask, c->ctx->server_name, 0 /* hop count */, str_map_find (&c->ctx->config, "server_info")); irc_send_reply (c, IRC_RPL_ENDOFLINKS, mask); } static void irc_handle_kill (const struct irc_message *msg, struct client *c) { if (msg->params.len < 2) RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command); if (!(c->mode & IRC_USER_MODE_OPERATOR)) RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES); struct client *target; if (!(target = str_map_find (&c->ctx->users, msg->params.vector[0]))) RETURN_WITH_REPLY (c, IRC_ERR_NOSUCHNICK, msg->params.vector[0]); char *reason = xstrdup_printf ("Killed by %s: %s", c->nickname, msg->params.vector[1]); client_close_link (target, reason); free (reason); } static void irc_handle_die (const struct irc_message *msg, struct client *c) { (void) msg; if (!(c->mode & IRC_USER_MODE_OPERATOR)) RETURN_WITH_REPLY (c, IRC_ERR_NOPRIVILEGES); if (!c->ctx->quitting) irc_initiate_quit (c->ctx); } // ----------------------------------------------------------------------------- static void irc_register_handlers (struct server_context *ctx) { // TODO: add an index for IRC_ERR_NOSUCHSERVER validation? // TODO: add a minimal parameter count? // TODO: add a field for oper-only commands? static struct irc_command message_handlers[] = { { "CAP", false, irc_handle_cap, 0, 0 }, { "PASS", false, irc_handle_pass, 0, 0 }, { "NICK", false, irc_handle_nick, 0, 0 }, { "USER", false, irc_handle_user, 0, 0 }, { "USERHOST", true, irc_handle_userhost, 0, 0 }, { "LUSERS", true, irc_handle_lusers, 0, 0 }, { "MOTD", true, irc_handle_motd, 0, 0 }, { "PING", true, irc_handle_ping, 0, 0 }, { "PONG", false, irc_handle_pong, 0, 0 }, { "QUIT", false, irc_handle_quit, 0, 0 }, { "TIME", true, irc_handle_time, 0, 0 }, { "VERSION", true, irc_handle_version, 0, 0 }, { "USERS", true, irc_handle_users, 0, 0 }, { "SUMMON", true, irc_handle_summon, 0, 0 }, { "AWAY", true, irc_handle_away, 0, 0 }, { "ADMIN", true, irc_handle_admin, 0, 0 }, { "STATS", true, irc_handle_stats, 0, 0 }, { "LINKS", true, irc_handle_links, 0, 0 }, { "MODE", true, irc_handle_mode, 0, 0 }, { "PRIVMSG", true, irc_handle_privmsg, 0, 0 }, { "NOTICE", true, irc_handle_notice, 0, 0 }, { "JOIN", true, irc_handle_join, 0, 0 }, { "PART", true, irc_handle_part, 0, 0 }, { "KICK", true, irc_handle_kick, 0, 0 }, { "INVITE", true, irc_handle_invite, 0, 0 }, { "TOPIC", true, irc_handle_topic, 0, 0 }, { "LIST", true, irc_handle_list, 0, 0 }, { "NAMES", true, irc_handle_names, 0, 0 }, { "WHO", true, irc_handle_who, 0, 0 }, { "WHOIS", true, irc_handle_whois, 0, 0 }, { "WHOWAS", true, irc_handle_whowas, 0, 0 }, { "ISON", true, irc_handle_ison, 0, 0 }, { "KILL", true, irc_handle_kill, 0, 0 }, { "DIE", true, irc_handle_die, 0, 0 }, }; for (size_t i = 0; i < N_ELEMENTS (message_handlers); i++) { const struct irc_command *cmd = &message_handlers[i]; str_map_set (&ctx->handlers, cmd->name, (void *) cmd); } } static void irc_process_message (const struct irc_message *msg, const char *raw, void *user_data) { (void) raw; struct client *c = user_data; if (c->closing_link) return; c->n_received_messages++; c->received_bytes += strlen (raw) + 2; if (!flood_detector_check (&c->antiflood)) { client_close_link (c, "Excess flood"); return; } struct irc_command *cmd = str_map_find (&c->ctx->handlers, msg->command); if (!cmd) irc_send_reply (c, IRC_ERR_UNKNOWNCOMMAND, msg->command); else { cmd->n_received++; cmd->bytes_received += strlen (raw) + 2; if (cmd->requires_registration && !c->registered) irc_send_reply (c, IRC_ERR_NOTREGISTERED); else cmd->handler (msg, c); } } // --- Network I/O ------------------------------------------------------------- static bool irc_try_read (struct client *c) { struct str *buf = &c->read_buffer; ssize_t n_read; while (true) { str_ensure_space (buf, 512); n_read = recv (c->socket_fd, buf->str + buf->len, buf->alloc - buf->len - 1 /* null byte */, 0); if (n_read > 0) { buf->str[buf->len += n_read] = '\0'; // TODO: discard characters above the 512 character limit irc_process_buffer (buf, irc_process_message, c); continue; } if (n_read == 0) { client_kill (c, NULL); return false; } if (errno == EAGAIN) return true; if (errno == EINTR) continue; print_debug ("%s: %s: %s", __func__, "recv", strerror (errno)); client_kill (c, strerror (errno)); return false; } } static bool irc_try_read_tls (struct client *c) { if (c->ssl_tx_want_rx) return true; struct str *buf = &c->read_buffer; c->ssl_rx_want_tx = false; while (true) { str_ensure_space (buf, 512); ERR_clear_error (); int n_read = SSL_read (c->ssl, buf->str + buf->len, buf->alloc - buf->len - 1 /* null byte */); const char *error_info = NULL; switch (xssl_get_error (c->ssl, n_read, &error_info)) { case SSL_ERROR_NONE: buf->str[buf->len += n_read] = '\0'; // TODO: discard characters above the 512 character limit irc_process_buffer (buf, irc_process_message, c); continue; case SSL_ERROR_ZERO_RETURN: client_kill (c, NULL); return false; case SSL_ERROR_WANT_READ: return true; case SSL_ERROR_WANT_WRITE: c->ssl_rx_want_tx = true; return true; case XSSL_ERROR_TRY_AGAIN: continue; default: print_debug ("%s: %s: %s", __func__, "SSL_read", error_info); client_kill (c, error_info); return false; } } } static bool irc_try_write (struct client *c) { struct str *buf = &c->write_buffer; ssize_t n_written; while (buf->len) { n_written = send (c->socket_fd, buf->str, buf->len, 0); if (n_written >= 0) { str_remove_slice (buf, 0, n_written); continue; } if (errno == EAGAIN) return true; if (errno == EINTR) continue; print_debug ("%s: %s: %s", __func__, "send", strerror (errno)); client_kill (c, strerror (errno)); return false; } return true; } static bool irc_try_write_tls (struct client *c) { if (c->ssl_rx_want_tx) return true; struct str *buf = &c->write_buffer; c->ssl_tx_want_rx = false; while (buf->len) { ERR_clear_error (); int n_written = SSL_write (c->ssl, buf->str, buf->len); const char *error_info = NULL; switch (xssl_get_error (c->ssl, n_written, &error_info)) { case SSL_ERROR_NONE: str_remove_slice (buf, 0, n_written); continue; case SSL_ERROR_ZERO_RETURN: client_kill (c, NULL); return false; case SSL_ERROR_WANT_WRITE: return true; case SSL_ERROR_WANT_READ: c->ssl_tx_want_rx = true; return true; case XSSL_ERROR_TRY_AGAIN: continue; default: print_debug ("%s: %s: %s", __func__, "SSL_write", error_info); client_kill (c, error_info); return false; } } return true; } static bool irc_autodetect_tls (struct client *c) { // Trivial SSL/TLS autodetection. The first block of data returned by // recv() must be at least three bytes long for this to work reliably, // but that should not pose a problem in practice. // // SSL2: 1xxx xxxx | xxxx xxxx | <1> // (message length) (client hello) // SSL3/TLS: <22> | <3> | xxxx xxxx // (handshake)| (protocol version) // // Such byte sequences should never occur at the beginning of regular IRC // communication, which usually begins with USER/NICK/PASS/SERVICE. char buf[3]; start: switch (recv (c->socket_fd, buf, sizeof buf, MSG_PEEK)) { case 3: if ((buf[0] & 0x80) && buf[2] == 1) return true; case 2: if (buf[0] == 22 && buf[1] == 3) return true; break; case 1: if (buf[0] == 22) return true; break; case 0: break; default: if (errno == EINTR) goto start; } return false; } static bool client_initialize_tls (struct client *c) { const char *error_info = NULL; if (!c->ctx->ssl_ctx) { error_info = "TLS support disabled"; goto error_ssl_1; } ERR_clear_error (); c->ssl = SSL_new (c->ctx->ssl_ctx); if (!c->ssl) goto error_ssl_2; if (!SSL_set_fd (c->ssl, c->socket_fd)) goto error_ssl_3; SSL_set_accept_state (c->ssl); return true; error_ssl_3: SSL_free (c->ssl); c->ssl = NULL; error_ssl_2: error_info = ERR_reason_error_string (ERR_get_error ()); error_ssl_1: print_debug ("could not initialize TLS for %s: %s", c->address, error_info); return false; } static void on_client_ready (const struct pollfd *pfd, void *user_data) { struct client *c = user_data; if (!c->initialized) { hard_assert (pfd->events == POLLIN); if (irc_autodetect_tls (c) && !client_initialize_tls (c)) { client_kill (c, NULL); return; } c->initialized = true; client_set_ping_timer (c); } if (c->ssl) { // Reads may want to write, writes may want to read, poll() may // return unexpected things in `revents'... let's try both if (!irc_try_read_tls (c) || !irc_try_write_tls (c)) return; } else if (!irc_try_read (c) || !irc_try_write (c)) return; client_update_poller (c, pfd); // The purpose of the `closing_link' state is to transfer the `ERROR' if (c->closing_link && !c->half_closed && !c->write_buffer.len) { // To make sure the client has received our ERROR message, we must // first half-close the connection, otherwise it could happen that they // receive a RST from our TCP stack first when we receive further data // We only send the "close notify" alert if libssl can write to the // socket at this moment. All the other data has been already written, // though, and the client will receive a TCP half-close as usual, so // it's not that important if the alert actually gets through. if (c->ssl) (void) SSL_shutdown (c->ssl); // Either the shutdown succeeds, in which case we set a flag so that // we don't retry this action and wait until we get an EOF, or it fails // and we just kill the client straight away if (!shutdown (c->socket_fd, SHUT_WR)) c->half_closed = true; else client_kill (c, NULL); } } static void client_update_poller (struct client *c, const struct pollfd *pfd) { int new_events = POLLIN; if (c->ssl) { if (c->write_buffer.len || c->ssl_rx_want_tx) new_events |= POLLOUT; // While we're waiting for an opposite event, we ignore the original if (c->ssl_rx_want_tx) new_events &= ~POLLIN; if (c->ssl_tx_want_rx) new_events &= ~POLLOUT; } else if (c->write_buffer.len) new_events |= POLLOUT; hard_assert (new_events != 0); if (!pfd || pfd->events != new_events) poller_fd_set (&c->socket_event, new_events); } static bool irc_try_fetch_client (struct server_context *ctx, int listen_fd) { // XXX: `struct sockaddr_storage' is not the most portable thing struct sockaddr_storage peer; socklen_t peer_len = sizeof peer; int fd = accept (listen_fd, (struct sockaddr *) &peer, &peer_len); if (fd == -1) { if (errno == EAGAIN) return false; if (errno == EINTR || errno == ECONNABORTED) return true; // TODO: handle resource exhaustion (EMFILE, ENFILE) specially // (stop accepting new connections and wait until we close some; // also set a timer in case of ENFILE). print_fatal ("%s: %s", "accept", strerror (errno)); irc_initiate_quit (ctx); return false; } if (ctx->max_connections != 0 && ctx->n_clients >= ctx->max_connections) { print_debug ("connection limit reached, refusing connection"); close (fd); return true; } char host[NI_MAXHOST] = "unknown", port[NI_MAXSERV] = "unknown"; int err = getnameinfo ((struct sockaddr *) &peer, peer_len, host, sizeof host, port, sizeof port, NI_NUMERICSERV); if (err) print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); char *address = format_host_port_pair (host, port); print_debug ("accepted connection from %s", address); struct client *c = xmalloc (sizeof *c); client_init (c); c->ctx = ctx; c->opened = time (NULL); c->socket_fd = fd; c->hostname = xstrdup (host); c->address = address; c->last_active = time (NULL); LIST_PREPEND (ctx->clients, c); ctx->n_clients++; poller_fd_init (&c->socket_event, &c->ctx->poller, c->socket_fd); c->socket_event.dispatcher = (poller_fd_fn) on_client_ready; c->socket_event.user_data = c; poller_timer_init (&c->kill_timer, &c->ctx->poller); c->kill_timer.dispatcher = on_client_kill_timer; c->kill_timer.user_data = c; poller_timer_init (&c->timeout_timer, &c->ctx->poller); c->timeout_timer.dispatcher = on_client_timeout_timer; c->timeout_timer.user_data = c; poller_timer_init (&c->ping_timer, &c->ctx->poller); c->ping_timer.dispatcher = on_client_ping_timer; c->ping_timer.user_data = c; set_blocking (fd, false); client_update_poller (c, NULL); client_set_kill_timer (c); return true; } static void on_irc_client_available (const struct pollfd *pfd, void *user_data) { struct server_context *ctx = user_data; while (irc_try_fetch_client (ctx, pfd->fd)) ; } // --- Application setup ------------------------------------------------------- static int irc_ssl_verify_callback (int verify_ok, X509_STORE_CTX *ctx) { (void) verify_ok; (void) ctx; // We only want to provide additional privileges based on the client's // certificate, so let's not terminate the connection because of a failure. return 1; } static void irc_ssl_info_callback (const SSL *ssl, int where, int ret) { // For debugging only; provides us with the most important information struct str s; str_init (&s); if (where & SSL_CB_LOOP) str_append_printf (&s, "loop (%s) ", SSL_state_string_long (ssl)); if (where & SSL_CB_EXIT) str_append_printf (&s, "exit (%d in %s) ", ret, SSL_state_string_long (ssl)); if (where & SSL_CB_READ) str_append (&s, "read "); if (where & SSL_CB_WRITE) str_append (&s, "write "); if (where & SSL_CB_ALERT) str_append_printf (&s, "alert (%s: %s) ", SSL_alert_type_string_long (ret), SSL_alert_desc_string_long (ret)); if (where & SSL_CB_HANDSHAKE_START) str_append (&s, "handshake start "); if (where & SSL_CB_HANDSHAKE_DONE) str_append (&s, "handshake done "); print_debug ("ssl <%p> %s", ssl, s.str); str_free (&s); } static bool irc_initialize_ssl_ctx (struct server_context *ctx, const char *cert_path, const char *key_path, struct error **e) { ERR_clear_error (); ctx->ssl_ctx = SSL_CTX_new (SSLv23_server_method ()); if (!ctx->ssl_ctx) { error_set (e, "%s: %s", "could not initialize TLS", ERR_reason_error_string (ERR_get_error ())); return false; } SSL_CTX_set_verify (ctx->ssl_ctx, SSL_VERIFY_PEER | SSL_VERIFY_CLIENT_ONCE, irc_ssl_verify_callback); if (g_debug_mode) SSL_CTX_set_info_callback (ctx->ssl_ctx, irc_ssl_info_callback); const unsigned char session_id_context[SSL_MAX_SSL_SESSION_ID_LENGTH] = PROGRAM_NAME; (void) SSL_CTX_set_session_id_context (ctx->ssl_ctx, session_id_context, sizeof session_id_context); // Gah, spare me your awkward semantics, I just want to push data! SSL_CTX_set_mode (ctx->ssl_ctx, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER | SSL_MODE_ENABLE_PARTIAL_WRITE); // Disable deprecated protocols (see RFC 7568) SSL_CTX_set_options (ctx->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); // XXX: perhaps we should read the files ourselves for better messages const char *ciphers = str_map_find (&ctx->config, "tls_ciphers"); if (!SSL_CTX_set_cipher_list (ctx->ssl_ctx, ciphers)) error_set (e, "failed to select any cipher from the cipher list"); else if (!SSL_CTX_use_certificate_chain_file (ctx->ssl_ctx, cert_path)) error_set (e, "%s: %s", "setting the TLS certificate failed", ERR_reason_error_string (ERR_get_error ())); else if (!SSL_CTX_use_PrivateKey_file (ctx->ssl_ctx, key_path, SSL_FILETYPE_PEM)) error_set (e, "%s: %s", "setting the TLS private key failed", ERR_reason_error_string (ERR_get_error ())); else // TODO: SSL_CTX_check_private_key()? It has probably already been // checked by SSL_CTX_use_PrivateKey_file() above. return true; SSL_CTX_free (ctx->ssl_ctx); ctx->ssl_ctx = NULL; return false; } static bool irc_initialize_tls (struct server_context *ctx, struct error **e) { const char *tls_cert = str_map_find (&ctx->config, "tls_cert"); const char *tls_key = str_map_find (&ctx->config, "tls_key"); // Only try to enable SSL support if the user configures it; it is not // a failure if no one has requested it. if (!tls_cert && !tls_key) return true; if (!tls_cert) error_set (e, "no TLS certificate set"); else if (!tls_key) error_set (e, "no TLS private key set"); if (!tls_cert || !tls_key) return false; bool result = false; char *cert_path = resolve_filename (tls_cert, resolve_relative_config_filename); char *key_path = resolve_filename (tls_key, resolve_relative_config_filename); if (!cert_path) error_set (e, "%s: %s", "cannot open file", tls_cert); else if (!key_path) error_set (e, "%s: %s", "cannot open file", tls_key); else result = irc_initialize_ssl_ctx (ctx, cert_path, key_path, e); free (cert_path); free (key_path); return result; } static bool irc_initialize_catalog (struct server_context *ctx, struct error **e) { hard_assert (ctx->catalog == (nl_catd) -1); const char *catalog = str_map_find (&ctx->config, "catalog"); if (!catalog) return true; char *path = resolve_filename (catalog, resolve_relative_config_filename); if (!path) { error_set (e, "%s: %s", "cannot open file", catalog); return false; } ctx->catalog = catopen (path, NL_CAT_LOCALE); free (path); if (ctx->catalog == (nl_catd) -1) { error_set (e, "%s: %s", "failed reading the message catalog file", strerror (errno)); return false; } return true; } static bool irc_initialize_motd (struct server_context *ctx, struct error **e) { hard_assert (ctx->motd.len == 0); const char *motd = str_map_find (&ctx->config, "motd"); if (!motd) return true; char *path = resolve_filename (motd, resolve_relative_config_filename); if (!path) { error_set (e, "%s: %s", "cannot open file", motd); return false; } FILE *fp = fopen (path, "r"); free (path); if (!fp) { error_set (e, "%s: %s", "failed reading the MOTD file", strerror (errno)); return false; } struct str line; str_init (&line); while (read_line (fp, &line)) str_vector_add_owned (&ctx->motd, str_steal (&line)); str_free (&line); fclose (fp); return true; } /// This function handles values that require validation before their first use, /// or some kind of a transformation (such as conversion to an integer) needs /// to be done before they can be used directly. static bool irc_parse_config (struct server_context *ctx, struct error **e) { unsigned long ul; #define PARSE_UNSIGNED(name, min, max) \ const char *name = str_map_find (&ctx->config, #name); \ hard_assert (name != NULL); \ if (!xstrtoul (&ul, name, 10) || ul > max || ul < min) \ { \ error_set (e, "invalid configuration value for `%s': %s", \ #name, "the number is invalid or out of range"); \ return false; \ } \ ctx->name = ul PARSE_UNSIGNED (ping_interval, 1, UINT_MAX); PARSE_UNSIGNED (max_connections, 0, UINT_MAX); bool result = true; struct str_vector fingerprints; str_vector_init (&fingerprints); const char *operators = str_map_find (&ctx->config, "operators"); if (operators) cstr_split_ignore_empty (operators, ',', &fingerprints); for (size_t i = 0; i < fingerprints.len; i++) { const char *key = fingerprints.vector[i]; if (!irc_is_valid_fingerprint (key)) { error_set (e, "invalid configuration value for `%s': %s", "operators", "invalid fingerprint value"); result = false; break; } str_map_set (&ctx->operators, key, (void *) 1); } str_vector_free (&fingerprints); return result; } static bool irc_initialize_server_name (struct server_context *ctx, struct error **e) { enum validation_result res; const char *server_name = str_map_find (&ctx->config, "server_name"); if (server_name) { res = irc_validate_hostname (server_name); if (res != VALIDATION_OK) { error_set (e, "invalid configuration value for `%s': %s", "server_name", irc_validate_to_str (res)); return false; } ctx->server_name = xstrdup (server_name); } else { long host_name_max = sysconf (_SC_HOST_NAME_MAX); if (host_name_max <= 0) host_name_max = _POSIX_HOST_NAME_MAX; char hostname[host_name_max + 1]; if (gethostname (hostname, sizeof hostname)) { error_set (e, "%s: %s", "getting the hostname failed", strerror (errno)); return false; } res = irc_validate_hostname (hostname); if (res != VALIDATION_OK) { error_set (e, "`%s' is not set and the hostname (`%s') cannot be used: %s", "server_name", hostname, irc_validate_to_str (res)); return false; } ctx->server_name = xstrdup (hostname); } return true; } static bool lock_pid_file (const char *path, struct error **e) { // When using XDG_RUNTIME_DIR, the file needs to either have its // access time bumped every 6 hours, or have the sticky bit set int fd = open (path, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH /* 644 */ | S_ISVTX /* sticky */); if (fd < 0) { error_set (e, "can't open `%s': %s", path, strerror (errno)); return false; } struct flock lock = { .l_type = F_WRLCK, .l_start = 0, .l_whence = SEEK_SET, .l_len = 0, }; if (fcntl (fd, F_SETLK, &lock)) { error_set (e, "can't lock `%s': %s", path, strerror (errno)); xclose (fd); return false; } struct str pid; str_init (&pid); str_append_printf (&pid, "%ld", (long) getpid ()); if (ftruncate (fd, 0) || write (fd, pid.str, pid.len) != (ssize_t) pid.len) { error_set (e, "can't write to `%s': %s", path, strerror (errno)); xclose (fd); return false; } str_free (&pid); // Intentionally not closing the file descriptor; it must stay alive // for the entire life of the application return true; } static bool irc_lock_pid_file (struct server_context *ctx, struct error **e) { const char *path = str_map_find (&ctx->config, "pid_file"); if (!path) return true; char *resolved = resolve_filename (path, resolve_relative_runtime_filename); bool result = lock_pid_file (resolved, e); free (resolved); return result; } static int irc_listen (struct addrinfo *gai_iter) { int fd = socket (gai_iter->ai_family, gai_iter->ai_socktype, gai_iter->ai_protocol); if (fd == -1) return -1; set_cloexec (fd); int yes = 1; soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, &yes, sizeof yes) != -1); soft_assert (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof yes) != -1); char host[NI_MAXHOST], port[NI_MAXSERV]; host[0] = port[0] = '\0'; int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, host, sizeof host, port, sizeof port, NI_NUMERICHOST | NI_NUMERICSERV); if (err) print_debug ("%s: %s", "getnameinfo", gai_strerror (err)); char *address = format_host_port_pair (host, port); if (bind (fd, gai_iter->ai_addr, gai_iter->ai_addrlen)) print_error ("bind to %s failed: %s", address, strerror (errno)); else if (listen (fd, 16 /* arbitrary number */)) print_error ("listen on %s failed: %s", address, strerror (errno)); else { print_status ("listening on %s", address); free (address); return fd; } free (address); xclose (fd); return -1; } static void irc_listen_resolve (struct server_context *ctx, const char *host, const char *port, struct addrinfo *gai_hints) { struct addrinfo *gai_result, *gai_iter; int err = getaddrinfo (host, port, gai_hints, &gai_result); if (err) { char *address = format_host_port_pair (host, port); print_error ("bind to %s failed: %s: %s", address, "getaddrinfo", gai_strerror (err)); free (address); return; } int fd; for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next) { if ((fd = irc_listen (gai_iter)) == -1) continue; set_blocking (fd, false); struct poller_fd *event = &ctx->listen_events[ctx->n_listen_fds]; poller_fd_init (event, &ctx->poller, fd); event->dispatcher = (poller_fd_fn) on_irc_client_available; event->user_data = ctx; ctx->listen_fds[ctx->n_listen_fds++] = fd; poller_fd_set (event, POLLIN); break; } freeaddrinfo (gai_result); } static bool irc_setup_listen_fds (struct server_context *ctx, struct error **e) { const char *bind_host = str_map_find (&ctx->config, "bind_host"); const char *bind_port = str_map_find (&ctx->config, "bind_port"); hard_assert (bind_port != NULL); // We have a default value for this struct addrinfo gai_hints; memset (&gai_hints, 0, sizeof gai_hints); gai_hints.ai_socktype = SOCK_STREAM; gai_hints.ai_flags = AI_PASSIVE; struct str_vector ports; str_vector_init (&ports); cstr_split_ignore_empty (bind_port, ',', &ports); ctx->listen_fds = xcalloc (ports.len, sizeof *ctx->listen_fds); ctx->listen_events = xcalloc (ports.len, sizeof *ctx->listen_events); for (size_t i = 0; i < ports.len; i++) irc_listen_resolve (ctx, bind_host, ports.vector[i], &gai_hints); str_vector_free (&ports); if (!ctx->n_listen_fds) { error_set (e, "%s: %s", "network setup failed", "no ports to listen on"); return false; } return true; } // --- Main -------------------------------------------------------------------- static void on_signal_pipe_readable (const struct pollfd *fd, struct server_context *ctx) { char dummy; (void) read (fd->fd, &dummy, 1); if (g_termination_requested && !ctx->quitting) irc_initiate_quit (ctx); } static void daemonize (struct server_context *ctx) { print_status ("daemonizing..."); if (chdir ("/")) exit_fatal ("%s: %s", "chdir", strerror (errno)); // Because of systemd, we need to exit the parent process _after_ writing // a PID file, otherwise our grandchild would receive a SIGTERM int sync_pipe[2]; if (pipe (sync_pipe)) exit_fatal ("%s: %s", "pipe", strerror (errno)); pid_t pid; if ((pid = fork ()) < 0) exit_fatal ("%s: %s", "fork", strerror (errno)); else if (pid) { // Wait until all write ends of the pipe are closed, which can mean // either success or failure, we don't need to care xclose (sync_pipe[PIPE_WRITE]); char dummy; if (read (sync_pipe[PIPE_READ], &dummy, 1) < 0) exit_fatal ("%s: %s", "read", strerror (errno)); exit (EXIT_SUCCESS); } setsid (); signal (SIGHUP, SIG_IGN); if ((pid = fork ()) < 0) exit_fatal ("%s: %s", "fork", strerror (errno)); else if (pid) exit (EXIT_SUCCESS); openlog (PROGRAM_NAME, LOG_NDELAY | LOG_NOWAIT | LOG_PID, 0); g_log_message_real = log_message_syslog; // Write the PID file (if so configured) and get rid of the pipe, so that // the read() in our grandparent finally returns zero (no write ends) struct error *e = NULL; if (!irc_lock_pid_file (ctx, &e)) exit_fatal ("%s", e->message); xclose (sync_pipe[PIPE_READ]); xclose (sync_pipe[PIPE_WRITE]); // XXX: we may close our own descriptors this way, crippling ourselves; // there is no real guarantee that we will start with all three // descriptors open. In theory we could try to enumerate the descriptors // at the start of main(). for (int i = 0; i < 3; i++) xclose (i); int tty = open ("/dev/null", O_RDWR); if (tty != 0 || dup (0) != 1 || dup (0) != 2) exit_fatal ("failed to reopen FD's: %s", strerror (errno)); } int main (int argc, char *argv[]) { // Need to call this first as all string maps depend on it siphash_wrapper_randomize (); static const struct opt opts[] = { { 'd', "debug", NULL, 0, "run in debug mode (do not daemonize)" }, { 'h', "help", NULL, 0, "display this help and exit" }, { 'V', "version", NULL, 0, "output version information and exit" }, { 'w', "write-default-cfg", "FILENAME", OPT_OPTIONAL_ARG | OPT_LONG_ONLY, "write a default configuration file and exit" }, { 0, NULL, NULL, 0, NULL } }; struct opt_handler oh; opt_handler_init (&oh, argc, argv, opts, NULL, "Experimental IRC daemon."); int c; while ((c = opt_handler_get (&oh)) != -1) switch (c) { case 'd': g_debug_mode = true; break; case 'h': opt_handler_usage (&oh, stdout); exit (EXIT_SUCCESS); case 'V': printf (PROGRAM_NAME " " PROGRAM_VERSION "\n"); exit (EXIT_SUCCESS); case 'w': call_simple_config_write_default (optarg, g_config_table); exit (EXIT_SUCCESS); default: print_error ("wrong options"); opt_handler_usage (&oh, stderr); exit (EXIT_FAILURE); } opt_handler_free (&oh); print_status (PROGRAM_NAME " " PROGRAM_VERSION " starting"); setup_signal_handlers (); SSL_library_init (); atexit (EVP_cleanup); SSL_load_error_strings (); atexit (ERR_free_strings); struct server_context ctx; server_context_init (&ctx); ctx.started = time (NULL); irc_register_handlers (&ctx); irc_register_cap_handlers (&ctx); struct error *e = NULL; if (!simple_config_update_from_file (&ctx.config, &e)) { print_error ("error loading configuration: %s", e->message); error_free (e); exit (EXIT_FAILURE); } poller_fd_init (&ctx.signal_event, &ctx.poller, g_signal_pipe[0]); ctx.signal_event.dispatcher = (poller_fd_fn) on_signal_pipe_readable; ctx.signal_event.user_data = &ctx; poller_fd_set (&ctx.signal_event, POLLIN); if (!irc_initialize_tls (&ctx, &e) || !irc_initialize_server_name (&ctx, &e) || !irc_initialize_motd (&ctx, &e) || !irc_initialize_catalog (&ctx, &e) || !irc_parse_config (&ctx, &e) || !irc_setup_listen_fds (&ctx, &e)) exit_fatal ("%s", e->message); if (!g_debug_mode) daemonize (&ctx); else if (!irc_lock_pid_file (&ctx, &e)) exit_fatal ("%s", e->message); ctx.polling = true; while (ctx.polling) poller_run (&ctx.poller); server_context_free (&ctx); return EXIT_SUCCESS; }