From 2fe3c7ed455f9f233d1cd4180d8a33f7628a2c1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C5=99emysl=20Janouch?= Date: Sat, 2 Aug 2014 00:09:23 +0200 Subject: [PATCH] kike: implement the ping-pong and QUIT --- src/common.c | 9 ++++ src/kike.c | 145 +++++++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 144 insertions(+), 10 deletions(-) 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_unregister (struct client *c, const char *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; + + c->registered = false; +} + static void client_kill (struct client *c, const char *reason) { - // TODO: multicast a QUIT message with `reason' || "Client exited" - (void) reason; - - // TODO: do further cleanup if the client has successfully registered etc. + 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); } }