From 2fe3c7ed455f9f233d1cd4180d8a33f7628a2c1d Mon Sep 17 00:00:00 2001
From: Přemysl Janouch
Date: Sat, 2 Aug 2014 00:09:23 +0200
Subject: kike: implement the ping-pong and QUIT
---
src/common.c | 9 ++++
src/kike.c | 145 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
2 files changed, 144 insertions(+), 10 deletions(-)
(limited to 'src')
diff --git a/src/common.c b/src/common.c
index fba82b4..2593cdc 100644
--- a/src/common.c
+++ b/src/common.c
@@ -961,6 +961,15 @@ poller_timers_find (struct poller_timers *self,
return -1;
}
+static ssize_t
+poller_timers_find_by_data (struct poller_timers *self, void *data)
+{
+ for (size_t i = 0; i < self->len; i++)
+ if (self->info[i].user_data == data)
+ return i;
+ return -1;
+}
+
static void
poller_timers_add (struct poller_timers *self,
poller_timer_func dispatcher, void *data, int timeout_ms)
diff --git a/src/kike.c b/src/kike.c
index 7f7cdb8..aae5c18 100644
--- a/src/kike.c
+++ b/src/kike.c
@@ -225,6 +225,7 @@ struct client
bool initialized; ///< Has any data been received yet?
bool registered; ///< The user has registered
+ bool closing_link; ///< Closing link
bool ssl_rx_want_tx; ///< SSL_read() wants to write
bool ssl_tx_want_rx; ///< SSL_write() wants to read
@@ -439,19 +440,38 @@ server_context_free (struct server_context *self)
// --- Main program ------------------------------------------------------------
+static void client_cancel_timers (struct client *);
+static void client_set_kill_timer (struct client *);
+
static void
-client_kill (struct client *c, const char *reason)
+client_unregister (struct client *c, const char *reason)
{
- // TODO: multicast a QUIT message with `reason' || "Client exited"
- (void) reason;
+ if (!c->registered)
+ return;
+
+ // TODO: multicast a `:prefix QUIT :reason' to all people present on all
+ // channels we were on.
+ // TODO: remove ourselves from the channels, ...
+ str_map_set (&c->ctx->users, c->nickname, NULL);
+ free (c->nickname);
+ c->nickname = NULL;
- // TODO: do further cleanup if the client has successfully registered etc.
+ c->registered = false;
+}
+
+static void
+client_kill (struct client *c, const char *reason)
+{
+ client_unregister (c, reason ? reason : "Client exited");
struct server_context *ctx = c->ctx;
ssize_t i = poller_find_by_fd (&ctx->poller, c->socket_fd);
if (i != -1)
poller_remove_at_index (&ctx->poller, i);
+ client_cancel_timers (c);
+ if (c->ssl)
+ (void) SSL_shutdown (c->ssl);
xclose (c->socket_fd);
c->socket_fd = -1;
client_free (c);
@@ -497,6 +517,83 @@ irc_get_text (struct server_context *ctx, int id, const char *def)
return catgets (ctx->catalog, 1, id, def);
}
+static void
+irc_close_link (struct client *c, const char *reason)
+{
+ if (!soft_assert (!c->closing_link))
+ return;
+
+ // We push an `ERROR' message to the write buffer and let the poller send
+ // it, with some arbitrary timeout. The `closing_link' state makes sure
+ // that a/ we ignore any successive messages, and b/ that the connection
+ // is killed after the write buffer is transferred and emptied.
+ irc_send (c, "ERROR :Closing Link: %s[%s] (%s)", c->nickname,
+ c->hostname /* TODO host IP? */, reason);
+ c->closing_link = true;
+
+ client_unregister (c, reason);
+ client_set_kill_timer (c);
+}
+
+// --- Timers ------------------------------------------------------------------
+
+static void
+client_cancel_timers (struct client *c)
+{
+ ssize_t i;
+ struct poller_timers *timers = &c->ctx->poller.timers;
+ while ((i = poller_timers_find_by_data (timers, c)) != -1)
+ poller_timers_remove_at_index (timers, i);
+}
+
+static void
+client_set_timer (struct client *c, poller_timer_func fn, unsigned interval)
+{
+ client_cancel_timers (c);
+ poller_timers_add (&c->ctx->poller.timers, fn, c, interval * 1000);
+}
+
+static void
+on_irc_client_kill_timer (void *user_data)
+{
+ struct client *c = user_data;
+ hard_assert (!c->initialized || c->closing_link);
+ client_kill (c, NULL);
+}
+
+static void
+client_set_kill_timer (struct client *c)
+{
+ client_set_timer (c, on_irc_client_kill_timer, c->ctx->ping_interval);
+}
+
+static void
+on_irc_client_timeout_timer (void *user_data)
+{
+ struct client *c = user_data;
+ struct str reason;
+ str_init (&reason);
+ str_append_printf (&reason, "Ping timeout: >%u seconds",
+ c->ctx->ping_interval);
+ irc_close_link (c, reason.str);
+ str_free (&reason);
+}
+
+static void
+on_irc_client_ping_timer (void *user_data)
+{
+ struct client *c = user_data;
+ hard_assert (!c->closing_link);
+ irc_send (c, "PING :%s", c->ctx->server_name);
+ client_set_timer (c, on_irc_client_timeout_timer, c->ctx->ping_interval);
+}
+
+static void
+client_set_ping_timer (struct client *c)
+{
+ client_set_timer (c, on_irc_client_ping_timer, c->ctx->ping_interval);
+}
+
// --- IRC command handling ----------------------------------------------------
enum
@@ -774,6 +871,28 @@ irc_handle_ping (const struct irc_message *msg, struct client *c)
c->ctx->server_name, msg->params.vector[0]);
}
+static void
+irc_handle_pong (const struct irc_message *msg, struct client *c)
+{
+ // We are the only server, so we don't have to care too much
+ if (msg->params.len < 1)
+ irc_send_reply (c, IRC_ERR_NOORIGIN);
+ else
+ // Set a new timer to send another PING
+ client_set_ping_timer (c);
+}
+
+static void
+irc_handle_quit (const struct irc_message *msg, struct client *c)
+{
+ struct str reason;
+ str_init (&reason);
+ str_append_printf (&reason, "Quit: %s",
+ msg->params.len > 0 ? msg->params.vector[0] : c->nickname);
+ irc_close_link (c, reason.str);
+ str_free (&reason);
+}
+
static void
irc_handle_time (const struct irc_message *msg, struct client *c)
{
@@ -812,6 +931,7 @@ static void
irc_register_handlers (struct server_context *ctx)
{
// TODO: add an index for IRC_ERR_NOSUCHSERVER validation?
+ // TODO: more commands, see RFC 2812 :!
static const struct irc_command message_handlers[] =
{
{ "PASS", false, irc_handle_pass },
@@ -821,6 +941,8 @@ irc_register_handlers (struct server_context *ctx)
{ "LUSERS", true, irc_handle_lusers },
{ "MOTD", true, irc_handle_motd },
{ "PING", true, irc_handle_ping },
+ { "PONG", false, irc_handle_pong },
+ { "QUIT", false, irc_handle_quit },
{ "TIME", true, irc_handle_time },
{ "VERSION", true, irc_handle_version },
};
@@ -838,11 +960,10 @@ irc_process_message (const struct irc_message *msg,
{
(void) raw;
- // XXX: we may want to discard everything following a QUIT etc.
- // We can set a flag within the client object.
- // TODO: see RFC 2812 :!
-
struct client *c = user_data;
+ if (c->closing_link)
+ return;
+
struct irc_command *cmd = str_map_find (&c->ctx->handlers, msg->command);
if (!cmd)
irc_send_reply (c, IRC_ERR_UNKNOWNCOMMAND, msg->command);
@@ -1073,6 +1194,7 @@ on_irc_client_ready (const struct pollfd *pfd, void *user_data)
return;
}
c->initialized = true;
+ client_set_ping_timer (c);
}
int new_events = 0;
@@ -1105,6 +1227,10 @@ on_irc_client_ready (const struct pollfd *pfd, void *user_data)
if (pfd->events != new_events)
poller_set (&c->ctx->poller, c->socket_fd, new_events,
(poller_dispatcher_func) on_irc_client_ready, c);
+
+ // The purpose of the `closing_link' state is to transfer the `ERROR'
+ if (c->closing_link && !c->write_buffer.len)
+ client_kill (c, NULL);
}
static void
@@ -1151,11 +1277,10 @@ on_irc_client_available (const struct pollfd *pfd, void *user_data)
c->hostname = xstrdup (host);
LIST_PREPEND (ctx->clients, c);
- // TODO: set a timeout on the socket, something like 3 minutes, then we
- // should terminate the connection.
set_blocking (fd, false);
poller_set (&ctx->poller, fd, POLLIN,
(poller_dispatcher_func) on_irc_client_ready, c);
+ client_set_kill_timer (c);
}
}
--
cgit v1.2.3-70-g09d2