diff options
Diffstat (limited to 'client.c')
-rw-r--r-- | client.c | 531 |
1 files changed, 366 insertions, 165 deletions
diff --git a/client.c b/client.c index 25707a8..23cde36 100644 --- a/client.c +++ b/client.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 @@ -12,10 +12,22 @@ * * You should have received a copy of the GNU General Public License * along with this program. If not, see <https://www.gnu.org/licenses/>. + * + * Additional permission under GNU GPL version 3 section 7: + * + * If you modify this Program, or any covered work, by linking or + * combining it with OpenSSL (or a modified version of that library), + * containing parts covered by the terms of the OpenSSL License and the + * original SSLeay license, the licensors of this Program grant you + * additional permission to convey the resulting work. Corresponding + * Source for a non-source form of such a combination shall include the + * source code for the parts of OpenSSL used as well as that of the + * covered work. */ #include <assert.h> #include <err.h> +#include <fcntl.h> #include <regex.h> #include <stdarg.h> #include <stdbool.h> @@ -30,70 +42,87 @@ #include "bounce.h" -bool clientCA; -bool clientSTS = true; +enum Cap clientCaps = 0 + | CapConsumer + | CapPassive + | CapReadMarker + | CapSTS + | CapSelfMessage + | CapServerTime; + +char *clientOrigin; char *clientPass; char *clientAway; +char *clientQuit; static size_t active; -enum Need { - BIT(NeedNick), - BIT(NeedUser), - BIT(NeedPass), - BIT(NeedCapEnd), -}; +static void activeIncr(const struct Client *client) { + if (client->need) return; + if (client->caps & CapPassive) return; + if (!active++) { + serverEnqueue("AWAY\r\n"); + } +} -struct Client { - struct tls *tls; - enum Need need; - size_t consumer; - size_t setPos; - enum Cap caps; - char buf[MessageCap]; - size_t len; - bool error; -}; +static void activeDecr(const struct Client *client) { + if (client->need) return; + if (client->caps & CapPassive) return; + if (!--active && !stateAway) { + serverEnqueue("AWAY :%s\r\n", clientAway); + } +} -struct Client *clientAlloc(struct tls *tls) { +struct Client *clientAlloc(int sock, struct tls *tls) { struct Client *client = calloc(1, sizeof(*client)); if (!client) err(EX_OSERR, "calloc"); + fcntl(sock, F_SETFL, O_NONBLOCK); + client->sock = sock; client->tls = tls; - client->need = NeedNick | NeedUser | (clientPass ? NeedPass : 0); - if (clientCA && tls_peer_cert_provided(tls)) { + client->time = time(NULL); + client->idle = client->time; + client->need = NeedHandshake | NeedNick | NeedUser; + if (clientPass) client->need |= NeedPass; + return client; +} + +static void clientHandshake(struct Client *client) { + int error = tls_handshake(client->tls); + if (error == TLS_WANT_POLLIN || error == TLS_WANT_POLLOUT) return; + if (error) { + warnx("client tls_handshake: %s", tls_error(client->tls)); + client->remove = true; + return; + } + client->need &= ~NeedHandshake; + if ((clientCaps & CapSASL) && tls_peer_cert_provided(client->tls)) { client->need &= ~NeedPass; } - return client; } void clientFree(struct Client *client) { - if (!client->need) { - if (!(client->caps & CapPassive) && !--active) { - serverFormat("AWAY :%s\r\n", clientAway); - } - } + activeDecr(client); tls_close(client->tls); tls_free(client->tls); free(client); } -bool clientError(const struct Client *client) { - return client->error; -} - void clientSend(struct Client *client, const char *ptr, size_t len) { - if (verbose) fprintf(stderr, "\x1B[34m%.*s\x1B[m", (int)len, ptr); + verboseLog("<-", ptr, len); + fcntl(client->sock, F_SETFL, 0); while (len) { ssize_t ret = tls_write(client->tls, ptr, len); if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue; if (ret < 0) { warnx("client tls_write: %s", tls_error(client->tls)); - client->error = true; - return; + client->remove = true; + break; } ptr += ret; len -= ret; } + fcntl(client->sock, F_SETFL, O_NONBLOCK); + client->idle = time(NULL); } void clientFormat(struct Client *client, const char *format, ...) { @@ -111,9 +140,9 @@ static void passRequired(struct Client *client) { client, ":%s 464 * :Password incorrect\r\n" "ERROR :Password incorrect\r\n", - ORIGIN + clientOrigin ); - client->error = true; + client->remove = true; } static void maybeSync(struct Client *client) { @@ -121,9 +150,7 @@ static void maybeSync(struct Client *client) { if (!client->need) { stateSync(client); if (client->setPos) ringSet(client->consumer, client->setPos); - if (!(client->caps & CapPassive) && !active++) { - serverFormat("AWAY\r\n"); - } + activeIncr(client); } } @@ -137,7 +164,7 @@ static void handleNick(struct Client *client, struct Message *msg) { static void handleUser(struct Client *client, struct Message *msg) { if (!msg->params[0]) { - client->error = true; + client->remove = true; return; } if (client->need & NeedPass) { @@ -153,25 +180,27 @@ static void handleUser(struct Client *client, struct Message *msg) { static void handlePass(struct Client *client, struct Message *msg) { if (!clientPass) return; if (!msg->params[0]) { - client->error = true; + client->remove = true; return; } - if (!strcmp(crypt(msg->params[0], clientPass), clientPass)) { +#ifdef __OpenBSD__ + int error = crypt_checkpass(msg->params[0], clientPass); +#else + int error = strcmp(crypt(msg->params[0], clientPass), clientPass); +#endif + explicit_bzero(msg->params[0], strlen(msg->params[0])); + if (error) { + passRequired(client); + } else { client->need &= ~NeedPass; maybeSync(client); - } else { - passRequired(client); } - explicit_bzero(msg->params[0], strlen(msg->params[0])); } static void handleCap(struct Client *client, struct Message *msg) { if (!msg->params[0]) msg->params[0] = ""; - enum Cap avail = (stateCaps & ~CapSASL) - | CapServerTime | CapConsumer | CapPassive - | (clientCA ? CapSASL : 0) - | (clientSTS ? CapSTS : 0); + enum Cap avail = clientCaps | (stateCaps & ~CapSASL); const char *values[CapBits] = { [CapSASLBit] = "EXTERNAL", [CapSTSBit] = "duration=2147483647", @@ -190,12 +219,12 @@ static void handleCap(struct Client *client, struct Message *msg) { if (avail & CapCapNotify) client->caps |= CapCapNotify; clientFormat( client, ":%s CAP * LS :%s\r\n", - ORIGIN, capList(avail, values) + clientOrigin, capList(avail, values) ); } else { clientFormat( client, ":%s CAP * LS :%s\r\n", - ORIGIN, capList(avail, NULL) + clientOrigin, capList(avail, NULL) ); } @@ -203,106 +232,277 @@ static void handleCap(struct Client *client, struct Message *msg) { if (client->need) client->need |= NeedCapEnd; enum Cap caps = capParse(msg->params[1], values); if (caps == (avail & caps)) { - client->caps |= caps; if (caps & CapConsumer && values[CapConsumerBit]) { client->setPos = strtoull(values[CapConsumerBit], NULL, 10); } - clientFormat(client, ":%s CAP * ACK :%s\r\n", ORIGIN, msg->params[1]); + if (caps & CapPassive && !(client->caps & CapPassive)) { + activeDecr(client); + } + client->caps |= caps; + clientFormat( + client, ":%s CAP * ACK :%s\r\n", + clientOrigin, msg->params[1] + ); } else { - clientFormat(client, ":%s CAP * NAK :%s\r\n", ORIGIN, msg->params[1]); + clientFormat( + client, ":%s CAP * NAK :%s\r\n", + clientOrigin, msg->params[1] + ); } } else if (!strcmp(msg->params[0], "LIST")) { clientFormat( client, ":%s CAP * LIST :%s\r\n", - ORIGIN, capList(client->caps, NULL) + clientOrigin, capList(client->caps, NULL) ); } else { - clientFormat(client, ":%s 410 * :Invalid CAP subcommand\r\n", ORIGIN); + clientFormat( + client, ":%s 410 * :Invalid CAP subcommand\r\n", clientOrigin + ); } } static void handleAuthenticate(struct Client *client, struct Message *msg) { if (!msg->params[0]) msg->params[0] = ""; - if (!strcmp(msg->params[0], "EXTERNAL")) { + bool cert = (clientCaps & CapSASL) && tls_peer_cert_provided(client->tls); + if (cert && !strcmp(msg->params[0], "EXTERNAL")) { clientFormat(client, "AUTHENTICATE +\r\n"); - } else if (!strcmp(msg->params[0], "+")) { + } else if (cert && !strcmp(msg->params[0], "+")) { + const char *account = (stateAccount ? stateAccount : "*"); clientFormat( - client, ":%s 900 * %s * :You are now logged in as *\r\n", - ORIGIN, stateEcho() + client, ":%s 900 * %s %s :You are now logged in as %s\r\n", + clientOrigin, stateEcho(), account, account ); clientFormat( client, ":%s 903 * :SASL authentication successful\r\n", - ORIGIN + clientOrigin ); } else { clientFormat( client, ":%s 904 * :SASL authentication failed\r\n", - ORIGIN + clientOrigin ); } } -static void handleQuit(struct Client *client, struct Message *msg) { +static void handleJoin(struct Client *client, struct Message *msg) { + (void)client; (void)msg; - clientFormat(client, "ERROR :Detaching\r\n"); - client->error = true; + // irssi intentionally sends an invalid JOIN command, at + // an invalid time (during client registration), on every + // connection. Utterly mind-boggling. Ignore it so the + // connection doesn't just get dropped like it deserves to be. } -static void handlePrivmsg(struct Client *client, struct Message *msg) { - if (!msg->params[0] || !msg->params[1]) return; +static void handleQuit(struct Client *client, struct Message *msg) { + const char *mesg = msg->params[0]; + if (mesg && !strncmp(mesg, "$pounce", 7) && (!mesg[7] || mesg[7] == ' ')) { + mesg += 7; + mesg += strspn(mesg, " "); + clientQuit = strdup(mesg); + if (!clientQuit) err(EX_OSERR, "strdup"); + } else { + clientFormat(client, "ERROR :Detaching\r\n"); + client->remove = true; + } +} - int origin; - char line[MessageCap]; - snprintf( - line, sizeof(line), "@%s %n:%s %s %s :%s", - (msg->tags ? msg->tags : ""), &origin, - stateEcho(), msg->cmd, msg->params[0], msg->params[1] - ); - size_t diff = ringDiff(client->consumer); - ringProduce((msg->tags ? line : &line[origin])); - if (!diff) ringConsume(NULL, client->consumer); - if (!strcmp(msg->params[0], stateNick())) return; +static bool hasTag(const char *tags, const char *tag) { + if (!tags) return false; + size_t len = strlen(tag); + bool val = strchr(tag, '='); + while (*tags && *tags != ' ') { + if ( + !strncmp(tags, tag, len) && + (!tags[len] || strchr((val ? "; " : "=; "), tags[len])) + ) return true; + tags += strcspn(tags, "; "); + tags += (*tags == ';'); + } + return false; +} + +static const char *synthLabel(struct Client *client) { + enum { LabelCap = 64 }; + static char buf[sizeof("label=") + LabelCap]; + snprintf(buf, sizeof(buf), "label=pounce~%zu", client->consumer); + return buf; +} +static void reserialize( + char *buf, size_t cap, const char *origin, const struct Message *msg +) { + char *ptr = buf, *end = &buf[cap]; if (msg->tags) { + ptr = seprintf(ptr, end, "@%s ", msg->tags); + } + if (origin || msg->origin) { + ptr = seprintf(ptr, end, ":%s ", (origin ? origin : msg->origin)); + } + ptr = seprintf(ptr, end, "%s", msg->cmd); + for (size_t i = 0; i < ParamCap && msg->params[i]; ++i) { + if (i + 1 == ParamCap || !msg->params[i + 1]) { + ptr = seprintf(ptr, end, " :%s", msg->params[i]); + } else { + ptr = seprintf(ptr, end, " %s", msg->params[i]); + } + } +} + +static void clientProduce(struct Client *client, const char *line) { + size_t diff = ringDiff(client->consumer); + ringProduce(line); + if (!diff && !(client->caps & CapEchoMessage)) { + ringConsume(NULL, client->consumer); + } +} + +static void handlePrivmsg(struct Client *client, struct Message *msg) { + if (!msg->params[0]) return; + char buf[MessageCap]; + bool self = !strcmp(msg->params[0], stateNick()); + if (!(stateCaps & CapEchoMessage) || self) { + reserialize(buf, sizeof(buf), stateEcho(), msg); + clientProduce(client, buf); + } + if (self) return; + reserialize(buf, sizeof(buf), NULL, msg); + if (stateCaps & CapEchoMessage && !hasTag(msg->tags, "label")) { serverFormat( - "@%s %s %s :%s\r\n", - msg->tags, msg->cmd, msg->params[0], msg->params[1] + "@%s%c%s\r\n", + synthLabel(client), + (buf[0] == '@' ? ';' : ' '), + (buf[0] == '@' ? &buf[1] : buf) ); } else { - serverFormat("%s %s :%s\r\n", msg->cmd, msg->params[0], msg->params[1]); + serverFormat("%s\r\n", buf); } } -static void handleTagmsg(struct Client *client, struct Message *msg) { - if (!msg->tags || !msg->params[0]) return; - char line[MessageCap]; +static void handlePalaver(struct Client *client, struct Message *msg) { + if (client->need & NeedPass) return; + char buf[MessageCap]; + reserialize(buf, sizeof(buf), NULL, msg); + clientProduce(client, buf); +} + +struct Marker { + char *target; + char *timestamp; +}; + +static struct { + struct Marker *ptr; + size_t cap, len; +} markers; + +void clientGetMarker(struct Client *client, const char *target) { + for (size_t i = 0; i < markers.len; ++i) { + struct Marker marker = markers.ptr[i]; + if (strcasecmp(marker.target, target)) continue; + clientFormat( + client, ":%s MARKREAD %s timestamp=%s\r\n", + clientOrigin, target, marker.timestamp + ); + return; + } + clientFormat(client, ":%s MARKREAD %s *\r\n", clientOrigin, target); +} + +static void clientSetMarker( + struct Client *client, const char *target, const char *timestamp +) { + struct Marker *marker = NULL; + for (size_t i = 0; i < markers.len; ++i) { + marker = &markers.ptr[i]; + if (strcasecmp(marker->target, target)) continue; + if (strcmp(timestamp, marker->timestamp) < 0) { + clientFormat( + client, ":%s MARKREAD %s timestamp=%s\r\n", + clientOrigin, target, marker->timestamp + ); + return; + } + set(&marker->timestamp, timestamp); + goto notify; + } + if (markers.len == markers.cap) { + markers.cap = (markers.cap ? markers.cap * 2 : 8); + markers.ptr = realloc(markers.ptr, sizeof(*markers.ptr) * markers.cap); + if (!markers.ptr) err(EX_OSERR, "realloc"); + } + marker = &markers.ptr[markers.len++]; + *marker = (struct Marker) {0}; + set(&marker->target, target); + set(&marker->timestamp, timestamp); +notify:; + char buf[512]; snprintf( - line, sizeof(line), "@%s :%s TAGMSG %s", - msg->tags, stateEcho(), msg->params[0] + buf, sizeof(buf), ":%s MARKREAD %s timestamp=%s", + clientOrigin, marker->target, marker->timestamp ); - size_t diff = ringDiff(client->consumer); - ringProduce(line); - if (!diff) ringConsume(NULL, client->consumer); - if (!strcmp(msg->params[0], stateNick())) return; - serverFormat("@%s TAGMSG %s\r\n", msg->tags, msg->params[0]); + ringProduce(buf); +} + +static regex_t *TimestampRegex(void) { + static const char *Pattern = { +#define R2D "[0-9]{2}" + "^timestamp=[0-9]{4,}-" R2D "-" R2D + "T" R2D ":" R2D ":" R2D "[.][0-9]{3}Z$" +#undef R2D + }; + static bool compiled; + static regex_t regex; + if (!compiled) { + int error = regcomp(®ex, Pattern, REG_EXTENDED | REG_NOSUB); + assert(!error); + } + compiled = true; + return ®ex; +} + +static void handleMarkRead(struct Client *client, struct Message *msg) { + if (!msg->params[0]) { + clientFormat( + client, "FAIL MARKREAD NEED_MORE_PARAMS :Missing parameters\r\n" + ); + } else if (!msg->params[1]) { + clientGetMarker(client, msg->params[0]); + } else if (regexec(TimestampRegex(), msg->params[1], 0, NULL, 0)) { + clientFormat( + client, "FAIL MARKREAD INVALID_PARAMS %s :Invalid parameters\r\n", + msg->params[1] + ); + } else { + clientSetMarker(client, msg->params[0], &msg->params[1][10]); + } +} + +static void handlePong(struct Client *client, struct Message *msg) { + (void)client; + (void)msg; } static const struct { + bool intercept; + bool need; const char *cmd; Handler *fn; - bool need; } Handlers[] = { - { "AUTHENTICATE", handleAuthenticate, false }, - { "CAP", handleCap, false }, - { "NICK", handleNick, false }, - { "NOTICE", handlePrivmsg, true }, - { "PASS", handlePass, false }, - { "PRIVMSG", handlePrivmsg, true }, - { "QUIT", handleQuit, true }, - { "TAGMSG", handleTagmsg, true }, - { "USER", handleUser, false }, + { false, false, "AUTHENTICATE", handleAuthenticate }, + { false, false, "JOIN", handleJoin }, + { false, false, "NICK", handleNick }, + { false, false, "PASS", handlePass }, + { false, false, "USER", handleUser }, + { true, false, "CAP", handleCap }, + { true, false, "PALAVER", handlePalaver }, + { true, false, "PONG", handlePong }, + { true, true, "MARKREAD", handleMarkRead }, + { true, true, "NOTICE", handlePrivmsg }, + { true, true, "PRIVMSG", handlePrivmsg }, + { true, true, "QUIT", handleQuit }, + { true, true, "TAGMSG", handlePrivmsg }, }; static void clientParse(struct Client *client, char *line) { @@ -314,7 +514,7 @@ static void clientParse(struct Client *client, char *line) { Handlers[i].fn(client, &msg); return; } - client->error = true; + client->remove = true; } static bool intercept(const char *line, size_t len) { @@ -325,16 +525,22 @@ static bool intercept(const char *line, size_t len) { len -= sp - line; line = sp; } - if (len >= 4 && !memcmp(line, "CAP ", 4)) return true; - if (len == 4 && !memcmp(line, "QUIT", 4)) return true; - if (len >= 5 && !memcmp(line, "QUIT ", 5)) return true; - if (len >= 7 && !memcmp(line, "NOTICE ", 7)) return true; - if (len >= 7 && !memcmp(line, "TAGMSG ", 7)) return true; - if (len >= 8 && !memcmp(line, "PRIVMSG ", 8)) return true; + for (size_t i = 0; i < ARRAY_LEN(Handlers); ++i) { + if (!Handlers[i].intercept) continue; + size_t n = strlen(Handlers[i].cmd); + if (len < n) continue; + if (memcmp(line, Handlers[i].cmd, n)) continue; + if (len == n || line[n] == ' ' || line[n] == '\r') return true; + } return false; } void clientRecv(struct Client *client) { + if (client->need & NeedHandshake) { + clientHandshake(client); + return; + } + ssize_t read = tls_read( client->tls, &client->buf[client->len], sizeof(client->buf) - client->len @@ -342,34 +548,29 @@ void clientRecv(struct Client *client) { if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) return; if (read <= 0) { if (read < 0) warnx("client tls_read: %s", tls_error(client->tls)); - client->error = true; + client->remove = true; return; } client->len += read; - char *crlf; + char *lf; char *line = client->buf; for (;;) { - crlf = memmem(line, &client->buf[client->len] - line, "\r\n", 2); - if (!crlf) break; - if (verbose) { - fprintf(stderr, "\x1B[33m%.*s\x1B[m\n", (int)(crlf - line), line); - } - if (client->need || intercept(line, crlf - line)) { - crlf[0] = '\0'; + lf = memchr(line, '\n', &client->buf[client->len] - line); + if (!lf) break; + verboseLog("->", line, lf - line); + if (client->need || intercept(line, lf - line)) { + lf[0] = '\0'; + if (lf - line && lf[-1] == '\r') lf[-1] = '\0'; clientParse(client, line); } else { - serverSend(line, crlf + 2 - line); + serverSend(line, lf + 1 - line); } - line = crlf + 2; + line = lf + 1; } client->len -= line - client->buf; memmove(client->buf, line, client->len); -} - -size_t clientDiff(const struct Client *client) { - if (client->need) return 0; - return ringDiff(client->consumer); + client->idle = time(NULL); } static int wordcmp(const char *line, size_t i, const char *word) { @@ -391,34 +592,22 @@ static int wordcmp(const char *line, size_t i, const char *word) { : (int)len - (int)strlen(word); } -static size_t strlcpyn(char *dst, const char *src, size_t cap, size_t len) { - if (len < cap) { - memcpy(dst, src, len); - dst[len] = '\0'; - } else { - memcpy(dst, src, cap - 1); - dst[cap - 1] = '\0'; - } - return len; -} - // s/..(..)../\1/g -static char *snip(char *dst, size_t cap, const char *src, const regex_t *regex) { - size_t len = 0; +static char * +snip(char *dst, size_t cap, const char *src, const regex_t *regex) { + char *ptr = dst, *end = &dst[cap]; regmatch_t match[2]; assert(regex->re_nsub); for (; *src; src += match[0].rm_eo) { if (regexec(regex, src, 2, match, 0)) break; - len += strlcpyn(&dst[len], src, cap - len, match[0].rm_so); - if (len >= cap) return NULL; - len += strlcpyn( - &dst[len], &src[match[1].rm_so], - cap - len, match[1].rm_eo - match[1].rm_so + ptr = seprintf( + ptr, end, "%.*s%.*s", + (int)match[0].rm_so, src, + (int)(match[1].rm_eo - match[1].rm_so), &src[match[1].rm_so] ); - if (len >= cap) return NULL; } - len += strlcpy(&dst[len], src, cap - len); - return (len < cap ? dst : NULL); + ptr = seprintf(ptr, end, "%s", src); + return (ptr == end ? NULL : dst); } static regex_t *compile(regex_t *regex, const char *pattern) { @@ -496,6 +685,14 @@ static const char *filterMultiPrefix(const char *line) { } } +static const char *filterReadMarker(const char *line) { + return (wordcmp(line, 0, "MARKREAD") ? line : NULL); +} + +static const char *filterPalaverApp(const char *line) { + return (wordcmp(line, 0, "PALAVER") ? line : NULL); +} + static const char *filterSetname(const char *line) { return (wordcmp(line, 0, "SETNAME") ? line : NULL); } @@ -510,13 +707,7 @@ static const char *filterUserhostInNames(const char *line) { ); } -static const char *filterTags(const char *line) { - if (line[0] != '@') return line; - const char *sp = strchr(line, ' '); - return (sp ? sp + 1 : NULL); -} - -static Filter *Filters[] = { +static Filter *Filters[CapBits] = { [CapAccountNotifyBit] = filterAccountNotify, [CapAwayNotifyBit] = filterAwayNotify, [CapBatchBit] = filterBatch, @@ -527,18 +718,22 @@ static Filter *Filters[] = { [CapLabeledResponseBit] = filterLabeledResponse, [CapMessageTagsBit] = filterMessageTags, [CapMultiPrefixBit] = filterMultiPrefix, + [CapPalaverAppBit] = filterPalaverApp, + [CapReadMarkerBit] = filterReadMarker, [CapSetnameBit] = filterSetname, [CapUserhostInNamesBit] = filterUserhostInNames, }; -static bool hasTime(const char *line) { - if (!strncmp(line, "@time=", 6)) return true; - while (*line && *line != ' ') { - line += strcspn(line, "; "); - if (!strncmp(line, ";time=", 6)) return true; - if (*line == ';') line++; - } - return false; +static const char *filterEchoMessage(struct Client *client, const char *line) { + if (line[0] != '@') return line; + if (!hasTag(&line[1], synthLabel(client))) return line; + return NULL; +} + +static const char *filterTags(const char *line) { + if (line[0] != '@') return line; + const char *sp = strchr(line, ' '); + return (sp ? sp + 1 : NULL); } void clientConsume(struct Client *client) { @@ -546,10 +741,13 @@ void clientConsume(struct Client *client) { const char *line = ringPeek(&time, client->consumer); if (!line) return; - if (stateCaps & TagCaps && !(client->caps & TagCaps)) { + enum Cap diff = client->caps ^ (clientCaps | stateCaps); + if (diff & CapEchoMessage) { + line = filterEchoMessage(client, line); + } + if (line && stateCaps & TagCaps && !(client->caps & TagCaps)) { line = filterTags(line); } - enum Cap diff = client->caps ^ stateCaps; for (size_t i = 0; line && i < ARRAY_LEN(Filters); ++i) { if (!Filters[i]) continue; if (diff & (1 << i)) line = Filters[i](line); @@ -559,7 +757,10 @@ void clientConsume(struct Client *client) { return; } - if (client->caps & CapServerTime && !hasTime(line)) { + if ( + client->caps & CapServerTime && + (line[0] != '@' || !hasTag(&line[1], "time")) + ) { char ts[sizeof("YYYY-MM-DDThh:mm:ss")]; struct tm *tm = gmtime(&time.tv_sec); strftime(ts, sizeof(ts), "%FT%T", tm); @@ -580,5 +781,5 @@ void clientConsume(struct Client *client) { } else { clientFormat(client, "%s\r\n", line); } - if (!client->error) ringConsume(NULL, client->consumer); + if (!client->remove) ringConsume(NULL, client->consumer); } |