]> jfr.im git - solanum.git/blob - ircd/msgbuf.c
doc/reference.conf: document the auth::umodes configuration option
[solanum.git] / ircd / msgbuf.c
1 /*
2 * solanum - an advanced ircd.
3 * Copyright (c) 2016 Ariadne Conill <ariadne@dereferenced.org>.
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"
25 #include "client.h"
26 #include "ircd.h"
27
28 static 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
38 static 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
50 static void
51 msgbuf_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;
69 in++;
70 } else {
71 *out++ = *in++;
72 }
73 } else {
74 *out++ = *in++;
75 }
76 }
77
78 /* copy final '\0' */
79 *out = *in;
80 }
81
82 /*
83 * parse a message into a MsgBuf.
84 * returns 0 on success, 1 on error.
85 */
86 int
87 msgbuf_parse(struct MsgBuf *msgbuf, char *line)
88 {
89 char *ch = line;
90
91 msgbuf_init(msgbuf);
92
93 if (*ch == '@') {
94 char *t = ch + 1;
95
96 ch = strchr(ch, ' ');
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) {
108 char *next = strchr(t, ';');
109 char *eq = strchr(t, '=');
110
111 if (next != NULL) {
112 *next = '\0';
113
114 if (eq > next)
115 eq = NULL;
116 }
117
118 if (eq != NULL)
119 *eq++ = '\0';
120
121 if (*t != '\0') {
122 msgbuf_unescape_value(eq);
123 msgbuf_append_tag(msgbuf, t, eq, 0);
124 }
125
126 if (next != NULL) {
127 t = next + 1;
128 } else {
129 break;
130 }
131 }
132 } else {
133 return 1;
134 }
135 }
136
137 /* truncate message if it's too long */
138 if (strlen(ch) > DATALEN) {
139 ch[DATALEN] = '\0';
140 }
141
142 if (*ch == ':') {
143 ch++;
144 msgbuf->origin = ch;
145
146 char *end = strchr(ch, ' ');
147 if (end == NULL)
148 return 4;
149
150 *end = '\0';
151 ch = end + 1;
152 }
153
154 if (*ch == '\0')
155 return 2;
156
157 msgbuf->endp = &ch[strlen(ch)];
158 msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA);
159 if (msgbuf->n_para == 0)
160 return 3;
161
162 msgbuf->cmd = msgbuf->para[0];
163 return 0;
164 }
165
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
171 void
172 msgbuf_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
200 /*
201 * Unparse msgbuf tags into a buffer
202 * returns the length of the tags written
203 */
204 static size_t
205 msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
206 {
207 bool has_tags = false;
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;
214
215 if ((msgbuf->tags[i].capmask & capmask) == 0)
216 continue;
217
218 if (has_tags) {
219 if (output >= end)
220 break;
221 *output++ = ';';
222 } else {
223 if (output >= end)
224 break;
225 *output++ = '@';
226 }
227
228 if (msgbuf->tags[i].key == NULL)
229 continue;
230
231 len = strlen(msgbuf->tags[i].key);
232 if (len == 0)
233 continue;
234
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 }
269 }
270
271 has_tags = true;
272 commit = output;
273 }
274
275 if (has_tags)
276 *commit++ = ' ';
277
278 *commit = 0;
279 return commit - buf;
280 }
281
282 int
283 msgbuf_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
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 */
296 int
297 msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
298 {
299 size_t tags_buflen;
300 size_t used = 0;
301 int ret;
302
303 memset(buf, 0, *buflen);
304
305 tags_buflen = *buflen;
306 if (tags_buflen > TAGSLEN + 1)
307 tags_buflen = TAGSLEN + 1;
308
309 if (msgbuf->n_tags > 0)
310 used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask);
311
312 const size_t data_bufmax = (used + DATALEN + 1);
313 if (*buflen > data_bufmax)
314 *buflen = data_bufmax;
315
316 ret = rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name);
317 if (ret > 0)
318 used += ret;
319
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;
334
335 return used;
336 }
337
338 /*
339 * unparse a pure MsgBuf into a buffer.
340 * if origin is NULL, me.name will be used.
341 * cmd should not be NULL.
342 * returns 0 on success, 1 on error.
343 */
344 int
345 msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask)
346 {
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";
358 }
359
360 rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]);
361 }
362
363 return 0;
364 }
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 */
372 int
373 msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va)
374 {
375 size_t buflen_copy = buflen;
376 char *ws;
377 size_t prefixlen;
378
379 msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask);
380 prefixlen = strlen(buf);
381
382 ws = buf + prefixlen;
383 vsnprintf(ws, buflen_copy - prefixlen, fmt, va);
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 */
394 int
395 msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...)
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 }
406
407 void
408 msgbuf_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
426 void
427 msgbuf_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
437 buf_head_t*
438 msgbuf_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;
443 struct MsgBuf_cache_entry *tail = NULL;
444 int n = 0;
445
446 caps &= cache->overall_capmask;
447
448 while (entry != NULL) {
449 if (entry->caps == caps) {
450 /* Cache hit */
451 result = entry;
452 break;
453 }
454
455 tail = prev;
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;
469 if (tail != NULL)
470 tail->next = NULL;
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
500 void
501 msgbuf_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 }