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