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
35static 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
59static 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"
76static char large_message[MSG_LENGTH];
77
78struct 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
87static void
88call_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
96static void
97send_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
111static void
112send_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
126static bool
127check_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
148static void
149send_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
158static void
159send_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
168static bool
169check_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
179static void
180send_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
211static void
212send_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
222static bool
223check_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
254static bool
255check_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
273static struct msg_t
274parse_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
306static struct msg_t
307parse_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
325static void
326check_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
369static void
370check_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
444static void
445check_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
461static void
462check_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
474static void
475check_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
500static void
501send_openlog_callback (void *clousure)
502{
503 int options = *(int *) clousure;
504 send_openlog (options);
505}
506
507static void
508send_openlog_callback_large (void *clousure)
509{
510 int options = *(int *) clousure;
511 send_openlog_large (options);
512}
513
514static void
515check_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
537static int
538do_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

source code of glibc/misc/tst-syslog.c