]>
Commit | Line | Data |
---|---|---|
acddc0ed | 1 | // SPDX-License-Identifier: GPL-2.0-or-later |
1c6261a9 DL |
2 | /* |
3 | * Copyright (C) 2021 David Lamparter for NetDEF, Inc. | |
1c6261a9 DL |
4 | */ |
5 | ||
6 | #include "zebra.h" | |
7 | #include "zlog_5424.h" | |
8 | ||
9 | #include <sys/types.h> | |
10 | #include <pwd.h> | |
11 | #include <grp.h> | |
12 | ||
13 | #include "lib/command.h" | |
14 | #include "lib/libfrr.h" | |
15 | #include "lib/log_vty.h" | |
16 | ||
17 | DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config"); | |
18 | DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items"); | |
19 | ||
20 | static int target_cmp(const struct zlog_cfg_5424_user *a, | |
21 | const struct zlog_cfg_5424_user *b) | |
22 | { | |
23 | return strcmp(a->name, b->name); | |
24 | } | |
25 | ||
26 | DECLARE_RBTREE_UNIQ(targets, struct zlog_cfg_5424_user, targets_item, | |
27 | target_cmp); | |
28 | DEFINE_QOBJ_TYPE(zlog_cfg_5424_user); | |
29 | ||
30 | static struct targets_head targets = INIT_RBTREE_UNIQ(targets); | |
31 | static struct thread_master *log_5424_master; | |
32 | ||
33 | static void clear_dst(struct zlog_cfg_5424_user *cfg); | |
34 | ||
35 | struct log_option { | |
36 | const char *name; | |
37 | ptrdiff_t offs; | |
38 | bool dflt; | |
39 | }; | |
40 | ||
41 | /* clang-format off */ | |
42 | static struct log_option log_opts[] = { | |
43 | { "code-location", offsetof(struct zlog_cfg_5424, kw_location) }, | |
44 | { "version", offsetof(struct zlog_cfg_5424, kw_version) }, | |
45 | { "unique-id", offsetof(struct zlog_cfg_5424, kw_uid), true }, | |
46 | { "error-category", offsetof(struct zlog_cfg_5424, kw_ec), true }, | |
47 | { "format-args", offsetof(struct zlog_cfg_5424, kw_args) }, | |
48 | {}, | |
49 | }; | |
50 | ||
51 | #define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC) | |
52 | #define DFLT_FACILITY LOG_DAEMON | |
53 | #define DFLT_PRIO_MIN LOG_DEBUG | |
54 | /* clang-format on */ | |
55 | ||
56 | enum unix_special { | |
57 | SPECIAL_NONE = 0, | |
58 | SPECIAL_SYSLOG, | |
59 | SPECIAL_JOURNALD, | |
60 | }; | |
61 | ||
62 | static struct zlog_cfg_5424_user *log_5424_alloc(const char *name) | |
63 | { | |
64 | struct zlog_cfg_5424_user *cfg; | |
65 | ||
66 | cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg)); | |
67 | cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name); | |
68 | ||
69 | cfg->cfg.master = log_5424_master; | |
70 | cfg->cfg.kw_location = true; | |
71 | cfg->cfg.kw_version = false; | |
72 | cfg->cfg.facility = DFLT_FACILITY; | |
73 | cfg->cfg.prio_min = DFLT_PRIO_MIN; | |
74 | cfg->cfg.ts_flags = DFLT_TS_FLAGS; | |
75 | clear_dst(cfg); | |
76 | ||
77 | for (struct log_option *opt = log_opts; opt->name; opt++) { | |
78 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); | |
79 | *ptr = opt->dflt; | |
80 | } | |
81 | ||
82 | zlog_5424_init(&cfg->cfg); | |
83 | ||
84 | QOBJ_REG(cfg, zlog_cfg_5424_user); | |
85 | targets_add(&targets, cfg); | |
86 | return cfg; | |
87 | } | |
88 | ||
89 | static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen) | |
90 | { | |
91 | targets_del(&targets, cfg); | |
92 | QOBJ_UNREG(cfg); | |
93 | ||
94 | zlog_5424_fini(&cfg->cfg, keepopen); | |
95 | clear_dst(cfg); | |
96 | ||
97 | XFREE(MTYPE_LOG_5424_DATA, cfg->filename); | |
98 | XFREE(MTYPE_LOG_5424_DATA, cfg->name); | |
99 | XFREE(MTYPE_LOG_5424_CONFIG, cfg); | |
100 | } | |
101 | ||
102 | static void clear_dst(struct zlog_cfg_5424_user *cfg) | |
103 | { | |
104 | XFREE(MTYPE_LOG_5424_DATA, cfg->filename); | |
105 | cfg->cfg.filename = cfg->filename; | |
106 | ||
107 | XFREE(MTYPE_LOG_5424_DATA, cfg->file_user); | |
108 | XFREE(MTYPE_LOG_5424_DATA, cfg->file_group); | |
109 | XFREE(MTYPE_LOG_5424_DATA, cfg->envvar); | |
110 | ||
111 | cfg->cfg.fd = -1; | |
112 | cfg->cfg.file_uid = -1; | |
113 | cfg->cfg.file_gid = -1; | |
114 | cfg->cfg.file_mode = LOGFILE_MASK & 0666; | |
115 | cfg->cfg.file_nocreate = false; | |
116 | cfg->cfg.dst = ZLOG_5424_DST_NONE; | |
117 | } | |
118 | ||
119 | static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) | |
120 | { | |
121 | if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) | |
122 | vty_out(vty, | |
123 | "%% Changes will be applied when exiting this config block\n"); | |
124 | ||
125 | cfg->reconf_dst = true; | |
126 | return CMD_SUCCESS; | |
127 | } | |
128 | ||
129 | static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty) | |
130 | { | |
131 | if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) | |
132 | vty_out(vty, | |
133 | "%% Changes will be applied when exiting this config block\n"); | |
134 | ||
135 | cfg->reconf_meta = true; | |
136 | return CMD_SUCCESS; | |
137 | } | |
138 | ||
139 | static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) | |
140 | { | |
141 | if (cfg->cfg.dst == ZLOG_5424_DST_NONE) | |
142 | return CMD_SUCCESS; | |
143 | ||
144 | clear_dst(cfg); | |
145 | return reconf_dst(cfg, vty); | |
146 | } | |
147 | ||
1c6261a9 | 148 | #include "lib/zlog_5424_cli_clippy.c" |
1c6261a9 DL |
149 | |
150 | DEFPY_NOSH(log_5424_target, | |
151 | log_5424_target_cmd, | |
152 | "log extended-syslog EXTLOGNAME", | |
153 | "Logging control\n" | |
154 | "Extended RFC5424 syslog (including file targets)\n" | |
155 | "Name identifying this syslog target\n") | |
156 | { | |
157 | struct zlog_cfg_5424_user *cfg, ref; | |
158 | ||
159 | ref.name = (char *)extlogname; | |
160 | cfg = targets_find(&targets, &ref); | |
161 | ||
162 | if (!cfg) | |
163 | cfg = log_5424_alloc(extlogname); | |
164 | ||
165 | VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg); | |
166 | return CMD_SUCCESS; | |
167 | } | |
168 | ||
169 | DEFPY(no_log_5424_target, | |
170 | no_log_5424_target_cmd, | |
171 | "no log extended-syslog EXTLOGNAME", | |
172 | NO_STR | |
173 | "Logging control\n" | |
174 | "Extended RFC5424 syslog (including file targets)\n" | |
175 | "Name identifying this syslog target\n") | |
176 | { | |
177 | struct zlog_cfg_5424_user *cfg, ref; | |
178 | ||
179 | ref.name = (char *)extlogname; | |
180 | cfg = targets_find(&targets, &ref); | |
181 | ||
182 | if (!cfg) { | |
183 | vty_out(vty, "%% No extended syslog target named \"%s\"\n", | |
184 | extlogname); | |
185 | return CMD_WARNING; | |
186 | } | |
187 | ||
188 | log_5424_free(cfg, false); | |
189 | return CMD_SUCCESS; | |
190 | } | |
191 | ||
192 | /* "format <rfc3164|rfc5424|local-syslogd|journald>$fmt" */ | |
193 | #define FORMAT_HELP \ | |
194 | "Select log message formatting\n" \ | |
195 | "RFC3164 (legacy) syslog\n" \ | |
196 | "RFC5424 (modern) syslog, supports structured data (default)\n" \ | |
197 | "modified RFC3164 without hostname for local syslogd (/dev/log)\n" \ | |
198 | "journald (systemd log) native format\n" \ | |
199 | /* end */ | |
200 | ||
201 | static enum zlog_5424_format log_5424_fmt(const char *fmt, | |
202 | enum zlog_5424_format dflt) | |
203 | { | |
204 | if (!fmt) | |
205 | return dflt; | |
206 | else if (!strcmp(fmt, "rfc5424")) | |
207 | return ZLOG_FMT_5424; | |
208 | else if (!strcmp(fmt, "rfc3164")) | |
209 | return ZLOG_FMT_3164; | |
210 | else if (!strcmp(fmt, "local-syslogd")) | |
211 | return ZLOG_FMT_LOCAL; | |
212 | else if (!strcmp(fmt, "journald")) | |
213 | return ZLOG_FMT_JOURNALD; | |
214 | ||
215 | return dflt; | |
216 | } | |
217 | ||
218 | DEFPY(log_5424_destination_file, | |
219 | log_5424_destination_file_cmd, | |
220 | "[no] destination file$type PATH " | |
221 | "[create$create [{user WORD|group WORD|mode PERMS}]" | |
222 | "|no-create$nocreate] " | |
223 | "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]", | |
224 | NO_STR | |
225 | "Log destination setup\n" | |
226 | "Log to file\n" | |
227 | "Path to destination\n" | |
228 | "Create file if it does not exist\n" | |
229 | "Set file owner\n" | |
230 | "User name\n" | |
231 | "Set file group\n" | |
232 | "Group name\n" | |
233 | "Set permissions\n" | |
234 | "File permissions (octal)\n" | |
235 | "Do not create file if it does not exist\n" | |
236 | FORMAT_HELP) | |
237 | { | |
238 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
239 | enum zlog_5424_dst dst; | |
240 | bool reconf = true, warn_perm = false; | |
241 | char *prev_user, *prev_group; | |
242 | mode_t perm_val = LOGFILE_MASK & 0666; | |
243 | enum zlog_5424_format fmtv; | |
244 | ||
245 | if (no) | |
246 | return reconf_clear_dst(cfg, vty); | |
247 | ||
248 | fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); | |
249 | ||
250 | if (perms) { | |
251 | char *errp = (char *)perms; | |
252 | ||
253 | perm_val = strtoul(perms, &errp, 8); | |
254 | if (*errp || errp == perms || perm_val == 0 || | |
255 | (perm_val & ~0666)) { | |
256 | vty_out(vty, "%% Invalid permissions value \"%s\"\n", | |
257 | perms); | |
258 | return CMD_WARNING; | |
259 | } | |
260 | } | |
261 | ||
262 | dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO | |
263 | : ZLOG_5424_DST_FILE; | |
264 | ||
265 | if (cfg->filename && !strcmp(path, cfg->filename) && | |
266 | dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv) | |
267 | reconf = false; | |
268 | ||
269 | /* keep for compare below */ | |
270 | prev_user = cfg->file_user; | |
271 | prev_group = cfg->file_group; | |
272 | cfg->file_user = NULL; | |
273 | cfg->file_group = NULL; | |
274 | ||
275 | clear_dst(cfg); | |
276 | ||
277 | cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); | |
278 | cfg->cfg.dst = dst; | |
279 | cfg->cfg.filename = cfg->filename; | |
280 | cfg->cfg.fmt = fmtv; | |
281 | ||
282 | if (nocreate) | |
283 | cfg->cfg.file_nocreate = true; | |
284 | else { | |
285 | if (user) { | |
286 | struct passwd *pwent; | |
287 | ||
288 | warn_perm |= (prev_user && strcmp(user, prev_user)); | |
289 | cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user); | |
290 | ||
291 | errno = 0; | |
292 | pwent = getpwnam(user); | |
293 | if (!pwent) | |
294 | vty_out(vty, | |
295 | "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n", | |
296 | user, | |
297 | errno ? safe_strerror(errno) | |
298 | : "No entry by this user name"); | |
299 | else | |
300 | cfg->cfg.file_uid = pwent->pw_uid; | |
301 | } | |
302 | if (group) { | |
303 | struct group *grent; | |
304 | ||
305 | warn_perm |= (prev_group && strcmp(group, prev_group)); | |
306 | cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group); | |
307 | ||
308 | errno = 0; | |
309 | grent = getgrnam(group); | |
310 | if (!grent) | |
311 | vty_out(vty, | |
312 | "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n", | |
313 | group, | |
314 | errno ? safe_strerror(errno) | |
315 | : "No entry by this group name"); | |
316 | else | |
317 | cfg->cfg.file_gid = grent->gr_gid; | |
318 | } | |
319 | } | |
320 | XFREE(MTYPE_LOG_5424_DATA, prev_user); | |
321 | XFREE(MTYPE_LOG_5424_DATA, prev_group); | |
322 | ||
323 | if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) { | |
324 | struct stat st; | |
325 | ||
326 | if (stat(cfg->filename, &st) == 0) { | |
327 | warn_perm |= (st.st_uid != cfg->cfg.file_uid); | |
328 | warn_perm |= (st.st_gid != cfg->cfg.file_gid); | |
329 | } | |
330 | } | |
331 | if (warn_perm) | |
332 | vty_out(vty, | |
333 | "%% Warning: ownership and permission bits are only applied when creating\n" | |
334 | "%% log files. Use system tools to change existing files.\n" | |
335 | "%% FRR may also be missing necessary privileges to set these.\n"); | |
336 | ||
337 | if (reconf) | |
338 | return reconf_dst(cfg, vty); | |
339 | ||
340 | return CMD_SUCCESS; | |
341 | } | |
342 | ||
343 | /* FIFOs are for legacy /dev/log implementations; using this is very much not | |
344 | * recommended since it can unexpectedly block in logging calls. Also the fd | |
345 | * would need to be reopened when the process at the other end restarts. None | |
346 | * of this is handled - use at your own caution. It's _HIDDEN for a purpose. | |
347 | */ | |
348 | ALIAS_HIDDEN(log_5424_destination_file, | |
349 | log_5424_destination_fifo_cmd, | |
350 | "[no] destination fifo$type PATH " | |
351 | "[create$create [{owner WORD|group WORD|permissions PERMS}]" | |
352 | "|no-create$nocreate] " | |
353 | "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]", | |
354 | NO_STR | |
355 | "Log destination setup\n" | |
356 | "Log to filesystem FIFO\n" | |
357 | "Path to destination\n" | |
358 | "Create file if it does not exist\n" | |
359 | "Set file owner\n" | |
360 | "User name\n" | |
361 | "Set file group\n" | |
362 | "Group name\n" | |
363 | "Set permissions\n" | |
364 | "File permissions (octal)\n" | |
365 | "Do not create file if it does not exist\n" | |
366 | FORMAT_HELP) | |
367 | ||
368 | static int dst_unix(struct vty *vty, const char *no, const char *path, | |
369 | enum zlog_5424_format fmt, enum unix_special special) | |
370 | { | |
371 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
372 | ||
373 | if (no) | |
374 | return reconf_clear_dst(cfg, vty); | |
375 | ||
376 | cfg->unix_special = special; | |
377 | ||
378 | if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename && | |
379 | !strcmp(path, cfg->filename) && cfg->cfg.active && | |
380 | cfg->cfg.fmt == fmt) | |
381 | return CMD_SUCCESS; | |
382 | ||
383 | clear_dst(cfg); | |
384 | ||
385 | cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); | |
386 | cfg->cfg.dst = ZLOG_5424_DST_UNIX; | |
387 | cfg->cfg.filename = cfg->filename; | |
388 | cfg->cfg.fmt = fmt; | |
389 | ||
390 | cfg->cfg.reconn_backoff = 25; | |
391 | cfg->cfg.reconn_backoff_cur = 25; | |
392 | cfg->cfg.reconn_backoff_max = 10000; | |
393 | return reconf_dst(cfg, vty); | |
394 | } | |
395 | ||
396 | DEFPY(log_5424_destination_unix, | |
397 | log_5424_destination_unix_cmd, | |
398 | "[no] destination unix PATH " | |
399 | "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]", | |
400 | NO_STR | |
401 | "Log destination setup\n" | |
402 | "Log to unix socket\n" | |
403 | "Unix socket path\n" | |
404 | FORMAT_HELP) | |
405 | { | |
406 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
407 | enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); | |
408 | ||
409 | return dst_unix(vty, no, path, fmtv, SPECIAL_NONE); | |
410 | } | |
411 | ||
412 | DEFPY(log_5424_destination_journald, | |
413 | log_5424_destination_journald_cmd, | |
414 | "[no] destination journald", | |
415 | NO_STR | |
416 | "Log destination setup\n" | |
417 | "Log directly to systemd's journald\n") | |
418 | { | |
419 | return dst_unix(vty, no, "/run/systemd/journal/socket", | |
420 | ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD); | |
421 | } | |
422 | ||
423 | #if defined(__FreeBSD_version) && (__FreeBSD_version >= 1200061) | |
424 | #define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 | |
425 | #elif defined(__NetBSD_Version__) && (__NetBSD_Version__ >= 500000000) | |
426 | #define ZLOG_FMT_DEV_LOG ZLOG_FMT_5424 | |
427 | #else | |
428 | #define ZLOG_FMT_DEV_LOG ZLOG_FMT_LOCAL | |
429 | #endif | |
430 | ||
431 | DEFPY(log_5424_destination_syslog, | |
432 | log_5424_destination_syslog_cmd, | |
433 | "[no] destination syslog [supports-rfc5424]$supp5424", | |
434 | NO_STR | |
435 | "Log destination setup\n" | |
436 | "Log directly to syslog\n" | |
437 | "Use RFC5424 format (please refer to documentation)\n") | |
438 | { | |
439 | int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG; | |
440 | ||
441 | /* unfortunately, there is no way to detect 5424 support */ | |
442 | return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG); | |
443 | } | |
444 | ||
445 | /* could add something like | |
446 | * "destination <udp|tcp>$proto <A.B.C.D|X:X::X:X> (1-65535)$port" | |
447 | * here, but there are 2 reasons not to do that: | |
448 | * | |
449 | * - each FRR daemon would open its own connection, there's no system level | |
450 | * aggregation. That's the system's syslogd's job. It likely also | |
451 | * supports directing & filtering log messages with configurable rules. | |
452 | * - we're likely not going to support DTLS or TLS for more secure logging; | |
453 | * adding this would require a considerable amount of additional config | |
454 | * and an entire TLS library to begin with. A proper syslogd implements | |
455 | * all of this, why reinvent the wheel? | |
456 | */ | |
457 | ||
458 | DEFPY(log_5424_destination_fd, | |
459 | log_5424_destination_fd_cmd, | |
460 | "[no] destination <fd <(0-63)$fd|envvar WORD>|stdout$fd1|stderr$fd2>" | |
461 | "[format <rfc3164|rfc5424|local-syslogd|journald>$fmt]", | |
462 | NO_STR | |
463 | "Log destination setup\n" | |
464 | "Log to pre-opened file descriptor\n" | |
465 | "File descriptor number (must be open at startup)\n" | |
466 | "Read file descriptor number from environment variable\n" | |
467 | "Environment variable name\n" | |
468 | "Log to standard output\n" | |
469 | "Log to standard error output\n" | |
470 | FORMAT_HELP) | |
471 | { | |
472 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
473 | bool envvar_problem = false; | |
474 | enum zlog_5424_format fmtv; | |
475 | ||
476 | if (no) | |
477 | return reconf_clear_dst(cfg, vty); | |
478 | ||
479 | fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); | |
480 | ||
481 | if (envvar) { | |
482 | char *envval; | |
483 | ||
484 | envval = getenv(envvar); | |
485 | if (!envval) | |
486 | envvar_problem = true; | |
487 | else { | |
488 | char *errp = envval; | |
489 | ||
490 | fd = strtoul(envval, &errp, 0); | |
491 | if (errp == envval || *errp) | |
492 | envvar_problem = true; | |
493 | } | |
494 | ||
495 | if (envvar_problem) | |
496 | fd = -1; | |
497 | } else if (fd1) | |
498 | fd = 1; | |
499 | else if (fd2) | |
500 | fd = 2; | |
501 | ||
502 | if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd && | |
503 | cfg->cfg.active && cfg->cfg.fmt == fmtv) | |
504 | return CMD_SUCCESS; | |
505 | ||
506 | clear_dst(cfg); | |
507 | ||
508 | cfg->cfg.dst = ZLOG_5424_DST_FD; | |
509 | cfg->cfg.fd = fd; | |
510 | cfg->cfg.fmt = fmtv; | |
511 | if (envvar) | |
512 | cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar); | |
513 | ||
514 | if (envvar_problem) | |
515 | vty_out(vty, | |
516 | "%% environment variable \"%s\" not present or invalid.\n", | |
517 | envvar); | |
518 | if (!frr_is_startup_fd(fd)) | |
519 | vty_out(vty, | |
520 | "%% file descriptor %d was not open when this process was started\n", | |
521 | (int)fd); | |
522 | if (envvar_problem || !frr_is_startup_fd(fd)) | |
523 | vty_out(vty, | |
524 | "%% configuration will be saved but has no effect currently\n"); | |
525 | ||
526 | return reconf_dst(cfg, vty); | |
527 | } | |
528 | ||
529 | DEFPY(log_5424_destination_none, | |
530 | log_5424_destination_none_cmd, | |
531 | "[no] destination [none]", | |
532 | NO_STR | |
533 | "Log destination setup\n" | |
534 | "Deconfigure destination\n") | |
535 | { | |
536 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
537 | ||
538 | return reconf_clear_dst(cfg, vty); | |
539 | } | |
540 | ||
541 | /* end of destinations */ | |
542 | ||
543 | DEFPY(log_5424_prio, | |
544 | log_5424_prio_cmd, | |
545 | "[no] priority <emergencies|alerts|critical|errors|warnings|notifications|informational|debugging>$levelarg", | |
546 | NO_STR | |
547 | "Set minimum message priority to include for this target\n" | |
548 | LOG_LEVEL_DESC) | |
549 | { | |
550 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
551 | int prio_min = log_level_match(levelarg); | |
552 | ||
553 | if (prio_min == cfg->cfg.prio_min) | |
554 | return CMD_SUCCESS; | |
555 | ||
556 | cfg->cfg.prio_min = prio_min; | |
557 | return reconf_meta(cfg, vty); | |
558 | } | |
559 | ||
560 | DEFPY(log_5424_facility, | |
561 | log_5424_facility_cmd, | |
562 | "[no] facility <kern|user|mail|daemon|auth|syslog|lpr|news|uucp|cron|local0|local1|local2|local3|local4|local5|local6|local7>$facilityarg", | |
563 | NO_STR | |
564 | "Set syslog facility to use\n" | |
565 | LOG_FACILITY_DESC) | |
566 | { | |
567 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
568 | int facility = facility_match(facilityarg); | |
569 | ||
570 | if (cfg->cfg.facility == facility) | |
571 | return CMD_SUCCESS; | |
572 | ||
573 | cfg->cfg.facility = facility; | |
574 | return reconf_meta(cfg, vty); | |
575 | } | |
576 | ||
577 | DEFPY(log_5424_meta, | |
578 | log_5424_meta_cmd, | |
579 | "[no] structured-data <code-location|version|unique-id|error-category|format-args>$option", | |
580 | NO_STR | |
581 | "Select structured data (key/value pairs) to include in each message\n" | |
582 | "FRR source code location\n" | |
583 | "FRR version\n" | |
584 | "Unique message identifier (XXXXX-XXXXX)\n" | |
585 | "Error category (EC numeric)\n" | |
586 | "Individual formatted log message arguments\n") | |
587 | { | |
588 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
589 | bool val = !no, *ptr; | |
590 | struct log_option *opt = log_opts; | |
591 | ||
592 | while (opt->name && strcmp(opt->name, option)) | |
593 | opt++; | |
594 | if (!opt->name) | |
595 | return CMD_WARNING; | |
596 | ||
597 | ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); | |
598 | if (*ptr == val) | |
599 | return CMD_SUCCESS; | |
600 | ||
601 | *ptr = val; | |
602 | return reconf_meta(cfg, vty); | |
603 | } | |
604 | ||
605 | DEFPY(log_5424_ts_prec, | |
606 | log_5424_ts_prec_cmd, | |
607 | "[no] timestamp precision (0-9)", | |
608 | NO_STR | |
609 | "Timestamp options\n" | |
610 | "Number of sub-second digits to include\n" | |
611 | "Number of sub-second digits to include\n") | |
612 | { | |
613 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
614 | uint32_t ts_flags = cfg->cfg.ts_flags; | |
615 | ||
616 | ts_flags &= ~ZLOG_TS_PREC; | |
617 | if (no) | |
618 | ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC; | |
619 | else | |
620 | ts_flags |= precision; | |
621 | ||
622 | if (ts_flags == cfg->cfg.ts_flags) | |
623 | return CMD_SUCCESS; | |
624 | ||
625 | cfg->cfg.ts_flags = ts_flags; | |
626 | return reconf_meta(cfg, vty); | |
627 | } | |
628 | ||
629 | DEFPY(log_5424_ts_local, | |
630 | log_5424_ts_local_cmd, | |
631 | "[no] timestamp local-time", | |
632 | NO_STR | |
633 | "Timestamp options\n" | |
634 | "Use local system time zone rather than UTC\n") | |
635 | { | |
636 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
637 | uint32_t ts_flags = cfg->cfg.ts_flags; | |
638 | ||
639 | ts_flags &= ~ZLOG_TS_UTC; | |
640 | if (no) | |
641 | ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC; | |
642 | else | |
643 | ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC; | |
644 | ||
645 | if (ts_flags == cfg->cfg.ts_flags) | |
646 | return CMD_SUCCESS; | |
647 | ||
648 | cfg->cfg.ts_flags = ts_flags; | |
649 | return reconf_meta(cfg, vty); | |
650 | } | |
651 | ||
652 | static int log_5424_node_exit(struct vty *vty) | |
653 | { | |
654 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); | |
655 | ||
656 | if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE) | |
657 | vty_out(vty, "%% applying changes.\n"); | |
658 | ||
659 | if (cfg->reconf_dst) | |
660 | zlog_5424_apply_dst(&cfg->cfg); | |
661 | else if (cfg->reconf_meta) | |
662 | zlog_5424_apply_meta(&cfg->cfg); | |
663 | ||
664 | cfg->reconf_dst = cfg->reconf_meta = false; | |
665 | return 1; | |
666 | } | |
667 | ||
668 | static int log_5424_config_write(struct vty *vty) | |
669 | { | |
670 | struct zlog_cfg_5424_user *cfg; | |
671 | ||
672 | frr_each (targets, &targets, cfg) { | |
673 | const char *fmt_str = ""; | |
674 | ||
675 | vty_out(vty, "log extended %s\n", cfg->name); | |
676 | ||
677 | switch (cfg->cfg.fmt) { | |
678 | case ZLOG_FMT_5424: | |
679 | fmt_str = " format rfc5424"; | |
680 | break; | |
681 | case ZLOG_FMT_3164: | |
682 | fmt_str = " format rfc3164"; | |
683 | break; | |
684 | case ZLOG_FMT_LOCAL: | |
685 | fmt_str = " format local-syslogd"; | |
686 | break; | |
687 | case ZLOG_FMT_JOURNALD: | |
688 | fmt_str = " format journald"; | |
689 | break; | |
690 | } | |
691 | ||
692 | switch (cfg->cfg.dst) { | |
693 | case ZLOG_5424_DST_NONE: | |
694 | vty_out(vty, " ! no destination configured\n"); | |
695 | break; | |
696 | ||
697 | case ZLOG_5424_DST_FD: | |
698 | if (cfg->cfg.fmt == ZLOG_FMT_5424) | |
699 | fmt_str = ""; | |
700 | ||
701 | if (cfg->envvar) | |
702 | vty_out(vty, " destination fd envvar %s%s\n", | |
703 | cfg->envvar, fmt_str); | |
704 | else if (cfg->cfg.fd == 1) | |
705 | vty_out(vty, " destination stdout%s\n", | |
706 | fmt_str); | |
707 | else if (cfg->cfg.fd == 2) | |
708 | vty_out(vty, " destination stderr%s\n", | |
709 | fmt_str); | |
710 | else | |
711 | vty_out(vty, " destination fd %d%s\n", | |
712 | cfg->cfg.fd, fmt_str); | |
713 | break; | |
714 | ||
715 | case ZLOG_5424_DST_FILE: | |
716 | case ZLOG_5424_DST_FIFO: | |
717 | if (cfg->cfg.fmt == ZLOG_FMT_5424) | |
718 | fmt_str = ""; | |
719 | ||
720 | vty_out(vty, " destination %s %s", | |
721 | (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" | |
722 | : "file", | |
723 | cfg->filename); | |
724 | ||
725 | if (cfg->cfg.file_nocreate) | |
726 | vty_out(vty, " no-create"); | |
727 | else if (cfg->file_user || cfg->file_group || | |
728 | cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) { | |
729 | vty_out(vty, " create"); | |
730 | ||
731 | if (cfg->file_user) | |
732 | vty_out(vty, " user %s", | |
733 | cfg->file_user); | |
734 | if (cfg->file_group) | |
735 | vty_out(vty, " group %s", | |
736 | cfg->file_group); | |
737 | if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) | |
738 | vty_out(vty, " mode %04o", | |
739 | cfg->cfg.file_mode); | |
740 | } | |
741 | vty_out(vty, "%s\n", fmt_str); | |
742 | break; | |
743 | ||
744 | case ZLOG_5424_DST_UNIX: | |
745 | switch (cfg->unix_special) { | |
746 | case SPECIAL_NONE: | |
747 | vty_out(vty, " destination unix %s%s\n", | |
748 | cfg->filename, fmt_str); | |
749 | break; | |
750 | case SPECIAL_SYSLOG: | |
751 | if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG) | |
752 | vty_out(vty, " destination syslog\n"); | |
753 | else | |
754 | vty_out(vty, | |
755 | " destination syslog supports-rfc5424\n"); | |
756 | break; | |
757 | case SPECIAL_JOURNALD: | |
758 | vty_out(vty, " destination journald\n"); | |
759 | break; | |
760 | } | |
761 | break; | |
762 | } | |
763 | ||
764 | if (cfg->cfg.prio_min != LOG_DEBUG) | |
765 | vty_out(vty, " priority %s\n", | |
766 | zlog_priority_str(cfg->cfg.prio_min)); | |
767 | if (cfg->cfg.facility != DFLT_FACILITY) | |
768 | vty_out(vty, " facility %s\n", | |
769 | facility_name(cfg->cfg.facility)); | |
770 | ||
771 | for (struct log_option *opt = log_opts; opt->name; opt++) { | |
772 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); | |
773 | ||
774 | if (*ptr != opt->dflt) | |
775 | vty_out(vty, " %sstructured-data %s\n", | |
776 | *ptr ? "" : "no ", opt->name); | |
777 | } | |
778 | ||
779 | if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC) | |
780 | vty_out(vty, " timestamp precision %u\n", | |
781 | cfg->cfg.ts_flags & ZLOG_TS_PREC); | |
782 | ||
783 | if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) { | |
784 | if (cfg->cfg.ts_flags & ZLOG_TS_UTC) | |
785 | vty_out(vty, " no timestamp local-time\n"); | |
786 | else | |
787 | vty_out(vty, " timestamp local-time\n"); | |
788 | } | |
789 | ||
790 | vty_out(vty, "!\n"); | |
791 | } | |
792 | return 0; | |
793 | } | |
794 | ||
795 | static int log_5424_show(struct vty *vty) | |
796 | { | |
797 | struct zlog_cfg_5424_user *cfg; | |
798 | ||
799 | frr_each (targets, &targets, cfg) { | |
800 | vty_out(vty, "\nExtended log target %pSQq\n", cfg->name); | |
801 | ||
802 | switch (cfg->cfg.dst) { | |
803 | case ZLOG_5424_DST_NONE: | |
804 | vty_out(vty, | |
805 | " Inactive (no destination configured)\n"); | |
806 | break; | |
807 | ||
808 | case ZLOG_5424_DST_FD: | |
809 | if (cfg->envvar) | |
810 | vty_out(vty, | |
811 | " logging to fd %d from environment variable %pSE\n", | |
812 | cfg->cfg.fd, cfg->envvar); | |
813 | else if (cfg->cfg.fd == 1) | |
814 | vty_out(vty, " logging to stdout\n"); | |
815 | else if (cfg->cfg.fd == 2) | |
816 | vty_out(vty, " logging to stderr\n"); | |
817 | else | |
818 | vty_out(vty, " logging to fd %d\n", | |
819 | cfg->cfg.fd); | |
820 | break; | |
821 | ||
822 | case ZLOG_5424_DST_FILE: | |
823 | case ZLOG_5424_DST_FIFO: | |
824 | case ZLOG_5424_DST_UNIX: | |
825 | vty_out(vty, " logging to %s: %pSE\n", | |
826 | (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" | |
827 | : (cfg->cfg.dst == ZLOG_5424_DST_UNIX) | |
828 | ? "unix socket" | |
829 | : "file", | |
830 | cfg->filename); | |
831 | break; | |
832 | } | |
833 | ||
834 | vty_out(vty, " log level: %s, facility: %s\n", | |
835 | zlog_priority_str(cfg->cfg.prio_min), | |
836 | facility_name(cfg->cfg.facility)); | |
837 | ||
838 | bool any_meta = false, first = true; | |
839 | ||
840 | for (struct log_option *opt = log_opts; opt->name; opt++) { | |
841 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); | |
842 | ||
843 | any_meta |= *ptr; | |
844 | } | |
845 | ||
846 | if (!any_meta) | |
847 | continue; | |
848 | ||
849 | switch (cfg->cfg.fmt) { | |
850 | case ZLOG_FMT_5424: | |
851 | case ZLOG_FMT_JOURNALD: | |
852 | vty_out(vty, " structured data: "); | |
853 | ||
854 | for (struct log_option *opt = log_opts; opt->name; | |
855 | opt++) { | |
856 | bool *ptr = (bool *)(((char *)&cfg->cfg) + | |
857 | opt->offs); | |
858 | ||
859 | if (*ptr) { | |
860 | vty_out(vty, "%s%s", first ? "" : ", ", | |
861 | opt->name); | |
862 | first = false; | |
863 | } | |
864 | } | |
865 | break; | |
866 | ||
bde30e78 DS |
867 | case ZLOG_FMT_3164: |
868 | case ZLOG_FMT_LOCAL: | |
1c6261a9 DL |
869 | vty_out(vty, |
870 | " structured data is not supported by the selected format\n"); | |
871 | break; | |
872 | } | |
873 | ||
874 | vty_out(vty, "\n"); | |
875 | ||
876 | size_t lost_msgs; | |
877 | int last_errno; | |
878 | bool stale_errno; | |
879 | struct timeval err_ts; | |
880 | int64_t since; | |
881 | ||
882 | zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno, | |
883 | &stale_errno, &err_ts); | |
884 | vty_out(vty, " number of lost messages: %zu\n", lost_msgs); | |
885 | ||
886 | if (last_errno == 0) | |
887 | since = 0; | |
888 | else | |
889 | since = monotime_since(&err_ts, NULL); | |
890 | vty_out(vty, | |
891 | " last error: %s (%lld.%06llds ago, currently %s)\n", | |
892 | last_errno ? safe_strerror(last_errno) : "none", | |
893 | since / 1000000LL, since % 1000000LL, | |
894 | stale_errno ? "OK" : "erroring"); | |
895 | } | |
896 | return 0; | |
897 | } | |
898 | ||
899 | static struct cmd_node extlog_node = { | |
900 | .name = "extended", | |
901 | .node = EXTLOG_NODE, | |
902 | .parent_node = CONFIG_NODE, | |
903 | .prompt = "%s(config-ext-log)# ", | |
904 | ||
905 | .config_write = log_5424_config_write, | |
906 | .node_exit = log_5424_node_exit, | |
907 | }; | |
908 | ||
909 | static void log_5424_autocomplete(vector comps, struct cmd_token *token) | |
910 | { | |
911 | struct zlog_cfg_5424_user *cfg; | |
912 | ||
913 | frr_each (targets, &targets, cfg) | |
914 | vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name)); | |
915 | } | |
916 | ||
917 | static const struct cmd_variable_handler log_5424_var_handlers[] = { | |
918 | {.tokenname = "EXTLOGNAME", .completions = log_5424_autocomplete}, | |
919 | {.completions = NULL}, | |
920 | }; | |
921 | ||
922 | void log_5424_cmd_init(void) | |
923 | { | |
924 | hook_register(zlog_cli_show, log_5424_show); | |
925 | ||
926 | cmd_variable_handler_register(log_5424_var_handlers); | |
927 | ||
928 | /* CLI commands. */ | |
929 | install_node(&extlog_node); | |
930 | install_default(EXTLOG_NODE); | |
931 | ||
932 | install_element(CONFIG_NODE, &log_5424_target_cmd); | |
933 | install_element(CONFIG_NODE, &no_log_5424_target_cmd); | |
934 | ||
935 | install_element(EXTLOG_NODE, &log_5424_destination_file_cmd); | |
936 | install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd); | |
937 | install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd); | |
938 | install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd); | |
939 | install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd); | |
940 | install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd); | |
941 | ||
942 | install_element(EXTLOG_NODE, &log_5424_meta_cmd); | |
943 | install_element(EXTLOG_NODE, &log_5424_prio_cmd); | |
944 | install_element(EXTLOG_NODE, &log_5424_facility_cmd); | |
945 | install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd); | |
946 | install_element(EXTLOG_NODE, &log_5424_ts_local_cmd); | |
947 | } | |
948 | ||
949 | /* hooks */ | |
950 | ||
951 | static int log_5424_early_init(struct thread_master *master); | |
952 | static int log_5424_rotate(void); | |
953 | static int log_5424_fini(void); | |
954 | ||
955 | __attribute__((_CONSTRUCTOR(475))) static void zlog_5424_startup_init(void) | |
956 | { | |
957 | hook_register(frr_early_init, log_5424_early_init); | |
958 | hook_register(zlog_rotate, log_5424_rotate); | |
959 | hook_register(frr_fini, log_5424_fini); | |
960 | } | |
961 | ||
962 | static int log_5424_early_init(struct thread_master *master) | |
963 | { | |
964 | log_5424_master = master; | |
965 | ||
966 | return 0; | |
967 | } | |
968 | ||
969 | static int log_5424_rotate(void) | |
970 | { | |
971 | struct zlog_cfg_5424_user *cfg; | |
972 | ||
973 | frr_each (targets, &targets, cfg) | |
974 | if (!zlog_5424_rotate(&cfg->cfg)) | |
975 | zlog_err( | |
976 | "log rotation on extended log target %s failed", | |
977 | cfg->name); | |
978 | ||
979 | return 0; | |
980 | } | |
981 | ||
982 | static int log_5424_fini(void) | |
983 | { | |
984 | struct zlog_cfg_5424_user *cfg; | |
985 | ||
986 | while ((cfg = targets_pop(&targets))) | |
987 | log_5424_free(cfg, true); | |
988 | ||
989 | log_5424_master = NULL; | |
990 | ||
991 | return 0; | |
992 | } |