aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPřemysl Janouch <p.janouch@gmail.com>2015-05-09 22:10:58 +0200
committerPřemysl Janouch <p.janouch@gmail.com>2015-05-09 22:14:02 +0200
commit2eda3110b3bdf85f04682230ed60eb02b8a06510 (patch)
treebcc00175c7869028d0f0bf73aaf210e81309e628
parent19b2eda70e02329fcd0ab7a05968711faa539837 (diff)
downloadxK-2eda3110b3bdf85f04682230ed60eb02b8a06510.tar.gz
xK-2eda3110b3bdf85f04682230ed60eb02b8a06510.tar.xz
xK-2eda3110b3bdf85f04682230ed60eb02b8a06510.zip
degesch: asynchronous connecting etc.
I'm sorry, couldn't keep the diff small. All the ZyklonB heritage code is shit anyway.
-rw-r--r--common.c233
-rw-r--r--degesch.c965
2 files changed, 733 insertions, 465 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,70 +2611,491 @@ 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)
+// 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;
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void irc_send (struct server *s,
+ const char *format, ...) ATTRIBUTE_PRINTF (2, 3);
+
+static void
+irc_send (struct server *s, const char *format, ...)
+{
+ if (!soft_assert (irc_is_connected (s)))
{
- error_set (e, "%s: %s: %s",
- "connection failed", "getaddrinfo", gai_strerror (err));
- return false;
+ print_debug ("tried sending a message to a dead server connection");
+ return;
}
- int sockfd;
- for (gai_iter = gai_result; gai_iter; gai_iter = gai_iter->ai_next)
+ 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)
{
- sockfd = socket (gai_iter->ai_family,
- gai_iter->ai_socktype, gai_iter->ai_protocol);
- if (sockfd == -1)
- continue;
- set_cloexec (sockfd);
+ input_hide (&s->ctx->input);
- int yes = 1;
- soft_assert (setsockopt (sockfd, SOL_SOCKET, SO_KEEPALIVE,
- &yes, sizeof yes) != -1);
+ char *term = irc_to_term (s->ctx, str.str);
+ fprintf (stderr, "[IRC] <== \"%s\"\n", term);
+ free (term);
- const char *real_host = host;
+ input_show (&s->ctx->input);
+ }
+ str_append (&str, "\r\n");
- // 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;
+ 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));
+ }
+ else if (write (s->socket, str.str, str.len) != (ssize_t) str.len)
+ LOG_LIBC_FAILURE ("write");
- char *address = format_host_port_pair (real_host, port);
- buffer_send_status (s->ctx, s->buffer, "Connecting to %s...", address);
- free (address);
+ str_free (&str);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+irc_shutdown (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);
+}
+
+static void
+irc_destroy_connector (struct server *s)
+{
+ connector_free (s->connector);
+ free (s->connector);
+ s->connector = NULL;
+
+ // Not connecting anymore
+ s->state = IRC_DISCONNECTED;
+}
+
+static void
+try_finish_quit (struct app_context *ctx)
+{
+ // TODO: multiserver
+ if (ctx->quitting && !irc_is_connected (&ctx->server))
+ ctx->polling = false;
+}
+
+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);
+ else if (s->state == IRC_CONNECTING)
+ irc_destroy_connector (s);
+
+ ctx->quitting = true;
+ try_finish_quit (ctx);
+}
+
+static void
+on_irc_disconnected (struct server *s)
+{
+ hard_assert (irc_is_connected (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);
+
+ if (s->ctx->quitting)
+ try_finish_quit (s->ctx);
+ else
+ irc_queue_reconnect (s);
+}
+
+// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+static void
+on_irc_ping_timeout (void *user_data)
+{
+ struct server *s = user_data;
+ buffer_send_error (s->ctx, s->buffer, "Connection timeout");
+ on_irc_disconnected (s);
+}
+
+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, s->irc_user->nickname));
+}
+
+// --- Processing server output ------------------------------------------------
+
+static void irc_process_message
+ (const struct irc_message *msg, const char *raw, void *user_data);
+
+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
+on_irc_readable (const struct pollfd *fd, struct server *s)
+{
+ if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
+ print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
+
+ (void) set_blocking (s->socket, false);
- if (!connect (sockfd, gai_iter->ai_addr, gai_iter->ai_addrlen))
+ 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))
+ {
+ 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))
+ {
+ 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);
+
+ if (disconnected)
+ on_irc_disconnected (s);
+ else
+ irc_reset_connection_timeouts (s);
+}
+
+// --- Connection establishment ------------------------------------------------
+
+static void
+irc_register (struct server *s)
+{
+ 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");
+
+ // These are filled automatically if needed
+ hard_assert (nickname && username && realname);
+
+ 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 : " ");
+
+ // 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;
+}
+
+static void
+irc_finish_connection (struct server *s, int socket)
+{
+ struct app_context *ctx = s->ctx;
+
+ s->socket = socket;
- xclose (sockfd);
+ struct error *e = NULL;
+ bool use_ssl = get_config_boolean (ctx, "server.ssl");
+ if (use_ssl && !irc_initialize_ssl (s, &e))
+ {
+ 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;
}
- freeaddrinfo (gai_result);
+ buffer_send_status (ctx, s->buffer, "Connection established");
+ s->state = IRC_CONNECTED;
+
+ 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;
+
+ poller_fd_set (&s->read_event, POLLIN);
+ irc_reset_connection_timeouts (s);
+
+ irc_register (s);
+}
+
+static void
+irc_on_connector_connecting (void *user_data, const char *address)
+{
+ struct server *s = user_data;
+ buffer_send_status (s->ctx, s->buffer, "Connecting to %s...", address);
+}
+
+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);
+}
+
+static void
+irc_on_connector_failure (void *user_data)
+{
+ struct server *s = user_data;
+ irc_destroy_connector (s);
+ irc_queue_reconnect (s);
+}
+
+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);
+}
+
+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);
+
+ 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;
- if (!gai_iter)
+ s->state = IRC_CONNECTING;
+ s->connector = connector;
+
+ if (!connector_add_target (connector, host, port, e))
{
- error_set (e, "connection failed");
+ irc_destroy_connector (s);
return false;
}
- s->socket = sockfd;
+ connector_step (connector);
return true;
}
-// --- More readline funky stuff -----------------------------------------------
+static void
+irc_initiate_connect (struct server *s)
+{
+ hard_assert (s->state == IRC_DISCONNECTED);
+ struct app_context *ctx = s->ctx;
+
+ 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"))
+ {
+ // No sense in trying to reconnect
+ buffer_send_error (ctx, s->buffer,
+ "No hostname specified in configuration");
+ return;
+ }
+
+ 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");
+
+ char *irc_port = xstrdup_printf ("%" PRIi64, irc_port_int);
+ char *socks_port = xstrdup_printf ("%" PRIi64, socks_port_int);
+
+ // TODO: the SOCKS code needs a rewrite so that we don't block on it either
+ struct error *e = NULL;
+ if (socks_host)
+ {
+ 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)
+ 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)
+ {
+ buffer_send_error (s->ctx, s->buffer, "%s", e->message);
+ error_free (e);
+ irc_queue_reconnect (s);
+ }
+}
+
+// --- Input prompt ------------------------------------------------------------
static char *
make_unseen_prefix (struct app_context *ctx)
@@ -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;
}
@@ -4725,318 +5072,6 @@ process_input (struct app_context *ctx, char *user_input)
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);
-
- 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);
-}
-
-static void
-on_irc_ping_timeout (void *user_data)
-{
- struct server *s = user_data;
- buffer_send_error (s->ctx, s->buffer, "Connection timeout");
- on_irc_disconnected (s);
-}
-
-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"));
-}
-
-static void
-irc_reset_connection_timeouts (struct server *s)
-{
- 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
-on_irc_readable (const struct pollfd *fd, struct server *s)
-{
- if (fd->revents & ~(POLLIN | POLLHUP | POLLERR))
- print_debug ("fd %d: unexpected revents: %d", fd->fd, fd->revents);
-
- (void) set_blocking (s->socket, false);
-
- 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))
- {
- 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))
- {
- 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);
-
- 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)
-{
- // 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"))
- {
- error_set (e, "No hostname specified in configuration");
- *should_retry = false;
- return false;
- }
-
- 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);
-
- 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");
-
- // These are filled automatically if needed
- hard_assert (nickname && username && realname);
-
- bool use_ssl = get_config_boolean (ctx, "server.ssl");
- if (socks_host)
- {
- 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;
- }
- 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;
- }
-
- s->state = IRC_CONNECTED;
- buffer_send_status (ctx, s->buffer, "Connection established");
-
- 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;
-
- poller_fd_set (&s->read_event, POLLIN);
- irc_reset_connection_timeouts (s);
-
- 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 : " ");
-
- // 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;
-}
-
// --- Word completion ---------------------------------------------------------
// The amount of crap that goes into this is truly insane.
@@ -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");
}