From 2eda3110b3bdf85f04682230ed60eb02b8a06510 Mon Sep 17 00:00:00 2001 From: Přemysl Janouch Date: Sat, 9 May 2015 22:10:58 +0200 Subject: degesch: asynchronous connecting etc. I'm sorry, couldn't keep the diff small. All the ZyklonB heritage code is shit anyway. --- common.c | 233 ++++++++ degesch.c | 1899 +++++++++++++++++++++++++++++++------------------------------ 2 files changed, 1200 insertions(+), 932 deletions(-) diff --git a/common.c b/common.c index 820863d..32ccc01 100644 --- a/common.c +++ b/common.c @@ -90,6 +90,239 @@ log_message_syslog (void *user_data, const char *quote, const char *fmt, syslog (prio, "%s%s", quote, buf); } +// --- Connector --------------------------------------------------------------- + +// This is a helper that tries to establish a connection with any address on +// a given list. Sadly it also introduces a bit of a callback hell. + +struct connector_target +{ + LIST_HEADER (struct connector_target) + + char *hostname; ///< Target hostname or address + char *service; ///< Target service name or port + + struct addrinfo *results; ///< Resolved target + struct addrinfo *iter; ///< Current endpoint +}; + +static struct connector_target * +connector_target_new (void) +{ + struct connector_target *self = xmalloc (sizeof *self); + return self; +} + +static void +connector_target_destroy (struct connector_target *self) +{ + free (self->hostname); + free (self->service); + freeaddrinfo (self->results); + free (self); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +struct connector +{ + int socket; ///< Socket FD for the connection + struct poller_fd connected_event; ///< We've connected or failed + struct connector_target *targets; ///< Targets + struct connector_target *targets_t; ///< Tail of targets + + void *user_data; ///< User data for callbacks + + // You may destroy the connector object in these two main callbacks: + + /// Connection has been successfully established + void (*on_connected) (void *user_data, int socket); + /// Failed to establish a connection to either target + void (*on_failure) (void *user_data); + + // Optional: + + /// Connecting to a new address + void (*on_connecting) (void *user_data, const char *address); + /// Connecting to the last address has failed + void (*on_error) (void *user_data, const char *error); +}; + +static void +connector_notify_connecting (struct connector *self, + struct connector_target *target, struct addrinfo *gai_iter) +{ + if (!self->on_connecting) + return; + + const char *real_host = target->hostname; + + // We don't really need this, so we can let it quietly fail + char buf[NI_MAXHOST]; + int err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, + buf, sizeof buf, NULL, 0, NI_NUMERICHOST); + if (err) + LOG_FUNC_FAILURE ("getnameinfo", gai_strerror (err)); + else + real_host = buf; + + char *address = format_host_port_pair (real_host, target->service); + self->on_connecting (self->user_data, address); + free (address); +} + +static void +connector_notify_error (struct connector *self, const char *error) +{ + if (self->on_error) + self->on_error (self->user_data, error); +} + +static void +connector_prepare_next (struct connector *self) +{ + struct connector_target *target = self->targets; + if (!(target->iter = target->iter->ai_next)) + { + LIST_UNLINK_WITH_TAIL (self->targets, self->targets_t, target); + connector_target_destroy (target); + } +} + +static void +connector_step (struct connector *self) +{ + struct connector_target *target = self->targets; + if (!target) + { + // Total failure, none of the targets has succeeded + self->on_failure (self->user_data); + return; + } + + struct addrinfo *gai_iter = target->iter; + hard_assert (gai_iter != NULL); + + connector_notify_connecting (self, target, gai_iter); + + int fd = self->socket = socket (gai_iter->ai_family, + gai_iter->ai_socktype, gai_iter->ai_protocol); + if (fd == -1) + { + connector_notify_error (self, strerror (errno)); + + connector_prepare_next (self); + connector_step (self); + return; + } + + set_cloexec (fd); + set_blocking (fd, false); + + int yes = 1; + soft_assert (setsockopt (fd, SOL_SOCKET, SO_KEEPALIVE, + &yes, sizeof yes) != -1); + + if (!connect (fd, gai_iter->ai_addr, gai_iter->ai_addrlen)) + { + set_blocking (fd, true); + self->on_connected (self->user_data, fd); + return; + } + else if (errno != EINPROGRESS) + { + connector_notify_error (self, strerror (errno)); + xclose (fd); + + connector_prepare_next (self); + connector_step (self); + return; + } + + self->connected_event.fd = self->socket = fd; + poller_fd_set (&self->connected_event, POLLOUT); + + connector_prepare_next (self); +} + +static void +connector_on_ready (const struct pollfd *pfd, struct connector *self) +{ + // See http://cr.yp.to/docs/connect.html if this doesn't work. + // The second connect() method doesn't work with DragonflyBSD. + + int error = 0; + socklen_t error_len = sizeof error; + hard_assert (!getsockopt (pfd->fd, + SOL_SOCKET, SO_ERROR, &error, &error_len)); + + if (error) + { + connector_notify_error (self, strerror (errno)); + + xclose (self->socket); + self->socket = -1; + + connector_step (self); + } + else + { + poller_fd_reset (&self->connected_event); + self->socket = -1; + + set_blocking (pfd->fd, true); + self->on_connected (self->user_data, pfd->fd); + } +} + +static void +connector_init (struct connector *self, struct poller *poller) +{ + memset (self, 0, sizeof *self); + self->socket = -1; + poller_fd_init (&self->connected_event, poller, self->socket); + self->connected_event.user_data = self; + self->connected_event.dispatcher = (poller_fd_fn) connector_on_ready; +} + +static void +connector_free (struct connector *self) +{ + poller_fd_reset (&self->connected_event); + if (self->socket != -1) + xclose (self->socket); + + LIST_FOR_EACH (struct connector_target, iter, self->targets) + connector_target_destroy (iter); +} + +static bool +connector_add_target (struct connector *self, + const char *hostname, const char *service, struct error **e) +{ + struct addrinfo hints, *results; + memset (&hints, 0, sizeof hints); + hints.ai_socktype = SOCK_STREAM; + + // TODO: even this should be done asynchronously, most likely in + // a thread pool, similarly to how libuv does it + int err = getaddrinfo (hostname, service, &hints, &results); + if (err) + { + error_set (e, "%s: %s", "getaddrinfo", gai_strerror (err)); + return false; + } + + struct connector_target *target = connector_target_new (); + target->hostname = xstrdup (hostname); + target->service = xstrdup (service); + target->results = results; + target->iter = target->results; + + LIST_APPEND_WITH_TAIL (self->targets, self->targets_t, target); + return true; +} + // --- SOCKS 5/4a (blocking implementation) ------------------------------------ // These are awkward protocols. Note that the `username' is used differently diff --git a/degesch.c b/degesch.c index f4e1a81..7cffb3b 100644 --- a/degesch.c +++ b/degesch.c @@ -977,6 +977,7 @@ buffer_destroy (struct buffer *self) enum server_state { IRC_DISCONNECTED, ///< Not connected + IRC_CONNECTING, ///< Connecting to the server IRC_CONNECTED, ///< Trying to register IRC_REGISTERED ///< We can chat now }; @@ -984,8 +985,11 @@ enum server_state struct server { struct app_context *ctx; ///< Application context + bool reconnect; ///< Whether to reconnect on conn. fail. + unsigned long reconnect_delay; ///< Reconnect delay in seconds enum server_state state; ///< Connection state + struct connector *connector; ///< Connection establisher int socket; ///< Socket FD of the server struct str read_buffer; ///< Input yet to be processed @@ -1021,9 +1025,9 @@ struct server struct poller_timer reconnect_tmr; ///< We should reconnect now }; -static void on_irc_ping_timeout (void *user_data); static void on_irc_timeout (void *user_data); -static void on_irc_reconnect_timeout (void *user_data); +static void on_irc_ping_timeout (void *user_data); +static void irc_initiate_connect (struct server *s); static void server_init (struct server *self, struct poller *poller) @@ -1048,13 +1052,18 @@ server_init (struct server *self, struct poller *poller) self->ping_tmr.user_data = self; poller_timer_init (&self->reconnect_tmr, poller); - self->reconnect_tmr.dispatcher = on_irc_reconnect_timeout; + self->reconnect_tmr.dispatcher = (poller_timer_fn) irc_initiate_connect; self->reconnect_tmr.user_data = self; } static void server_free (struct server *self) { + if (self->connector) + { + connector_free (self->connector); + free (self->connector); + } if (self->socket != -1) { xclose (self->socket); @@ -1086,8 +1095,6 @@ struct app_context struct config config; ///< Program configuration char *attrs[ATTR_COUNT]; ///< Terminal attributes bool no_colors; ///< Colour output mode - bool reconnect; ///< Whether to reconnect on conn. fail. - unsigned long reconnect_delay; ///< Reconnect delay in seconds bool isolate_buffers; ///< Isolate global/server buffers struct server server; ///< Our only server so far @@ -2453,11 +2460,11 @@ irc_remove_user_from_channel (struct user *user, struct channel *channel) irc_channel_unlink_user (channel, iter); } -// --- Supporting code --------------------------------------------------------- +// --- Core functionality ------------------------------------------------------ -static bool irc_connect (struct server *s, bool *should_retry, struct error **); -static void irc_cancel_timers (struct server *s); -static void on_irc_reconnect_timeout (void *user_data); +// Most of the core IRC code comes from ZyklonB which is mostly blocking. +// While it's fairly easy to follow, it also stinks. It needs to be rewritten +// to be as asynchronous as possible. See kike.c for reference. static bool irc_is_connected (struct server *s) @@ -2466,117 +2473,36 @@ irc_is_connected (struct server *s) } static void -irc_shutdown (struct server *s) +irc_cancel_timers (struct server *s) { - // TODO: set a timer after which we cut the connection? - // Generally non-critical - if (s->ssl) - soft_assert (SSL_shutdown (s->ssl) != -1); - else - soft_assert (shutdown (s->socket, SHUT_WR) == 0); + poller_timer_reset (&s->timeout_tmr); + poller_timer_reset (&s->ping_tmr); + poller_timer_reset (&s->reconnect_tmr); } static void -try_finish_quit (struct app_context *ctx) +irc_reset_connection_timeouts (struct server *s) { - // TODO: multiserver - if (ctx->quitting && !irc_is_connected (&ctx->server)) - ctx->polling = false; + irc_cancel_timers (s); + poller_timer_set (&s->timeout_tmr, 3 * 60 * 1000); + poller_timer_set (&s->ping_tmr, (3 * 60 + 30) * 1000); } static void -initiate_quit (struct app_context *ctx) -{ - // Destroy the user interface - input_stop (&ctx->input); - - buffer_send_status (ctx, ctx->global_buffer, "Shutting down"); - - // Initiate a connection close - // TODO: multiserver - struct server *s = &ctx->server; - if (irc_is_connected (s)) - // XXX: when we go async, we'll have to flush output buffers first - irc_shutdown (s); - - ctx->quitting = true; - try_finish_quit (ctx); -} - -// As of 2015, everything should be in UTF-8. And if it's not, we'll decode it -// as ISO Latin 1. This function should not be called on the whole message. -static char * -irc_to_utf8 (struct app_context *ctx, const char *text) +irc_queue_reconnect (struct server *s) { - size_t len = strlen (text) + 1; - if (utf8_validate (text, len)) - return xstrdup (text); - return iconv_xstrdup (ctx->latin1_to_utf8, (char *) text, len, NULL); -} + // As long as the user wants us to, that is + if (!s->reconnect) + return; -// This function is used to output debugging IRC traffic to the terminal. -// It's far from ideal, as any non-UTF-8 text degrades the entire line to -// ISO Latin 1. But it should work good enough most of the time. -static char * -irc_to_term (struct app_context *ctx, const char *text) -{ - char *utf8 = irc_to_utf8 (ctx, text); - char *term = iconv_xstrdup (ctx->term_from_utf8, utf8, -1, NULL); - free (utf8); - return term; + // TODO: exponentional backoff + hard_assert (s->socket == -1); + buffer_send_status (s->ctx, s->buffer, + "Trying to reconnect in %ld seconds...", s->reconnect_delay); + poller_timer_set (&s->reconnect_tmr, s->reconnect_delay * 1000); } -static bool irc_send (struct server *s, - const char *format, ...) ATTRIBUTE_PRINTF (2, 3); - -static bool -irc_send (struct server *s, const char *format, ...) -{ - if (!soft_assert (irc_is_connected (s))) - { - print_debug ("tried sending a message to a dead server connection"); - return false; - } - - va_list ap; - va_start (ap, format); - struct str str; - str_init (&str); - str_append_vprintf (&str, format, ap); - va_end (ap); - - if (g_debug_mode) - { - input_hide (&s->ctx->input); - - char *term = irc_to_term (s->ctx, str.str); - fprintf (stderr, "[IRC] <== \"%s\"\n", term); - free (term); - - input_show (&s->ctx->input); - } - str_append (&str, "\r\n"); - - bool result = true; - if (s->ssl) - { - // TODO: call SSL_get_error() to detect if a clean shutdown has occured - if (SSL_write (s->ssl, str.str, str.len) != (int) str.len) - { - LOG_FUNC_FAILURE ("SSL_write", - ERR_error_string (ERR_get_error (), NULL)); - result = false; - } - } - else if (write (s->socket, str.str, str.len) != (ssize_t) str.len) - { - LOG_LIBC_FAILURE ("write"); - result = false; - } - - str_free (&str); - return result; -} +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - static bool irc_initialize_ssl_ctx (struct server *s, struct error **e) @@ -2685,498 +2611,681 @@ error_ssl_1: return false; } -static bool -irc_establish_connection (struct server *s, - const char *host, const char *port, struct error **e) +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +// As of 2015, everything should be in UTF-8. And if it's not, we'll decode it +// as ISO Latin 1. This function should not be called on the whole message. +static char * +irc_to_utf8 (struct app_context *ctx, const char *text) { - struct addrinfo gai_hints, *gai_result, *gai_iter; - memset (&gai_hints, 0, sizeof gai_hints); - gai_hints.ai_socktype = SOCK_STREAM; + size_t len = strlen (text) + 1; + if (utf8_validate (text, len)) + return xstrdup (text); + return iconv_xstrdup (ctx->latin1_to_utf8, (char *) text, len, NULL); +} - int err = getaddrinfo (host, port, &gai_hints, &gai_result); - if (err) - { - error_set (e, "%s: %s: %s", - "connection failed", "getaddrinfo", gai_strerror (err)); - return false; - } +// This function is used to output debugging IRC traffic to the terminal. +// It's far from ideal, as any non-UTF-8 text degrades the entire line to +// ISO Latin 1. But it should work good enough most of the time. +static char * +irc_to_term (struct app_context *ctx, const char *text) +{ + char *utf8 = irc_to_utf8 (ctx, text); + char *term = iconv_xstrdup (ctx->term_from_utf8, utf8, -1, NULL); + free (utf8); + return term; +} - int sockfd; - for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next) - { - sockfd = socket (gai_iter->ai_family, - gai_iter->ai_socktype, gai_iter->ai_protocol); - if (sockfd == -1) - continue; - set_cloexec (sockfd); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - int yes = 1; - soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE, - &yes, sizeof yes) != -1); +static void irc_send (struct server *s, + const char *format, ...) ATTRIBUTE_PRINTF (2, 3); - const char *real_host = host; +static void +irc_send (struct server *s, const char *format, ...) +{ + if (!soft_assert (irc_is_connected (s))) + { + print_debug ("tried sending a message to a dead server connection"); + return; + } - // Let's try to resolve the address back into a real hostname; - // we don't really need this, so we can let it quietly fail - char buf[NI_MAXHOST]; - err = getnameinfo (gai_iter->ai_addr, gai_iter->ai_addrlen, - buf, sizeof buf, NULL, 0, NI_NUMERICHOST); - if (err) - LOG_FUNC_FAILURE ("getnameinfo", gai_strerror (err)); - else - real_host = buf; + va_list ap; + va_start (ap, format); + struct str str; + str_init (&str); + str_append_vprintf (&str, format, ap); + va_end (ap); - char *address = format_host_port_pair (real_host, port); - buffer_send_status (s->ctx, s->buffer, "Connecting to %s...", address); - free (address); + if (g_debug_mode) + { + input_hide (&s->ctx->input); - if (!connect (sockfd, gai_iter->ai_addr, gai_iter->ai_addrlen)) - break; + char *term = irc_to_term (s->ctx, str.str); + fprintf (stderr, "[IRC] <== \"%s\"\n", term); + free (term); - xclose (sockfd); + input_show (&s->ctx->input); } + str_append (&str, "\r\n"); - freeaddrinfo (gai_result); - - if (!gai_iter) + if (s->ssl) { - error_set (e, "connection failed"); - return false; + // TODO: call SSL_get_error() to detect if a clean shutdown has occured + if (SSL_write (s->ssl, str.str, str.len) != (int) str.len) + LOG_FUNC_FAILURE ("SSL_write", + ERR_error_string (ERR_get_error (), NULL)); } + else if (write (s->socket, str.str, str.len) != (ssize_t) str.len) + LOG_LIBC_FAILURE ("write"); - s->socket = sockfd; - return true; + str_free (&str); } -// --- More readline funky stuff ----------------------------------------------- +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static char * -make_unseen_prefix (struct app_context *ctx) +static void +irc_shutdown (struct server *s) { - struct str active_buffers; - str_init (&active_buffers); - - size_t i = 0; - LIST_FOR_EACH (struct buffer, iter, ctx->buffers) - { - i++; - if (!iter->unseen_messages_count) - continue; + // TODO: set a timer after which we cut the connection? + // Generally non-critical + if (s->ssl) + soft_assert (SSL_shutdown (s->ssl) != -1); + else + soft_assert (shutdown (s->socket, SHUT_WR) == 0); +} - if (active_buffers.len) - str_append_c (&active_buffers, ','); - str_append_printf (&active_buffers, "%zu", i); - } +static void +irc_destroy_connector (struct server *s) +{ + connector_free (s->connector); + free (s->connector); + s->connector = NULL; - if (active_buffers.len) - return str_steal (&active_buffers); + // Not connecting anymore + s->state = IRC_DISCONNECTED; +} - str_free (&active_buffers); - return NULL; +static void +try_finish_quit (struct app_context *ctx) +{ + // TODO: multiserver + if (ctx->quitting && !irc_is_connected (&ctx->server)) + ctx->polling = false; } static void -make_prompt (struct app_context *ctx, struct str *output) +initiate_quit (struct app_context *ctx) { - struct buffer *buffer = ctx->current_buffer; - if (!buffer) - return; + // Destroy the user interface + input_stop (&ctx->input); - str_append_c (output, '['); + buffer_send_status (ctx, ctx->global_buffer, "Shutting down"); - char *unseen_prefix = make_unseen_prefix (ctx); - if (unseen_prefix) - str_append_printf (output, "(%s) ", unseen_prefix); - free (unseen_prefix); + // Initiate a connection close + // TODO: multiserver + struct server *s = &ctx->server; + if (irc_is_connected (s)) + // XXX: when we go async, we'll have to flush output buffers first + irc_shutdown (s); + else if (s->state == IRC_CONNECTING) + irc_destroy_connector (s); - str_append_printf (output, "%d:%s", - buffer_get_index (ctx, buffer), buffer->name); - if (buffer->type == BUFFER_CHANNEL && *buffer->channel->mode) - str_append_printf (output, "(%s)", buffer->channel->mode); - - if (buffer != ctx->global_buffer) - { - struct server *s = buffer->server; - str_append_c (output, ' '); - if (!irc_is_connected (s)) - str_append (output, "(disconnected)"); - else - { - str_append (output, s->irc_user->nickname); - if (*s->irc_user_mode) - str_append_printf (output, "(%s)", s->irc_user_mode); - } - } - - str_append_c (output, ']'); -} + ctx->quitting = true; + try_finish_quit (ctx); +} static void -refresh_prompt (struct app_context *ctx) +on_irc_disconnected (struct server *s) { - bool have_attributes = !!get_attribute_printer (stdout); - - struct str prompt; - str_init (&prompt); - make_prompt (ctx, &prompt); - str_append_c (&prompt, ' '); + hard_assert (irc_is_connected (s)); - if (have_attributes) + // Get rid of the dead socket and related things + if (s->ssl) { - // XXX: to be completely correct, we should use tputs, but we cannot - input_set_prompt (&ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c", - INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT], - INPUT_END_IGNORE, - prompt.str, - INPUT_START_IGNORE, ctx->attrs[ATTR_RESET], - INPUT_END_IGNORE)); + SSL_free (s->ssl); + s->ssl = NULL; + SSL_CTX_free (s->ssl_ctx); + s->ssl_ctx = NULL; } + + xclose (s->socket); + s->socket = -1; + s->state = IRC_DISCONNECTED; + + user_unref (s->irc_user); + s->irc_user = NULL; + + free (s->irc_user_mode); + s->irc_user_mode = NULL; + free (s->irc_user_host); + s->irc_user_host = NULL; + + s->read_event.closed = true; + poller_fd_reset (&s->read_event); + + // All of our timers have lost their meaning now + irc_cancel_timers (s); + + if (s->ctx->quitting) + try_finish_quit (s->ctx); else - input_set_prompt (&ctx->input, xstrdup (prompt.str)); - str_free (&prompt); + irc_queue_reconnect (s); } -// --- Helpers ----------------------------------------------------------------- +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static char * -irc_cut_nickname (const char *prefix) +static void +on_irc_ping_timeout (void *user_data) { - return xstrndup (prefix, strcspn (prefix, "!@")); + struct server *s = user_data; + buffer_send_error (s->ctx, s->buffer, "Connection timeout"); + on_irc_disconnected (s); } -static const char * -irc_find_userhost (const char *prefix) +static void +on_irc_timeout (void *user_data) { - const char *p = strchr (prefix, '!'); - return p ? p + 1 : NULL; + // Provoke a response from the server + struct server *s = user_data; + irc_send (s, "PING :%s", get_config_string (s->ctx, s->irc_user->nickname)); } -static bool -irc_is_this_us (struct server *s, const char *prefix) +// --- Processing server output ------------------------------------------------ + +static void irc_process_message + (const struct irc_message *msg, const char *raw, void *user_data); + +enum irc_read_result { - char *nick = irc_cut_nickname (prefix); - bool result = !irc_strcmp (nick, s->irc_user->nickname); - free (nick); - return result; -} + IRC_READ_OK, ///< Some data were read successfully + IRC_READ_EOF, ///< The server has closed connection + IRC_READ_AGAIN, ///< No more data at the moment + IRC_READ_ERROR ///< General connection failure +}; -static bool -irc_is_channel (struct server *s, const char *ident) +static enum irc_read_result +irc_fill_read_buffer_ssl (struct server *s, struct str *buf) { - (void) s; // TODO: parse prefixes from server features + int n_read; +start: + n_read = SSL_read (s->ssl, buf->str + buf->len, + buf->alloc - buf->len - 1 /* null byte */); - return *ident && !!strchr ("#&+!", *ident); + const char *error_info = NULL; + switch (xssl_get_error (s->ssl, n_read, &error_info)) + { + case SSL_ERROR_NONE: + buf->str[buf->len += n_read] = '\0'; + return IRC_READ_OK; + case SSL_ERROR_ZERO_RETURN: + return IRC_READ_EOF; + case SSL_ERROR_WANT_READ: + return IRC_READ_AGAIN; + case SSL_ERROR_WANT_WRITE: + { + // Let it finish the handshake as we don't poll for writability; + // any errors are to be collected by SSL_read() in the next iteration + struct pollfd pfd = { .fd = s->socket, .events = POLLOUT }; + soft_assert (poll (&pfd, 1, 0) > 0); + goto start; + } + case XSSL_ERROR_TRY_AGAIN: + goto start; + default: + LOG_FUNC_FAILURE ("SSL_read", error_info); + return IRC_READ_ERROR; + } } -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static struct buffer * -irc_get_buffer_for_message (struct server *s, - const struct irc_message *msg, const char *target) +static enum irc_read_result +irc_fill_read_buffer (struct server *s, struct str *buf) { - struct buffer *buffer = str_map_find (&s->irc_buffer_map, target); - if (irc_is_channel (s, target)) - { - struct channel *channel = str_map_find (&s->irc_channels, target); - hard_assert ((channel && buffer) || - (channel && !buffer) || (!channel && !buffer)); + ssize_t n_read; +start: + n_read = recv (s->socket, buf->str + buf->len, + buf->alloc - buf->len - 1 /* null byte */, 0); - // This is weird - if (!channel) - return NULL; - } - else if (!buffer) + if (n_read > 0) { - // Implying that the target is us + buf->str[buf->len += n_read] = '\0'; + return IRC_READ_OK; + } + if (n_read == 0) + return IRC_READ_EOF; - // Don't make user buffers for servers (they can send NOTICEs) - if (!irc_find_userhost (msg->prefix)) - return s->buffer; + if (errno == EAGAIN) + return IRC_READ_AGAIN; + if (errno == EINTR) + goto start; - char *nickname = irc_cut_nickname (msg->prefix); - buffer = irc_get_or_make_user_buffer (s, nickname); - free (nickname); - } - return buffer; + LOG_LIBC_FAILURE ("recv"); + return IRC_READ_ERROR; } -static bool -irc_is_highlight (struct server *s, const char *message) +static void +on_irc_readable (const struct pollfd *fd, struct server *s) { - // Well, this is rather crude but it should make most users happy. - // Ideally we could do this at least in proper Unicode. - char *copy = xstrdup (message); - for (char *p = copy; *p; p++) - *p = irc_tolower (*p); - - char *nick = xstrdup (s->irc_user->nickname); - for (char *p = nick; *p; p++) - *p = irc_tolower (*p); + if (fd->revents & ~(POLLIN | POLLHUP | POLLERR)) + print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents); - // Special characters allowed in nicknames by RFC 2812: []\`_^{|} and - - // Also excluded from the ASCII: common user channel prefixes: +%@&~ - const char *delimiters = ",.;:!?()<>/=#$* \t\r\n\v\f\"'"; + (void) set_blocking (s->socket, false); - bool result = false; - char *save = NULL; - for (char *token = strtok_r (copy, delimiters, &save); - token; token = strtok_r (NULL, delimiters, &save)) - if (!strcmp (token, nick)) + struct str *buf = &s->read_buffer; + enum irc_read_result (*fill_buffer)(struct server *, struct str *) + = s->ssl + ? irc_fill_read_buffer_ssl + : irc_fill_read_buffer; + bool disconnected = false; + while (true) + { + str_ensure_space (buf, 512); + switch (fill_buffer (s, buf)) { - result = true; + case IRC_READ_AGAIN: + goto end; + case IRC_READ_ERROR: + buffer_send_error (s->ctx, s->buffer, + "Reading from the IRC server failed"); + disconnected = true; + goto end; + case IRC_READ_EOF: + buffer_send_error (s->ctx, s->buffer, + "The IRC server closed the connection"); + disconnected = true; + goto end; + case IRC_READ_OK: break; } - free (copy); - free (nick); - return result; -} + if (buf->len >= (1 << 20)) + { + buffer_send_error (s->ctx, s->buffer, + "The IRC server seems to spew out data frantically"); + irc_shutdown (s); + goto end; + } + } +end: + (void) set_blocking (s->socket, true); + irc_process_buffer (buf, irc_process_message, s); -// --- Input handling ---------------------------------------------------------- + if (disconnected) + on_irc_disconnected (s); + else + irc_reset_connection_timeouts (s); +} -// TODO: we will need a proper mode parser; to be shared with kike -// TODO: we alse definitely need to parse server capability messages +// --- Connection establishment ------------------------------------------------ static void -irc_handle_join (struct server *s, const struct irc_message *msg) +irc_register (struct server *s) { - if (!msg->prefix || msg->params.len < 1) - return; + const char *nickname = get_config_string (s->ctx, "server.nickname"); + const char *username = get_config_string (s->ctx, "server.username"); + const char *realname = get_config_string (s->ctx, "server.realname"); - const char *channel_name = msg->params.vector[0]; - if (!irc_is_channel (s, channel_name)) - return; + // These are filled automatically if needed + hard_assert (nickname && username && realname); - struct channel *channel = str_map_find (&s->irc_channels, channel_name); - struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel_name); - hard_assert ((channel && buffer) || - (channel && !buffer) || (!channel && !buffer)); + irc_send (s, "NICK %s", nickname); + // IRC servers may ignore the last argument if it's empty + irc_send (s, "USER %s 8 * :%s", username, *realname ? realname : " "); - // We've joined a new channel - if (!channel && irc_is_this_us (s, msg->prefix)) - { - buffer = buffer_new (); - buffer->type = BUFFER_CHANNEL; - buffer->name = xstrdup (channel_name); - buffer->server = s; - buffer->channel = channel = - irc_make_channel (s, xstrdup (channel_name)); - LIST_APPEND_WITH_TAIL (s->ctx->buffers, s->ctx->buffers_tail, buffer); - str_map_set (&s->irc_buffer_map, channel->name, buffer); + // XXX: maybe we should wait for the first message from the server + // FIXME: the user may exist already after we've reconnected. Either + // make sure that there's no reference of this nick upon disconnection, + // or search in "irc_users" first... or something. + s->irc_user = irc_make_user (s, xstrdup (nickname)); + s->irc_user_mode = xstrdup (""); + s->irc_user_host = NULL; +} - buffer_activate (s->ctx, buffer); - } +static void +irc_finish_connection (struct server *s, int socket) +{ + struct app_context *ctx = s->ctx; - // This is weird, ignoring - if (!channel) - return; + s->socket = socket; - // Get or make a user object - char *nickname = irc_cut_nickname (msg->prefix); - struct user *user = str_map_find (&s->irc_users, nickname); - if (!user) - user = irc_make_user (s, nickname); - else + struct error *e = NULL; + bool use_ssl = get_config_boolean (ctx, "server.ssl"); + if (use_ssl && !irc_initialize_ssl (s, &e)) { - user = user_ref (user); - free (nickname); + buffer_send_error (ctx, s->buffer, "Connection failed: %s", e->message); + error_free (e); + + xclose (s->socket); + s->socket = -1; + + irc_queue_reconnect (s); + return; } - // Link the user with the channel - struct user_channel *user_channel = user_channel_new (); - user_channel->channel = channel; - LIST_PREPEND (user->channels, user_channel); + buffer_send_status (ctx, s->buffer, "Connection established"); + s->state = IRC_CONNECTED; - struct channel_user *channel_user = channel_user_new (); - channel_user->user = user; - channel_user->modes = xstrdup (""); - LIST_PREPEND (channel->users, channel_user); + poller_fd_init (&s->read_event, &ctx->poller, s->socket); + s->read_event.dispatcher = (poller_fd_fn) on_irc_readable; + s->read_event.user_data = s; - // Finally log the message - if (buffer) - { - buffer_send (s->ctx, buffer, BUFFER_LINE_JOIN, 0, - .who = irc_to_utf8 (s->ctx, msg->prefix), - .object = irc_to_utf8 (s->ctx, channel_name)); - } + poller_fd_set (&s->read_event, POLLIN); + irc_reset_connection_timeouts (s); + + irc_register (s); } static void -irc_handle_kick (struct server *s, const struct irc_message *msg) +irc_on_connector_connecting (void *user_data, const char *address) { - if (!msg->prefix || msg->params.len < 2) - return; + struct server *s = user_data; + buffer_send_status (s->ctx, s->buffer, "Connecting to %s...", address); +} - const char *channel_name = msg->params.vector[0]; - const char *target = msg->params.vector[1]; - if (!irc_is_channel (s, channel_name) - || irc_is_channel (s, target)) - return; +static void +irc_on_connector_error (void *user_data, const char *error) +{ + struct server *s = user_data; + buffer_send_error (s->ctx, s->buffer, "Connection failed: %s", error); +} - const char *message = ""; - if (msg->params.len > 2) - message = msg->params.vector[2]; +static void +irc_on_connector_failure (void *user_data) +{ + struct server *s = user_data; + irc_destroy_connector (s); + irc_queue_reconnect (s); +} - struct user *user = str_map_find (&s->irc_users, target); - struct channel *channel = str_map_find (&s->irc_channels, channel_name); - struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel_name); - hard_assert ((channel && buffer) || - (channel && !buffer) || (!channel && !buffer)); +static void +irc_on_connector_connected (void *user_data, int socket) +{ + struct server *s = user_data; + irc_destroy_connector (s); + irc_finish_connection (s, socket); +} - // It would be is weird for this to be false - if (user && channel) - irc_remove_user_from_channel (user, channel); +static bool +irc_setup_connector (struct server *s, + const char *host, const char *port, struct error **e) +{ + struct connector *connector = xmalloc (sizeof *connector); + connector_init (connector, &s->ctx->poller); - if (buffer) + connector->user_data = s; + connector->on_connecting = irc_on_connector_connecting; + connector->on_error = irc_on_connector_error; + connector->on_connected = irc_on_connector_connected; + connector->on_failure = irc_on_connector_failure; + + s->state = IRC_CONNECTING; + s->connector = connector; + + if (!connector_add_target (connector, host, port, e)) { - buffer_send (s->ctx, buffer, BUFFER_LINE_KICK, 0, - .who = irc_to_utf8 (s->ctx, msg->prefix), - .object = irc_to_utf8 (s->ctx, target), - .reason = irc_to_utf8 (s->ctx, message)); + irc_destroy_connector (s); + return false; } -} -static void -irc_handle_mode (struct server *s, const struct irc_message *msg) -{ - // TODO: parse the mode change and apply it - // TODO: log a message + connector_step (connector); + return true; } static void -irc_handle_nick (struct server *s, const struct irc_message *msg) +irc_initiate_connect (struct server *s) { - if (!msg->prefix || msg->params.len < 1) - return; + hard_assert (s->state == IRC_DISCONNECTED); + struct app_context *ctx = s->ctx; - const char *new_nickname = msg->params.vector[0]; + const char *irc_host = get_config_string (ctx, "server.irc_host"); + int64_t irc_port_int = get_config_integer (ctx, "server.irc_port"); - char *nickname = irc_cut_nickname (msg->prefix); - struct user *user = str_map_find (&s->irc_users, nickname); - free (nickname); - if (!user) + if (!get_config_string (ctx, "server.irc_host")) + { + // No sense in trying to reconnect + buffer_send_error (ctx, s->buffer, + "No hostname specified in configuration"); return; + } - // What the fuck - // TODO: probably log a message and force a reconnect - if (str_map_find (&s->irc_users, new_nickname)) - return; + const char *socks_host = get_config_string (ctx, "server.socks_host"); + int64_t socks_port_int = get_config_integer (ctx, "server.socks_port"); - // Log a message in any PM buffer and rename it; - // we may even have one for ourselves - struct buffer *pm_buffer = - str_map_find (&s->irc_buffer_map, user->nickname); - if (pm_buffer) - { - str_map_set (&s->irc_buffer_map, new_nickname, pm_buffer); - str_map_set (&s->irc_buffer_map, user->nickname, NULL); + const char *socks_username = + get_config_string (ctx, "server.socks_username"); + const char *socks_password = + get_config_string (ctx, "server.socks_password"); - char *who = irc_is_this_us (s, msg->prefix) - ? irc_to_utf8 (s->ctx, msg->prefix) - : NULL; - buffer_send (s->ctx, pm_buffer, BUFFER_LINE_NICK, 0, - .who = who, - .object = irc_to_utf8 (s->ctx, new_nickname)); - // TODO: use a full weechat-style buffer name here - buffer_rename (s->ctx, pm_buffer, new_nickname); - } + char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int); + char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int); - if (irc_is_this_us (s, msg->prefix)) + // TODO: the SOCKS code needs a rewrite so that we don't block on it either + struct error *e = NULL; + if (socks_host) { - // Log a message in all open buffers on this server - struct str_map_iter iter; - str_map_iter_init (&iter, &s->irc_buffer_map); - struct buffer *buffer; - while ((buffer = str_map_iter_next (&iter))) - { - // We've already done that - if (buffer == pm_buffer) - continue; + char *address = format_host_port_pair (irc_host, irc_port); + char *socks_address = format_host_port_pair (socks_host, socks_port); + buffer_send_status (ctx, s->buffer, + "Connecting to %s via %s...", address, socks_address); + free (socks_address); + free (address); - buffer_send (s->ctx, buffer, BUFFER_LINE_NICK, 0, - .object = irc_to_utf8 (s->ctx, new_nickname)); + struct error *error = NULL; + int fd = socks_connect (socks_host, socks_port, irc_host, irc_port, + socks_username, socks_password, &error); + if (fd != -1) + irc_finish_connection (s, fd); + else + { + error_set (&e, "%s: %s", "SOCKS connection failed", error->message); + error_free (error); } } else + irc_setup_connector (s, irc_host, irc_port, &e); + + free (irc_port); + free (socks_port); + + if (e) { - // Log a message in all channels the user is in - LIST_FOR_EACH (struct user_channel, iter, user->channels) - { - struct buffer *buffer = - str_map_find (&s->irc_buffer_map, iter->channel->name); - hard_assert (buffer != NULL); - buffer_send (s->ctx, buffer, BUFFER_LINE_NICK, 0, - .who = irc_to_utf8 (s->ctx, msg->prefix), - .object = irc_to_utf8 (s->ctx, new_nickname)); - } + buffer_send_error (s->ctx, s->buffer, "%s", e->message); + error_free (e); + irc_queue_reconnect (s); } +} - // Finally rename the user - str_map_set (&s->irc_users, new_nickname, user_ref (user)); - str_map_set (&s->irc_users, user->nickname, NULL); +// --- Input prompt ------------------------------------------------------------ - free (user->nickname); - user->nickname = xstrdup (new_nickname); +static char * +make_unseen_prefix (struct app_context *ctx) +{ + struct str active_buffers; + str_init (&active_buffers); - // We might have renamed ourselves - refresh_prompt (s->ctx); -} + size_t i = 0; + LIST_FOR_EACH (struct buffer, iter, ctx->buffers) + { + i++; + if (!iter->unseen_messages_count) + continue; -static void -irc_handle_ctcp_reply (struct server *s, - const struct irc_message *msg, struct ctcp_chunk *chunk) -{ - char *nickname = irc_cut_nickname (msg->prefix); - char *nickname_utf8 = irc_to_utf8 (s->ctx, nickname); - char *tag_utf8 = irc_to_utf8 (s->ctx, chunk->tag.str); - char *text_utf8 = irc_to_utf8 (s->ctx, chunk->text.str); + if (active_buffers.len) + str_append_c (&active_buffers, ','); + str_append_printf (&active_buffers, "%zu", i); + } - buffer_send_status (s->ctx, s->buffer, - "CTCP reply from %s: %s %s", nickname_utf8, tag_utf8, text_utf8); + if (active_buffers.len) + return str_steal (&active_buffers); - free (nickname); - free (nickname_utf8); - free (tag_utf8); - free (text_utf8); + str_free (&active_buffers); + return NULL; } static void -irc_handle_notice_text (struct server *s, - const struct irc_message *msg, struct str *text) +make_prompt (struct app_context *ctx, struct str *output) { - const char *target = msg->params.vector[0]; - struct buffer *buffer = irc_get_buffer_for_message (s, msg, target); + struct buffer *buffer = ctx->current_buffer; + if (!buffer) + return; - if (buffer) - { - // TODO: some more obvious indication of highlights - int flags = irc_is_highlight (s, text->str) - ? BUFFER_LINE_HIGHLIGHT - : 0; - buffer_send (s->ctx, buffer, BUFFER_LINE_NOTICE, flags, - .who = irc_to_utf8 (s->ctx, msg->prefix), - .text = irc_to_utf8 (s->ctx, text->str)); + str_append_c (output, '['); + + char *unseen_prefix = make_unseen_prefix (ctx); + if (unseen_prefix) + str_append_printf (output, "(%s) ", unseen_prefix); + free (unseen_prefix); + + str_append_printf (output, "%d:%s", + buffer_get_index (ctx, buffer), buffer->name); + if (buffer->type == BUFFER_CHANNEL && *buffer->channel->mode) + str_append_printf (output, "(%s)", buffer->channel->mode); + + if (buffer != ctx->global_buffer) + { + struct server *s = buffer->server; + str_append_c (output, ' '); + if (!irc_is_connected (s)) + str_append (output, "(disconnected)"); + else + { + str_append (output, s->irc_user->nickname); + if (*s->irc_user_mode) + str_append_printf (output, "(%s)", s->irc_user_mode); + } } + + str_append_c (output, ']'); } static void -irc_handle_notice (struct server *s, const struct irc_message *msg) +refresh_prompt (struct app_context *ctx) { - if (!msg->prefix || msg->params.len < 2) - return; + bool have_attributes = !!get_attribute_printer (stdout); - // This ignores empty messages which we should never receive anyway - struct ctcp_chunk *chunks = ctcp_parse (msg->params.vector[1]); - LIST_FOR_EACH (struct ctcp_chunk, iter, chunks) - if (!iter->is_extended) - irc_handle_notice_text (s, msg, &iter->text); - else - irc_handle_ctcp_reply (s, msg, iter); - ctcp_destroy (chunks); + struct str prompt; + str_init (&prompt); + make_prompt (ctx, &prompt); + str_append_c (&prompt, ' '); + + if (have_attributes) + { + // XXX: to be completely correct, we should use tputs, but we cannot + input_set_prompt (&ctx->input, xstrdup_printf ("%c%s%c%s%c%s%c", + INPUT_START_IGNORE, ctx->attrs[ATTR_PROMPT], + INPUT_END_IGNORE, + prompt.str, + INPUT_START_IGNORE, ctx->attrs[ATTR_RESET], + INPUT_END_IGNORE)); + } + else + input_set_prompt (&ctx->input, xstrdup (prompt.str)); + str_free (&prompt); +} + +// --- Helpers ----------------------------------------------------------------- + +static char * +irc_cut_nickname (const char *prefix) +{ + return xstrndup (prefix, strcspn (prefix, "!@")); +} + +static const char * +irc_find_userhost (const char *prefix) +{ + const char *p = strchr (prefix, '!'); + return p ? p + 1 : NULL; +} + +static bool +irc_is_this_us (struct server *s, const char *prefix) +{ + char *nick = irc_cut_nickname (prefix); + bool result = !irc_strcmp (nick, s->irc_user->nickname); + free (nick); + return result; +} + +static bool +irc_is_channel (struct server *s, const char *ident) +{ + (void) s; // TODO: parse prefixes from server features + + return *ident && !!strchr ("#&+!", *ident); +} + +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +static struct buffer * +irc_get_buffer_for_message (struct server *s, + const struct irc_message *msg, const char *target) +{ + struct buffer *buffer = str_map_find (&s->irc_buffer_map, target); + if (irc_is_channel (s, target)) + { + struct channel *channel = str_map_find (&s->irc_channels, target); + hard_assert ((channel && buffer) || + (channel && !buffer) || (!channel && !buffer)); + + // This is weird + if (!channel) + return NULL; + } + else if (!buffer) + { + // Implying that the target is us + + // Don't make user buffers for servers (they can send NOTICEs) + if (!irc_find_userhost (msg->prefix)) + return s->buffer; + + char *nickname = irc_cut_nickname (msg->prefix); + buffer = irc_get_or_make_user_buffer (s, nickname); + free (nickname); + } + return buffer; +} + +static bool +irc_is_highlight (struct server *s, const char *message) +{ + // Well, this is rather crude but it should make most users happy. + // Ideally we could do this at least in proper Unicode. + char *copy = xstrdup (message); + for (char *p = copy; *p; p++) + *p = irc_tolower (*p); + + char *nick = xstrdup (s->irc_user->nickname); + for (char *p = nick; *p; p++) + *p = irc_tolower (*p); + + // Special characters allowed in nicknames by RFC 2812: []\`_^{|} and - + // Also excluded from the ASCII: common user channel prefixes: +%@&~ + const char *delimiters = ",.;:!?()<>/=#$* \t\r\n\v\f\"'"; + + bool result = false; + char *save = NULL; + for (char *token = strtok_r (copy, delimiters, &save); + token; token = strtok_r (NULL, delimiters, &save)) + if (!strcmp (token, nick)) + { + result = true; + break; + } + + free (copy); + free (nick); + return result; } +// --- Input handling ---------------------------------------------------------- + +// TODO: we will need a proper mode parser; to be shared with kike +// TODO: we alse definitely need to parse server capability messages + static void -irc_handle_part (struct server *s, const struct irc_message *msg) +irc_handle_join (struct server *s, const struct irc_message *msg) { if (!msg->prefix || msg->params.len < 1) return; @@ -3185,14 +3294,77 @@ irc_handle_part (struct server *s, const struct irc_message *msg) if (!irc_is_channel (s, channel_name)) return; - const char *message = ""; - if (msg->params.len > 1) - message = msg->params.vector[1]; + struct channel *channel = str_map_find (&s->irc_channels, channel_name); + struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel_name); + hard_assert ((channel && buffer) || + (channel && !buffer) || (!channel && !buffer)); + + // We've joined a new channel + if (!channel && irc_is_this_us (s, msg->prefix)) + { + buffer = buffer_new (); + buffer->type = BUFFER_CHANNEL; + buffer->name = xstrdup (channel_name); + buffer->server = s; + buffer->channel = channel = + irc_make_channel (s, xstrdup (channel_name)); + LIST_APPEND_WITH_TAIL (s->ctx->buffers, s->ctx->buffers_tail, buffer); + str_map_set (&s->irc_buffer_map, channel->name, buffer); + + buffer_activate (s->ctx, buffer); + } + + // This is weird, ignoring + if (!channel) + return; + // Get or make a user object char *nickname = irc_cut_nickname (msg->prefix); struct user *user = str_map_find (&s->irc_users, nickname); - free (nickname); + if (!user) + user = irc_make_user (s, nickname); + else + { + user = user_ref (user); + free (nickname); + } + + // Link the user with the channel + struct user_channel *user_channel = user_channel_new (); + user_channel->channel = channel; + LIST_PREPEND (user->channels, user_channel); + + struct channel_user *channel_user = channel_user_new (); + channel_user->user = user; + channel_user->modes = xstrdup (""); + LIST_PREPEND (channel->users, channel_user); + + // Finally log the message + if (buffer) + { + buffer_send (s->ctx, buffer, BUFFER_LINE_JOIN, 0, + .who = irc_to_utf8 (s->ctx, msg->prefix), + .object = irc_to_utf8 (s->ctx, channel_name)); + } +} + +static void +irc_handle_kick (struct server *s, const struct irc_message *msg) +{ + if (!msg->prefix || msg->params.len < 2) + return; + + const char *channel_name = msg->params.vector[0]; + const char *target = msg->params.vector[1]; + if (!irc_is_channel (s, channel_name) + || irc_is_channel (s, target)) + return; + + const char *message = ""; + if (msg->params.len > 2) + message = msg->params.vector[2]; + struct user *user = str_map_find (&s->irc_users, target); struct channel *channel = str_map_find (&s->irc_channels, channel_name); struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel_name); hard_assert ((channel && buffer) || @@ -3204,29 +3376,204 @@ irc_handle_part (struct server *s, const struct irc_message *msg) if (buffer) { - buffer_send (s->ctx, buffer, BUFFER_LINE_PART, 0, + buffer_send (s->ctx, buffer, BUFFER_LINE_KICK, 0, .who = irc_to_utf8 (s->ctx, msg->prefix), - .object = irc_to_utf8 (s->ctx, channel_name), + .object = irc_to_utf8 (s->ctx, target), .reason = irc_to_utf8 (s->ctx, message)); } } static void -irc_handle_ping (struct server *s, const struct irc_message *msg) +irc_handle_mode (struct server *s, const struct irc_message *msg) { - if (msg->params.len) - irc_send (s, "PONG :%s", msg->params.vector[0]); - else - irc_send (s, "PONG"); + // TODO: parse the mode change and apply it + // TODO: log a message } -static char * -ctime_now (char buf[26]) +static void +irc_handle_nick (struct server *s, const struct irc_message *msg) { - struct tm tm_; - time_t now = time (NULL); - if (!asctime_r (localtime_r (&now, &tm_), buf)) - return NULL; + if (!msg->prefix || msg->params.len < 1) + return; + + const char *new_nickname = msg->params.vector[0]; + + char *nickname = irc_cut_nickname (msg->prefix); + struct user *user = str_map_find (&s->irc_users, nickname); + free (nickname); + if (!user) + return; + + // What the fuck + // TODO: probably log a message and force a reconnect + if (str_map_find (&s->irc_users, new_nickname)) + return; + + // Log a message in any PM buffer and rename it; + // we may even have one for ourselves + struct buffer *pm_buffer = + str_map_find (&s->irc_buffer_map, user->nickname); + if (pm_buffer) + { + str_map_set (&s->irc_buffer_map, new_nickname, pm_buffer); + str_map_set (&s->irc_buffer_map, user->nickname, NULL); + + char *who = irc_is_this_us (s, msg->prefix) + ? irc_to_utf8 (s->ctx, msg->prefix) + : NULL; + buffer_send (s->ctx, pm_buffer, BUFFER_LINE_NICK, 0, + .who = who, + .object = irc_to_utf8 (s->ctx, new_nickname)); + // TODO: use a full weechat-style buffer name here + buffer_rename (s->ctx, pm_buffer, new_nickname); + } + + if (irc_is_this_us (s, msg->prefix)) + { + // Log a message in all open buffers on this server + struct str_map_iter iter; + str_map_iter_init (&iter, &s->irc_buffer_map); + struct buffer *buffer; + while ((buffer = str_map_iter_next (&iter))) + { + // We've already done that + if (buffer == pm_buffer) + continue; + + buffer_send (s->ctx, buffer, BUFFER_LINE_NICK, 0, + .object = irc_to_utf8 (s->ctx, new_nickname)); + } + } + else + { + // Log a message in all channels the user is in + LIST_FOR_EACH (struct user_channel, iter, user->channels) + { + struct buffer *buffer = + str_map_find (&s->irc_buffer_map, iter->channel->name); + hard_assert (buffer != NULL); + buffer_send (s->ctx, buffer, BUFFER_LINE_NICK, 0, + .who = irc_to_utf8 (s->ctx, msg->prefix), + .object = irc_to_utf8 (s->ctx, new_nickname)); + } + } + + // Finally rename the user + str_map_set (&s->irc_users, new_nickname, user_ref (user)); + str_map_set (&s->irc_users, user->nickname, NULL); + + free (user->nickname); + user->nickname = xstrdup (new_nickname); + + // We might have renamed ourselves + refresh_prompt (s->ctx); +} + +static void +irc_handle_ctcp_reply (struct server *s, + const struct irc_message *msg, struct ctcp_chunk *chunk) +{ + char *nickname = irc_cut_nickname (msg->prefix); + char *nickname_utf8 = irc_to_utf8 (s->ctx, nickname); + char *tag_utf8 = irc_to_utf8 (s->ctx, chunk->tag.str); + char *text_utf8 = irc_to_utf8 (s->ctx, chunk->text.str); + + buffer_send_status (s->ctx, s->buffer, + "CTCP reply from %s: %s %s", nickname_utf8, tag_utf8, text_utf8); + + free (nickname); + free (nickname_utf8); + free (tag_utf8); + free (text_utf8); +} + +static void +irc_handle_notice_text (struct server *s, + const struct irc_message *msg, struct str *text) +{ + const char *target = msg->params.vector[0]; + struct buffer *buffer = irc_get_buffer_for_message (s, msg, target); + + if (buffer) + { + // TODO: some more obvious indication of highlights + int flags = irc_is_highlight (s, text->str) + ? BUFFER_LINE_HIGHLIGHT + : 0; + buffer_send (s->ctx, buffer, BUFFER_LINE_NOTICE, flags, + .who = irc_to_utf8 (s->ctx, msg->prefix), + .text = irc_to_utf8 (s->ctx, text->str)); + } +} + +static void +irc_handle_notice (struct server *s, const struct irc_message *msg) +{ + if (!msg->prefix || msg->params.len < 2) + return; + + // This ignores empty messages which we should never receive anyway + struct ctcp_chunk *chunks = ctcp_parse (msg->params.vector[1]); + LIST_FOR_EACH (struct ctcp_chunk, iter, chunks) + if (!iter->is_extended) + irc_handle_notice_text (s, msg, &iter->text); + else + irc_handle_ctcp_reply (s, msg, iter); + ctcp_destroy (chunks); +} + +static void +irc_handle_part (struct server *s, const struct irc_message *msg) +{ + if (!msg->prefix || msg->params.len < 1) + return; + + const char *channel_name = msg->params.vector[0]; + if (!irc_is_channel (s, channel_name)) + return; + + const char *message = ""; + if (msg->params.len > 1) + message = msg->params.vector[1]; + + char *nickname = irc_cut_nickname (msg->prefix); + struct user *user = str_map_find (&s->irc_users, nickname); + free (nickname); + + struct channel *channel = str_map_find (&s->irc_channels, channel_name); + struct buffer *buffer = str_map_find (&s->irc_buffer_map, channel_name); + hard_assert ((channel && buffer) || + (channel && !buffer) || (!channel && !buffer)); + + // It would be is weird for this to be false + if (user && channel) + irc_remove_user_from_channel (user, channel); + + if (buffer) + { + buffer_send (s->ctx, buffer, BUFFER_LINE_PART, 0, + .who = irc_to_utf8 (s->ctx, msg->prefix), + .object = irc_to_utf8 (s->ctx, channel_name), + .reason = irc_to_utf8 (s->ctx, message)); + } +} + +static void +irc_handle_ping (struct server *s, const struct irc_message *msg) +{ + if (msg->params.len) + irc_send (s, "PONG :%s", msg->params.vector[0]); + else + irc_send (s, "PONG"); +} + +static char * +ctime_now (char buf[26]) +{ + struct tm tm_; + time_t now = time (NULL); + if (!asctime_r (localtime_r (&now, &tm_), buf)) + return NULL; // Annoying thing *strchr (buf, '\n') = '\0'; @@ -4428,7 +4775,7 @@ handle_command_connect (struct app_context *ctx, char *arguments) } irc_cancel_timers (s); - on_irc_reconnect_timeout (s); + irc_initiate_connect (s); return true; } @@ -4559,482 +4906,170 @@ try_handle_command_help_option (struct app_context *ctx, const char *name) "Option \"%s\":", name); buffer_send_status (ctx, ctx->global_buffer, " Description: %s", schema->comment); - buffer_send_status (ctx, ctx->global_buffer, - " Type: %s", config_item_type_name (schema->type)); - buffer_send_status (ctx, ctx->global_buffer, - " Default: %s", schema->default_ ? schema->default_ : "null"); - - struct str tmp; - str_init (&tmp); - config_item_write (item, false, &tmp); - buffer_send_status (ctx, ctx->global_buffer, - " Current value: %s", tmp.str); - str_free (&tmp); - return true; -} - -static bool -handle_command_help (struct app_context *ctx, char *arguments) -{ - if (!*arguments) - { - buffer_send_status (ctx, ctx->global_buffer, "%s", ""); - buffer_send_status (ctx, ctx->global_buffer, "Commands:"); - for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++) - { - struct command_handler *handler = &g_command_handlers[i]; - buffer_send_status (ctx, ctx->global_buffer, " %s: %s", - handler->name, handler->description); - } - return true; - } - - char *command = cut_word (&arguments); - for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++) - { - struct command_handler *handler = &g_command_handlers[i]; - if (strcasecmp_ascii (command, handler->name)) - continue; - - buffer_send_status (ctx, ctx->global_buffer, "%s", ""); - buffer_send_status (ctx, ctx->global_buffer, "%s: %s", - handler->name, handler->description); - buffer_send_status (ctx, ctx->global_buffer, " Arguments: %s", - handler->usage); - return true; - } - - if (!try_handle_command_help_option (ctx, command)) - buffer_send_error (ctx, ctx->global_buffer, - "%s: %s", "No such command or option", command); - return true; -} - -static int -command_handler_cmp_by_length (const void *a, const void *b) -{ - const struct command_handler *first = a; - const struct command_handler *second = b; - return strlen (first->name) - strlen (second->name); -} - -static void -init_partial_matching_user_command_map (struct str_map *partial) -{ - // Trivially create a partial matching map - str_map_init (partial); - partial->key_xfrm = tolower_ascii_strxfrm; - - // We process them from the longest to the shortest one, - // so that common prefixes favor shorter entries - struct command_handler *by_length[N_ELEMENTS (g_command_handlers)]; - for (size_t i = 0; i < N_ELEMENTS (by_length); i++) - by_length[i] = &g_command_handlers[i]; - qsort (by_length, N_ELEMENTS (by_length), sizeof *by_length, - command_handler_cmp_by_length); - - for (size_t i = N_ELEMENTS (by_length); i--; ) - { - char *copy = xstrdup (by_length[i]->name); - for (size_t part = strlen (copy); part; part--) - { - copy[part] = '\0'; - str_map_set (partial, copy, by_length[i]); - } - free (copy); - } -} - -static void -process_user_command (struct app_context *ctx, char *command) -{ - static bool initialized = false; - static struct str_map partial; - if (!initialized) - { - init_partial_matching_user_command_map (&partial); - initialized = true; - } - - char *name = cut_word (&command); - if (try_handle_buffer_goto (ctx, name)) - return; - - struct command_handler *handler = str_map_find (&partial, name); - if (!handler) - buffer_send_error (ctx, ctx->global_buffer, - "%s: %s", "No such command", name); - else if (!handler->handler (ctx, command)) - buffer_send_error (ctx, ctx->global_buffer, - "%s: /%s %s", "Usage", handler->name, handler->usage); -} - -// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -static void -send_message_to_target (struct server *s, - const char *target, char *message, struct buffer *buffer) -{ - if (!irc_is_connected (s)) - { - buffer_send_error (s->ctx, buffer, "Not connected"); - return; - } - - SEND_AUTOSPLIT_PRIVMSG (s, target, message); -} - -static void -send_message_to_current_buffer (struct app_context *ctx, char *message) -{ - struct buffer *buffer = ctx->current_buffer; - hard_assert (buffer != NULL); - - switch (buffer->type) - { - case BUFFER_GLOBAL: - case BUFFER_SERVER: - buffer_send_error (ctx, buffer, "This buffer is not a channel"); - break; - case BUFFER_CHANNEL: - send_message_to_target (buffer->server, - buffer->channel->name, message, buffer); - break; - case BUFFER_PM: - send_message_to_target (buffer->server, - buffer->user->nickname, message, buffer); - break; - } -} - -static void -process_input (struct app_context *ctx, char *user_input) -{ - char *input; - size_t len; - - if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len))) - print_error ("character conversion failed for `%s'", "user input"); - else if (input[0] != '/') - send_message_to_current_buffer (ctx, input); - else if (input[1] == '/') - send_message_to_current_buffer (ctx, input + 1); - else - process_user_command (ctx, input + 1); - - free (input); -} - -// --- Supporting code (continued) --------------------------------------------- - -enum irc_read_result -{ - IRC_READ_OK, ///< Some data were read successfully - IRC_READ_EOF, ///< The server has closed connection - IRC_READ_AGAIN, ///< No more data at the moment - IRC_READ_ERROR ///< General connection failure -}; - -static enum irc_read_result -irc_fill_read_buffer_ssl (struct server *s, struct str *buf) -{ - int n_read; -start: - n_read = SSL_read (s->ssl, buf->str + buf->len, - buf->alloc - buf->len - 1 /* null byte */); - - const char *error_info = NULL; - switch (xssl_get_error (s->ssl, n_read, &error_info)) - { - case SSL_ERROR_NONE: - buf->str[buf->len += n_read] = '\0'; - return IRC_READ_OK; - case SSL_ERROR_ZERO_RETURN: - return IRC_READ_EOF; - case SSL_ERROR_WANT_READ: - return IRC_READ_AGAIN; - case SSL_ERROR_WANT_WRITE: - { - // Let it finish the handshake as we don't poll for writability; - // any errors are to be collected by SSL_read() in the next iteration - struct pollfd pfd = { .fd = s->socket, .events = POLLOUT }; - soft_assert (poll (&pfd, 1, 0) > 0); - goto start; - } - case XSSL_ERROR_TRY_AGAIN: - goto start; - default: - LOG_FUNC_FAILURE ("SSL_read", error_info); - return IRC_READ_ERROR; - } -} - -static enum irc_read_result -irc_fill_read_buffer (struct server *s, struct str *buf) -{ - ssize_t n_read; -start: - n_read = recv (s->socket, buf->str + buf->len, - buf->alloc - buf->len - 1 /* null byte */, 0); - - if (n_read > 0) - { - buf->str[buf->len += n_read] = '\0'; - return IRC_READ_OK; - } - if (n_read == 0) - return IRC_READ_EOF; - - if (errno == EAGAIN) - return IRC_READ_AGAIN; - if (errno == EINTR) - goto start; - - LOG_LIBC_FAILURE ("recv"); - return IRC_READ_ERROR; -} - -static void -irc_cancel_timers (struct server *s) -{ - poller_timer_reset (&s->timeout_tmr); - poller_timer_reset (&s->ping_tmr); - poller_timer_reset (&s->reconnect_tmr); -} - -static void -irc_queue_reconnect (struct server *s) -{ - // TODO: exponentional backoff - hard_assert (s->socket == -1); - buffer_send_status (s->ctx, s->buffer, - "Trying to reconnect in %ld seconds...", s->ctx->reconnect_delay); - poller_timer_set (&s->reconnect_tmr, s->ctx->reconnect_delay * 1000); -} - -static void -on_irc_reconnect_timeout (void *user_data) -{ - struct server *s = user_data; - - struct error *e = NULL; - bool should_retry = false; - if (irc_connect (s, &should_retry, &e)) - return; - - buffer_send_error (s->ctx, s->buffer, "%s", e->message); - error_free (e); - - if (should_retry) - irc_queue_reconnect (s); -} - -static void -on_irc_disconnected (struct server *s) -{ - // Get rid of the dead socket and related things - if (s->ssl) - { - SSL_free (s->ssl); - s->ssl = NULL; - SSL_CTX_free (s->ssl_ctx); - s->ssl_ctx = NULL; - } - - xclose (s->socket); - s->socket = -1; - s->state = IRC_DISCONNECTED; - - user_unref (s->irc_user); - s->irc_user = NULL; - - free (s->irc_user_mode); - s->irc_user_mode = NULL; - free (s->irc_user_host); - s->irc_user_host = NULL; - - s->read_event.closed = true; - poller_fd_reset (&s->read_event); - - // All of our timers have lost their meaning now - irc_cancel_timers (s); + buffer_send_status (ctx, ctx->global_buffer, + " Type: %s", config_item_type_name (schema->type)); + buffer_send_status (ctx, ctx->global_buffer, + " Default: %s", schema->default_ ? schema->default_ : "null"); - if (s->ctx->quitting) - try_finish_quit (s->ctx); - else if (!s->ctx->reconnect) - // XXX: not sure if we want this in a client - // FIXME: no, we don't, would need to be changed for multiserver anyway - initiate_quit (s->ctx); - else - irc_queue_reconnect (s); + struct str tmp; + str_init (&tmp); + config_item_write (item, false, &tmp); + buffer_send_status (ctx, ctx->global_buffer, + " Current value: %s", tmp.str); + str_free (&tmp); + return true; } -static void -on_irc_ping_timeout (void *user_data) +static bool +handle_command_help (struct app_context *ctx, char *arguments) { - struct server *s = user_data; - buffer_send_error (s->ctx, s->buffer, "Connection timeout"); - on_irc_disconnected (s); -} + if (!*arguments) + { + buffer_send_status (ctx, ctx->global_buffer, "%s", ""); + buffer_send_status (ctx, ctx->global_buffer, "Commands:"); + for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++) + { + struct command_handler *handler = &g_command_handlers[i]; + buffer_send_status (ctx, ctx->global_buffer, " %s: %s", + handler->name, handler->description); + } + return true; + } -static void -on_irc_timeout (void *user_data) -{ - // Provoke a response from the server - struct server *s = user_data; - irc_send (s, "PING :%s", get_config_string (s->ctx, "server.nickname")); + char *command = cut_word (&arguments); + for (size_t i = 0; i < N_ELEMENTS (g_command_handlers); i++) + { + struct command_handler *handler = &g_command_handlers[i]; + if (strcasecmp_ascii (command, handler->name)) + continue; + + buffer_send_status (ctx, ctx->global_buffer, "%s", ""); + buffer_send_status (ctx, ctx->global_buffer, "%s: %s", + handler->name, handler->description); + buffer_send_status (ctx, ctx->global_buffer, " Arguments: %s", + handler->usage); + return true; + } + + if (!try_handle_command_help_option (ctx, command)) + buffer_send_error (ctx, ctx->global_buffer, + "%s: %s", "No such command or option", command); + return true; } -static void -irc_reset_connection_timeouts (struct server *s) +static int +command_handler_cmp_by_length (const void *a, const void *b) { - irc_cancel_timers (s); - poller_timer_set (&s->timeout_tmr, 3 * 60 * 1000); - poller_timer_set (&s->ping_tmr, (3 * 60 + 30) * 1000); + const struct command_handler *first = a; + const struct command_handler *second = b; + return strlen (first->name) - strlen (second->name); } static void -on_irc_readable (const struct pollfd *fd, struct server *s) +init_partial_matching_user_command_map (struct str_map *partial) { - if (fd->revents & ~(POLLIN | POLLHUP | POLLERR)) - print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents); + // Trivially create a partial matching map + str_map_init (partial); + partial->key_xfrm = tolower_ascii_strxfrm; - (void) set_blocking (s->socket, false); + // We process them from the longest to the shortest one, + // so that common prefixes favor shorter entries + struct command_handler *by_length[N_ELEMENTS (g_command_handlers)]; + for (size_t i = 0; i < N_ELEMENTS (by_length); i++) + by_length[i] = &g_command_handlers[i]; + qsort (by_length, N_ELEMENTS (by_length), sizeof *by_length, + command_handler_cmp_by_length); - struct str *buf = &s->read_buffer; - enum irc_read_result (*fill_buffer)(struct server *, struct str *) - = s->ssl - ? irc_fill_read_buffer_ssl - : irc_fill_read_buffer; - bool disconnected = false; - while (true) + for (size_t i = N_ELEMENTS (by_length); i--; ) { - str_ensure_space (buf, 512); - switch (fill_buffer (s, buf)) - { - case IRC_READ_AGAIN: - goto end; - case IRC_READ_ERROR: - buffer_send_error (s->ctx, s->buffer, - "Reading from the IRC server failed"); - disconnected = true; - goto end; - case IRC_READ_EOF: - buffer_send_error (s->ctx, s->buffer, - "The IRC server closed the connection"); - disconnected = true; - goto end; - case IRC_READ_OK: - break; - } - - if (buf->len >= (1 << 20)) + char *copy = xstrdup (by_length[i]->name); + for (size_t part = strlen (copy); part; part--) { - buffer_send_error (s->ctx, s->buffer, - "The IRC server seems to spew out data frantically"); - irc_shutdown (s); - goto end; + copy[part] = '\0'; + str_map_set (partial, copy, by_length[i]); } + free (copy); } -end: - (void) set_blocking (s->socket, true); - irc_process_buffer (buf, irc_process_message, s); - - if (disconnected) - on_irc_disconnected (s); - else - irc_reset_connection_timeouts (s); } -static bool -irc_connect (struct server *s, bool *should_retry, struct error **e) +static void +process_user_command (struct app_context *ctx, char *command) { - // TODO: connect asynchronously so that we don't freeze - struct app_context *ctx = s->ctx; - *should_retry = true; - - const char *irc_host = get_config_string (ctx, "server.irc_host"); - int64_t irc_port_int = get_config_integer (ctx, "server.irc_port"); - - if (!get_config_string (ctx, "server.irc_host")) + static bool initialized = false; + static struct str_map partial; + if (!initialized) { - error_set (e, "No hostname specified in configuration"); - *should_retry = false; - return false; + init_partial_matching_user_command_map (&partial); + initialized = true; } - const char *socks_host = get_config_string (ctx, "server.socks_host"); - int64_t socks_port_int = get_config_integer (ctx, "server.socks_port"); - const char *socks_username = - get_config_string (ctx, "server.socks_username"); - const char *socks_password = - get_config_string (ctx, "server.socks_password"); - - // FIXME: use it as a number everywhere, there's no need for named services - // FIXME: memory leak - char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int); - char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int); + char *name = cut_word (&command); + if (try_handle_buffer_goto (ctx, name)) + return; - const char *nickname = get_config_string (ctx, "server.nickname"); - const char *username = get_config_string (ctx, "server.username"); - const char *realname = get_config_string (ctx, "server.realname"); + struct command_handler *handler = str_map_find (&partial, name); + if (!handler) + buffer_send_error (ctx, ctx->global_buffer, + "%s: %s", "No such command", name); + else if (!handler->handler (ctx, command)) + buffer_send_error (ctx, ctx->global_buffer, + "%s: /%s %s", "Usage", handler->name, handler->usage); +} - // These are filled automatically if needed - hard_assert (nickname && username && realname); +// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - bool use_ssl = get_config_boolean (ctx, "server.ssl"); - if (socks_host) +static void +send_message_to_target (struct server *s, + const char *target, char *message, struct buffer *buffer) +{ + if (!irc_is_connected (s)) { - char *address = format_host_port_pair (irc_host, irc_port); - char *socks_address = format_host_port_pair (socks_host, socks_port); - buffer_send_status (ctx, s->buffer, - "Connecting to %s via %s...", address, socks_address); - free (socks_address); - free (address); - - struct error *error = NULL; - int fd = socks_connect (socks_host, socks_port, irc_host, irc_port, - socks_username, socks_password, &error); - if (fd == -1) - { - error_set (e, "%s: %s", "SOCKS connection failed", error->message); - error_free (error); - return false; - } - s->socket = fd; + buffer_send_error (s->ctx, buffer, "Not connected"); + return; } - else if (!irc_establish_connection (s, irc_host, irc_port, e)) - return false; - if (use_ssl && !irc_initialize_ssl (s, e)) - { - xclose (s->socket); - s->socket = -1; - return false; - } + SEND_AUTOSPLIT_PRIVMSG (s, target, message); +} - s->state = IRC_CONNECTED; - buffer_send_status (ctx, s->buffer, "Connection established"); +static void +send_message_to_current_buffer (struct app_context *ctx, char *message) +{ + struct buffer *buffer = ctx->current_buffer; + hard_assert (buffer != NULL); - poller_fd_init (&s->read_event, &ctx->poller, s->socket); - s->read_event.dispatcher = (poller_fd_fn) on_irc_readable; - s->read_event.user_data = s; + switch (buffer->type) + { + case BUFFER_GLOBAL: + case BUFFER_SERVER: + buffer_send_error (ctx, buffer, "This buffer is not a channel"); + break; + case BUFFER_CHANNEL: + send_message_to_target (buffer->server, + buffer->channel->name, message, buffer); + break; + case BUFFER_PM: + send_message_to_target (buffer->server, + buffer->user->nickname, message, buffer); + break; + } +} - poller_fd_set (&s->read_event, POLLIN); - irc_reset_connection_timeouts (s); +static void +process_input (struct app_context *ctx, char *user_input) +{ + char *input; + size_t len; - irc_send (s, "NICK %s", nickname); - // IRC servers may ignore the last argument if it's empty - irc_send (s, "USER %s 8 * :%s", username, *realname ? realname : " "); + if (!(input = iconv_xstrdup (ctx->term_to_utf8, user_input, -1, &len))) + print_error ("character conversion failed for `%s'", "user input"); + else if (input[0] != '/') + send_message_to_current_buffer (ctx, input); + else if (input[1] == '/') + send_message_to_current_buffer (ctx, input + 1); + else + process_user_command (ctx, input + 1); - // XXX: maybe we should wait for the first message from the server - // FIXME: the user may exist already after we've reconnected. Either - // make sure that there's no reference of this nick upon disconnection, - // or search in "irc_users" first... or something. - s->irc_user = irc_make_user (s, xstrdup (nickname)); - s->irc_user_mode = xstrdup (""); - s->irc_user_host = NULL; - return true; + free (input); } // --- Word completion --------------------------------------------------------- @@ -5854,11 +5889,11 @@ load_configuration (struct app_context *ctx) error_free (e); } - ctx->reconnect = - get_config_boolean (ctx, "server.reconnect"); ctx->isolate_buffers = get_config_boolean (ctx, "behaviour.isolate_buffers"); - ctx->reconnect_delay = + ctx->server.reconnect = + get_config_boolean (ctx, "server.reconnect"); + ctx->server.reconnect_delay = get_config_integer (ctx, "server.reconnect_delay"); } -- cgit v1.2.3-54-g00ecf