1 | /* Basic tests for syslog interfaces. |
2 | Copyright (C) 2022-2024 Free Software Foundation, Inc. |
3 | This file is part of the GNU C Library. |
4 | |
5 | The GNU C Library is free software; you can redistribute it and/or |
6 | modify it under the terms of the GNU Lesser General Public |
7 | License as published by the Free Software Foundation; either |
8 | version 2.1 of the License, or (at your option) any later version. |
9 | |
10 | The GNU C Library is distributed in the hope that it will be useful, |
11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
13 | Lesser General Public License for more details. |
14 | |
15 | You should have received a copy of the GNU Lesser General Public |
16 | License along with the GNU C Library; if not, see |
17 | <https://www.gnu.org/licenses/>. */ |
18 | |
19 | #include <array_length.h> |
20 | #include <fcntl.h> |
21 | #include <paths.h> |
22 | #include <netinet/in.h> |
23 | #include <support/capture_subprocess.h> |
24 | #include <support/check.h> |
25 | #include <support/xstdio.h> |
26 | #include <support/xsocket.h> |
27 | #include <support/xunistd.h> |
28 | #include <stdarg.h> |
29 | #include <stdbool.h> |
30 | #include <stddef.h> |
31 | #include <stdlib.h> |
32 | #include <syslog.h> |
33 | #include <sys/un.h> |
34 | |
35 | static const int facilities[] = |
36 | { |
37 | LOG_KERN, |
38 | LOG_USER, |
39 | LOG_MAIL, |
40 | LOG_DAEMON, |
41 | LOG_AUTH, |
42 | LOG_SYSLOG, |
43 | LOG_LPR, |
44 | LOG_NEWS, |
45 | LOG_UUCP, |
46 | LOG_CRON, |
47 | LOG_AUTHPRIV, |
48 | LOG_FTP, |
49 | LOG_LOCAL0, |
50 | LOG_LOCAL1, |
51 | LOG_LOCAL2, |
52 | LOG_LOCAL3, |
53 | LOG_LOCAL4, |
54 | LOG_LOCAL5, |
55 | LOG_LOCAL6, |
56 | LOG_LOCAL7, |
57 | }; |
58 | |
59 | static const int priorities[] = |
60 | { |
61 | LOG_EMERG, |
62 | LOG_ALERT, |
63 | LOG_CRIT, |
64 | LOG_ERR, |
65 | LOG_WARNING, |
66 | LOG_NOTICE, |
67 | LOG_INFO, |
68 | LOG_DEBUG |
69 | }; |
70 | |
71 | #define IDENT_LENGTH 64 |
72 | #define MSG_LENGTH 1024 |
73 | |
74 | #define SYSLOG_MSG_BASE "syslog_message" |
75 | #define OPENLOG_IDENT "openlog_ident" |
76 | static char large_message[MSG_LENGTH]; |
77 | |
78 | struct msg_t |
79 | { |
80 | int priority; |
81 | int facility; |
82 | char ident[IDENT_LENGTH]; |
83 | char msg[MSG_LENGTH]; |
84 | pid_t pid; |
85 | }; |
86 | |
87 | static void |
88 | call_vsyslog (int priority, const char *format, ...) |
89 | { |
90 | va_list ap; |
91 | va_start (ap, format); |
92 | vsyslog (pri: priority, fmt: format, ap: ap); |
93 | va_end (ap); |
94 | } |
95 | |
96 | static void |
97 | send_vsyslog (int options) |
98 | { |
99 | for (size_t i = 0; i < array_length (facilities); i++) |
100 | { |
101 | for (size_t j = 0; j < array_length (priorities); j++) |
102 | { |
103 | int facility = facilities[i]; |
104 | int priority = priorities[j]; |
105 | call_vsyslog (priority: facility | priority, format: "%s %d %d" , SYSLOG_MSG_BASE, |
106 | facility, priority); |
107 | } |
108 | } |
109 | } |
110 | |
111 | static void |
112 | send_syslog (int options) |
113 | { |
114 | for (size_t i = 0; i < array_length (facilities); i++) |
115 | { |
116 | for (size_t j = 0; j < array_length (priorities); j++) |
117 | { |
118 | int facility = facilities[i]; |
119 | int priority = priorities[j]; |
120 | syslog (facility | priority, "%s %d %d" , SYSLOG_MSG_BASE, facility, |
121 | priority); |
122 | } |
123 | } |
124 | } |
125 | |
126 | static bool |
127 | check_syslog_message (const struct msg_t *msg, int msgnum, int options, |
128 | pid_t pid) |
129 | { |
130 | if (msgnum == array_length (facilities) * array_length (priorities) - 1) |
131 | return false; |
132 | |
133 | int i = msgnum / array_length (priorities); |
134 | int j = msgnum % array_length (priorities); |
135 | |
136 | int expected_facility = facilities[i]; |
137 | /* With no preceding openlog, syslog default to LOG_USER. */ |
138 | if (expected_facility == LOG_KERN) |
139 | expected_facility = LOG_USER; |
140 | int expected_priority = priorities[j]; |
141 | |
142 | TEST_COMPARE (msg->facility, expected_facility); |
143 | TEST_COMPARE (msg->priority, expected_priority); |
144 | |
145 | return true; |
146 | } |
147 | |
148 | static void |
149 | send_syslog_large (int options) |
150 | { |
151 | int facility = LOG_USER; |
152 | int priority = LOG_INFO; |
153 | |
154 | syslog (facility | priority, "%s %d %d" , large_message, facility, |
155 | priority); |
156 | } |
157 | |
158 | static void |
159 | send_vsyslog_large (int options) |
160 | { |
161 | int facility = LOG_USER; |
162 | int priority = LOG_INFO; |
163 | |
164 | call_vsyslog (priority: facility | priority, format: "%s %d %d" , large_message, facility, |
165 | priority); |
166 | } |
167 | |
168 | static bool |
169 | check_syslog_message_large (const struct msg_t *msg, int msgnum, int options, |
170 | pid_t pid) |
171 | { |
172 | TEST_COMPARE (msg->facility, LOG_USER); |
173 | TEST_COMPARE (msg->priority, LOG_INFO); |
174 | TEST_COMPARE_STRING (msg->msg, large_message); |
175 | |
176 | return false; |
177 | } |
178 | |
179 | static void |
180 | send_openlog (int options) |
181 | { |
182 | /* Define a non-default IDENT and a not default facility. */ |
183 | openlog (OPENLOG_IDENT, option: options, LOG_LOCAL0); |
184 | for (size_t j = 0; j < array_length (priorities); j++) |
185 | { |
186 | int priority = priorities[j]; |
187 | syslog (priority, "%s %d %d" , SYSLOG_MSG_BASE, LOG_LOCAL0, priority); |
188 | } |
189 | closelog (); |
190 | |
191 | /* Back to the default IDENT with a non default facility. */ |
192 | openlog (NULL, option: 0, LOG_LOCAL6); |
193 | for (size_t j = 0; j < array_length (priorities); j++) |
194 | { |
195 | int priority = priorities[j]; |
196 | syslog (LOG_LOCAL7 | priority, "%s %d %d" , SYSLOG_MSG_BASE, LOG_LOCAL7, |
197 | priority); |
198 | } |
199 | closelog (); |
200 | |
201 | /* LOG_KERN does not change the internal default facility. */ |
202 | openlog (NULL, option: 0, LOG_KERN); |
203 | for (size_t j = 0; j < array_length (priorities); j++) |
204 | { |
205 | int priority = priorities[j]; |
206 | syslog (priority, "%s %d %d" , SYSLOG_MSG_BASE, LOG_KERN, priority); |
207 | } |
208 | closelog (); |
209 | } |
210 | |
211 | static void |
212 | send_openlog_large (int options) |
213 | { |
214 | /* Define a non-default IDENT and a not default facility. */ |
215 | openlog (OPENLOG_IDENT, option: options, LOG_LOCAL0); |
216 | |
217 | syslog (LOG_INFO, "%s %d %d" , large_message, LOG_LOCAL0, LOG_INFO); |
218 | |
219 | closelog (); |
220 | } |
221 | |
222 | static bool |
223 | check_openlog_message (const struct msg_t *msg, int msgnum, |
224 | int options, pid_t pid) |
225 | { |
226 | if (msgnum == 3 * array_length (priorities) - 1) |
227 | return false; |
228 | |
229 | int expected_priority = priorities[msgnum % array_length (priorities)]; |
230 | TEST_COMPARE (msg->priority, expected_priority); |
231 | |
232 | char expected_ident[IDENT_LENGTH]; |
233 | snprintf (s: expected_ident, maxlen: sizeof (expected_ident), format: "%s%s%.0d%s:" , |
234 | OPENLOG_IDENT, |
235 | options & LOG_PID ? "[" : "" , |
236 | options & LOG_PID ? pid : 0, |
237 | options & LOG_PID ? "]" : "" ); |
238 | |
239 | if (msgnum < array_length (priorities)) |
240 | { |
241 | if (options & LOG_PID) |
242 | TEST_COMPARE (msg->pid, pid); |
243 | TEST_COMPARE_STRING (msg->ident, expected_ident); |
244 | TEST_COMPARE (msg->facility, LOG_LOCAL0); |
245 | } |
246 | else if (msgnum < 2 * array_length (priorities)) |
247 | TEST_COMPARE (msg->facility, LOG_LOCAL7); |
248 | else if (msgnum < 3 * array_length (priorities)) |
249 | TEST_COMPARE (msg->facility, LOG_KERN); |
250 | |
251 | return true; |
252 | } |
253 | |
254 | static bool |
255 | check_openlog_message_large (const struct msg_t *msg, int msgnum, |
256 | int options, pid_t pid) |
257 | { |
258 | char expected_ident[IDENT_LENGTH]; |
259 | snprintf (s: expected_ident, maxlen: sizeof (expected_ident), format: "%s%s%.0d%s:" , |
260 | OPENLOG_IDENT, |
261 | options & LOG_PID ? "[" : "" , |
262 | options & LOG_PID ? pid : 0, |
263 | options & LOG_PID ? "]" : "" ); |
264 | |
265 | TEST_COMPARE_STRING (msg->ident, expected_ident); |
266 | TEST_COMPARE_STRING (msg->msg, large_message); |
267 | TEST_COMPARE (msg->priority, LOG_INFO); |
268 | TEST_COMPARE (msg->facility, LOG_LOCAL0); |
269 | |
270 | return false; |
271 | } |
272 | |
273 | static struct msg_t |
274 | parse_syslog_msg (const char *msg) |
275 | { |
276 | struct msg_t r = { .pid = -1 }; |
277 | int number; |
278 | int wsb, wsa; |
279 | |
280 | #define STRINPUT(size) XSTRINPUT(size) |
281 | #define XSTRINPUT(size) "%" # size "s" |
282 | |
283 | /* The message in the form: |
284 | <179>Apr 8 14:51:19 tst-syslog: message 176 3 */ |
285 | int n = sscanf (msg, "<%3d>%*s %*d %*d:%*d:%*d%n %n" STRINPUT(IDENT_LENGTH) |
286 | " " STRINPUT(MSG_LENGTH) " %*d %*d" , |
287 | &number, &wsb, &wsa, r.ident, r.msg); |
288 | TEST_COMPARE (n, 3); |
289 | /* It should only one space between timestamp and message. */ |
290 | TEST_COMPARE (wsa - wsb, 1); |
291 | |
292 | r.facility = number & LOG_FACMASK; |
293 | r.priority = number & LOG_PRIMASK; |
294 | |
295 | char *pid_start = strchr (r.ident, '['); |
296 | if (pid_start != NULL) |
297 | { |
298 | char *pid_end = strchr (r.ident, ']'); |
299 | if (pid_end != NULL) |
300 | r.pid = strtoul (pid_start + 1, NULL, 10); |
301 | } |
302 | |
303 | return r; |
304 | } |
305 | |
306 | static struct msg_t |
307 | parse_syslog_console (const char *msg) |
308 | { |
309 | int priority; |
310 | int facility; |
311 | struct msg_t r; |
312 | |
313 | /* The message in the form: |
314 | openlog_ident: syslog_message 128 0 */ |
315 | int n = sscanf (msg, STRINPUT(IDENT_LENGTH) " " STRINPUT(MSG_LENGTH) " %d %d" , |
316 | r.ident, r.msg, &facility, &priority); |
317 | TEST_COMPARE (n, 4); |
318 | |
319 | r.facility = facility; |
320 | r.priority = priority; |
321 | |
322 | return r; |
323 | } |
324 | |
325 | static void |
326 | check_syslog_udp (void (*syslog_send)(int), int options, |
327 | bool (*syslog_check)(const struct msg_t *, int, int, |
328 | pid_t)) |
329 | { |
330 | struct sockaddr_un addr = |
331 | { |
332 | .sun_family = AF_UNIX, |
333 | .sun_path = _PATH_LOG |
334 | }; |
335 | |
336 | socklen_t addrlen = sizeof (addr); |
337 | int server_udp = xsocket (AF_UNIX, SOCK_DGRAM | SOCK_CLOEXEC, 0); |
338 | xbind (server_udp, (struct sockaddr *) &addr, addrlen); |
339 | |
340 | pid_t sender_pid = xfork (); |
341 | if (sender_pid == 0) |
342 | { |
343 | syslog_send (options); |
344 | _exit (0); |
345 | } |
346 | |
347 | int msgnum = 0; |
348 | while (1) |
349 | { |
350 | char buf[2048]; |
351 | size_t l = xrecvfrom (server_udp, buf, sizeof (buf), 0, |
352 | (struct sockaddr *) &addr, &addrlen); |
353 | buf[l] = '\0'; |
354 | |
355 | struct msg_t msg = parse_syslog_msg (msg: buf); |
356 | if (!syslog_check (&msg, msgnum++, options, sender_pid)) |
357 | break; |
358 | } |
359 | |
360 | xclose (server_udp); |
361 | |
362 | int status; |
363 | xwaitpid (sender_pid, status: &status, flags: 0); |
364 | TEST_COMPARE (status, 0); |
365 | |
366 | unlink (_PATH_LOG); |
367 | } |
368 | |
369 | static void |
370 | check_syslog_tcp (void (*syslog_send)(int), int options, |
371 | bool (*syslog_check)(const struct msg_t *, int, int, |
372 | pid_t)) |
373 | { |
374 | struct sockaddr_un addr = |
375 | { |
376 | .sun_family = AF_UNIX, |
377 | .sun_path = _PATH_LOG |
378 | }; |
379 | socklen_t addrlen = sizeof (addr); |
380 | |
381 | int server_tcp = xsocket (AF_UNIX, SOCK_STREAM | SOCK_CLOEXEC, 0); |
382 | xbind (server_tcp, (struct sockaddr *) &addr, addrlen); |
383 | xlisten (server_tcp, 5); |
384 | |
385 | pid_t sender_pid = xfork (); |
386 | if (sender_pid == 0) |
387 | { |
388 | syslog_send (options); |
389 | _exit (0); |
390 | } |
391 | |
392 | int client_tcp = xaccept (server_tcp, NULL, NULL); |
393 | |
394 | char buf[2048], *rb = buf; |
395 | size_t rbl = sizeof (buf); |
396 | size_t prl = 0; /* Track the size of the partial record. */ |
397 | int msgnum = 0; |
398 | |
399 | while (1) |
400 | { |
401 | size_t rl = xrecvfrom (client_tcp, rb, rbl - prl, 0, NULL, NULL); |
402 | if (rl == 0) |
403 | break; |
404 | |
405 | /* Iterate over the buffer to find and check the record. */ |
406 | size_t l = rl + prl; |
407 | char *b = buf; |
408 | while (1) |
409 | { |
410 | /* With TCP each record ends with a '\0'. */ |
411 | char *e = memchr (b, '\0', l); |
412 | if (e != NULL) |
413 | { |
414 | struct msg_t msg = parse_syslog_msg (msg: b); |
415 | if (!syslog_check (&msg, msgnum++, options, sender_pid)) |
416 | break; |
417 | |
418 | /* Advance to the next record. */ |
419 | ptrdiff_t diff = e + 1 - b; |
420 | b += diff; |
421 | l -= diff; |
422 | } |
423 | else |
424 | { |
425 | /* Move the partial record to the start of the buffer. */ |
426 | memmove (buf, b, l); |
427 | rb = buf + l; |
428 | prl = l; |
429 | break; |
430 | } |
431 | } |
432 | } |
433 | |
434 | xclose (client_tcp); |
435 | xclose (server_tcp); |
436 | |
437 | int status; |
438 | xwaitpid (sender_pid, status: &status, flags: 0); |
439 | TEST_COMPARE (status, 0); |
440 | |
441 | unlink (_PATH_LOG); |
442 | } |
443 | |
444 | static void |
445 | check_syslog_console_read (FILE *fp) |
446 | { |
447 | char buf[512]; |
448 | int msgnum = 0; |
449 | while (fgets (s: buf, n: sizeof (buf), stream: fp) != NULL) |
450 | { |
451 | struct msg_t msg = parse_syslog_console (msg: buf); |
452 | TEST_COMPARE_STRING (msg.ident, OPENLOG_IDENT ":" ); |
453 | TEST_COMPARE (msg.priority, priorities[msgnum]); |
454 | TEST_COMPARE (msg.facility, LOG_LOCAL0); |
455 | |
456 | if (++msgnum == array_length (priorities)) |
457 | break; |
458 | } |
459 | } |
460 | |
461 | static void |
462 | check_syslog_console_read_large (FILE *fp) |
463 | { |
464 | char buf[2048]; |
465 | TEST_VERIFY (fgets (buf, sizeof (buf), fp) != NULL); |
466 | struct msg_t msg = parse_syslog_console (msg: buf); |
467 | |
468 | TEST_COMPARE_STRING (msg.ident, OPENLOG_IDENT ":" ); |
469 | TEST_COMPARE_STRING (msg.msg, large_message); |
470 | TEST_COMPARE (msg.priority, LOG_INFO); |
471 | TEST_COMPARE (msg.facility, LOG_LOCAL0); |
472 | } |
473 | |
474 | static void |
475 | check_syslog_console (void (*syslog_send)(int), |
476 | void (*syslog_check)(FILE *fp)) |
477 | { |
478 | xmkfifo (_PATH_CONSOLE, mode: 0666); |
479 | |
480 | pid_t sender_pid = xfork (); |
481 | if (sender_pid == 0) |
482 | { |
483 | syslog_send (LOG_CONS); |
484 | _exit (0); |
485 | } |
486 | |
487 | { |
488 | FILE *fp = xfopen (_PATH_CONSOLE, mode: "r+" ); |
489 | syslog_check (fp); |
490 | xfclose (fp); |
491 | } |
492 | |
493 | int status; |
494 | xwaitpid (sender_pid, status: &status, flags: 0); |
495 | TEST_COMPARE (status, 0); |
496 | |
497 | unlink (_PATH_CONSOLE); |
498 | } |
499 | |
500 | static void |
501 | send_openlog_callback (void *clousure) |
502 | { |
503 | int options = *(int *) clousure; |
504 | send_openlog (options); |
505 | } |
506 | |
507 | static void |
508 | send_openlog_callback_large (void *clousure) |
509 | { |
510 | int options = *(int *) clousure; |
511 | send_openlog_large (options); |
512 | } |
513 | |
514 | static void |
515 | check_syslog_perror (bool large) |
516 | { |
517 | struct support_capture_subprocess result; |
518 | result = support_capture_subprocess (callback: large |
519 | ? send_openlog_callback_large |
520 | : send_openlog_callback, |
521 | closure: &(int){LOG_PERROR}); |
522 | |
523 | FILE *mfp = fmemopen (result.err.buffer, result.err.length, "r" ); |
524 | if (mfp == NULL) |
525 | FAIL_EXIT1 ("fmemopen: %m" ); |
526 | if (large) |
527 | check_syslog_console_read_large (fp: mfp); |
528 | else |
529 | check_syslog_console_read (fp: mfp); |
530 | xfclose (mfp); |
531 | |
532 | support_capture_subprocess_check (&result, context: "tst-openlog-child" , status_or_signal: 0, |
533 | allowed: sc_allow_stderr); |
534 | support_capture_subprocess_free (&result); |
535 | } |
536 | |
537 | static int |
538 | do_test (void) |
539 | { |
540 | /* Send every combination of facility/priority over UDP and TCP. */ |
541 | check_syslog_udp (syslog_send: send_syslog, options: 0, syslog_check: check_syslog_message); |
542 | check_syslog_tcp (syslog_send: send_syslog, options: 0, syslog_check: check_syslog_message); |
543 | |
544 | /* Also check vsyslog. */ |
545 | check_syslog_udp (syslog_send: send_vsyslog, options: 0, syslog_check: check_syslog_message); |
546 | check_syslog_tcp (syslog_send: send_vsyslog, options: 0, syslog_check: check_syslog_message); |
547 | |
548 | /* Run some openlog/syslog/closelog combinations. */ |
549 | check_syslog_udp (syslog_send: send_openlog, options: 0, syslog_check: check_openlog_message); |
550 | check_syslog_tcp (syslog_send: send_openlog, options: 0, syslog_check: check_openlog_message); |
551 | |
552 | /* Check the LOG_PID option. */ |
553 | check_syslog_udp (syslog_send: send_openlog, LOG_PID, syslog_check: check_openlog_message); |
554 | check_syslog_tcp (syslog_send: send_openlog, LOG_PID, syslog_check: check_openlog_message); |
555 | |
556 | /* Check the LOG_CONS option. */ |
557 | check_syslog_console (syslog_send: send_openlog, syslog_check: check_syslog_console_read); |
558 | |
559 | /* Check the LOG_PERROR option. */ |
560 | check_syslog_perror (false); |
561 | |
562 | /* Similar tests as before, but with a large message to trigger the |
563 | syslog path that uses dynamically allocated memory. */ |
564 | memset (large_message, 'a', sizeof large_message - 1); |
565 | large_message[sizeof large_message - 1] = '\0'; |
566 | |
567 | check_syslog_udp (syslog_send: send_syslog_large, options: 0, syslog_check: check_syslog_message_large); |
568 | check_syslog_tcp (syslog_send: send_syslog_large, options: 0, syslog_check: check_syslog_message_large); |
569 | |
570 | check_syslog_udp (syslog_send: send_vsyslog_large, options: 0, syslog_check: check_syslog_message_large); |
571 | check_syslog_tcp (syslog_send: send_vsyslog_large, options: 0, syslog_check: check_syslog_message_large); |
572 | |
573 | check_syslog_udp (syslog_send: send_openlog_large, options: 0, syslog_check: check_openlog_message_large); |
574 | check_syslog_tcp (syslog_send: send_openlog_large, options: 0, syslog_check: check_openlog_message_large); |
575 | |
576 | check_syslog_udp (syslog_send: send_openlog_large, LOG_PID, syslog_check: check_openlog_message_large); |
577 | check_syslog_tcp (syslog_send: send_openlog_large, LOG_PID, syslog_check: check_openlog_message_large); |
578 | |
579 | check_syslog_console (syslog_send: send_openlog_large, syslog_check: check_syslog_console_read_large); |
580 | |
581 | check_syslog_perror (true); |
582 | |
583 | return 0; |
584 | } |
585 | |
586 | #include <support/test-driver.c> |
587 | |