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-70-g09d2