aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--kike-replies1
-rw-r--r--kike.c67
2 files changed, 68 insertions, 0 deletions
diff --git a/kike-replies b/kike-replies
index c5deec5..c585740 100644
--- a/kike-replies
+++ b/kike-replies
@@ -48,6 +48,7 @@
403 IRC_ERR_NOSUCHCHANNEL "%s :No such channel"
404 IRC_ERR_CANNOTSENDTOCHAN "%s :Cannot send to channel"
409 IRC_ERR_NOORIGIN ":No origin specified"
+410 IRC_ERR_INVALIDCAPCMD "%s :%s"
411 IRC_ERR_NORECIPIENT ":No recipient given (%s)"
412 IRC_ERR_NOTEXTTOSEND ":No text to send"
421 IRC_ERR_UNKNOWNCOMMAND "%s: Unknown command"
diff --git a/kike.c b/kike.c
index df30ec0..4f9e3cd 100644
--- a/kike.c
+++ b/kike.c
@@ -308,10 +308,13 @@ struct client
struct poller_timer kill_timer; ///< Hard kill timeout
bool initialized; ///< Has any data been received yet?
+ bool cap_negotiating; ///< Negotiating capabilities
bool registered; ///< The user has registered
bool closing_link; ///< Closing link
bool half_closed; ///< Closing link: conn. is half-closed
+ unsigned long cap_version; ///< CAP protocol version
+
bool ssl_rx_want_tx; ///< SSL_read() wants to write
bool ssl_tx_want_rx; ///< SSL_write() wants to read
SSL *ssl; ///< SSL connection
@@ -339,6 +342,7 @@ client_init (struct client *self)
self->socket_fd = -1;
str_init (&self->read_buffer);
str_init (&self->write_buffer);
+ self->cap_version = 301;
// TODO: make this configurable and more fine-grained
flood_detector_init (&self->antiflood, 10, 20);
str_map_init (&self->invites);
@@ -1078,6 +1082,8 @@ irc_try_finish_registration (struct client *c)
struct server_context *ctx = c->ctx;
if (!c->nickname || !c->username || !c->realname)
return;
+ if (c->registered || c->cap_negotiating)
+ return;
c->registered = true;
irc_send_reply (c, IRC_RPL_WELCOME, c->nickname, c->username, c->hostname);
@@ -1110,6 +1116,66 @@ irc_try_finish_registration (struct client *c)
}
static void
+irc_handle_cap (const struct irc_message *msg, struct client *c)
+{
+ if (msg->params.len < 1)
+ RETURN_WITH_REPLY (c, IRC_ERR_NEEDMOREPARAMS, msg->command);
+
+ struct str_vector v;
+ str_vector_init (&v);
+
+ const char *subcommand = msg->params.vector[0];
+ const char *params = "";
+ if (msg->params.len > 1)
+ {
+ params = msg->params.vector[1];
+ split_str_ignore_empty (params, ' ', &v);
+ }
+
+ const char *target = c->nickname ? c->nickname : "*";
+ if (!irc_strcmp (subcommand, "LS"))
+ {
+ if (v.len == 1 && !xstrtoul (&c->cap_version, v.vector[0], 10))
+ irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
+ subcommand, "Ignoring invalid protocol version number");
+
+ c->cap_negotiating = true;
+ // TODO: actually implement a few capabilities
+ client_send (c, "CAP %s LS :", target);
+ }
+ else if (!irc_strcmp (subcommand, "LIST"))
+ {
+ // TODO: list currently enabled capabilities
+ client_send (c, "CAP %s LIST :", target);
+ }
+ else if (!irc_strcmp (subcommand, "REQ"))
+ {
+ c->cap_negotiating = true;
+ // TODO: process the capability change request, "-" disables
+ if (v.len)
+ client_send (c, "CAP %s NAK :%s", target, params);
+ else
+ client_send (c, "CAP %s ACK :%s", target, params);
+ }
+ else if (!irc_strcmp (subcommand, "ACK"))
+ {
+ if (v.len)
+ irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
+ subcommand, "No acknowledgable capabilities supported");
+ }
+ else if (!irc_strcmp (subcommand, "END"))
+ {
+ c->cap_negotiating = false;
+ irc_try_finish_registration (c);
+ }
+ else
+ irc_send_reply (c, IRC_ERR_INVALIDCAPCMD,
+ subcommand, "Invalid CAP subcommand");
+
+ str_vector_free (&v);
+}
+
+static void
irc_handle_pass (const struct irc_message *msg, struct client *c)
{
if (c->registered)
@@ -2516,6 +2582,7 @@ irc_register_handlers (struct server_context *ctx)
// TODO: add a field for oper-only commands?
static const struct irc_command message_handlers[] =
{
+ { "CAP", false, irc_handle_cap },
{ "PASS", false, irc_handle_pass },
{ "NICK", false, irc_handle_nick },
{ "USER", false, irc_handle_user },