]> jfr.im git - solanum.git/blame - ircd/msgbuf.c
ircd/authproc.c: avoid crash on lack of any configured DNSBLs
[solanum.git] / ircd / msgbuf.c
CommitLineData
a8e69f5d 1/*
a6f63a82 2 * solanum - an advanced ircd.
3fc0499e 3 * Copyright (c) 2016 Ariadne Conill <ariadne@dereferenced.org>.
a8e69f5d
AC
4 *
5 * Permission to use, copy, modify, and/or distribute this software for any
6 * purpose with or without fee is hereby granted, provided that the above
7 * copyright notice and this permission notice is present in all copies.
8 *
9 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
10 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
11 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
12 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
13 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
14 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
15 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
16 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
17 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
18 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
19 * POSSIBILITY OF SUCH DAMAGE.
20 */
21
22#include "stdinc.h"
23#include "ircd_defs.h"
24#include "msgbuf.h"
691adddd
AC
25#include "client.h"
26#include "ircd.h"
a8e69f5d 27
f3564f47
SA
28static const char tag_escape_table[256] = {
29 /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
30 /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 'n', 0, 0, 'r', 0, 0,
31 /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
32 /* 2x */ 's', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
33 /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ':', 0, 0, 0, 0,
34 /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
35 /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0,
36};
37
38static const char tag_unescape_table[256] = {
39 /* x0 x1 x2 x3 x4 x5 x6 x7 x8 x9 xA xB xC xD xE xF */
40 /* 0x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
41 /* 1x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
42 /* 2x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
43 /* 3x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ';', 0, 0, 0, 0, 0,
44 /* 4x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
45 /* 5x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\\', 0, 0, 0,
46 /* 6x */ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,'\n', 0,
47 /* 7x */ 0, 0,'\r', ' ', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
48};
49
50static void
51msgbuf_unescape_value(char *value)
52{
53 char *in = value;
54 char *out = value;
55
56 if (value == NULL)
57 return;
58
59 while (*in != '\0') {
60 if (*in == '\\') {
61 const char unescape = tag_unescape_table[(unsigned char)*++in];
62
63 /* "\\\0" is unescaped to the character itself, "\0" */
64 if (*in == '\0')
65 break;
66
67 if (unescape) {
68 *out++ = unescape;
05a16d98 69 in++;
f3564f47
SA
70 } else {
71 *out++ = *in++;
72 }
73 } else {
74 *out++ = *in++;
75 }
76 }
77
78 /* copy final '\0' */
79 *out = *in;
80}
81
a8e69f5d
AC
82/*
83 * parse a message into a MsgBuf.
84 * returns 0 on success, 1 on error.
85 */
86int
87msgbuf_parse(struct MsgBuf *msgbuf, char *line)
88{
f3564f47 89 char *ch = line;
a8e69f5d
AC
90
91 msgbuf_init(msgbuf);
92
f3564f47 93 if (*ch == '@') {
08006c16 94 char *t = ch + 1;
a8e69f5d
AC
95
96 ch = strchr(ch, ' ');
f3564f47
SA
97
98 /* truncate tags if they're too long */
99 if ((ch != NULL && (ch - line) + 1 > TAGSLEN) || (ch == NULL && strlen(line) >= TAGSLEN)) {
100 ch = &line[TAGSLEN - 1];
101 }
102
103 if (ch != NULL) {
104 /* NULL terminate the tags string */
105 *ch++ = '\0';
106
107 while (1) {
a8e69f5d
AC
108 char *next = strchr(t, ';');
109 char *eq = strchr(t, '=');
110
f3564f47 111 if (next != NULL) {
a8e69f5d
AC
112 *next = '\0';
113
08006c16
AC
114 if (eq > next)
115 eq = NULL;
116 }
a8e69f5d
AC
117
118 if (eq != NULL)
08006c16 119 *eq++ = '\0';
a8e69f5d 120
f3564f47
SA
121 if (*t != '\0') {
122 msgbuf_unescape_value(eq);
d670fe52 123 msgbuf_append_tag(msgbuf, t, eq, 0);
f3564f47 124 }
269dd686 125
f3564f47 126 if (next != NULL) {
269dd686 127 t = next + 1;
f3564f47 128 } else {
269dd686 129 break;
f3564f47 130 }
a8e69f5d 131 }
f3564f47
SA
132 } else {
133 return 1;
a8e69f5d
AC
134 }
135 }
136
f3564f47
SA
137 /* truncate message if it's too long */
138 if (strlen(ch) > DATALEN) {
139 ch[DATALEN] = '\0';
140 }
a8e69f5d 141
f3564f47 142 if (*ch == ':') {
a8e69f5d
AC
143 ch++;
144 msgbuf->origin = ch;
145
146 char *end = strchr(ch, ' ');
147 if (end == NULL)
f3564f47 148 return 4;
a8e69f5d
AC
149
150 *end = '\0';
f3564f47 151 ch = end + 1;
a8e69f5d
AC
152 }
153
154 if (*ch == '\0')
f3564f47 155 return 2;
a8e69f5d 156
4e14f9a4 157 msgbuf->endp = &ch[strlen(ch)];
33ded5fc
SA
158 msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA);
159 if (msgbuf->n_para == 0)
f3564f47 160 return 3;
a8e69f5d 161
33ded5fc 162 msgbuf->cmd = msgbuf->para[0];
a8e69f5d
AC
163 return 0;
164}
4a13e3f1 165
4e14f9a4
EK
166/*
167 * Unparse the tail of a msgbuf perfectly, preserving framing details
168 * msgbuf->para[n] will reach to the end of the line
169 */
170
171void
172msgbuf_reconstruct_tail(struct MsgBuf *msgbuf, size_t n)
173{
174 if (msgbuf->endp == NULL || n > msgbuf->n_para)
175 return;
176
177 char *c;
178 const char *c_;
179
180 if (n == 0)
181 c_ = msgbuf->para[n];
182 else
183 c_ = msgbuf->para[n-1] + strlen(msgbuf->para[n-1]) + 1;
184
185 if (n == msgbuf->n_para && c_ == msgbuf->endp)
186 return;
187
188 msgbuf->para[n] = c_;
189 /* promote to non-const. msgbuf->endp witnesses that this is allowed */
190 c = msgbuf->endp - (msgbuf->endp - c_);
191
192 for ( ; c < msgbuf->endp; c++)
193 {
194 if (*c == '\0')
195 *c = ' ';
196 }
197}
198
199
f3564f47
SA
200/*
201 * Unparse msgbuf tags into a buffer
202 * returns the length of the tags written
203 */
204static size_t
205msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 206{
57dd2c6a 207 bool has_tags = false;
f3564f47
SA
208 char *commit = buf;
209 char *output = buf;
210 const char * const end = &buf[buflen - 2]; /* this is where the final ' ' goes */
211
212 for (size_t i = 0; i < msgbuf->n_tags; i++) {
213 size_t len;
4a13e3f1 214
4a13e3f1
AC
215 if ((msgbuf->tags[i].capmask & capmask) == 0)
216 continue;
217
57dd2c6a 218 if (has_tags) {
f3564f47
SA
219 if (output >= end)
220 break;
221 *output++ = ';';
57dd2c6a 222 } else {
f3564f47
SA
223 if (output >= end)
224 break;
225 *output++ = '@';
57dd2c6a 226 }
4a13e3f1 227
f3564f47
SA
228 if (msgbuf->tags[i].key == NULL)
229 continue;
230
231 len = strlen(msgbuf->tags[i].key);
232 if (len == 0)
233 continue;
4a13e3f1 234
f3564f47
SA
235 if (output + len > end)
236 break;
237 strcat(output, msgbuf->tags[i].key);
238 output += len;
239
240 if (msgbuf->tags[i].value != NULL) {
241 if (output >= end)
242 break;
243 *output++ = '=';
244
245 len = strlen(msgbuf->tags[i].value);
246 /* this only checks the unescaped length,
247 * but the escaped length could be longer
248 */
249 if (output + len > end)
250 break;
251
252 for (size_t n = 0; n < len; n++) {
253 const unsigned char c = msgbuf->tags[i].value[n];
254 const char escape = tag_escape_table[c];
255
256 if (escape) {
257 if (output + 2 > end)
258 break;
259
260 *output++ = '\\';
261 *output++ = escape;
262 } else {
263 if (output >= end)
264 break;
265
266 *output++ = c;
267 }
268 }
4a13e3f1 269 }
f3564f47
SA
270
271 has_tags = true;
272 commit = output;
4a13e3f1
AC
273 }
274
57dd2c6a 275 if (has_tags)
f3564f47
SA
276 *commit++ = ' ';
277
278 *commit = 0;
279 return commit - buf;
4a13e3f1
AC
280}
281
4b1cce65
SA
282int
283msgbuf_unparse_linebuf_tags(char *buf, size_t buflen, void *data)
284{
285 struct MsgBuf_str_data *str_data = data;
286
287 return msgbuf_unparse_tags(buf, buflen, str_data->msgbuf, str_data->caps);
288}
289
f3564f47
SA
290/*
291 * unparse a MsgBuf and message prefix into a buffer
292 * if origin is NULL, me.name will be used.
293 * cmd should not be NULL.
294 * updates buflen to correctly allow remaining message data to be added
295 */
4b1cce65 296int
f3564f47 297msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 298{
f3564f47 299 size_t tags_buflen;
4b1cce65
SA
300 size_t used = 0;
301 int ret;
f3564f47
SA
302
303 memset(buf, 0, *buflen);
304
305 tags_buflen = *buflen;
306 if (tags_buflen > TAGSLEN + 1)
307 tags_buflen = TAGSLEN + 1;
4a13e3f1
AC
308
309 if (msgbuf->n_tags > 0)
4b1cce65 310 used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask);
f3564f47 311
4b1cce65 312 const size_t data_bufmax = (used + DATALEN + 1);
f3564f47
SA
313 if (*buflen > data_bufmax)
314 *buflen = data_bufmax;
4a13e3f1 315
4b1cce65
SA
316 ret = rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name);
317 if (ret > 0)
318 used += ret;
5559c3cf 319
4b1cce65
SA
320 if (msgbuf->cmd != NULL) {
321 ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd);
322 if (ret > 0)
323 used += ret;
324 }
325
326 if (msgbuf->target != NULL) {
327 ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target);
328 if (ret > 0)
329 used += ret;
330 }
331
332 if (used > data_bufmax - 1)
333 used = data_bufmax - 1;
71c875fb 334
4b1cce65 335 return used;
4a13e3f1
AC
336}
337
338/*
339 * unparse a pure MsgBuf into a buffer.
340 * if origin is NULL, me.name will be used.
f3564f47 341 * cmd should not be NULL.
4a13e3f1
AC
342 * returns 0 on success, 1 on error.
343 */
344int
f3564f47 345msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
4a13e3f1 346{
f3564f47
SA
347 size_t buflen_copy = buflen;
348
349 msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask);
350
351 for (size_t i = 0; i < msgbuf->n_para; i++) {
352 const char *fmt;
353
354 if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) {
355 fmt = (i == 0) ? ":%s" : " :%s";
356 } else {
357 fmt = (i == 0) ? "%s" : " %s";
4a13e3f1 358 }
f3564f47
SA
359
360 rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]);
4a13e3f1
AC
361 }
362
363 return 0;
364}
8f64d325
AC
365
366/*
367 * unparse a MsgBuf stem + format string into a buffer
368 * if origin is NULL, me.name will be used.
369 * cmd may not be NULL.
370 * returns 0 on success, 1 on error.
371 */
372int
f3564f47 373msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va)
8f64d325 374{
f3564f47 375 size_t buflen_copy = buflen;
8f64d325
AC
376 char *ws;
377 size_t prefixlen;
378
f3564f47 379 msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask);
8f64d325
AC
380 prefixlen = strlen(buf);
381
382 ws = buf + prefixlen;
f3564f47 383 vsnprintf(ws, buflen_copy - prefixlen, fmt, va);
8f64d325
AC
384
385 return 0;
386}
387
388/*
389 * unparse a MsgBuf stem + format string into a buffer (with va_list handling)
390 * if origin is NULL, me.name will be used.
391 * cmd may not be NULL.
392 * returns 0 on success, 1 on error.
393 */
394int
f3564f47 395msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...)
8f64d325
AC
396{
397 va_list va;
398 int res;
399
400 va_start(va, fmt);
401 res = msgbuf_vunparse_fmt(buf, buflen, head, capmask, fmt, va);
402 va_end(va);
403
404 return res;
405}
4b1cce65
SA
406
407void
408msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message)
409{
410 cache->msgbuf = msgbuf;
411 cache->head = NULL;
412 cache->overall_capmask = 0;
413
414 for (size_t i = 0; i < msgbuf->n_tags; i++) {
415 cache->overall_capmask |= msgbuf->tags[i].capmask;
416 }
417
418 for (int i = 0; i < MSGBUF_CACHE_SIZE; i++) {
419 cache->entry[i].caps = 0;
420 cache->entry[i].next = NULL;
421 }
422
423 rb_fsnprint(cache->message, sizeof(cache->message), message);
424}
425
426void
427msgbuf_cache_initf(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message, const char *format, ...)
428{
429 va_list va;
430 rb_strf_t strings = { .format = format, .format_args = &va, .next = message };
431
432 va_start(va, format);
433 msgbuf_cache_init(cache, msgbuf, &strings);
434 va_end(va);
435}
436
437buf_head_t*
438msgbuf_cache_get(struct MsgBuf_cache *cache, unsigned int caps)
439{
440 struct MsgBuf_cache_entry *entry = cache->head;
441 struct MsgBuf_cache_entry *prev = NULL;
442 struct MsgBuf_cache_entry *result = NULL;
888d20da 443 struct MsgBuf_cache_entry *tail = NULL;
4b1cce65
SA
444 int n = 0;
445
d5e424bc
EK
446 caps &= cache->overall_capmask;
447
4b1cce65
SA
448 while (entry != NULL) {
449 if (entry->caps == caps) {
450 /* Cache hit */
451 result = entry;
452 break;
453 }
454
888d20da 455 tail = prev;
4b1cce65
SA
456 prev = entry;
457 entry = entry->next;
458 n++;
459 }
460
461 if (result == NULL) {
462 if (n < MSGBUF_CACHE_SIZE) {
463 /* Cache miss, allocate a new entry */
464 result = &cache->entry[n];
465 prev = NULL;
466 } else {
467 /* Cache full, replace the last entry */
468 result = prev;
888d20da
EK
469 if (tail != NULL)
470 tail->next = NULL;
4b1cce65
SA
471 prev = NULL;
472
473 rb_linebuf_donebuf(&result->linebuf);
474 }
475
476 /* Construct the line using the tags followed by the no tags line */
477 struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = cache->msgbuf, .caps = caps };
478 rb_strf_t strings[2] = {
479 { .func = msgbuf_unparse_linebuf_tags, .func_args = &msgbuf_str_data, .length = TAGSLEN + 1, .next = &strings[1] },
480 { .format = cache->message, .length = DATALEN + 1, .next = NULL }
481 };
482
483 result->caps = caps;
484 rb_linebuf_newbuf(&result->linebuf);
485 rb_linebuf_put(&result->linebuf, &strings[0]);
486 }
487
488 /* Move it to the top */
489 if (cache->head != result) {
490 if (prev != NULL) {
491 prev->next = result->next;
492 }
493 result->next = cache->head;
494 cache->head = result;
495 }
496
497 return &result->linebuf;
498}
499
500void
501msgbuf_cache_free(struct MsgBuf_cache *cache)
502{
503 struct MsgBuf_cache_entry *entry = cache->head;
504
505 while (entry != NULL) {
506 rb_linebuf_donebuf(&entry->linebuf);
507 entry = entry->next;
508 }
509
510 cache->head = NULL;
511}