/src/frr/lib/zlog_5424_cli.c
Line | Count | Source |
1 | | // SPDX-License-Identifier: GPL-2.0-or-later |
2 | | /* |
3 | | * Copyright (C) 2021 David Lamparter for NetDEF, Inc. |
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 | 8 | DEFINE_MTYPE_STATIC(LOG, LOG_5424_CONFIG, "extended syslog config"); |
18 | 8 | DEFINE_MTYPE_STATIC(LOG, LOG_5424_DATA, "extended syslog config items"); |
19 | 8 | |
20 | 8 | static int target_cmp(const struct zlog_cfg_5424_user *a, |
21 | 8 | const struct zlog_cfg_5424_user *b) |
22 | 8 | { |
23 | 0 | return strcmp(a->name, b->name); |
24 | 0 | } |
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 event_loop *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 | 0 | #define DFLT_TS_FLAGS (6 | ZLOG_TS_UTC) |
52 | 0 | #define DFLT_FACILITY LOG_DAEMON |
53 | 0 | #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 | 0 | { |
64 | 0 | struct zlog_cfg_5424_user *cfg; |
65 | |
|
66 | 0 | cfg = XCALLOC(MTYPE_LOG_5424_CONFIG, sizeof(*cfg)); |
67 | 0 | cfg->name = XSTRDUP(MTYPE_LOG_5424_DATA, name); |
68 | |
|
69 | 0 | cfg->cfg.master = log_5424_master; |
70 | 0 | cfg->cfg.kw_location = true; |
71 | 0 | cfg->cfg.kw_version = false; |
72 | 0 | cfg->cfg.facility = DFLT_FACILITY; |
73 | 0 | cfg->cfg.prio_min = DFLT_PRIO_MIN; |
74 | 0 | cfg->cfg.ts_flags = DFLT_TS_FLAGS; |
75 | 0 | clear_dst(cfg); |
76 | |
|
77 | 0 | for (struct log_option *opt = log_opts; opt->name; opt++) { |
78 | 0 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); |
79 | 0 | *ptr = opt->dflt; |
80 | 0 | } |
81 | |
|
82 | 0 | zlog_5424_init(&cfg->cfg); |
83 | |
|
84 | 0 | QOBJ_REG(cfg, zlog_cfg_5424_user); |
85 | 0 | targets_add(&targets, cfg); |
86 | 0 | return cfg; |
87 | 0 | } |
88 | | |
89 | | static void log_5424_free(struct zlog_cfg_5424_user *cfg, bool keepopen) |
90 | 0 | { |
91 | 0 | targets_del(&targets, cfg); |
92 | 0 | QOBJ_UNREG(cfg); |
93 | |
|
94 | 0 | zlog_5424_fini(&cfg->cfg, keepopen); |
95 | 0 | clear_dst(cfg); |
96 | |
|
97 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->filename); |
98 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->name); |
99 | 0 | XFREE(MTYPE_LOG_5424_CONFIG, cfg); |
100 | 0 | } |
101 | | |
102 | | static void clear_dst(struct zlog_cfg_5424_user *cfg) |
103 | 0 | { |
104 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->filename); |
105 | 0 | cfg->cfg.filename = cfg->filename; |
106 | |
|
107 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->file_user); |
108 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->file_group); |
109 | 0 | XFREE(MTYPE_LOG_5424_DATA, cfg->envvar); |
110 | |
|
111 | 0 | cfg->cfg.fd = -1; |
112 | 0 | cfg->cfg.file_uid = -1; |
113 | 0 | cfg->cfg.file_gid = -1; |
114 | 0 | cfg->cfg.file_mode = LOGFILE_MASK & 0666; |
115 | 0 | cfg->cfg.file_nocreate = false; |
116 | 0 | cfg->cfg.dst = ZLOG_5424_DST_NONE; |
117 | 0 | } |
118 | | |
119 | | static int reconf_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) |
120 | 0 | { |
121 | 0 | if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) |
122 | 0 | vty_out(vty, |
123 | 0 | "%% Changes will be applied when exiting this config block\n"); |
124 | |
|
125 | 0 | cfg->reconf_dst = true; |
126 | 0 | return CMD_SUCCESS; |
127 | 0 | } |
128 | | |
129 | | static int reconf_meta(struct zlog_cfg_5424_user *cfg, struct vty *vty) |
130 | 0 | { |
131 | 0 | if (!cfg->reconf_dst && !cfg->reconf_meta && vty->type != VTY_FILE) |
132 | 0 | vty_out(vty, |
133 | 0 | "%% Changes will be applied when exiting this config block\n"); |
134 | |
|
135 | 0 | cfg->reconf_meta = true; |
136 | 0 | return CMD_SUCCESS; |
137 | 0 | } |
138 | | |
139 | | static int reconf_clear_dst(struct zlog_cfg_5424_user *cfg, struct vty *vty) |
140 | 0 | { |
141 | 0 | if (cfg->cfg.dst == ZLOG_5424_DST_NONE) |
142 | 0 | return CMD_SUCCESS; |
143 | | |
144 | 0 | clear_dst(cfg); |
145 | 0 | return reconf_dst(cfg, vty); |
146 | 0 | } |
147 | | |
148 | | #include "lib/zlog_5424_cli_clippy.c" |
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 | 0 | { |
157 | 0 | struct zlog_cfg_5424_user *cfg, ref; |
158 | |
|
159 | 0 | ref.name = (char *)extlogname; |
160 | 0 | cfg = targets_find(&targets, &ref); |
161 | |
|
162 | 0 | if (!cfg) |
163 | 0 | cfg = log_5424_alloc(extlogname); |
164 | |
|
165 | 0 | VTY_PUSH_CONTEXT(EXTLOG_NODE, cfg); |
166 | 0 | return CMD_SUCCESS; |
167 | 0 | } |
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 | 0 | { |
177 | 0 | struct zlog_cfg_5424_user *cfg, ref; |
178 | |
|
179 | 0 | ref.name = (char *)extlogname; |
180 | 0 | cfg = targets_find(&targets, &ref); |
181 | |
|
182 | 0 | if (!cfg) { |
183 | 0 | vty_out(vty, "%% No extended syslog target named \"%s\"\n", |
184 | 0 | extlogname); |
185 | 0 | return CMD_WARNING; |
186 | 0 | } |
187 | | |
188 | 0 | log_5424_free(cfg, false); |
189 | 0 | return CMD_SUCCESS; |
190 | 0 | } |
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 | 0 | { |
204 | 0 | if (!fmt) |
205 | 0 | return dflt; |
206 | 0 | else if (!strcmp(fmt, "rfc5424")) |
207 | 0 | return ZLOG_FMT_5424; |
208 | 0 | else if (!strcmp(fmt, "rfc3164")) |
209 | 0 | return ZLOG_FMT_3164; |
210 | 0 | else if (!strcmp(fmt, "local-syslogd")) |
211 | 0 | return ZLOG_FMT_LOCAL; |
212 | 0 | else if (!strcmp(fmt, "journald")) |
213 | 0 | return ZLOG_FMT_JOURNALD; |
214 | | |
215 | 0 | return dflt; |
216 | 0 | } |
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 | 0 | { |
238 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
239 | 0 | enum zlog_5424_dst dst; |
240 | 0 | bool reconf = true, warn_perm = false; |
241 | 0 | char *prev_user, *prev_group; |
242 | 0 | mode_t perm_val = LOGFILE_MASK & 0666; |
243 | 0 | enum zlog_5424_format fmtv; |
244 | |
|
245 | 0 | if (no) |
246 | 0 | return reconf_clear_dst(cfg, vty); |
247 | | |
248 | 0 | fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); |
249 | |
|
250 | 0 | if (perms) { |
251 | 0 | char *errp = (char *)perms; |
252 | |
|
253 | 0 | perm_val = strtoul(perms, &errp, 8); |
254 | 0 | if (*errp || errp == perms || perm_val == 0 || |
255 | 0 | (perm_val & ~0666)) { |
256 | 0 | vty_out(vty, "%% Invalid permissions value \"%s\"\n", |
257 | 0 | perms); |
258 | 0 | return CMD_WARNING; |
259 | 0 | } |
260 | 0 | } |
261 | | |
262 | 0 | dst = (strcmp(type, "fifo") == 0) ? ZLOG_5424_DST_FIFO |
263 | 0 | : ZLOG_5424_DST_FILE; |
264 | |
|
265 | 0 | if (cfg->filename && !strcmp(path, cfg->filename) && |
266 | 0 | dst == cfg->cfg.dst && cfg->cfg.active && cfg->cfg.fmt == fmtv) |
267 | 0 | reconf = false; |
268 | | |
269 | | /* keep for compare below */ |
270 | 0 | prev_user = cfg->file_user; |
271 | 0 | prev_group = cfg->file_group; |
272 | 0 | cfg->file_user = NULL; |
273 | 0 | cfg->file_group = NULL; |
274 | |
|
275 | 0 | clear_dst(cfg); |
276 | |
|
277 | 0 | cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); |
278 | 0 | cfg->cfg.dst = dst; |
279 | 0 | cfg->cfg.filename = cfg->filename; |
280 | 0 | cfg->cfg.fmt = fmtv; |
281 | |
|
282 | 0 | if (nocreate) |
283 | 0 | cfg->cfg.file_nocreate = true; |
284 | 0 | else { |
285 | 0 | if (user) { |
286 | 0 | struct passwd *pwent; |
287 | |
|
288 | 0 | warn_perm |= (prev_user && strcmp(user, prev_user)); |
289 | 0 | cfg->file_user = XSTRDUP(MTYPE_LOG_5424_DATA, user); |
290 | |
|
291 | 0 | errno = 0; |
292 | 0 | pwent = getpwnam(user); |
293 | 0 | if (!pwent) |
294 | 0 | vty_out(vty, |
295 | 0 | "%% Could not look up user \"%s\" (%s), file owner will be left untouched!\n", |
296 | 0 | user, |
297 | 0 | errno ? safe_strerror(errno) |
298 | 0 | : "No entry by this user name"); |
299 | 0 | else |
300 | 0 | cfg->cfg.file_uid = pwent->pw_uid; |
301 | 0 | } |
302 | 0 | if (group) { |
303 | 0 | struct group *grent; |
304 | |
|
305 | 0 | warn_perm |= (prev_group && strcmp(group, prev_group)); |
306 | 0 | cfg->file_group = XSTRDUP(MTYPE_LOG_5424_DATA, group); |
307 | |
|
308 | 0 | errno = 0; |
309 | 0 | grent = getgrnam(group); |
310 | 0 | if (!grent) |
311 | 0 | vty_out(vty, |
312 | 0 | "%% Could not look up group \"%s\" (%s), file group will be left untouched!\n", |
313 | 0 | group, |
314 | 0 | errno ? safe_strerror(errno) |
315 | 0 | : "No entry by this group name"); |
316 | 0 | else |
317 | 0 | cfg->cfg.file_gid = grent->gr_gid; |
318 | 0 | } |
319 | 0 | } |
320 | 0 | XFREE(MTYPE_LOG_5424_DATA, prev_user); |
321 | 0 | XFREE(MTYPE_LOG_5424_DATA, prev_group); |
322 | |
|
323 | 0 | if (cfg->cfg.file_uid != (uid_t)-1 || cfg->cfg.file_gid != (gid_t)-1) { |
324 | 0 | struct stat st; |
325 | |
|
326 | 0 | if (stat(cfg->filename, &st) == 0) { |
327 | 0 | warn_perm |= (st.st_uid != cfg->cfg.file_uid); |
328 | 0 | warn_perm |= (st.st_gid != cfg->cfg.file_gid); |
329 | 0 | } |
330 | 0 | } |
331 | 0 | if (warn_perm) |
332 | 0 | vty_out(vty, |
333 | 0 | "%% Warning: ownership and permission bits are only applied when creating\n" |
334 | 0 | "%% log files. Use system tools to change existing files.\n" |
335 | 0 | "%% FRR may also be missing necessary privileges to set these.\n"); |
336 | |
|
337 | 0 | if (reconf) |
338 | 0 | return reconf_dst(cfg, vty); |
339 | | |
340 | 0 | return CMD_SUCCESS; |
341 | 0 | } |
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 | 0 | { |
371 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
372 | |
|
373 | 0 | if (no) |
374 | 0 | return reconf_clear_dst(cfg, vty); |
375 | | |
376 | 0 | cfg->unix_special = special; |
377 | |
|
378 | 0 | if (cfg->cfg.dst == ZLOG_5424_DST_UNIX && cfg->filename && |
379 | 0 | !strcmp(path, cfg->filename) && cfg->cfg.active && |
380 | 0 | cfg->cfg.fmt == fmt) |
381 | 0 | return CMD_SUCCESS; |
382 | | |
383 | 0 | clear_dst(cfg); |
384 | |
|
385 | 0 | cfg->filename = XSTRDUP(MTYPE_LOG_5424_DATA, path); |
386 | 0 | cfg->cfg.dst = ZLOG_5424_DST_UNIX; |
387 | 0 | cfg->cfg.filename = cfg->filename; |
388 | 0 | cfg->cfg.fmt = fmt; |
389 | |
|
390 | 0 | cfg->cfg.reconn_backoff = 25; |
391 | 0 | cfg->cfg.reconn_backoff_cur = 25; |
392 | 0 | cfg->cfg.reconn_backoff_max = 10000; |
393 | 0 | return reconf_dst(cfg, vty); |
394 | 0 | } |
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 | 0 | { |
406 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
407 | 0 | enum zlog_5424_format fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); |
408 | |
|
409 | 0 | return dst_unix(vty, no, path, fmtv, SPECIAL_NONE); |
410 | 0 | } |
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 | 0 | { |
419 | 0 | return dst_unix(vty, no, "/run/systemd/journal/socket", |
420 | 0 | ZLOG_FMT_JOURNALD, SPECIAL_JOURNALD); |
421 | 0 | } |
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 | 0 | #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 | 0 | { |
439 | 0 | int format = supp5424 ? ZLOG_FMT_5424 : ZLOG_FMT_DEV_LOG; |
440 | | |
441 | | /* unfortunately, there is no way to detect 5424 support */ |
442 | 0 | return dst_unix(vty, no, "/dev/log", format, SPECIAL_SYSLOG); |
443 | 0 | } |
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 | 0 | { |
472 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
473 | 0 | bool envvar_problem = false; |
474 | 0 | enum zlog_5424_format fmtv; |
475 | |
|
476 | 0 | if (no) |
477 | 0 | return reconf_clear_dst(cfg, vty); |
478 | | |
479 | 0 | fmtv = log_5424_fmt(fmt, ZLOG_FMT_5424); |
480 | |
|
481 | 0 | if (envvar) { |
482 | 0 | char *envval; |
483 | |
|
484 | 0 | envval = getenv(envvar); |
485 | 0 | if (!envval) |
486 | 0 | envvar_problem = true; |
487 | 0 | else { |
488 | 0 | char *errp = envval; |
489 | |
|
490 | 0 | fd = strtoul(envval, &errp, 0); |
491 | 0 | if (errp == envval || *errp) |
492 | 0 | envvar_problem = true; |
493 | 0 | } |
494 | |
|
495 | 0 | if (envvar_problem) |
496 | 0 | fd = -1; |
497 | 0 | } else if (fd1) |
498 | 0 | fd = 1; |
499 | 0 | else if (fd2) |
500 | 0 | fd = 2; |
501 | |
|
502 | 0 | if (cfg->cfg.dst == ZLOG_5424_DST_FD && cfg->cfg.fd == fd && |
503 | 0 | cfg->cfg.active && cfg->cfg.fmt == fmtv) |
504 | 0 | return CMD_SUCCESS; |
505 | | |
506 | 0 | clear_dst(cfg); |
507 | |
|
508 | 0 | cfg->cfg.dst = ZLOG_5424_DST_FD; |
509 | 0 | cfg->cfg.fd = fd; |
510 | 0 | cfg->cfg.fmt = fmtv; |
511 | 0 | if (envvar) |
512 | 0 | cfg->envvar = XSTRDUP(MTYPE_LOG_5424_DATA, envvar); |
513 | |
|
514 | 0 | if (envvar_problem) |
515 | 0 | vty_out(vty, |
516 | 0 | "%% environment variable \"%s\" not present or invalid.\n", |
517 | 0 | envvar); |
518 | 0 | if (!frr_is_startup_fd(fd)) |
519 | 0 | vty_out(vty, |
520 | 0 | "%% file descriptor %d was not open when this process was started\n", |
521 | 0 | (int)fd); |
522 | 0 | if (envvar_problem || !frr_is_startup_fd(fd)) |
523 | 0 | vty_out(vty, |
524 | 0 | "%% configuration will be saved but has no effect currently\n"); |
525 | |
|
526 | 0 | return reconf_dst(cfg, vty); |
527 | 0 | } |
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 | 0 | { |
536 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
537 | |
|
538 | 0 | return reconf_clear_dst(cfg, vty); |
539 | 0 | } |
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 | 0 | { |
550 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
551 | 0 | int prio_min = log_level_match(levelarg); |
552 | |
|
553 | 0 | if (prio_min == cfg->cfg.prio_min) |
554 | 0 | return CMD_SUCCESS; |
555 | | |
556 | 0 | cfg->cfg.prio_min = prio_min; |
557 | 0 | return reconf_meta(cfg, vty); |
558 | 0 | } |
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 | 0 | { |
567 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
568 | 0 | int facility = facility_match(facilityarg); |
569 | |
|
570 | 0 | if (cfg->cfg.facility == facility) |
571 | 0 | return CMD_SUCCESS; |
572 | | |
573 | 0 | cfg->cfg.facility = facility; |
574 | 0 | return reconf_meta(cfg, vty); |
575 | 0 | } |
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 | 0 | { |
588 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
589 | 0 | bool val = !no, *ptr; |
590 | 0 | struct log_option *opt = log_opts; |
591 | |
|
592 | 0 | while (opt->name && strcmp(opt->name, option)) |
593 | 0 | opt++; |
594 | 0 | if (!opt->name) |
595 | 0 | return CMD_WARNING; |
596 | | |
597 | 0 | ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); |
598 | 0 | if (*ptr == val) |
599 | 0 | return CMD_SUCCESS; |
600 | | |
601 | 0 | *ptr = val; |
602 | 0 | return reconf_meta(cfg, vty); |
603 | 0 | } |
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 | 0 | { |
613 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
614 | 0 | uint32_t ts_flags = cfg->cfg.ts_flags; |
615 | |
|
616 | 0 | ts_flags &= ~ZLOG_TS_PREC; |
617 | 0 | if (no) |
618 | 0 | ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_PREC; |
619 | 0 | else |
620 | 0 | ts_flags |= precision; |
621 | |
|
622 | 0 | if (ts_flags == cfg->cfg.ts_flags) |
623 | 0 | return CMD_SUCCESS; |
624 | | |
625 | 0 | cfg->cfg.ts_flags = ts_flags; |
626 | 0 | return reconf_meta(cfg, vty); |
627 | 0 | } |
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 | 0 | { |
636 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
637 | 0 | uint32_t ts_flags = cfg->cfg.ts_flags; |
638 | |
|
639 | 0 | ts_flags &= ~ZLOG_TS_UTC; |
640 | 0 | if (no) |
641 | 0 | ts_flags |= DFLT_TS_FLAGS & ZLOG_TS_UTC; |
642 | 0 | else |
643 | 0 | ts_flags |= (~DFLT_TS_FLAGS) & ZLOG_TS_UTC; |
644 | |
|
645 | 0 | if (ts_flags == cfg->cfg.ts_flags) |
646 | 0 | return CMD_SUCCESS; |
647 | | |
648 | 0 | cfg->cfg.ts_flags = ts_flags; |
649 | 0 | return reconf_meta(cfg, vty); |
650 | 0 | } |
651 | | |
652 | | static int log_5424_node_exit(struct vty *vty) |
653 | 0 | { |
654 | 0 | VTY_DECLVAR_CONTEXT(zlog_cfg_5424_user, cfg); |
655 | |
|
656 | 0 | if ((cfg->reconf_dst || cfg->reconf_meta) && vty->type != VTY_FILE) |
657 | 0 | vty_out(vty, "%% applying changes.\n"); |
658 | |
|
659 | 0 | if (cfg->reconf_dst) |
660 | 0 | zlog_5424_apply_dst(&cfg->cfg); |
661 | 0 | else if (cfg->reconf_meta) |
662 | 0 | zlog_5424_apply_meta(&cfg->cfg); |
663 | |
|
664 | 0 | cfg->reconf_dst = cfg->reconf_meta = false; |
665 | 0 | return 1; |
666 | 0 | } |
667 | | |
668 | | static int log_5424_config_write(struct vty *vty) |
669 | 0 | { |
670 | 0 | struct zlog_cfg_5424_user *cfg; |
671 | |
|
672 | 0 | frr_each (targets, &targets, cfg) { |
673 | 0 | const char *fmt_str = ""; |
674 | |
|
675 | 0 | vty_out(vty, "log extended %s\n", cfg->name); |
676 | |
|
677 | 0 | switch (cfg->cfg.fmt) { |
678 | 0 | case ZLOG_FMT_5424: |
679 | 0 | fmt_str = " format rfc5424"; |
680 | 0 | break; |
681 | 0 | case ZLOG_FMT_3164: |
682 | 0 | fmt_str = " format rfc3164"; |
683 | 0 | break; |
684 | 0 | case ZLOG_FMT_LOCAL: |
685 | 0 | fmt_str = " format local-syslogd"; |
686 | 0 | break; |
687 | 0 | case ZLOG_FMT_JOURNALD: |
688 | 0 | fmt_str = " format journald"; |
689 | 0 | break; |
690 | 0 | } |
691 | | |
692 | 0 | switch (cfg->cfg.dst) { |
693 | 0 | case ZLOG_5424_DST_NONE: |
694 | 0 | vty_out(vty, " ! no destination configured\n"); |
695 | 0 | break; |
696 | | |
697 | 0 | case ZLOG_5424_DST_FD: |
698 | 0 | if (cfg->cfg.fmt == ZLOG_FMT_5424) |
699 | 0 | fmt_str = ""; |
700 | |
|
701 | 0 | if (cfg->envvar) |
702 | 0 | vty_out(vty, " destination fd envvar %s%s\n", |
703 | 0 | cfg->envvar, fmt_str); |
704 | 0 | else if (cfg->cfg.fd == 1) |
705 | 0 | vty_out(vty, " destination stdout%s\n", |
706 | 0 | fmt_str); |
707 | 0 | else if (cfg->cfg.fd == 2) |
708 | 0 | vty_out(vty, " destination stderr%s\n", |
709 | 0 | fmt_str); |
710 | 0 | else |
711 | 0 | vty_out(vty, " destination fd %d%s\n", |
712 | 0 | cfg->cfg.fd, fmt_str); |
713 | 0 | break; |
714 | | |
715 | 0 | case ZLOG_5424_DST_FILE: |
716 | 0 | case ZLOG_5424_DST_FIFO: |
717 | 0 | if (cfg->cfg.fmt == ZLOG_FMT_5424) |
718 | 0 | fmt_str = ""; |
719 | |
|
720 | 0 | vty_out(vty, " destination %s %s", |
721 | 0 | (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" |
722 | 0 | : "file", |
723 | 0 | cfg->filename); |
724 | |
|
725 | 0 | if (cfg->cfg.file_nocreate) |
726 | 0 | vty_out(vty, " no-create"); |
727 | 0 | else if (cfg->file_user || cfg->file_group || |
728 | 0 | cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) { |
729 | 0 | vty_out(vty, " create"); |
730 | |
|
731 | 0 | if (cfg->file_user) |
732 | 0 | vty_out(vty, " user %s", |
733 | 0 | cfg->file_user); |
734 | 0 | if (cfg->file_group) |
735 | 0 | vty_out(vty, " group %s", |
736 | 0 | cfg->file_group); |
737 | 0 | if (cfg->cfg.file_mode != (LOGFILE_MASK & 0666)) |
738 | 0 | vty_out(vty, " mode %04o", |
739 | 0 | cfg->cfg.file_mode); |
740 | 0 | } |
741 | 0 | vty_out(vty, "%s\n", fmt_str); |
742 | 0 | break; |
743 | | |
744 | 0 | case ZLOG_5424_DST_UNIX: |
745 | 0 | switch (cfg->unix_special) { |
746 | 0 | case SPECIAL_NONE: |
747 | 0 | vty_out(vty, " destination unix %s%s\n", |
748 | 0 | cfg->filename, fmt_str); |
749 | 0 | break; |
750 | 0 | case SPECIAL_SYSLOG: |
751 | 0 | if (cfg->cfg.fmt == ZLOG_FMT_DEV_LOG) |
752 | 0 | vty_out(vty, " destination syslog\n"); |
753 | 0 | else |
754 | 0 | vty_out(vty, |
755 | 0 | " destination syslog supports-rfc5424\n"); |
756 | 0 | break; |
757 | 0 | case SPECIAL_JOURNALD: |
758 | 0 | vty_out(vty, " destination journald\n"); |
759 | 0 | break; |
760 | 0 | } |
761 | 0 | break; |
762 | 0 | } |
763 | | |
764 | 0 | if (cfg->cfg.prio_min != LOG_DEBUG) |
765 | 0 | vty_out(vty, " priority %s\n", |
766 | 0 | zlog_priority_str(cfg->cfg.prio_min)); |
767 | 0 | if (cfg->cfg.facility != DFLT_FACILITY) |
768 | 0 | vty_out(vty, " facility %s\n", |
769 | 0 | facility_name(cfg->cfg.facility)); |
770 | |
|
771 | 0 | for (struct log_option *opt = log_opts; opt->name; opt++) { |
772 | 0 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); |
773 | |
|
774 | 0 | if (*ptr != opt->dflt) |
775 | 0 | vty_out(vty, " %sstructured-data %s\n", |
776 | 0 | *ptr ? "" : "no ", opt->name); |
777 | 0 | } |
778 | |
|
779 | 0 | if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_PREC) |
780 | 0 | vty_out(vty, " timestamp precision %u\n", |
781 | 0 | cfg->cfg.ts_flags & ZLOG_TS_PREC); |
782 | |
|
783 | 0 | if ((cfg->cfg.ts_flags ^ DFLT_TS_FLAGS) & ZLOG_TS_UTC) { |
784 | 0 | if (cfg->cfg.ts_flags & ZLOG_TS_UTC) |
785 | 0 | vty_out(vty, " no timestamp local-time\n"); |
786 | 0 | else |
787 | 0 | vty_out(vty, " timestamp local-time\n"); |
788 | 0 | } |
789 | |
|
790 | 0 | vty_out(vty, "!\n"); |
791 | 0 | } |
792 | 0 | return 0; |
793 | 0 | } |
794 | | |
795 | | static int log_5424_show(struct vty *vty) |
796 | 0 | { |
797 | 0 | struct zlog_cfg_5424_user *cfg; |
798 | |
|
799 | 0 | frr_each (targets, &targets, cfg) { |
800 | 0 | vty_out(vty, "\nExtended log target %pSQq\n", cfg->name); |
801 | |
|
802 | 0 | switch (cfg->cfg.dst) { |
803 | 0 | case ZLOG_5424_DST_NONE: |
804 | 0 | vty_out(vty, |
805 | 0 | " Inactive (no destination configured)\n"); |
806 | 0 | break; |
807 | | |
808 | 0 | case ZLOG_5424_DST_FD: |
809 | 0 | if (cfg->envvar) |
810 | 0 | vty_out(vty, |
811 | 0 | " logging to fd %d from environment variable %pSE\n", |
812 | 0 | cfg->cfg.fd, cfg->envvar); |
813 | 0 | else if (cfg->cfg.fd == 1) |
814 | 0 | vty_out(vty, " logging to stdout\n"); |
815 | 0 | else if (cfg->cfg.fd == 2) |
816 | 0 | vty_out(vty, " logging to stderr\n"); |
817 | 0 | else |
818 | 0 | vty_out(vty, " logging to fd %d\n", |
819 | 0 | cfg->cfg.fd); |
820 | 0 | break; |
821 | | |
822 | 0 | case ZLOG_5424_DST_FILE: |
823 | 0 | case ZLOG_5424_DST_FIFO: |
824 | 0 | case ZLOG_5424_DST_UNIX: |
825 | 0 | vty_out(vty, " logging to %s: %pSE\n", |
826 | 0 | (cfg->cfg.dst == ZLOG_5424_DST_FIFO) ? "fifo" |
827 | 0 | : (cfg->cfg.dst == ZLOG_5424_DST_UNIX) |
828 | 0 | ? "unix socket" |
829 | 0 | : "file", |
830 | 0 | cfg->filename); |
831 | 0 | break; |
832 | 0 | } |
833 | | |
834 | 0 | vty_out(vty, " log level: %s, facility: %s\n", |
835 | 0 | zlog_priority_str(cfg->cfg.prio_min), |
836 | 0 | facility_name(cfg->cfg.facility)); |
837 | |
|
838 | 0 | bool any_meta = false, first = true; |
839 | |
|
840 | 0 | for (struct log_option *opt = log_opts; opt->name; opt++) { |
841 | 0 | bool *ptr = (bool *)(((char *)&cfg->cfg) + opt->offs); |
842 | |
|
843 | 0 | any_meta |= *ptr; |
844 | 0 | } |
845 | |
|
846 | 0 | if (!any_meta) |
847 | 0 | continue; |
848 | | |
849 | 0 | switch (cfg->cfg.fmt) { |
850 | 0 | case ZLOG_FMT_5424: |
851 | 0 | case ZLOG_FMT_JOURNALD: |
852 | 0 | vty_out(vty, " structured data: "); |
853 | |
|
854 | 0 | for (struct log_option *opt = log_opts; opt->name; |
855 | 0 | opt++) { |
856 | 0 | bool *ptr = (bool *)(((char *)&cfg->cfg) + |
857 | 0 | opt->offs); |
858 | |
|
859 | 0 | if (*ptr) { |
860 | 0 | vty_out(vty, "%s%s", first ? "" : ", ", |
861 | 0 | opt->name); |
862 | 0 | first = false; |
863 | 0 | } |
864 | 0 | } |
865 | 0 | break; |
866 | | |
867 | 0 | case ZLOG_FMT_3164: |
868 | 0 | case ZLOG_FMT_LOCAL: |
869 | 0 | vty_out(vty, |
870 | 0 | " structured data is not supported by the selected format\n"); |
871 | 0 | break; |
872 | 0 | } |
873 | | |
874 | 0 | vty_out(vty, "\n"); |
875 | |
|
876 | 0 | size_t lost_msgs; |
877 | 0 | int last_errno; |
878 | 0 | bool stale_errno; |
879 | 0 | struct timeval err_ts; |
880 | 0 | int64_t since; |
881 | |
|
882 | 0 | zlog_5424_state(&cfg->cfg, &lost_msgs, &last_errno, |
883 | 0 | &stale_errno, &err_ts); |
884 | 0 | vty_out(vty, " number of lost messages: %zu\n", lost_msgs); |
885 | |
|
886 | 0 | if (last_errno == 0) |
887 | 0 | since = 0; |
888 | 0 | else |
889 | 0 | since = monotime_since(&err_ts, NULL); |
890 | 0 | vty_out(vty, |
891 | 0 | " last error: %s (%lld.%06llds ago, currently %s)\n", |
892 | 0 | last_errno ? safe_strerror(last_errno) : "none", |
893 | 0 | since / 1000000LL, since % 1000000LL, |
894 | 0 | stale_errno ? "OK" : "erroring"); |
895 | 0 | } |
896 | 0 | return 0; |
897 | 0 | } |
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 | 0 | { |
911 | 0 | struct zlog_cfg_5424_user *cfg; |
912 | |
|
913 | 0 | frr_each (targets, &targets, cfg) |
914 | 0 | vector_set(comps, XSTRDUP(MTYPE_COMPLETION, cfg->name)); |
915 | 0 | } |
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 | 4 | { |
924 | 4 | hook_register(zlog_cli_show, log_5424_show); |
925 | | |
926 | 4 | cmd_variable_handler_register(log_5424_var_handlers); |
927 | | |
928 | | /* CLI commands. */ |
929 | 4 | install_node(&extlog_node); |
930 | 4 | install_default(EXTLOG_NODE); |
931 | | |
932 | 4 | install_element(CONFIG_NODE, &log_5424_target_cmd); |
933 | 4 | install_element(CONFIG_NODE, &no_log_5424_target_cmd); |
934 | | |
935 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_file_cmd); |
936 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_fifo_cmd); |
937 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_unix_cmd); |
938 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_journald_cmd); |
939 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_syslog_cmd); |
940 | 4 | install_element(EXTLOG_NODE, &log_5424_destination_fd_cmd); |
941 | | |
942 | 4 | install_element(EXTLOG_NODE, &log_5424_meta_cmd); |
943 | 4 | install_element(EXTLOG_NODE, &log_5424_prio_cmd); |
944 | 4 | install_element(EXTLOG_NODE, &log_5424_facility_cmd); |
945 | 4 | install_element(EXTLOG_NODE, &log_5424_ts_prec_cmd); |
946 | 4 | install_element(EXTLOG_NODE, &log_5424_ts_local_cmd); |
947 | 4 | } |
948 | | |
949 | | /* hooks */ |
950 | | |
951 | | static int log_5424_early_init(struct event_loop *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 | 8 | { |
957 | 8 | hook_register(frr_early_init, log_5424_early_init); |
958 | 8 | hook_register(zlog_rotate, log_5424_rotate); |
959 | 8 | hook_register(frr_fini, log_5424_fini); |
960 | 8 | } |
961 | | |
962 | | static int log_5424_early_init(struct event_loop *master) |
963 | 0 | { |
964 | 0 | log_5424_master = master; |
965 | |
|
966 | 0 | return 0; |
967 | 0 | } |
968 | | |
969 | | static int log_5424_rotate(void) |
970 | 0 | { |
971 | 0 | struct zlog_cfg_5424_user *cfg; |
972 | |
|
973 | 0 | frr_each (targets, &targets, cfg) |
974 | 0 | if (!zlog_5424_rotate(&cfg->cfg)) |
975 | 0 | zlog_err( |
976 | 0 | "log rotation on extended log target %s failed", |
977 | 0 | cfg->name); |
978 | |
|
979 | 0 | return 0; |
980 | 0 | } |
981 | | |
982 | | static int log_5424_fini(void) |
983 | 0 | { |
984 | 0 | struct zlog_cfg_5424_user *cfg; |
985 | |
|
986 | 0 | while ((cfg = targets_pop(&targets))) |
987 | 0 | log_5424_free(cfg, true); |
988 | |
|
989 | 0 | log_5424_master = NULL; |
990 | |
|
991 | 0 | return 0; |
992 | 0 | } |