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