From 422ea52ad66f7c7004e26ca625feec98d4763160 Mon Sep 17 00:00:00 2001 From: Egor Date: Fri, 3 Feb 2023 18:14:31 +0200 Subject: [PATCH] add multi-prefix cap --- common.h | 7 +++++-- irc.c | 52 ++++++++++++++++++++++++++++++++++++++++++---------- ircfmt.c | 43 ++++++++++++++++++------------------------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/common.h b/common.h index 2d4acd6..387f3f9 100644 --- a/common.h +++ b/common.h @@ -19,6 +19,8 @@ struct tls; #define REG_INIT 0 #define REG_CAPLS 1 #define REG_REGISTERED 2 +#define CAP_302 1 +#define CAP_MULTIPREFIX 2 struct client_data { SOCKET FD; struct tls *cctx; @@ -44,6 +46,7 @@ struct client_data { int cookie; time_t signon; time_t lastactivity; + unsigned int caps; }; struct channel { char *key; @@ -73,8 +76,8 @@ struct channel { #define DMODE_NOEXTERNAL 8 #define DMODE_SECRET 16 #define DMODE_TOPICPROT 32 -#define is_chanop(chan, client) (trie_get(&(chan)->o, (client)->key)) -#define is_chanvoice(chan, client) (trie_get(&(chan)->v, (client)->key)) +#define is_chanop(chan, client) (!!trie_get(&(chan)->o, (client)->key)) +#define is_chanvoice(chan, client) (!!trie_get(&(chan)->v, (client)->key)) extern struct trie_node *client_map; extern struct trie_node *channel_map; extern struct trie_node *autojoin; diff --git a/irc.c b/irc.c index 55d5aa9..b1e0a96 100644 --- a/irc.c +++ b/irc.c @@ -178,28 +178,61 @@ int processPacket(struct client_data *client, char *source, char *command, int a msg_pong(args[0]); sendbuf(client); } else if(strcmp(command, "CAP") == 0) { + static const struct cap { + const char *name; + unsigned caps; + } caps[] = { + { "multi-prefix", CAP_MULTIPREFIX }, + { "cap-notify", 0 }, + { 0, 0 } + }; + if (!args[0]) return err_needmoreparams(client, command), sendbuf(client), 0; stringupper(args[0]); - // don't enable CAP command until we actually have CAPs - // because clients can't handle NAKs - goto unknown; -#if 0 if (strcmp(args[0], "LS") == 0) { - // at least one client hangs if it doesn't see a known extension - copy_strf(&databuf, ":%s CAP * LS :multi-prefix\n", hostname); + if (args[1] && atoi(args[1]) >= 302) + client->caps |= CAP_302; + copy_strf(&databuf, ":%s CAP * LS :multi-prefix\r\n", hostname); sendbuf(client); if (!is_registered(client)) { client->regstate = REG_CAPLS; } } else if (strcmp(args[0], "LIST") == 0) { - copy_strf(&databuf, ":%s CAP * LIST :\n", hostname); + copy_strf(&databuf, ":%s CAP * LIST :\r\n", hostname); + for (const struct cap *c = caps; c->name; c++) { + if (client->caps & c->caps) + append_strf(&databuf, "%s ", c->name); + } + if (databuf.buf[databuf.len-1] == ' ') + databuf.len--; + append_strf(&databuf, "\r\n"); sendbuf(client); } else if (strcmp(args[0], "REQ") == 0) { if (!args[1]) // FIXME: error message return 0; - copy_strf(&databuf, ":%s CAP * NAK :%s\n", hostname, args[1]); - sendbuf(client); + char *p; + if ((p = strtok(args[1], " "))) { + do { + int invert = *p == '-'; + p += invert; + const struct cap *c; + for (c = caps; c->name; c++) { + if (strcmp(p, c->name) == 0) { + copy_strf(&databuf, ":%s CAP * ACK :%s%s\r\n", hostname, invert?"-":"", c->name); + if (!invert) + client->caps |= c->caps; + else + client->caps &= ~c->caps; + break; + } + } + if (!c->name) + copy_strf(&databuf, ":%s CAP * NAK :%s\r\n", hostname, p); + sendbuf(client); + } while ((p = strtok(NULL, " "))); + } + // FIXME: else error? } else if (strcmp(args[0], "END") == 0) { if (!is_registered(client)) { client->regstate = REG_INIT; @@ -208,7 +241,6 @@ int processPacket(struct client_data *client, char *source, char *command, int a } else { goto unknown; } -#endif } else if(strcmp(command, "NICK") == 0) { // User now registered if (!args[0]) diff --git a/ircfmt.c b/ircfmt.c index 61a6790..ad70f48 100644 --- a/ircfmt.c +++ b/ircfmt.c @@ -2,6 +2,19 @@ extern struct dynarr databuf; extern const char *hostname; extern const char *format_umodes(struct client_data *cl); +const char *format_prefixes(struct client_data *client, struct client_data *cl, struct channel *chan) { + if (!chan) return ""; + int o = is_chanop(chan, cl); + int v = is_chanvoice(chan, cl); + if (client->caps & CAP_MULTIPREFIX && o && v) + return "@+"; + else if (o) + return "@"; + else if (v) + return "+"; + else + return ""; +} void msg_nick(struct client_data *src, const char *newnick) { @@ -223,16 +236,9 @@ void rpl_nowaway(struct client_data *client) // 306 } void rpl_whoreply(struct client_data *client, struct client_data *cl, struct channel *chan) // 352 { - char prefix[2] = {0,0}; - if (chan) { - if (trie_get(&chan->o, cl->key)) - prefix[0] = '@'; - else if (trie_get(&chan->v, cl->key)) - prefix[0] = '+'; - } copy_strf(&databuf, ":%s 352 %s %s %s %s %s %s %s%s%s :1 %s\r\n", hostname, client->nick, chan ? chan->name : "*", cl->username, cl->hostname, hostname, cl->nick, - cl->away ? "G" : "H", cl->umode&UMODE_OPER ? "*" : "", prefix, cl->realname); + cl->away ? "G" : "H", cl->umode&UMODE_OPER ? "*" : "", format_prefixes(client, cl, chan), cl->realname); } void rpl_endofwho(struct client_data *client, const char *query) // 315 { @@ -267,14 +273,7 @@ void rpl_endofwhois(struct client_data *client, struct client_data *cl) // 318 } void rpl_whoischannels(struct client_data *client, struct client_data *cl, struct channel *chan) // 319 { - char prefix[2] = {0,0}; - if (chan) { - if (trie_get(&chan->o, cl->key)) - prefix[0] = '@'; - else if (trie_get(&chan->v, cl->key)) - prefix[0] = '+'; - } - copy_strf(&databuf, ":%s 319 %s %s :%s%s\r\n", hostname, client->nick, cl->nick, prefix, chan->name); + copy_strf(&databuf, ":%s 319 %s %s :%s%s\r\n", hostname, client->nick, cl->nick, format_prefixes(client, cl, chan), chan->name); } #if 0 void rpl_whoisspecial(struct client_data *client, struct client_data *cl, const char *info) // 320 @@ -382,16 +381,10 @@ int rpl_namreply(struct client_data *client, struct channel *chan, struct client return 0; } } - const char *prefix = NULL; - if (is_chanop(chan, cl)) - prefix = "@"; - else if (is_chanvoice(chan, cl)) - prefix = "+"; - else - prefix = ""; + const char *prefix = format_prefixes(client, cl, chan); if (databuf.len == 0) { - // TODO: secret channel "@" - copy_strf(&databuf, ":%s 353 %s = %s :", hostname, client->nick, chan->name); + const char *chanchar = chan->dmode & DMODE_SECRET ? "@" : "="; + copy_strf(&databuf, ":%s 353 %s %s %s :", hostname, client->nick, chanchar, chan->name); } else { if (databuf.len + 1 + strlen(prefix) + strlen(cl->nick) > 510) { append_str(&databuf, "\r\n"); -- 2.56.0