diff options
Diffstat (limited to 'state.c')
-rw-r--r-- | state.c | 128 |
1 files changed, 90 insertions, 38 deletions
diff --git a/state.c b/state.c index 59a61ed..a28b3ba 100644 --- a/state.c +++ b/state.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2019 C. McEnroe <june@causal.agency> +/* Copyright (C) 2019 June McEnroe <june@causal.agency> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by @@ -38,6 +38,8 @@ bool stateNoNames; enum Cap stateCaps; +char *stateAccount; +bool stateAway; typedef void Handler(struct Message *msg); @@ -59,26 +61,34 @@ void stateLogin( const char *pass, enum Cap blind, const char *plain, const char *nick, const char *user, const char *real ) { - serverFormat("CAP LS 302\r\n"); - if (pass) serverFormat("PASS :%s\r\n", pass); - if (blind) serverFormat("CAP REQ :%s\r\n", capList(blind, NULL)); if (plain) { - byte buf[AuthLen]; + byte buf[AuthLen] = {0}; size_t len = 1 + strlen(plain); - if (sizeof(buf) < len) { - errx(EX_SOFTWARE, "SASL PLAIN is too long"); - } - buf[0] = 0; - for (size_t i = 0; plain[i]; ++i) { - buf[1 + i] = (plain[i] == ':' ? 0 : plain[i]); - } + if (len > sizeof(buf)) errx(EX_USAGE, "SASL PLAIN too long"); + memcpy(&buf[1], plain, len - 1); + byte *sep = memchr(buf, ':', len); + if (!sep) errx(EX_USAGE, "SASL PLAIN missing colon"); + *sep = 0; base64(plainBase64, buf, len); - explicit_bzero(buf, sizeof(buf)); + explicit_bzero(buf, len); } + + serverFormat("CAP LS 302\r\n"); + if (pass) serverFormat("PASS :%s\r\n", pass); + if (blind) serverFormat("CAP REQ :%s\r\n", capList(blind, NULL)); serverFormat("NICK %s\r\n", nick); serverFormat("USER %s 0 * :%s\r\n", user, real); } +static const enum Cap DontReq = 0 + | CapConsumer + | CapPalaverApp + | CapPassive + | CapReadMarker + | CapSASL + | CapSTS + | CapUnsupported; + static void handleCap(struct Message *msg) { require(msg, false, 3); enum Cap caps; @@ -89,8 +99,15 @@ static void handleCap(struct Message *msg) { } if (!strcmp(msg->params[1], "LS") || !strcmp(msg->params[1], "NEW")) { - caps &= ~(CapSASL | CapSTS | CapUnsupported); - if (caps) serverFormat("CAP REQ :%s\r\n", capList(caps, NULL)); + caps &= ~DontReq; + if (caps & CapEchoMessage && !(caps & CapLabeledResponse)) { + caps &= ~CapEchoMessage; + } + if (caps) { + serverFormat("CAP REQ :%s\r\n", capList(caps, NULL)); + } else { + if (!(stateCaps & CapSASL)) serverFormat("CAP END\r\n"); + } } else if (!strcmp(msg->params[1], "ACK")) { stateCaps |= caps; @@ -120,7 +137,8 @@ static void handleAuthenticate(struct Message *msg) { } static void handleReplyLoggedIn(struct Message *msg) { - (void)msg; + require(msg, false, 3); + set(&stateAccount, msg->params[2]); serverFormat("CAP END\r\n"); } @@ -159,12 +177,6 @@ bool stateReady(void) { && intro.myInfo[0]; } -static void set(char **field, const char *value) { - if (*field) free(*field); - *field = strdup(value); - if (!*field) err(EX_OSERR, "strdup"); -} - static void handleErrorNicknameInUse(struct Message *msg) { if (self.nick) return; require(msg, false, 2); @@ -198,6 +210,7 @@ static void handleReplyMyInfo(struct Message *msg) { } static struct { + bool done; char **tokens; size_t cap, len; } support; @@ -215,12 +228,18 @@ static void supportAdd(const char *token) { static void handleReplyISupport(struct Message *msg) { require(msg, false, 1); + if (support.done) return; for (size_t i = 1; i < ParamCap; ++i) { if (!msg->params[i] || strchr(msg->params[i], ' ')) break; supportAdd(msg->params[i]); } } +static void handleReplyMOTDStart(struct Message *msg) { + (void)msg; + support.done = true; +} + struct Channel { char *name; char *topic; @@ -267,9 +286,9 @@ static bool originSelf(const char *origin) { size_t len = strlen(self.nick); if (strlen(origin) < len) return false; if (strncmp(origin, self.nick, len)) return false; - if (origin[len] != '!') return false; + if (origin[len] && origin[len] != '!') return false; - if (!self.origin || strcmp(self.origin, origin)) { + if (origin[len] && (!self.origin || strcmp(self.origin, origin))) { set(&self.origin, origin); } return true; @@ -280,6 +299,7 @@ static void handleNick(struct Message *msg) { if (!originSelf(msg->origin)) return; set(&self.nick, msg->params[0]); + if (!self.origin) return; char *rest = strchr(self.origin, '!'); assert(rest); size_t size = strlen(self.nick) + strlen(rest) + 1; @@ -317,6 +337,16 @@ static void handleReplyTopic(struct Message *msg) { chanTopic(msg->params[1], msg->params[2]); } +static void handleReplyUnaway(struct Message *msg) { + (void)msg; + stateAway = false; +} + +static void handleReplyNowAway(struct Message *msg) { + (void)msg; + stateAway = true; +} + static void handleError(struct Message *msg) { require(msg, false, 1); errx(EX_UNAVAILABLE, "%s", msg->params[0]); @@ -331,8 +361,13 @@ static const struct { { "003", handleReplyCreated }, { "004", handleReplyMyInfo }, { "005", handleReplyISupport }, + { "305", handleReplyUnaway }, + { "306", handleReplyNowAway }, { "332", handleReplyTopic }, + { "375", handleReplyMOTDStart }, + { "422", handleReplyMOTDStart }, { "433", handleErrorNicknameInUse }, + { "437", handleErrorNicknameInUse }, { "900", handleReplyLoggedIn }, { "904", handleErrorSASLFail }, { "905", handleErrorSASLFail }, @@ -364,18 +399,30 @@ void stateSync(struct Client *client) { client, ":%s NOTICE %s :" "pounce is GPLv3 fwee softwawe ^w^ code is avaiwable fwom %s\r\n", - ORIGIN, self.nick, SOURCE_URL + clientOrigin, self.nick, SOURCE_URL ); + if (stateAccount) { + clientFormat( + client, ":%s 900 %s %s %s :You are now logged in as %s\r\n", + clientOrigin, self.nick, stateEcho(), stateAccount, stateAccount + ); + } + clientFormat( - client, - ":%s 001 %s :%s\r\n" - ":%s 002 %s :%s\r\n" - ":%s 003 %s :%s\r\n" - ":%s 004 %s %s %s %s %s%s%s\r\n", - intro.origin, self.nick, intro.welcome, - intro.origin, self.nick, intro.yourHost, - intro.origin, self.nick, intro.created, + client, ":%s 001 %s :%s\r\n", + intro.origin, self.nick, intro.welcome + ); + clientFormat( + client, ":%s 002 %s :%s\r\n", + intro.origin, self.nick, intro.yourHost + ); + clientFormat( + client, ":%s 003 %s :%s\r\n", + intro.origin, self.nick, intro.created + ); + clientFormat( + client, ":%s 004 %s %s %s %s %s%s%s\r\n", intro.origin, self.nick, intro.myInfo[0], intro.myInfo[1], intro.myInfo[2], intro.myInfo[3], (intro.myInfo[4] ? " " : ""), (intro.myInfo[4] ? intro.myInfo[4] : "") @@ -399,16 +446,18 @@ void stateSync(struct Client *client) { ); } if (i < support.len) { - clientFormat(client, ":%s 005 %s", intro.origin, self.nick); + char buf[512], *ptr = buf, *end = &buf[sizeof(buf)]; + ptr = seprintf(ptr, end, ":%s 005 %s", intro.origin, self.nick); for (; i < support.len; ++i) { - clientFormat(client, " %s", support.tokens[i]); + ptr = seprintf(ptr, end, " %s", support.tokens[i]); } - clientFormat(client, " :are supported by this server\r\n"); + ptr = seprintf(ptr, end, " :are supported by this server\r\n"); + clientSend(client, buf, ptr - buf); } clientFormat( client, ":%s 422 %s :MOTD File is missing\r\n", - ORIGIN, self.nick + clientOrigin, self.nick ); if (chans.len) assert(self.origin); @@ -418,9 +467,12 @@ void stateSync(struct Client *client) { if (chan->topic) { clientFormat( client, ":%s 332 %s %s :%s\r\n", - ORIGIN, self.nick, chan->name, chan->topic + clientOrigin, self.nick, chan->name, chan->topic ); } + if (client->caps & CapReadMarker) { + clientGetMarker(client, chan->name); + } if (stateNoNames) continue; serverEnqueue("NAMES %s\r\n", chan->name); } |