]>
Commit | Line | Data |
---|---|---|
a8e69f5d AC |
1 | /* |
2 | * charybdis - an advanced ircd. | |
3 | * Copyright (c) 2016 William Pitcock <nenolod@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" | |
691adddd AC |
25 | #include "client.h" |
26 | #include "ircd.h" | |
a8e69f5d | 27 | |
f3564f47 SA |
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; | |
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 | */ | |
86 | int | |
87 | msgbuf_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 | |
33ded5fc SA |
157 | msgbuf->n_para = rb_string_to_array(ch, (char **)msgbuf->para, MAXPARA); |
158 | if (msgbuf->n_para == 0) | |
f3564f47 | 159 | return 3; |
a8e69f5d | 160 | |
33ded5fc | 161 | msgbuf->cmd = msgbuf->para[0]; |
a8e69f5d AC |
162 | return 0; |
163 | } | |
4a13e3f1 | 164 | |
f3564f47 SA |
165 | /* |
166 | * Unparse msgbuf tags into a buffer | |
167 | * returns the length of the tags written | |
168 | */ | |
169 | static size_t | |
170 | msgbuf_unparse_tags(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) | |
4a13e3f1 | 171 | { |
57dd2c6a | 172 | bool has_tags = false; |
f3564f47 SA |
173 | char *commit = buf; |
174 | char *output = buf; | |
175 | const char * const end = &buf[buflen - 2]; /* this is where the final ' ' goes */ | |
176 | ||
177 | for (size_t i = 0; i < msgbuf->n_tags; i++) { | |
178 | size_t len; | |
4a13e3f1 | 179 | |
4a13e3f1 AC |
180 | if ((msgbuf->tags[i].capmask & capmask) == 0) |
181 | continue; | |
182 | ||
57dd2c6a | 183 | if (has_tags) { |
f3564f47 SA |
184 | if (output >= end) |
185 | break; | |
186 | *output++ = ';'; | |
57dd2c6a | 187 | } else { |
f3564f47 SA |
188 | if (output >= end) |
189 | break; | |
190 | *output++ = '@'; | |
57dd2c6a | 191 | } |
4a13e3f1 | 192 | |
f3564f47 SA |
193 | if (msgbuf->tags[i].key == NULL) |
194 | continue; | |
195 | ||
196 | len = strlen(msgbuf->tags[i].key); | |
197 | if (len == 0) | |
198 | continue; | |
4a13e3f1 | 199 | |
f3564f47 SA |
200 | if (output + len > end) |
201 | break; | |
202 | strcat(output, msgbuf->tags[i].key); | |
203 | output += len; | |
204 | ||
205 | if (msgbuf->tags[i].value != NULL) { | |
206 | if (output >= end) | |
207 | break; | |
208 | *output++ = '='; | |
209 | ||
210 | len = strlen(msgbuf->tags[i].value); | |
211 | /* this only checks the unescaped length, | |
212 | * but the escaped length could be longer | |
213 | */ | |
214 | if (output + len > end) | |
215 | break; | |
216 | ||
217 | for (size_t n = 0; n < len; n++) { | |
218 | const unsigned char c = msgbuf->tags[i].value[n]; | |
219 | const char escape = tag_escape_table[c]; | |
220 | ||
221 | if (escape) { | |
222 | if (output + 2 > end) | |
223 | break; | |
224 | ||
225 | *output++ = '\\'; | |
226 | *output++ = escape; | |
227 | } else { | |
228 | if (output >= end) | |
229 | break; | |
230 | ||
231 | *output++ = c; | |
232 | } | |
233 | } | |
4a13e3f1 | 234 | } |
f3564f47 SA |
235 | |
236 | has_tags = true; | |
237 | commit = output; | |
4a13e3f1 AC |
238 | } |
239 | ||
57dd2c6a | 240 | if (has_tags) |
f3564f47 SA |
241 | *commit++ = ' '; |
242 | ||
243 | *commit = 0; | |
244 | return commit - buf; | |
4a13e3f1 AC |
245 | } |
246 | ||
4b1cce65 SA |
247 | int |
248 | msgbuf_unparse_linebuf_tags(char *buf, size_t buflen, void *data) | |
249 | { | |
250 | struct MsgBuf_str_data *str_data = data; | |
251 | ||
252 | return msgbuf_unparse_tags(buf, buflen, str_data->msgbuf, str_data->caps); | |
253 | } | |
254 | ||
f3564f47 SA |
255 | /* |
256 | * unparse a MsgBuf and message prefix into a buffer | |
257 | * if origin is NULL, me.name will be used. | |
258 | * cmd should not be NULL. | |
259 | * updates buflen to correctly allow remaining message data to be added | |
260 | */ | |
4b1cce65 | 261 | int |
f3564f47 | 262 | msgbuf_unparse_prefix(char *buf, size_t *buflen, const struct MsgBuf *msgbuf, unsigned int capmask) |
4a13e3f1 | 263 | { |
f3564f47 | 264 | size_t tags_buflen; |
4b1cce65 SA |
265 | size_t used = 0; |
266 | int ret; | |
f3564f47 SA |
267 | |
268 | memset(buf, 0, *buflen); | |
269 | ||
270 | tags_buflen = *buflen; | |
271 | if (tags_buflen > TAGSLEN + 1) | |
272 | tags_buflen = TAGSLEN + 1; | |
4a13e3f1 AC |
273 | |
274 | if (msgbuf->n_tags > 0) | |
4b1cce65 | 275 | used = msgbuf_unparse_tags(buf, tags_buflen, msgbuf, capmask); |
f3564f47 | 276 | |
4b1cce65 | 277 | const size_t data_bufmax = (used + DATALEN + 1); |
f3564f47 SA |
278 | if (*buflen > data_bufmax) |
279 | *buflen = data_bufmax; | |
4a13e3f1 | 280 | |
4b1cce65 SA |
281 | ret = rb_snprintf_append(buf, *buflen, ":%s ", msgbuf->origin != NULL ? msgbuf->origin : me.name); |
282 | if (ret > 0) | |
283 | used += ret; | |
5559c3cf | 284 | |
4b1cce65 SA |
285 | if (msgbuf->cmd != NULL) { |
286 | ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->cmd); | |
287 | if (ret > 0) | |
288 | used += ret; | |
289 | } | |
290 | ||
291 | if (msgbuf->target != NULL) { | |
292 | ret = rb_snprintf_append(buf, *buflen, "%s ", msgbuf->target); | |
293 | if (ret > 0) | |
294 | used += ret; | |
295 | } | |
296 | ||
297 | if (used > data_bufmax - 1) | |
298 | used = data_bufmax - 1; | |
71c875fb | 299 | |
4b1cce65 | 300 | return used; |
4a13e3f1 AC |
301 | } |
302 | ||
303 | /* | |
304 | * unparse a pure MsgBuf into a buffer. | |
305 | * if origin is NULL, me.name will be used. | |
f3564f47 | 306 | * cmd should not be NULL. |
4a13e3f1 AC |
307 | * returns 0 on success, 1 on error. |
308 | */ | |
309 | int | |
f3564f47 | 310 | msgbuf_unparse(char *buf, size_t buflen, const struct MsgBuf *msgbuf, unsigned int capmask) |
4a13e3f1 | 311 | { |
f3564f47 SA |
312 | size_t buflen_copy = buflen; |
313 | ||
314 | msgbuf_unparse_prefix(buf, &buflen_copy, msgbuf, capmask); | |
315 | ||
316 | for (size_t i = 0; i < msgbuf->n_para; i++) { | |
317 | const char *fmt; | |
318 | ||
319 | if (i == (msgbuf->n_para - 1) && strchr(msgbuf->para[i], ' ') != NULL) { | |
320 | fmt = (i == 0) ? ":%s" : " :%s"; | |
321 | } else { | |
322 | fmt = (i == 0) ? "%s" : " %s"; | |
4a13e3f1 | 323 | } |
f3564f47 SA |
324 | |
325 | rb_snprintf_append(buf, buflen_copy, fmt, msgbuf->para[i]); | |
4a13e3f1 AC |
326 | } |
327 | ||
328 | return 0; | |
329 | } | |
8f64d325 AC |
330 | |
331 | /* | |
332 | * unparse a MsgBuf stem + format string into a buffer | |
333 | * if origin is NULL, me.name will be used. | |
334 | * cmd may not be NULL. | |
335 | * returns 0 on success, 1 on error. | |
336 | */ | |
337 | int | |
f3564f47 | 338 | msgbuf_vunparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, va_list va) |
8f64d325 | 339 | { |
f3564f47 | 340 | size_t buflen_copy = buflen; |
8f64d325 AC |
341 | char *ws; |
342 | size_t prefixlen; | |
343 | ||
f3564f47 | 344 | msgbuf_unparse_prefix(buf, &buflen_copy, head, capmask); |
8f64d325 AC |
345 | prefixlen = strlen(buf); |
346 | ||
347 | ws = buf + prefixlen; | |
f3564f47 | 348 | vsnprintf(ws, buflen_copy - prefixlen, fmt, va); |
8f64d325 AC |
349 | |
350 | return 0; | |
351 | } | |
352 | ||
353 | /* | |
354 | * unparse a MsgBuf stem + format string into a buffer (with va_list handling) | |
355 | * if origin is NULL, me.name will be used. | |
356 | * cmd may not be NULL. | |
357 | * returns 0 on success, 1 on error. | |
358 | */ | |
359 | int | |
f3564f47 | 360 | msgbuf_unparse_fmt(char *buf, size_t buflen, const struct MsgBuf *head, unsigned int capmask, const char *fmt, ...) |
8f64d325 AC |
361 | { |
362 | va_list va; | |
363 | int res; | |
364 | ||
365 | va_start(va, fmt); | |
366 | res = msgbuf_vunparse_fmt(buf, buflen, head, capmask, fmt, va); | |
367 | va_end(va); | |
368 | ||
369 | return res; | |
370 | } | |
4b1cce65 SA |
371 | |
372 | void | |
373 | msgbuf_cache_init(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message) | |
374 | { | |
375 | cache->msgbuf = msgbuf; | |
376 | cache->head = NULL; | |
377 | cache->overall_capmask = 0; | |
378 | ||
379 | for (size_t i = 0; i < msgbuf->n_tags; i++) { | |
380 | cache->overall_capmask |= msgbuf->tags[i].capmask; | |
381 | } | |
382 | ||
383 | for (int i = 0; i < MSGBUF_CACHE_SIZE; i++) { | |
384 | cache->entry[i].caps = 0; | |
385 | cache->entry[i].next = NULL; | |
386 | } | |
387 | ||
388 | rb_fsnprint(cache->message, sizeof(cache->message), message); | |
389 | } | |
390 | ||
391 | void | |
392 | msgbuf_cache_initf(struct MsgBuf_cache *cache, const struct MsgBuf *msgbuf, const rb_strf_t *message, const char *format, ...) | |
393 | { | |
394 | va_list va; | |
395 | rb_strf_t strings = { .format = format, .format_args = &va, .next = message }; | |
396 | ||
397 | va_start(va, format); | |
398 | msgbuf_cache_init(cache, msgbuf, &strings); | |
399 | va_end(va); | |
400 | } | |
401 | ||
402 | buf_head_t* | |
403 | msgbuf_cache_get(struct MsgBuf_cache *cache, unsigned int caps) | |
404 | { | |
405 | struct MsgBuf_cache_entry *entry = cache->head; | |
406 | struct MsgBuf_cache_entry *prev = NULL; | |
407 | struct MsgBuf_cache_entry *result = NULL; | |
408 | int n = 0; | |
409 | ||
410 | while (entry != NULL) { | |
411 | if (entry->caps == caps) { | |
412 | /* Cache hit */ | |
413 | result = entry; | |
414 | break; | |
415 | } | |
416 | ||
417 | prev = entry; | |
418 | entry = entry->next; | |
419 | n++; | |
420 | } | |
421 | ||
422 | if (result == NULL) { | |
423 | if (n < MSGBUF_CACHE_SIZE) { | |
424 | /* Cache miss, allocate a new entry */ | |
425 | result = &cache->entry[n]; | |
426 | prev = NULL; | |
427 | } else { | |
428 | /* Cache full, replace the last entry */ | |
429 | result = prev; | |
430 | prev = NULL; | |
431 | ||
432 | rb_linebuf_donebuf(&result->linebuf); | |
433 | } | |
434 | ||
435 | /* Construct the line using the tags followed by the no tags line */ | |
436 | struct MsgBuf_str_data msgbuf_str_data = { .msgbuf = cache->msgbuf, .caps = caps }; | |
437 | rb_strf_t strings[2] = { | |
438 | { .func = msgbuf_unparse_linebuf_tags, .func_args = &msgbuf_str_data, .length = TAGSLEN + 1, .next = &strings[1] }, | |
439 | { .format = cache->message, .length = DATALEN + 1, .next = NULL } | |
440 | }; | |
441 | ||
442 | result->caps = caps; | |
443 | rb_linebuf_newbuf(&result->linebuf); | |
444 | rb_linebuf_put(&result->linebuf, &strings[0]); | |
445 | } | |
446 | ||
447 | /* Move it to the top */ | |
448 | if (cache->head != result) { | |
449 | if (prev != NULL) { | |
450 | prev->next = result->next; | |
451 | } | |
452 | result->next = cache->head; | |
453 | cache->head = result; | |
454 | } | |
455 | ||
456 | return &result->linebuf; | |
457 | } | |
458 | ||
459 | void | |
460 | msgbuf_cache_free(struct MsgBuf_cache *cache) | |
461 | { | |
462 | struct MsgBuf_cache_entry *entry = cache->head; | |
463 | ||
464 | while (entry != NULL) { | |
465 | rb_linebuf_donebuf(&entry->linebuf); | |
466 | entry = entry->next; | |
467 | } | |
468 | ||
469 | cache->head = NULL; | |
470 | } |