1 /* Copyright (C) 2019 C. McEnroe <june@causal.agency>
2 *
3 * This program is free software: you can redistribute it and/or modify
4 * it under the terms of the GNU General Public License as published by
5 * the Free Software Foundation, either version 3 of the License, or
6 * (at your option) any later version.
7 *
8 * This program is distributed in the hope that it will be useful,
9 * but WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <https://www.gnu.org/licenses/>.
15 *
16 * Additional permission under GNU GPL version 3 section 7:
17 *
18 * If you modify this Program, or any covered work, by linking or
19 * combining it with OpenSSL (or a modified version of that library),
20 * containing parts covered by the terms of the OpenSSL License and the
21 * original SSLeay license, the licensors of this Program grant you
22 * additional permission to convey the resulting work. Corresponding
23 * Source for a non-source form of such a combination shall include the
24 * source code for the parts of OpenSSL used as well as that of the
25 * covered work.
26 */
27
28 #include <assert.h>
29 #include <err.h>
30 #include <fcntl.h>
31 #include <regex.h>
32 #include <stdarg.h>
33 #include <stdbool.h>
34 #include <stdio.h>
35 #include <stdlib.h>
36 #include <string.h>
37 #include <strings.h>
38 #include <sysexits.h>
39 #include <time.h>
40 #include <tls.h>
41 #include <unistd.h>
42
43 #include "bounce.h"
44
45 enum Cap clientCaps = CapServerTime | CapConsumer | CapPassive | CapSTS;
46 char *clientPass;
47 char *clientAway;
48
49 static size_t active;
50
clientAlloc(int sock,struct tls * tls)51 struct Client *clientAlloc(int sock, struct tls *tls) {
52 struct Client *client = calloc(1, sizeof(*client));
53 if (!client) err(EX_OSERR, "calloc");
54 fcntl(sock, F_SETFL, O_NONBLOCK);
55 client->sock = sock;
56 client->tls = tls;
57 client->time = time(NULL);
58 client->idle = client->time;
59 client->need = NeedHandshake | NeedNick | NeedUser;
60 if (clientPass) client->need |= NeedPass;
61 return client;
62 }
63
clientHandshake(struct Client * client)64 static void clientHandshake(struct Client *client) {
65 int error = tls_handshake(client->tls);
66 if (error == TLS_WANT_POLLIN || error == TLS_WANT_POLLOUT) return;
67 if (error) {
68 warnx("client tls_handshake: %s", tls_error(client->tls));
69 client->error = true;
70 return;
71 }
72 client->need &= ~NeedHandshake;
73 if ((clientCaps & CapSASL) && tls_peer_cert_provided(client->tls)) {
74 client->need &= ~NeedPass;
75 }
76 }
77
clientFree(struct Client * client)78 void clientFree(struct Client *client) {
79 if (!client->need) {
80 if (!(client->caps & CapPassive) && !--active && !stateAway) {
81 serverEnqueue("AWAY :%s\r\n", clientAway);
82 }
83 }
84 tls_close(client->tls);
85 tls_free(client->tls);
86 free(client);
87 }
88
clientSend(struct Client * client,const char * ptr,size_t len)89 void clientSend(struct Client *client, const char *ptr, size_t len) {
90 verboseLog("<-", ptr, len);
91 fcntl(client->sock, F_SETFL, 0);
92 while (len) {
93 ssize_t ret = tls_write(client->tls, ptr, len);
94 if (ret == TLS_WANT_POLLIN || ret == TLS_WANT_POLLOUT) continue;
95 if (ret < 0) {
96 warnx("client tls_write: %s", tls_error(client->tls));
97 client->error = true;
98 break;
99 }
100 ptr += ret;
101 len -= ret;
102 }
103 fcntl(client->sock, F_SETFL, O_NONBLOCK);
104 client->idle = time(NULL);
105 }
106
clientFormat(struct Client * client,const char * format,...)107 void clientFormat(struct Client *client, const char *format, ...) {
108 char buf[MessageCap];
109 va_list ap;
110 va_start(ap, format);
111 int len = vsnprintf(buf, sizeof(buf), format, ap);
112 va_end(ap);
113 assert((size_t)len < sizeof(buf));
114 clientSend(client, buf, len);
115 }
116
passRequired(struct Client * client)117 static void passRequired(struct Client *client) {
118 clientFormat(
119 client,
120 ":%s 464 * :Password incorrect\r\n"
121 "ERROR :Password incorrect\r\n",
122 ORIGIN
123 );
124 client->error = true;
125 }
126
maybeSync(struct Client * client)127 static void maybeSync(struct Client *client) {
128 if (client->need == NeedPass) passRequired(client);
129 if (!client->need) {
130 stateSync(client);
131 if (client->setPos) ringSet(client->consumer, client->setPos);
132 if (!(client->caps & CapPassive) && !active++) {
133 serverEnqueue("AWAY\r\n");
134 }
135 }
136 }
137
138 typedef void Handler(struct Client *client, struct Message *msg);
139
handleNick(struct Client * client,struct Message * msg)140 static void handleNick(struct Client *client, struct Message *msg) {
141 (void)msg;
142 client->need &= ~NeedNick;
143 maybeSync(client);
144 }
145
handleUser(struct Client * client,struct Message * msg)146 static void handleUser(struct Client *client, struct Message *msg) {
147 if (!msg->params[0]) {
148 client->error = true;
149 return;
150 }
151 if (client->need & NeedPass) {
152 passRequired(client);
153 } else {
154 client->need &= ~NeedUser;
155 client->consumer = ringConsumer(msg->params[0]);
156 if (msg->params[0][0] == '-') client->caps |= CapPassive;
157 maybeSync(client);
158 }
159 }
160
handlePass(struct Client * client,struct Message * msg)161 static void handlePass(struct Client *client, struct Message *msg) {
162 if (!clientPass) return;
163 if (!msg->params[0]) {
164 client->error = true;
165 return;
166 }
167 #ifdef __OpenBSD__
168 int error = crypt_checkpass(msg->params[0], clientPass);
169 #else
170 int error = strcmp(crypt(msg->params[0], clientPass), clientPass);
171 #endif
172 if (error) {
173 passRequired(client);
174 } else {
175 client->need &= ~NeedPass;
176 maybeSync(client);
177 }
178 explicit_bzero(msg->params[0], strlen(msg->params[0]));
179 }
180
handleCap(struct Client * client,struct Message * msg)181 static void handleCap(struct Client *client, struct Message *msg) {
182 if (!msg->params[0]) msg->params[0] = "";
183
184 enum Cap avail = clientCaps | (stateCaps & ~CapSASL);
185 const char *values[CapBits] = {
186 [CapSASLBit] = "EXTERNAL",
187 [CapSTSBit] = "duration=2147483647",
188 };
189
190 if (!strcmp(msg->params[0], "END")) {
191 if (!client->need) return;
192 client->need &= ~NeedCapEnd;
193 maybeSync(client);
194
195 } else if (!strcmp(msg->params[0], "LS")) {
196 if (client->need) client->need |= NeedCapEnd;
197 int version = 0;
198 if (msg->params[1]) version = strtol(msg->params[1], NULL, 10);
199 if (version >= 302) {
200 if (avail & CapCapNotify) client->caps |= CapCapNotify;
201 clientFormat(
202 client, ":%s CAP * LS :%s\r\n",
203 ORIGIN, capList(avail, values)
204 );
205 } else {
206 clientFormat(
207 client, ":%s CAP * LS :%s\r\n",
208 ORIGIN, capList(avail, NULL)
209 );
210 }
211
212 } else if (!strcmp(msg->params[0], "REQ") && msg->params[1]) {
213 if (client->need) client->need |= NeedCapEnd;
214 enum Cap caps = capParse(msg->params[1], values);
215 if (caps == (avail & caps)) {
216 client->caps |= caps;
217 if (caps & CapConsumer && values[CapConsumerBit]) {
218 client->setPos = strtoull(values[CapConsumerBit], NULL, 10);
219 }
220 clientFormat(client, ":%s CAP * ACK :%s\r\n", ORIGIN, msg->params[1]);
221 } else {
222 clientFormat(client, ":%s CAP * NAK :%s\r\n", ORIGIN, msg->params[1]);
223 }
224
225 } else if (!strcmp(msg->params[0], "LIST")) {
226 clientFormat(
227 client, ":%s CAP * LIST :%s\r\n",
228 ORIGIN, capList(client->caps, NULL)
229 );
230
231 } else {
232 clientFormat(client, ":%s 410 * :Invalid CAP subcommand\r\n", ORIGIN);
233 }
234 }
235
handleAuthenticate(struct Client * client,struct Message * msg)236 static void handleAuthenticate(struct Client *client, struct Message *msg) {
237 if (!msg->params[0]) msg->params[0] = "";
238 bool cert = (clientCaps & CapSASL) && tls_peer_cert_provided(client->tls);
239 if (cert && !strcmp(msg->params[0], "EXTERNAL")) {
240 clientFormat(client, "AUTHENTICATE +\r\n");
241 } else if (cert && !strcmp(msg->params[0], "+")) {
242 clientFormat(
243 client, ":%s 900 * %s * :You are now logged in as *\r\n",
244 ORIGIN, stateEcho()
245 );
246 clientFormat(
247 client, ":%s 903 * :SASL authentication successful\r\n",
248 ORIGIN
249 );
250 } else {
251 clientFormat(
252 client, ":%s 904 * :SASL authentication failed\r\n",
253 ORIGIN
254 );
255 }
256 }
257
handleQuit(struct Client * client,struct Message * msg)258 static void handleQuit(struct Client *client, struct Message *msg) {
259 (void)msg;
260 clientFormat(client, "ERROR :Detaching\r\n");
261 client->error = true;
262 }
263
hasTag(const char * tags,const char * tag)264 static bool hasTag(const char *tags, const char *tag) {
265 if (!tags) return false;
266 size_t len = strlen(tag);
267 bool val = strchr(tag, '=');
268 while (*tags && *tags != ' ') {
269 if (
270 !strncmp(tags, tag, len) &&
271 (!tags[len] || strchr((val ? "; " : "=; "), tags[len]))
272 ) return true;
273 tags += strcspn(tags, "; ");
274 tags += (*tags == ';');
275 }
276 return false;
277 }
278
synthLabel(struct Client * client)279 static const char *synthLabel(struct Client *client) {
280 enum { LabelCap = 64 };
281 static char buf[sizeof("label=") + LabelCap];
282 snprintf(buf, sizeof(buf), "label=pounce~%zu", client->consumer);
283 return buf;
284 }
285
reserialize(char * buf,size_t cap,const char * origin,const struct Message * msg)286 static void reserialize(
287 char *buf, size_t cap, const char *origin, const struct Message *msg
288 ) {
289 char *ptr = buf, *end = &buf[cap];
290 if (msg->tags) {
291 ptr = seprintf(ptr, end, "@%s ", msg->tags);
292 }
293 if (origin || msg->origin) {
294 ptr = seprintf(ptr, end, ":%s ", (origin ? origin : msg->origin));
295 }
296 ptr = seprintf(ptr, end, "%s", msg->cmd);
297 for (size_t i = 0; i < ParamCap && msg->params[i]; ++i) {
298 if (i + 1 == ParamCap || !msg->params[i + 1]) {
299 ptr = seprintf(ptr, end, " :%s", msg->params[i]);
300 } else {
301 ptr = seprintf(ptr, end, " %s", msg->params[i]);
302 }
303 }
304 }
305
clientProduce(struct Client * client,const char * line)306 static void clientProduce(struct Client *client, const char *line) {
307 size_t diff = ringDiff(client->consumer);
308 ringProduce(line);
309 if (!diff && !(client->caps & CapEchoMessage)) {
310 ringConsume(NULL, client->consumer);
311 }
312 }
313
handlePrivmsg(struct Client * client,struct Message * msg)314 static void handlePrivmsg(struct Client *client, struct Message *msg) {
315 if (!msg->params[0]) return;
316 char buf[MessageCap];
317 bool self = !strcmp(msg->params[0], stateNick());
318 if (!(stateCaps & CapEchoMessage) || self) {
319 reserialize(buf, sizeof(buf), stateEcho(), msg);
320 clientProduce(client, buf);
321 }
322 if (self) return;
323 reserialize(buf, sizeof(buf), NULL, msg);
324 if (stateCaps & CapEchoMessage && !hasTag(msg->tags, "label")) {
325 serverFormat(
326 "@%s%c%s\r\n",
327 synthLabel(client),
328 (buf[0] == '@' ? ';' : ' '),
329 (buf[0] == '@' ? &buf[1] : buf)
330 );
331 } else {
332 serverFormat("%s\r\n", buf);
333 }
334 }
335
handlePalaver(struct Client * client,struct Message * msg)336 static void handlePalaver(struct Client *client, struct Message *msg) {
337 if (client->need & NeedPass) return;
338 char buf[MessageCap];
339 reserialize(buf, sizeof(buf), NULL, msg);
340 clientProduce(client, buf);
341 }
342
handlePong(struct Client * client,struct Message * msg)343 static void handlePong(struct Client *client, struct Message *msg) {
344 (void)client;
345 (void)msg;
346 }
347
348 static const struct {
349 bool intercept;
350 bool need;
351 const char *cmd;
352 Handler *fn;
353 } Handlers[] = {
354 { false, false, "AUTHENTICATE", handleAuthenticate },
355 { false, false, "NICK", handleNick },
356 { false, false, "PASS", handlePass },
357 { false, false, "USER", handleUser },
358 { true, false, "CAP", handleCap },
359 { true, false, "PALAVER", handlePalaver },
360 { true, false, "PONG", handlePong },
361 { true, true, "NOTICE", handlePrivmsg },
362 { true, true, "PRIVMSG", handlePrivmsg },
363 { true, true, "QUIT", handleQuit },
364 { true, true, "TAGMSG", handlePrivmsg },
365 };
366
clientParse(struct Client * client,char * line)367 static void clientParse(struct Client *client, char *line) {
368 struct Message msg = parse(line);
369 if (!msg.cmd) return;
370 for (size_t i = 0; i < ARRAY_LEN(Handlers); ++i) {
371 if (strcmp(msg.cmd, Handlers[i].cmd)) continue;
372 if (Handlers[i].need && client->need) break;
373 Handlers[i].fn(client, &msg);
374 return;
375 }
376 client->error = true;
377 }
378
intercept(const char * line,size_t len)379 static bool intercept(const char *line, size_t len) {
380 if (line[0] == '@') {
381 const char *sp = memchr(line, ' ', len);
382 if (!sp) return false;
383 sp++;
384 len -= sp - line;
385 line = sp;
386 }
387 for (size_t i = 0; i < ARRAY_LEN(Handlers); ++i) {
388 if (!Handlers[i].intercept) continue;
389 size_t n = strlen(Handlers[i].cmd);
390 if (len < n) continue;
391 if (memcmp(line, Handlers[i].cmd, n)) continue;
392 if (len == n || line[n] == ' ' || line[n] == '\r') return true;
393 }
394 return false;
395 }
396
clientRecv(struct Client * client)397 void clientRecv(struct Client *client) {
398 if (client->need & NeedHandshake) {
399 clientHandshake(client);
400 return;
401 }
402
403 ssize_t read = tls_read(
404 client->tls,
405 &client->buf[client->len], sizeof(client->buf) - client->len
406 );
407 if (read == TLS_WANT_POLLIN || read == TLS_WANT_POLLOUT) return;
408 if (read <= 0) {
409 if (read < 0) warnx("client tls_read: %s", tls_error(client->tls));
410 client->error = true;
411 return;
412 }
413 client->len += read;
414
415 char *lf;
416 char *line = client->buf;
417 for (;;) {
418 lf = memchr(line, '\n', &client->buf[client->len] - line);
419 if (!lf) break;
420 verboseLog("->", line, lf - line);
421 if (client->need || intercept(line, lf - line)) {
422 lf[0] = '\0';
423 if (lf - line && lf[-1] == '\r') lf[-1] = '\0';
424 clientParse(client, line);
425 } else {
426 serverSend(line, lf + 1 - line);
427 }
428 line = lf + 1;
429 }
430 client->len -= line - client->buf;
431 memmove(client->buf, line, client->len);
432 client->idle = time(NULL);
433 }
434
wordcmp(const char * line,size_t i,const char * word)435 static int wordcmp(const char *line, size_t i, const char *word) {
436 if (line[0] == '@') {
437 line += strcspn(line, " ");
438 if (*line) line++;
439 }
440 if (line[0] == ':') {
441 line += strcspn(line, " ");
442 if (*line) line++;
443 }
444 while (i--) {
445 line += strcspn(line, " ");
446 if (*line) line++;
447 }
448 size_t len = strcspn(line, " ");
449 return len == strlen(word)
450 ? strncmp(line, word, len)
451 : (int)len - (int)strlen(word);
452 }
453
454 // s/..(..)../\1/g
455 static char *
snip(char * dst,size_t cap,const char * src,const regex_t * regex)456 snip(char *dst, size_t cap, const char *src, const regex_t *regex) {
457 char *ptr = dst, *end = &dst[cap];
458 regmatch_t match[2];
459 assert(regex->re_nsub);
460 for (; *src; src += match[0].rm_eo) {
461 if (regexec(regex, src, 2, match, 0)) break;
462 ptr = seprintf(
463 ptr, end, "%.*s%.*s",
464 (int)match[0].rm_so, src,
465 (int)(match[1].rm_eo - match[1].rm_so), &src[match[1].rm_so]
466 );
467 }
468 ptr = seprintf(ptr, end, "%s", src);
469 return (ptr == end ? NULL : dst);
470 }
471
compile(regex_t * regex,const char * pattern)472 static regex_t *compile(regex_t *regex, const char *pattern) {
473 if (regex->re_nsub) return regex;
474 int error = regcomp(regex, pattern, REG_EXTENDED);
475 if (error) {
476 char buf[256];
477 regerror(error, regex, buf, sizeof(buf));
478 errx(EX_SOFTWARE, "regcomp: %s: %s", buf, pattern);
479 }
480 return regex;
481 }
482
483 typedef const char *Filter(const char *line);
484
filterAccountNotify(const char * line)485 static const char *filterAccountNotify(const char *line) {
486 return (wordcmp(line, 0, "ACCOUNT") ? line : NULL);
487 }
488
filterAwayNotify(const char * line)489 static const char *filterAwayNotify(const char *line) {
490 return (wordcmp(line, 0, "AWAY") ? line : NULL);
491 }
492
filterBatch(const char * line)493 static const char *filterBatch(const char *line) {
494 return (wordcmp(line, 0, "BATCH") ? line : NULL);
495 }
496
filterCapNotify(const char * line)497 static const char *filterCapNotify(const char *line) {
498 if (wordcmp(line, 0, "CAP")) return line;
499 if (!wordcmp(line, 1, "NEW")) return NULL;
500 if (!wordcmp(line, 1, "DEL")) return NULL;
501 return line;
502 }
503
filterChghost(const char * line)504 static const char *filterChghost(const char *line) {
505 return (wordcmp(line, 0, "CHGHOST") ? line : NULL);
506 }
507
filterExtendedJoin(const char * line)508 static const char *filterExtendedJoin(const char *line) {
509 if (wordcmp(line, 0, "JOIN")) return line;
510 static regex_t regex;
511 static char buf[MessageCap];
512 return snip(buf, sizeof(buf), line, compile(®ex, "(JOIN [^ ]+).+"));
513 }
514
filterInviteNotify(const char * line)515 static const char *filterInviteNotify(const char *line) {
516 if (wordcmp(line, 0, "INVITE")) return line;
517 return (wordcmp(line, 1, stateNick()) ? NULL : line);
518 }
519
filterLabeledResponse(const char * line)520 static const char *filterLabeledResponse(const char *line) {
521 return (wordcmp(line, 0, "ACK") ? line : NULL);
522 }
523
filterMessageTags(const char * line)524 static const char *filterMessageTags(const char *line) {
525 return (wordcmp(line, 0, "TAGMSG") ? line : NULL);
526 }
527
filterMultiPrefix(const char * line)528 static const char *filterMultiPrefix(const char *line) {
529 static char buf[MessageCap];
530 if (!wordcmp(line, 0, "352")) {
531 static regex_t regex;
532 return snip(
533 buf, sizeof(buf), line,
534 compile(®ex, "( [HG][*]?[~!@%&+])[~!@%&+]+")
535 );
536 } else if (!wordcmp(line, 0, "353")) {
537 static regex_t regex;
538 return snip(
539 buf, sizeof(buf), line,
540 compile(®ex, "( :?[~!@%&+])[~!@%&+]+")
541 );
542 } else {
543 return line;
544 }
545 }
546
filterPalaverApp(const char * line)547 static const char *filterPalaverApp(const char *line) {
548 return (wordcmp(line, 0, "PALAVER") ? line : NULL);
549 }
550
filterSetname(const char * line)551 static const char *filterSetname(const char *line) {
552 return (wordcmp(line, 0, "SETNAME") ? line : NULL);
553 }
554
filterUserhostInNames(const char * line)555 static const char *filterUserhostInNames(const char *line) {
556 if (wordcmp(line, 0, "353")) return line;
557 static regex_t regex;
558 static char buf[MessageCap];
559 return snip(
560 buf, sizeof(buf), line,
561 compile(®ex, "( :?[^!]+)![^ ]+")
562 );
563 }
564
565 static Filter *Filters[CapBits] = {
566 [CapAccountNotifyBit] = filterAccountNotify,
567 [CapAwayNotifyBit] = filterAwayNotify,
568 [CapBatchBit] = filterBatch,
569 [CapCapNotifyBit] = filterCapNotify,
570 [CapChghostBit] = filterChghost,
571 [CapExtendedJoinBit] = filterExtendedJoin,
572 [CapInviteNotifyBit] = filterInviteNotify,
573 [CapLabeledResponseBit] = filterLabeledResponse,
574 [CapMessageTagsBit] = filterMessageTags,
575 [CapMultiPrefixBit] = filterMultiPrefix,
576 [CapPalaverAppBit] = filterPalaverApp,
577 [CapSetnameBit] = filterSetname,
578 [CapUserhostInNamesBit] = filterUserhostInNames,
579 };
580
filterEchoMessage(struct Client * client,const char * line)581 static const char *filterEchoMessage(struct Client *client, const char *line) {
582 if (line[0] != '@') return line;
583 if (!hasTag(&line[1], synthLabel(client))) return line;
584 return NULL;
585 }
586
filterTags(const char * line)587 static const char *filterTags(const char *line) {
588 if (line[0] != '@') return line;
589 const char *sp = strchr(line, ' ');
590 return (sp ? sp + 1 : NULL);
591 }
592
clientConsume(struct Client * client)593 void clientConsume(struct Client *client) {
594 struct timeval time;
595 const char *line = ringPeek(&time, client->consumer);
596 if (!line) return;
597
598 enum Cap diff = client->caps ^ (clientCaps | stateCaps);
599 if (diff & CapEchoMessage) {
600 line = filterEchoMessage(client, line);
601 }
602 if (line && stateCaps & TagCaps && !(client->caps & TagCaps)) {
603 line = filterTags(line);
604 }
605 for (size_t i = 0; line && i < ARRAY_LEN(Filters); ++i) {
606 if (!Filters[i]) continue;
607 if (diff & (1 << i)) line = Filters[i](line);
608 }
609 if (!line) {
610 ringConsume(NULL, client->consumer);
611 return;
612 }
613
614 if (
615 client->caps & CapServerTime &&
616 (line[0] != '@' || !hasTag(&line[1], "time"))
617 ) {
618 char ts[sizeof("YYYY-MM-DDThh:mm:ss")];
619 struct tm *tm = gmtime(&time.tv_sec);
620 strftime(ts, sizeof(ts), "%FT%T", tm);
621 clientFormat(
622 client, "@time=%s.%03dZ;causal.agency/pos=%zu%c%s\r\n",
623 ts, (int)(time.tv_usec / 1000),
624 ringPos(client->consumer) + 1,
625 (line[0] == '@' ? ';' : ' '),
626 (line[0] == '@' ? &line[1] : line)
627 );
628 } else if (client->caps & CapConsumer) {
629 clientFormat(
630 client, "@causal.agency/pos=%zu%c%s\r\n",
631 ringPos(client->consumer) + 1,
632 (line[0] == '@' ? ';' : ' '),
633 (line[0] == '@' ? &line[1] : line)
634 );
635 } else {
636 clientFormat(client, "%s\r\n", line);
637 }
638 if (!client->error) ringConsume(NULL, client->consumer);
639 }
640