#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;
int cookie;
time_t signon;
time_t lastactivity;
+ unsigned int caps;
};
struct channel {
char *key;
#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;
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;
} else {
goto unknown;
}
-#endif
} else if(strcmp(command, "NICK") == 0) {
// User now registered
if (!args[0])
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)
{
}
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
{
}
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
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");