/src/freeradius-server/src/lib/server/log.c
Line | Count | Source |
1 | | /* |
2 | | * This program is free software; you can redistribute it and/or modify |
3 | | * it under the terms of the GNU General Public License as published by |
4 | | * the Free Software Foundation; either version 2 of the License, or |
5 | | * (at your option) any later version. |
6 | | * |
7 | | * This program is distributed in the hope that it will be useful, |
8 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
9 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
10 | | * GNU General Public License for more details. |
11 | | * |
12 | | * You should have received a copy of the GNU General Public License |
13 | | * along with this program; if not, write to the Free Software |
14 | | * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA |
15 | | */ |
16 | | |
17 | | /** |
18 | | * $Id: 6c865dacd2164db9b6c9550a3a908f0651c342e0 $ |
19 | | * |
20 | | * @brief Logging functions used by the server core. |
21 | | * @file src/lib/server/log.c |
22 | | * |
23 | | * @copyright 2000,2006 The FreeRADIUS server project |
24 | | * @copyright 2000 Miquel van Smoorenburg (miquels@cistron.nl) |
25 | | * @copyright 2000 Alan DeKok (aland@freeradius.org) |
26 | | * @copyright 2001 Chad Miller (cmiller@surfsouth.com) |
27 | | */ |
28 | | RCSID("$Id: 6c865dacd2164db9b6c9550a3a908f0651c342e0 $") |
29 | | |
30 | | #include <freeradius-devel/util/debug.h> |
31 | | #include <freeradius-devel/util/dict.h> |
32 | | #include <freeradius-devel/util/log.h> |
33 | | #include <freeradius-devel/util/syserror.h> |
34 | | #include <freeradius-devel/util/file.h> |
35 | | |
36 | | #include <freeradius-devel/server/log.h> |
37 | | #include <freeradius-devel/server/pair.h> |
38 | | #include <freeradius-devel/server/util.h> |
39 | | |
40 | | #include <freeradius-devel/unlang/xlat.h> |
41 | | |
42 | | #ifdef HAVE_SYS_STAT_H |
43 | | # include <sys/stat.h> |
44 | | #endif |
45 | | |
46 | | #include <fcntl.h> |
47 | | |
48 | | #ifdef HAVE_SYSLOG_H |
49 | | # include <syslog.h> |
50 | | #endif |
51 | | |
52 | | #include <sys/file.h> |
53 | | #include <pthread.h> |
54 | | |
55 | | static _Thread_local TALLOC_CTX *fr_vlog_request_pool; |
56 | | static _Thread_local fr_sbuff_t *fr_log_request_oid_buff; |
57 | | |
58 | | /** Syslog facility table |
59 | | * |
60 | | * Maps syslog facility keywords, to the syslog facility macros defined |
61 | | * in the system's syslog.h. |
62 | | * |
63 | | * @note Not all facilities are supported by every operating system. |
64 | | * If a facility is unavailable it will not appear in the table. |
65 | | */ |
66 | | fr_table_num_sorted_t const syslog_facility_table[] = { |
67 | | #ifdef LOG_AUTH |
68 | | { L("auth"), LOG_AUTH }, |
69 | | #endif |
70 | | |
71 | | #ifdef LOG_AUTHPRIV |
72 | | { L("authpriv"), LOG_AUTHPRIV }, |
73 | | #endif |
74 | | |
75 | | #ifdef LOG_CRON |
76 | | { L("cron"), LOG_CRON }, |
77 | | #endif |
78 | | |
79 | | #ifdef LOG_DAEMON |
80 | | { L("daemon"), LOG_DAEMON }, |
81 | | #endif |
82 | | |
83 | | #ifdef LOG_FTP |
84 | | { L("ftp"), LOG_FTP }, |
85 | | #endif |
86 | | |
87 | | #ifdef LOG_KERN |
88 | | { L("kern"), LOG_KERN }, |
89 | | #endif |
90 | | |
91 | | #ifdef LOG_LOCAL0 |
92 | | { L("local0"), LOG_LOCAL0 }, |
93 | | #endif |
94 | | |
95 | | #ifdef LOG_LOCAL1 |
96 | | { L("local1"), LOG_LOCAL1 }, |
97 | | #endif |
98 | | |
99 | | #ifdef LOG_LOCAL2 |
100 | | { L("local2"), LOG_LOCAL2 }, |
101 | | #endif |
102 | | |
103 | | #ifdef LOG_LOCAL3 |
104 | | { L("local3"), LOG_LOCAL3 }, |
105 | | #endif |
106 | | |
107 | | #ifdef LOG_LOCAL4 |
108 | | { L("local4"), LOG_LOCAL4 }, |
109 | | #endif |
110 | | |
111 | | #ifdef LOG_LOCAL5 |
112 | | { L("local5"), LOG_LOCAL5 }, |
113 | | #endif |
114 | | |
115 | | #ifdef LOG_LOCAL6 |
116 | | { L("local6"), LOG_LOCAL6 }, |
117 | | #endif |
118 | | |
119 | | #ifdef LOG_LOCAL7 |
120 | | { L("local7"), LOG_LOCAL7 }, |
121 | | #endif |
122 | | |
123 | | #ifdef LOG_LPR |
124 | | { L("lpr"), LOG_LPR }, |
125 | | #endif |
126 | | |
127 | | #ifdef LOG_MAIL |
128 | | { L("mail"), LOG_MAIL }, |
129 | | #endif |
130 | | |
131 | | #ifdef LOG_NEWS |
132 | | { L("news"), LOG_NEWS }, |
133 | | #endif |
134 | | |
135 | | #ifdef LOG_USER |
136 | | { L("user"), LOG_USER }, |
137 | | #endif |
138 | | |
139 | | #ifdef LOG_UUCP |
140 | | { L("uucp"), LOG_UUCP } |
141 | | #endif |
142 | | }; |
143 | | size_t syslog_facility_table_len = NUM_ELEMENTS(syslog_facility_table); |
144 | | |
145 | | /** Syslog severity table |
146 | | * |
147 | | * Maps syslog severity keywords, to the syslog severity macros defined |
148 | | * in the system's syslog.h file. |
149 | | * |
150 | | */ |
151 | | fr_table_num_sorted_t const syslog_severity_table[] = { |
152 | | #ifdef LOG_ALERT |
153 | | { L("alert"), LOG_ALERT }, |
154 | | #endif |
155 | | |
156 | | #ifdef LOG_CRIT |
157 | | { L("critical"), LOG_CRIT }, |
158 | | #endif |
159 | | |
160 | | #ifdef LOG_DEBUG |
161 | | { L("debug"), LOG_DEBUG }, |
162 | | #endif |
163 | | |
164 | | #ifdef LOG_EMERG |
165 | | { L("emergency"), LOG_EMERG }, |
166 | | #endif |
167 | | |
168 | | #ifdef LOG_ERR |
169 | | { L("error"), LOG_ERR }, |
170 | | #endif |
171 | | |
172 | | #ifdef LOG_INFO |
173 | | { L("info"), LOG_INFO }, |
174 | | #endif |
175 | | |
176 | | #ifdef LOG_NOTICE |
177 | | { L("notice"), LOG_NOTICE }, |
178 | | #endif |
179 | | |
180 | | #ifdef LOG_WARNING |
181 | | { L("warning"), LOG_WARNING }, |
182 | | #endif |
183 | | }; |
184 | | size_t syslog_severity_table_len = NUM_ELEMENTS(syslog_severity_table); |
185 | | |
186 | | fr_table_num_sorted_t const log_destination_table[] = { |
187 | | { L("file"), L_DST_FILES }, |
188 | | { L("files"), L_DST_FILES }, |
189 | | { L("null"), L_DST_NULL }, |
190 | | { L("stderr"), L_DST_STDERR }, |
191 | | { L("stdout"), L_DST_STDOUT }, |
192 | | { L("syslog"), L_DST_SYSLOG }, |
193 | | }; |
194 | | size_t log_destination_table_len = NUM_ELEMENTS(log_destination_table); |
195 | | |
196 | | static char const spaces[] = " "; |
197 | | |
198 | | static fr_dict_t const *dict_freeradius; |
199 | | |
200 | | extern fr_dict_autoload_t log_dict[]; |
201 | | fr_dict_autoload_t log_dict[] = { |
202 | | { .out = &dict_freeradius, .proto = "freeradius" }, |
203 | | DICT_AUTOLOAD_TERMINATOR |
204 | | }; |
205 | | |
206 | | static fr_dict_attr_t const *attr_module_failure_message; |
207 | | |
208 | | extern fr_dict_attr_autoload_t log_dict_attr[]; |
209 | | fr_dict_attr_autoload_t log_dict_attr[] = { |
210 | | { .out = &attr_module_failure_message, .name = "Module-Failure-Message", .type = FR_TYPE_STRING, .dict = &dict_freeradius }, |
211 | | DICT_AUTOLOAD_TERMINATOR |
212 | | }; |
213 | | |
214 | | typedef struct { |
215 | | char const *name; //!< name of this logging destination |
216 | | fr_log_t *log; //!< pointer to the log structure |
217 | | CONF_SECTION *cs; //!< where this log configuration came from |
218 | | |
219 | | fr_rb_node_t name_node; //!< tree by name |
220 | | fr_rb_node_t filename_node; //!< tree by name |
221 | | } fr_log_track_t; |
222 | | |
223 | | typedef struct { |
224 | | char const *name; //!< name of this logging source |
225 | | uint32_t id; //!< LOG_ID of this source |
226 | | fr_log_t *original; //!< the original fr_log_t |
227 | | fr_log_t **log; //!< where the logs should go |
228 | | |
229 | | fr_rb_node_t name_node; //!< tree by name only |
230 | | fr_rb_node_t id_node; //!< tree by ID |
231 | | } fr_log_src_t; |
232 | | |
233 | | static fr_rb_tree_t *dst_tree = NULL; |
234 | | static fr_rb_tree_t *filename_tree = NULL; |
235 | | static fr_rb_tree_t *src_tree = NULL; |
236 | | |
237 | | /** Send a server log message to its destination without evaluating its debug level |
238 | | * |
239 | | * @param[in] log destination. |
240 | | * @param[in] type of log message. |
241 | | * @param[in] file src file the log message was generated in. |
242 | | * @param[in] line number the log message was generated on. |
243 | | * @param[in] fmt with printf style substitution tokens. |
244 | | * @param[in] ... Substitution arguments. |
245 | | */ |
246 | | |
247 | | static CC_HINT(format (printf, 5, 6)) |
248 | | void log_always(fr_log_t const *log, fr_log_type_t type, |
249 | | char const *file, int line, |
250 | | char const *fmt, ...) |
251 | 0 | { |
252 | 0 | va_list ap; |
253 | |
|
254 | 0 | va_start(ap, fmt); |
255 | 0 | fr_vlog(log, type, file, line, fmt, ap); |
256 | 0 | va_end(ap); |
257 | 0 | } |
258 | | |
259 | | /** Whether a request specific debug message should be logged |
260 | | * |
261 | | * @param lvl of debugging this message should be logged at. |
262 | | * @param request The current request. |
263 | | * @return |
264 | | * - true if message should be logged. |
265 | | * - false if message shouldn't be logged. |
266 | | */ |
267 | | inline bool log_rdebug_enabled(fr_log_lvl_t lvl, request_t const *request) |
268 | 0 | { |
269 | 0 | if (!request->log.dst) return false; |
270 | | |
271 | 0 | return (request->log.lvl >= lvl); |
272 | 0 | } |
273 | | |
274 | | /** Cleanup the memory pool used by vlog_request |
275 | | * |
276 | | */ |
277 | | static int _fr_vlog_request_pool_free(void *arg) |
278 | 0 | { |
279 | 0 | return talloc_free(arg); |
280 | 0 | } |
281 | | |
282 | | /** Send a log message to its destination, possibly including fields from the request |
283 | | * |
284 | | * @param[in] type of log message, #L_ERR, #L_WARN, #L_INFO, #L_DBG. |
285 | | * @param[in] lvl Minimum required server or request level to output this message. |
286 | | * @param[in] request The current request. |
287 | | * @param[in] file src file the log message was generated in. |
288 | | * @param[in] line number the log message was generated on. |
289 | | * @param[in] fmt with printf style substitution tokens. |
290 | | * @param[in] ap Substitution arguments. |
291 | | * @param[in] uctx The #fr_log_t specifying the destination for log messages. |
292 | | */ |
293 | | void vlog_request(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
294 | | char const *file, int line, |
295 | | char const *fmt, va_list ap, void *uctx) |
296 | 0 | { |
297 | 0 | char const *filename; |
298 | 0 | FILE *fp = NULL; |
299 | |
|
300 | 0 | char *p; |
301 | 0 | char const *extra = ""; |
302 | 0 | uint8_t unlang_indent; |
303 | 0 | va_list aq; |
304 | |
|
305 | 0 | char const *fmt_location = ""; |
306 | 0 | char const *fmt_prefix = ""; |
307 | 0 | char const *fmt_module = ""; |
308 | 0 | char const *fmt_exp; |
309 | |
|
310 | 0 | fr_log_t *log_dst = uctx; |
311 | 0 | TALLOC_CTX *pool; |
312 | | |
313 | | /* |
314 | | * No output means no output. |
315 | | */ |
316 | 0 | if (!log_dst) return; |
317 | 0 | if (!log_rdebug_enabled(lvl, request)) return; |
318 | | |
319 | | /* |
320 | | * Allocate a thread local, 4k pool so we don't |
321 | | * need to keep allocating memory on the heap. |
322 | | */ |
323 | 0 | pool = fr_vlog_request_pool; |
324 | 0 | if (!pool) { |
325 | 0 | pool = talloc_pool(NULL, 4096); |
326 | 0 | if (!pool) { |
327 | 0 | fr_perror("Failed allocating memory for vlog_request_pool"); |
328 | 0 | return; |
329 | 0 | } |
330 | 0 | fr_atexit_thread_local(fr_vlog_request_pool, _fr_vlog_request_pool_free, pool); |
331 | 0 | } |
332 | | |
333 | 0 | filename = log_dst->file; |
334 | | |
335 | | /* |
336 | | * Debug messages get treated specially. |
337 | | */ |
338 | 0 | if ((type & L_DBG) != 0) { |
339 | | /* |
340 | | * If we're debugging to a file, then use that. |
341 | | * |
342 | | * @todo: have fr_vlog() take a fr_log_t*, so |
343 | | * that we can cache the opened descriptor, and |
344 | | * we don't need to re-open it on every log |
345 | | * message. |
346 | | */ |
347 | 0 | switch (log_dst->dst) { |
348 | 0 | case L_DST_FILES: |
349 | 0 | fp = fopen(log_dst->file, "a"); |
350 | 0 | if (!fp) goto finish; |
351 | 0 | break; |
352 | | |
353 | 0 | #if defined(HAVE_FOPENCOOKIE) || defined (HAVE_FUNOPEN) |
354 | 0 | case L_DST_FUNC: |
355 | 0 | { |
356 | 0 | # ifdef HAVE_FOPENCOOKIE |
357 | 0 | cookie_io_functions_t io; |
358 | | |
359 | | /* |
360 | | * These must be set separately as they have different prototypes. |
361 | | */ |
362 | 0 | io.read = NULL; |
363 | 0 | io.seek = NULL; |
364 | 0 | io.close = NULL; |
365 | 0 | io.write = log_dst->cookie_write; |
366 | |
|
367 | 0 | fp = fopencookie(log_dst->cookie, "w", io); |
368 | | # else |
369 | | fp = funopen(log_dst->cookie, NULL, log_dst->cookie_write, NULL, NULL); |
370 | | |
371 | | # endif |
372 | 0 | if (!fp) goto finish; |
373 | 0 | } |
374 | 0 | break; |
375 | 0 | #endif |
376 | 0 | default: |
377 | 0 | break; |
378 | 0 | } |
379 | 0 | goto print_fmt; |
380 | 0 | } |
381 | | |
382 | 0 | if (filename) { |
383 | 0 | char *exp; |
384 | 0 | log_dst_t *dst; |
385 | |
|
386 | 0 | dst = request->log.dst; |
387 | | |
388 | | /* |
389 | | * Prevent infinitely recursive calls if |
390 | | * xlat_aeval attempts to write to the request log. |
391 | | */ |
392 | 0 | request->log.dst = NULL; |
393 | | |
394 | | /* |
395 | | * This is SLOW! Doing it for every log message |
396 | | * in every request is NOT recommended! |
397 | | */ |
398 | 0 | if (xlat_aeval(request, &exp, request, filename, rad_filename_escape, NULL) < 0) return; |
399 | | |
400 | | /* |
401 | | * Restore the original logging function |
402 | | */ |
403 | 0 | request->log.dst = dst; |
404 | | |
405 | | /* |
406 | | * Ensure the directory structure exists, for |
407 | | * where we're going to write the log file. |
408 | | */ |
409 | 0 | p = strrchr(exp, FR_DIR_SEP); |
410 | 0 | if (p) { |
411 | 0 | *p = '\0'; |
412 | 0 | if (fr_mkdir(NULL, exp, -1, S_IRWXU, NULL, NULL) < 0) { |
413 | 0 | ERROR("Failed creating %s: %s", exp, fr_syserror(errno)); |
414 | 0 | talloc_free(exp); |
415 | 0 | return; |
416 | 0 | } |
417 | 0 | *p = FR_DIR_SEP; |
418 | 0 | } |
419 | | |
420 | 0 | fp = fopen(exp, "a"); |
421 | 0 | talloc_free(exp); |
422 | 0 | } |
423 | | |
424 | 0 | print_fmt: |
425 | | /* |
426 | | * Request prefix i.e. |
427 | | * |
428 | | * (0) <fmt> |
429 | | */ |
430 | 0 | if (request->name) { |
431 | 0 | if ((request->seq_start == 0) || (request->number == request->seq_start)) { |
432 | 0 | fmt_prefix = talloc_typed_asprintf(pool, "(%s) ", request->name); |
433 | 0 | } else { |
434 | 0 | fmt_prefix = talloc_typed_asprintf(pool, "(%s,%" PRIu64 ") ", |
435 | 0 | request->name, request->seq_start); |
436 | 0 | } |
437 | 0 | } |
438 | | |
439 | | /* |
440 | | * Make sure the indent isn't set to something crazy |
441 | | */ |
442 | 0 | unlang_indent = request->log.indent.unlang > sizeof(spaces) - 1 ? |
443 | 0 | sizeof(spaces) - 1 : |
444 | 0 | request->log.indent.unlang; |
445 | | |
446 | | /* |
447 | | * Module name and indentation i.e. |
448 | | * |
449 | | * test - <fmt> |
450 | | */ |
451 | 0 | if (request->module) { |
452 | 0 | uint8_t module_indent = request->log.indent.module > sizeof(spaces) - 1 ? |
453 | 0 | sizeof(spaces) - 1 : |
454 | 0 | request->log.indent.module; |
455 | 0 | fmt_module = talloc_typed_asprintf(pool, "%s - %.*s", request->module, module_indent, spaces); |
456 | 0 | } |
457 | | |
458 | | /* |
459 | | * If we don't copy the original ap we get a segfault from vasprintf. This is apparently |
460 | | * due to ap sometimes being implemented with a stack offset which is invalidated if |
461 | | * ap is passed into another function. See here: |
462 | | * http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html |
463 | | * |
464 | | * I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when |
465 | | * running unit tests which generate errors under CI. |
466 | | */ |
467 | 0 | va_copy(aq, ap); |
468 | 0 | if (!log_dst->suppress_secrets) { |
469 | 0 | fmt_exp = fr_vasprintf(pool, fmt, aq); |
470 | 0 | } else { |
471 | 0 | fmt_exp = fr_vasprintf_secure(pool, fmt, aq); |
472 | 0 | } |
473 | 0 | va_end(aq); |
474 | | |
475 | | /* |
476 | | * Logging to a file descriptor |
477 | | */ |
478 | 0 | if (fp) { |
479 | 0 | char time_buff[64]; /* The current timestamp */ |
480 | |
|
481 | 0 | time_t timeval; |
482 | 0 | timeval = time(NULL); |
483 | |
|
484 | | #if 0 |
485 | | fmt_location = talloc_typed_asprintf(pool, "%s[%i]: ", file, line); |
486 | | #endif |
487 | |
|
488 | 0 | #ifdef HAVE_GMTIME_R |
489 | 0 | if (log_dates_utc) { |
490 | 0 | struct tm utc; |
491 | 0 | gmtime_r(&timeval, &utc); |
492 | 0 | ASCTIME_R(&utc, time_buff, sizeof(time_buff)); |
493 | 0 | } else |
494 | 0 | #endif |
495 | 0 | { |
496 | 0 | CTIME_R(&timeval, time_buff, sizeof(time_buff)); |
497 | 0 | } |
498 | | |
499 | | /* |
500 | | * Strip trailing new lines |
501 | | */ |
502 | 0 | p = strrchr(time_buff, '\n'); |
503 | 0 | if (p) p[0] = '\0'; |
504 | |
|
505 | 0 | fprintf(fp, |
506 | 0 | "%s" /* location */ |
507 | 0 | "%s" /* prefix */ |
508 | 0 | "%s : " /* time */ |
509 | 0 | "%s" /* facility */ |
510 | 0 | "%.*s" /* indent */ |
511 | 0 | "%s" /* module */ |
512 | 0 | "%s" /* message */ |
513 | 0 | "\n", |
514 | 0 | fmt_location, |
515 | 0 | fmt_prefix, |
516 | 0 | time_buff, |
517 | 0 | fr_table_str_by_value(fr_log_levels, type, ""), |
518 | 0 | unlang_indent, spaces, |
519 | 0 | fmt_module, |
520 | 0 | fmt_exp); |
521 | 0 | fclose(fp); |
522 | 0 | goto finish; |
523 | 0 | } |
524 | | |
525 | | /* |
526 | | * Logging everywhere else |
527 | | * |
528 | | * Add the WARN / ERROR message after the request number, so it's clearer to the user that the |
529 | | * message is associated with the request. Set the type too, which lets fr_vlog() know that it |
530 | | * shouldn't add the prefix. |
531 | | */ |
532 | 0 | switch (type) { |
533 | 0 | case L_DBG_WARN: |
534 | 0 | extra = "WARNING: "; |
535 | 0 | type = L_DBG_WARN_REQ; |
536 | 0 | break; |
537 | | |
538 | 0 | case L_DBG_ERR: |
539 | 0 | extra = "ERROR: "; |
540 | 0 | type = L_DBG_ERR_REQ; |
541 | 0 | break; |
542 | 0 | default: |
543 | 0 | break; |
544 | 0 | } |
545 | | |
546 | 0 | log_always(log_dst, type, file, line, |
547 | 0 | "%s" /* prefix */ |
548 | 0 | "%.*s" /* indent */ |
549 | 0 | "%s" /* module */ |
550 | 0 | "%s" /* extra */ |
551 | 0 | "%s", /* message */ |
552 | 0 | fmt_prefix, |
553 | 0 | unlang_indent, spaces, |
554 | 0 | fmt_module, |
555 | 0 | extra, |
556 | 0 | fmt_exp); |
557 | |
|
558 | 0 | finish: |
559 | 0 | talloc_free_children(pool); |
560 | 0 | } |
561 | | |
562 | | /** Add a module failure message fr_pair_t to the request |
563 | | * |
564 | | * @param[in] request The current request. |
565 | | * @param[in] fmt with printf style substitution tokens. |
566 | | * @param[in] ap Substitution arguments. |
567 | | */ |
568 | | static void vlog_module_failure_msg(request_t *request, char const *fmt, va_list ap) |
569 | 0 | { |
570 | 0 | char *p; |
571 | 0 | fr_pair_t *vp; |
572 | 0 | va_list aq; |
573 | |
|
574 | 0 | if (!fmt || !request || !request->packet) return; |
575 | | |
576 | 0 | fr_assert(attr_module_failure_message); |
577 | |
|
578 | 0 | MEM(pair_prepend_request(&vp, attr_module_failure_message) >= 0); |
579 | | |
580 | | /* |
581 | | * If we don't copy the original ap we get a segfault from vasprintf. This is apparently |
582 | | * due to ap sometimes being implemented with a stack offset which is invalidated if |
583 | | * ap is passed into another function. See here: |
584 | | * http://julipedia.meroh.net/2011/09/using-vacopy-to-safely-pass-ap.html |
585 | | * |
586 | | * I don't buy that explanation, but doing a va_copy here does prevent SEGVs seen when |
587 | | * running unit tests which generate errors under CI. |
588 | | */ |
589 | 0 | va_copy(aq, ap); |
590 | 0 | p = fr_vasprintf(vp, fmt, aq); |
591 | 0 | va_end(aq); |
592 | |
|
593 | 0 | if (request->module && (request->module[0] != '\0')) { |
594 | 0 | fr_pair_value_aprintf(vp, "%s: %s", request->module, p); |
595 | 0 | talloc_free(p); |
596 | 0 | } else { |
597 | 0 | fr_pair_value_strdup_shallow(vp, p, true); |
598 | 0 | } |
599 | 0 | } |
600 | | |
601 | | /** Marshal variadic log arguments into a va_list and pass to normal logging functions |
602 | | * |
603 | | * @see log_request_error for more details. |
604 | | * |
605 | | * @param[in] type the log category. |
606 | | * @param[in] lvl of debugging this message should be logged at. |
607 | | * @param[in] request The current request. |
608 | | * @param[in] file src file the log message was generated in. |
609 | | * @param[in] line number the log message was generated on. |
610 | | * @param[in] fmt with printf style substitution tokens. |
611 | | * @param[in] ... Substitution arguments. |
612 | | */ |
613 | | void log_request(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
614 | | char const *file, int line, char const *fmt, ...) |
615 | 0 | { |
616 | 0 | va_list ap; |
617 | |
|
618 | 0 | if (!request->log.dst) return; |
619 | | |
620 | 0 | va_start(ap, fmt); |
621 | 0 | log_dst_foreach(request->log.dst, dst) { |
622 | 0 | if ((lvl > request->log.lvl) && (lvl > dst->lvl)) continue; |
623 | | |
624 | 0 | dst->func(type, lvl, request, file, line, fmt, ap, dst->uctx); |
625 | 0 | } |
626 | 0 | va_end(ap); |
627 | 0 | } |
628 | | |
629 | | /** Marshal variadic log arguments into a va_list and pass to error logging functions |
630 | | * |
631 | | * This could all be done in a macro, but it turns out some implementations of the |
632 | | * variadic macros do not work at all well if the va_list being written to is further |
633 | | * up the stack (which is required as you still need a function to convert the ellipsis |
634 | | * into a va_list). |
635 | | * |
636 | | * So, we use this small wrapper function instead, which will hopefully guarantee |
637 | | * consistent behaviour. |
638 | | * |
639 | | * @param[in] type the log category. |
640 | | * @param[in] lvl of debugging this message should be logged at. |
641 | | * @param[in] request The current request. |
642 | | * @param[in] file src file the log message was generated in. |
643 | | * @param[in] line number the log message was generated on. |
644 | | * @param[in] fmt with printf style substitution tokens. |
645 | | * @param[in] ... Substitution arguments. |
646 | | */ |
647 | | void log_request_error(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
648 | | char const *file, int line, char const *fmt, ...) |
649 | 0 | { |
650 | 0 | va_list ap; |
651 | |
|
652 | 0 | if (!request->log.dst) return; |
653 | | |
654 | 0 | va_start(ap, fmt); |
655 | 0 | log_dst_foreach(request->log.dst, dst) { |
656 | 0 | va_list copy; |
657 | 0 | va_copy(copy, ap); |
658 | |
|
659 | 0 | dst->func(type, lvl, request, file, line, fmt, copy, dst->uctx); |
660 | |
|
661 | 0 | va_end(copy); |
662 | 0 | } |
663 | |
|
664 | 0 | if ((type == L_ERR) || (type == L_DBG_ERR) || (type == L_DBG_ERR_REQ)) { |
665 | 0 | vlog_module_failure_msg(request, fmt, ap); |
666 | 0 | } |
667 | |
|
668 | 0 | va_end(ap); |
669 | 0 | } |
670 | | |
671 | | /** Drain any outstanding messages from the fr_strerror buffers |
672 | | * |
673 | | * This function drains any messages from fr_strerror buffer adding a prefix (fmt) |
674 | | * to the first message. |
675 | | * |
676 | | * @param[in] type the log category. |
677 | | * @param[in] lvl of debugging this message should be logged at. |
678 | | * @param[in] request The current request. |
679 | | * @param[in] file src file the log message was generated in. |
680 | | * @param[in] line number the log message was generated on. |
681 | | * @param[in] fmt with printf style substitution tokens. |
682 | | * @param[in] ... Substitution arguments. |
683 | | */ |
684 | | void log_request_perror(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
685 | | char const *file, int line, char const *fmt, ...) |
686 | 0 | { |
687 | 0 | char const *error; |
688 | |
|
689 | 0 | if (!request->log.dst) return; |
690 | | |
691 | | /* |
692 | | * No strerror gets us identical behaviour to log_request_error |
693 | | */ |
694 | 0 | error = fr_strerror_pop(); |
695 | 0 | if (!error) { |
696 | 0 | va_list ap; |
697 | |
|
698 | 0 | if (!fmt) return; /* NOOP */ |
699 | | |
700 | 0 | va_start(ap, fmt); |
701 | 0 | log_dst_foreach(request->log.dst, dst) { |
702 | 0 | va_list copy; |
703 | 0 | va_copy(copy, ap); |
704 | |
|
705 | 0 | dst->func(type, lvl, request, file, line, fmt, copy, dst->uctx); |
706 | |
|
707 | 0 | va_end(copy); |
708 | 0 | } |
709 | 0 | if ((type == L_ERR) || (type == L_DBG_ERR) || (type == L_DBG_ERR_REQ)) { |
710 | 0 | vlog_module_failure_msg(request, fmt, ap); |
711 | 0 | } |
712 | 0 | va_end(ap); |
713 | |
|
714 | 0 | return; /* DONE */ |
715 | 0 | } |
716 | | |
717 | | /* |
718 | | * Concatenate fmt with fr_strerror() |
719 | | */ |
720 | 0 | if (fmt) { |
721 | 0 | va_list ap; |
722 | 0 | char *tmp; |
723 | |
|
724 | 0 | va_start(ap, fmt); |
725 | 0 | tmp = fr_vasprintf(request, fmt, ap); |
726 | 0 | va_end(ap); |
727 | |
|
728 | 0 | if (!tmp) return; |
729 | | |
730 | 0 | log_request_error(type, lvl, request, file, line, "%s: %s", tmp, error); |
731 | 0 | talloc_free(tmp); |
732 | 0 | } else { |
733 | 0 | log_request_error(type, lvl, request, file, line, "%s", error); |
734 | 0 | } |
735 | | |
736 | | /* |
737 | | * Only the first message gets the prefix |
738 | | */ |
739 | 0 | while ((error = fr_strerror_pop())) log_request_error(type, lvl, request, file, line, "%s", error); |
740 | 0 | } |
741 | | |
742 | | /** Cleanup the memory pool used by the OID sbuff |
743 | | * |
744 | | */ |
745 | | static int _fr_log_request_oid_buff_free(void *arg) |
746 | 0 | { |
747 | 0 | return talloc_free(arg); |
748 | 0 | } |
749 | | |
750 | | /** Allocate an extensible sbuff for printing OID strings |
751 | | * |
752 | | */ |
753 | | static inline CC_HINT(always_inline) fr_sbuff_t *log_request_oid_buff(void) |
754 | 0 | { |
755 | 0 | fr_sbuff_t *sbuff; |
756 | 0 | fr_sbuff_uctx_talloc_t *tctx; |
757 | |
|
758 | 0 | sbuff = fr_log_request_oid_buff; |
759 | 0 | if (unlikely(!sbuff)) { |
760 | 0 | sbuff = talloc(NULL, fr_sbuff_t); |
761 | 0 | if (!sbuff) { |
762 | 0 | fr_perror("Failed allocating memory for fr_log_request_oid_buff"); |
763 | 0 | return NULL; |
764 | 0 | } |
765 | 0 | tctx = talloc(sbuff, fr_sbuff_uctx_talloc_t); |
766 | 0 | if (!tctx) { |
767 | 0 | fr_perror("Failed allocating memory for fr_sbuff_uctx_talloc_t"); |
768 | 0 | talloc_free(sbuff); |
769 | 0 | return NULL; |
770 | 0 | } |
771 | | |
772 | 0 | fr_sbuff_init_talloc(sbuff, sbuff, tctx, 1024, (FR_DICT_ATTR_MAX_NAME_LEN + 1) * FR_DICT_MAX_TLV_STACK); |
773 | |
|
774 | 0 | fr_atexit_thread_local(fr_log_request_oid_buff, _fr_log_request_oid_buff_free, sbuff); |
775 | 0 | } else { |
776 | 0 | fr_sbuff_set(sbuff, fr_sbuff_start(sbuff)); /* Reset position */ |
777 | 0 | } |
778 | | |
779 | 0 | return sbuff; |
780 | 0 | } |
781 | | |
782 | | /** Print a #fr_pair_t. |
783 | | * |
784 | | * @param[in] lvl Debug lvl (1-4). |
785 | | * @param[in] request to read logging params from. |
786 | | * @param[in] parent of pair to print, may be NULL. |
787 | | * @param[in] vp to print. |
788 | | * @param[in] prefix (optional). |
789 | | */ |
790 | | void log_request_pair(fr_log_lvl_t lvl, request_t *request, |
791 | | fr_pair_t const *parent, fr_pair_t const *vp, char const *prefix) |
792 | 0 | { |
793 | 0 | fr_sbuff_t *oid_buff; |
794 | |
|
795 | 0 | if (!request->log.dst) return; |
796 | | |
797 | 0 | if (!log_rdebug_enabled(lvl, request)) return; |
798 | | |
799 | 0 | PAIR_VERIFY(vp); |
800 | |
|
801 | 0 | oid_buff = log_request_oid_buff(); |
802 | |
|
803 | 0 | if (fr_pair_print_name(oid_buff, parent ? parent->da : NULL, &vp) <= 0) return; |
804 | | |
805 | | /* |
806 | | * Recursively print grouped attributes. |
807 | | */ |
808 | 0 | switch (vp->vp_type) { |
809 | 0 | case FR_TYPE_STRUCTURAL: |
810 | 0 | RDEBUGX(lvl, "%s%s{", prefix ? prefix : "", fr_sbuff_start(oid_buff)); |
811 | 0 | log_request_pair_list(lvl, request, vp, &vp->vp_group, NULL); |
812 | 0 | RDEBUGX(lvl, "}"); |
813 | 0 | break; |
814 | | |
815 | 0 | default: |
816 | 0 | fr_assert(fr_type_is_leaf(vp->vp_type)); |
817 | |
|
818 | 0 | RDEBUGX(lvl, "%s%s%pV", prefix ? prefix : "", fr_sbuff_start(oid_buff), &vp->data); |
819 | 0 | break; |
820 | 0 | } |
821 | 0 | } |
822 | | |
823 | | /** Print a #fr_pair_list_t |
824 | | * |
825 | | * @param[in] lvl Debug lvl (1-4). |
826 | | * @param[in] request to read logging params from. |
827 | | * @param[in] parent of vps to print, may be NULL. |
828 | | * @param[in] vps to print. |
829 | | * @param[in] prefix (optional). |
830 | | */ |
831 | | void log_request_pair_list(fr_log_lvl_t lvl, request_t *request, |
832 | | fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix) |
833 | 0 | { |
834 | 0 | if (fr_pair_list_empty(vps) || !request->log.dst) return; |
835 | | |
836 | 0 | if (!log_rdebug_enabled(lvl, request)) return; |
837 | | |
838 | 0 | RINDENT(); |
839 | 0 | fr_pair_list_foreach(vps, vp) { |
840 | 0 | PAIR_VERIFY(vp); |
841 | |
|
842 | 0 | log_request_pair(lvl, request, parent, vp, prefix); |
843 | 0 | } |
844 | 0 | REXDENT(); |
845 | 0 | } |
846 | | |
847 | | /** Print a packet which we received or sent |
848 | | * |
849 | | * @param[in] request to read logging params from. |
850 | | * @param[in] packet the packet in question |
851 | | * @param[in] list to print. |
852 | | * @param[in] type_da of Packet-Type |
853 | | * @param[in] received is this packet being received or being sent |
854 | | * @param[in] id do we log IDs, too |
855 | | */ |
856 | | void log_request_packet(request_t *request, fr_packet_t *packet, fr_pair_list_t *list, fr_dict_attr_t const *type_da, bool received, bool id) |
857 | 0 | { |
858 | 0 | ssize_t slen; |
859 | 0 | fr_dict_enum_value_t const *enumv; |
860 | 0 | char const *name; |
861 | 0 | char name_buffer[32]; |
862 | 0 | char id_buffer[32]; |
863 | 0 | char buffer[256]; |
864 | |
|
865 | 0 | if (!RDEBUG_ENABLED) return; |
866 | | |
867 | 0 | slen = fr_socket_to_str(buffer, sizeof(buffer), &packet->socket, received); |
868 | 0 | if (slen <= 0) buffer[0] = '\0'; |
869 | |
|
870 | 0 | if (id) { |
871 | 0 | (void) snprintf(id_buffer, sizeof(id_buffer), " ID %u", (unsigned int)packet->id); |
872 | 0 | } else { |
873 | 0 | id_buffer[0] = '\0'; |
874 | 0 | } |
875 | |
|
876 | 0 | enumv = fr_dict_enum_by_value(type_da, fr_box_uint32((uint32_t) packet->code)); |
877 | 0 | if (enumv) { |
878 | 0 | name = enumv->name; |
879 | 0 | } else { |
880 | 0 | (void) snprintf(name_buffer, sizeof(name_buffer), "%u", packet->code); |
881 | 0 | name = name_buffer; |
882 | 0 | } |
883 | |
|
884 | 0 | log_request(L_DBG, L_DBG_LVL_1, request, __FILE__, __LINE__, "%s %s%s %s", |
885 | 0 | received ? "Received" : "Sending", |
886 | 0 | name, |
887 | 0 | id_buffer, |
888 | 0 | buffer); |
889 | |
|
890 | 0 | if (received || request->parent) { |
891 | 0 | log_request_pair_list(L_DBG_LVL_1, request, NULL, list, NULL); |
892 | 0 | } else { |
893 | 0 | log_request_proto_pair_list(L_DBG_LVL_1, request, NULL, list, NULL); |
894 | 0 | } |
895 | 0 | } |
896 | | |
897 | | /** Print a list of protocol fr_pair_ts. |
898 | | * |
899 | | * @param[in] lvl Debug lvl (1-4). |
900 | | * @param[in] request to read logging params from. |
901 | | * @param[in] parent of vps to print, may be NULL. |
902 | | * @param[in] vps to print. |
903 | | * @param[in] prefix (optional). |
904 | | */ |
905 | | void log_request_proto_pair_list(fr_log_lvl_t lvl, request_t *request, |
906 | | fr_pair_t const *parent, fr_pair_list_t const *vps, char const *prefix) |
907 | 0 | { |
908 | 0 | if (fr_pair_list_empty(vps) || !request->log.dst) return; |
909 | | |
910 | 0 | if (!log_rdebug_enabled(lvl, request)) return; |
911 | | |
912 | 0 | RINDENT(); |
913 | 0 | fr_pair_list_foreach(vps, vp) { |
914 | 0 | PAIR_VERIFY(vp); |
915 | |
|
916 | 0 | if (!fr_dict_attr_common_parent(fr_dict_root(request->proto_dict), vp->da, true)) continue; |
917 | | |
918 | 0 | log_request_pair(lvl, request, parent, vp, prefix); |
919 | 0 | } |
920 | 0 | REXDENT(); |
921 | 0 | } |
922 | | |
923 | | /** Write the string being parsed, and a marker showing where the parse error occurred |
924 | | * |
925 | | * @param[in] type the log category. |
926 | | * @param[in] lvl of debugging this message should be logged at. |
927 | | * @param[in] request The current request. |
928 | | * @param[in] file src file the log message was generated in. |
929 | | * @param[in] line number the log message was generated on. |
930 | | * @param[in] str Subject string we're printing a marker for. |
931 | | * @param[in] str_len Subject string length. Use SIZE_MAX for the |
932 | | * length of the string. |
933 | | * @param[in] marker_idx The position of the marker relative to the string. |
934 | | * @param[in] marker_fmt What the parse error was. |
935 | | * @param[in] ... Arguments for fmt string. |
936 | | */ |
937 | | void log_request_marker(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
938 | | char const *file, int line, |
939 | | char const *str, size_t str_len, |
940 | | ssize_t marker_idx, char const *marker_fmt, ...) |
941 | 0 | { |
942 | 0 | char const *ellipses = ""; |
943 | 0 | rindent_t indent; |
944 | 0 | va_list ap; |
945 | 0 | char *error; |
946 | 0 | static char const marker_spaces[] = " "; /* 60 */ |
947 | |
|
948 | 0 | if (str_len == SIZE_MAX) str_len = strlen(str); |
949 | |
|
950 | 0 | if (marker_idx < 0) marker_idx = (marker_idx == SSIZE_MIN) ? SSIZE_MAX : -marker_idx; |
951 | |
|
952 | 0 | if ((size_t) marker_idx >= sizeof(marker_spaces)) { |
953 | 0 | size_t offset = (marker_idx - (sizeof(marker_spaces) - 1)) + ((sizeof(marker_spaces) * 3) / 4); |
954 | |
|
955 | 0 | marker_idx -= offset; |
956 | |
|
957 | 0 | if (offset >= str_len) { |
958 | 0 | str += str_len; |
959 | 0 | str_len = 0; |
960 | 0 | } else { |
961 | 0 | str += offset; |
962 | 0 | str_len -= offset; |
963 | 0 | } |
964 | |
|
965 | 0 | ellipses = "... "; |
966 | 0 | } |
967 | | |
968 | | /* |
969 | | * Don't want format markers being indented |
970 | | */ |
971 | 0 | indent = request->log.indent; |
972 | 0 | request->log.indent.module = 0; |
973 | 0 | request->log.indent.unlang = 0; |
974 | |
|
975 | 0 | va_start(ap, marker_fmt); |
976 | 0 | error = fr_vasprintf(request, marker_fmt, ap); |
977 | 0 | va_end(ap); |
978 | |
|
979 | 0 | log_request(type, lvl, request, file, line, "%s%.*s", ellipses, (int) str_len, str); |
980 | 0 | log_request(type, lvl, request, file, line, "%s%.*s^ %s", ellipses, (int) marker_idx, marker_spaces, error); |
981 | 0 | talloc_free(error); |
982 | |
|
983 | 0 | request->log.indent = indent; |
984 | 0 | } |
985 | | |
986 | | void log_request_hex(fr_log_type_t type, fr_log_lvl_t lvl, request_t *request, |
987 | | char const *file, int line, |
988 | | uint8_t const *data, size_t data_len) |
989 | 0 | { |
990 | 0 | size_t i, j, len; |
991 | 0 | char buffer[(0x10 * 3) + 1]; |
992 | 0 | char *p, *end = buffer + sizeof(buffer); |
993 | |
|
994 | 0 | for (i = 0; i < data_len; i += 0x10) { |
995 | 0 | len = 0x10; |
996 | 0 | if ((i + len) > data_len) len = data_len - i; |
997 | |
|
998 | 0 | for (p = buffer, j = 0; j < len; j++, p += 3) snprintf(p, end - p, "%02x ", data[i + j]); |
999 | 0 | log_request(type, lvl, request, file, line, "%04x: %s", (unsigned int)i, buffer); |
1000 | 0 | } |
1001 | 0 | } |
1002 | | |
1003 | | /** Function to provide as the readable callback to the event loop |
1004 | | * |
1005 | | * Writes any data read from a file descriptor to the request log, |
1006 | | * tries very hard not to chop lines in the middle, but will split |
1007 | | * at 1024 byte boundaries if forced to. |
1008 | | * |
1009 | | * @param[in] el UNUSED |
1010 | | * @param[in] fd UNUSED |
1011 | | * @param[in] flags UNUSED |
1012 | | * @param[in] uctx Pointer to a log_fd_event_ctx_t |
1013 | | */ |
1014 | | void log_request_fd_event(UNUSED fr_event_list_t *el, int fd, UNUSED int flags, void *uctx) |
1015 | 0 | { |
1016 | 0 | char buffer[1024]; |
1017 | 0 | log_fd_event_ctx_t *log_info = uctx; |
1018 | 0 | request_t *request = log_info->request; |
1019 | 0 | fr_sbuff_t sbuff; |
1020 | 0 | fr_sbuff_marker_t m_start, m_end; |
1021 | |
|
1022 | 0 | fr_sbuff_term_t const line_endings = FR_SBUFF_TERMS(L("\n"), L("\r")); |
1023 | |
|
1024 | 0 | if (!RDEBUG_ENABLEDX(log_info->lvl)) { |
1025 | 0 | while (read(fd, buffer, sizeof(buffer)) > 0); /* discard all input */ |
1026 | 0 | return; |
1027 | 0 | } |
1028 | | |
1029 | 0 | fr_sbuff_init_out(&sbuff, buffer, sizeof(buffer)); |
1030 | 0 | fr_sbuff_marker(&m_start, &sbuff); |
1031 | 0 | fr_sbuff_marker(&m_end, &sbuff); |
1032 | |
|
1033 | 0 | #ifndef NDEBUG |
1034 | 0 | memset(buffer, 0x42, sizeof(buffer)); |
1035 | 0 | #endif |
1036 | |
|
1037 | 0 | for (;;) { |
1038 | 0 | ssize_t slen; |
1039 | |
|
1040 | 0 | slen = read(fd, fr_sbuff_current(&m_end), fr_sbuff_remaining(&m_end)); |
1041 | 0 | if (slen < 0) { |
1042 | 0 | if (errno == EINTR) continue; |
1043 | | |
1044 | | /* |
1045 | | * This can happen if the I/O handler is |
1046 | | * manually called to clear out any pending |
1047 | | * data, and we're using a nonblocking FD. |
1048 | | * |
1049 | | * This can happen with the exec code if |
1050 | | * the EVFILT_PROC event gets passed before |
1051 | | * the EVFILT_READ event. |
1052 | | */ |
1053 | 0 | if (errno == EWOULDBLOCK) slen = 0; |
1054 | 0 | } |
1055 | 0 | if ((slen < 0) && (errno == EINTR)) continue; |
1056 | | |
1057 | 0 | if (slen > 0) fr_sbuff_advance(&m_end, slen); |
1058 | |
|
1059 | 0 | while (fr_sbuff_ahead(&m_end) > 0) { |
1060 | 0 | fr_sbuff_adv_until(&sbuff, fr_sbuff_ahead(&m_end), &line_endings, '\0'); |
1061 | | |
1062 | | /* |
1063 | | * Incomplete line, try and read the rest. |
1064 | | */ |
1065 | 0 | if ((slen > 0) && (fr_sbuff_used(&m_start) > 0) && |
1066 | 0 | !fr_sbuff_is_terminal(&sbuff, &line_endings)) { |
1067 | 0 | break; |
1068 | 0 | } |
1069 | | |
1070 | 0 | log_request(log_info->type, log_info->lvl, log_info->request, |
1071 | 0 | __FILE__, __LINE__, |
1072 | 0 | "%s%s%pV", |
1073 | 0 | log_info->prefix ? log_info->prefix : "", |
1074 | 0 | log_info->prefix ? " - " : "", |
1075 | 0 | fr_box_strvalue_len(fr_sbuff_current(&m_start), |
1076 | 0 | fr_sbuff_behind(&m_start))); |
1077 | |
|
1078 | 0 | fr_sbuff_advance(&sbuff, 1); /* Skip the whitespace */ |
1079 | 0 | fr_sbuff_set(&m_start, &sbuff); |
1080 | 0 | } |
1081 | | |
1082 | | /* |
1083 | | * Error or done |
1084 | | */ |
1085 | 0 | if (slen <= 0) break; |
1086 | | |
1087 | | /* |
1088 | | * Clear out the existing data |
1089 | | */ |
1090 | 0 | fr_sbuff_shift(&sbuff, fr_sbuff_used(&m_start), false); |
1091 | 0 | } |
1092 | 0 | } |
1093 | | |
1094 | | /** Log a fatal error, then exit |
1095 | | * |
1096 | | */ |
1097 | | void log_fatal(fr_log_t const *log, char const *file, int line, char const *fmt, ...) |
1098 | 0 | { |
1099 | 0 | va_list ap; |
1100 | |
|
1101 | 0 | va_start(ap, fmt); |
1102 | 0 | fr_vlog(log, L_ERR, file, line, fmt, ap); |
1103 | 0 | va_end(ap); |
1104 | |
|
1105 | 0 | fr_exit_now(EXIT_FAILURE); |
1106 | 0 | } |
1107 | | |
1108 | | /** Register a logging destination. |
1109 | | * |
1110 | | */ |
1111 | | static void log_register_dst(char const *name, fr_log_t *log, CONF_SECTION *cs) |
1112 | 0 | { |
1113 | 0 | fr_log_track_t *dst; |
1114 | |
|
1115 | 0 | MEM(dst = talloc_zero(dst_tree, fr_log_track_t)); |
1116 | 0 | dst->name = name; |
1117 | 0 | dst->log = log; |
1118 | 0 | dst->cs = cs; |
1119 | |
|
1120 | 0 | fr_rb_insert(dst_tree, dst); |
1121 | |
|
1122 | 0 | if (log->dst != L_DST_FILES) return; |
1123 | | |
1124 | 0 | fr_assert(log->file != NULL); |
1125 | |
|
1126 | 0 | fr_rb_insert(filename_tree, dst); |
1127 | 0 | } |
1128 | | |
1129 | | /** Get a logging destination by name. |
1130 | | * |
1131 | | */ |
1132 | | fr_log_t *log_dst_by_name(char const *name) |
1133 | 0 | { |
1134 | 0 | fr_log_track_t find, *found; |
1135 | |
|
1136 | 0 | memset(&find, 0, sizeof(find)); |
1137 | 0 | find.name = name; |
1138 | |
|
1139 | 0 | fr_rb_find((void **)&found, dst_tree, &find); |
1140 | 0 | return (found) ? found->log : NULL; |
1141 | 0 | } |
1142 | | |
1143 | | static int _log_free(fr_log_t *log) |
1144 | 0 | { |
1145 | 0 | fr_assert(log->dst == L_DST_FILES); |
1146 | |
|
1147 | 0 | fclose(log->handle); |
1148 | 0 | return 0; |
1149 | 0 | } |
1150 | | |
1151 | | static char const *log_destination = NULL; |
1152 | | static bool log_timestamp; |
1153 | | static bool log_timestamp_is_set; |
1154 | | |
1155 | | /* |
1156 | | * Parse an fr_log_t configuration. |
1157 | | */ |
1158 | | static const conf_parser_t log_config[] = { |
1159 | | { FR_CONF_POINTER("destination", FR_TYPE_STRING, 0, &log_destination), .dflt = "file" }, |
1160 | | #if 0 |
1161 | | /* |
1162 | | * @todo - once we allow this, also check that there's only _one_ destination |
1163 | | * which uses syslog_facility. |
1164 | | */ |
1165 | | { FR_CONF_OFFSET("syslog_facility", main_config_t, syslog_facility), .dflt = "daemon", |
1166 | | .func = cf_table_parse_int, |
1167 | | .uctx = &(cf_table_parse_ctx_t){ |
1168 | | .table = syslog_facility_table, |
1169 | | .len = &syslog_facility_table_len |
1170 | | } |
1171 | | }, |
1172 | | #endif |
1173 | | { FR_CONF_POINTER_IS_SET("timestamp", FR_TYPE_BOOL, 0, &log_timestamp) }, |
1174 | | { FR_CONF_OFFSET("file", fr_log_t, file), }, |
1175 | | { FR_CONF_OFFSET("colourise", fr_log_t, colourise) }, |
1176 | | { FR_CONF_OFFSET("line_number", fr_log_t, line_number) }, |
1177 | | { FR_CONF_OFFSET("use_utc", fr_log_t, dates_utc) }, |
1178 | | { FR_CONF_OFFSET("print_level", fr_log_t, print_level) }, |
1179 | | { FR_CONF_OFFSET("suppress_secrets", fr_log_t, suppress_secrets) }, |
1180 | | CONF_PARSER_TERMINATOR |
1181 | | }; |
1182 | | |
1183 | | /** Parse a named logging section. |
1184 | | * |
1185 | | * @todo - we should probably allow for TCP sockets, too. But then |
1186 | | * those can block. So we then also need a way to buffer outbound |
1187 | | * log messages, and discard log messages if the buffer is full. |
1188 | | * |
1189 | | * This should probably be done with a FILE*, and L_DST_FUNC. |
1190 | | */ |
1191 | | int log_parse_section(CONF_SECTION *cs) |
1192 | 0 | { |
1193 | 0 | fr_log_track_t *dst; |
1194 | 0 | fr_log_t *log; |
1195 | 0 | char const *name; |
1196 | |
|
1197 | 0 | name = cf_section_name2(cs); |
1198 | 0 | if (!name) name = "DEFAULT"; |
1199 | |
|
1200 | 0 | fr_rb_find((void **)&dst, dst_tree, &(fr_log_track_t) { |
1201 | 0 | .name = name, |
1202 | 0 | }); |
1203 | 0 | if (dst) { |
1204 | 0 | fr_strerror_printf("Cannot add duplicate log destination '%s'", name); |
1205 | 0 | return -1; |
1206 | 0 | } |
1207 | | |
1208 | 0 | MEM(log = talloc_zero(dst_tree, fr_log_t)); |
1209 | |
|
1210 | 0 | if (cf_section_rules_push(cs, log_config) < 0) { |
1211 | 0 | error: |
1212 | 0 | talloc_free(log); |
1213 | 0 | return -1; |
1214 | 0 | } |
1215 | | |
1216 | 0 | if (cf_section_parse(log, log, cs) < 0) goto error; |
1217 | | |
1218 | 0 | log->dst = fr_table_value_by_str(log_destination_table, log_destination, L_DST_NUM_DEST); |
1219 | 0 | switch (log->dst) { |
1220 | 0 | case L_DST_NUM_DEST: |
1221 | 0 | fr_strerror_printf("Unknown log_destination '%s'", log_destination); |
1222 | 0 | talloc_const_free(log_destination); |
1223 | 0 | log_destination = NULL; |
1224 | 0 | goto error; |
1225 | | |
1226 | 0 | #ifdef HAVE_SYSLOG_H |
1227 | 0 | case L_DST_SYSLOG: |
1228 | 0 | talloc_const_free(log_destination); |
1229 | 0 | log_destination = NULL; |
1230 | |
|
1231 | 0 | if (fr_log_init_syslog(log) < 0) goto error; |
1232 | 0 | break; |
1233 | 0 | #endif |
1234 | | |
1235 | 0 | case L_DST_STDOUT: |
1236 | 0 | talloc_const_free(log_destination); |
1237 | 0 | log_destination = NULL; |
1238 | |
|
1239 | 0 | if (fr_log_init_std(log, L_DST_STDOUT) < 0) goto error; |
1240 | 0 | break; |
1241 | | |
1242 | 0 | case L_DST_STDERR: |
1243 | 0 | talloc_const_free(log_destination); |
1244 | 0 | log_destination = NULL; |
1245 | |
|
1246 | 0 | if (fr_log_init_std(log, L_DST_STDERR) < 0) goto error; |
1247 | 0 | break; |
1248 | | |
1249 | 0 | case L_DST_FILES: |
1250 | 0 | talloc_const_free(log_destination); |
1251 | 0 | log_destination = NULL; |
1252 | |
|
1253 | 0 | if (!log->file) { |
1254 | 0 | fr_strerror_const("Specified \"files\" as a log destination, but no log filename was given!"); |
1255 | 0 | goto error; |
1256 | 0 | } |
1257 | | |
1258 | 0 | fr_rb_find((void **)&dst, filename_tree, &(fr_log_track_t) { |
1259 | 0 | .log = log, |
1260 | 0 | }); |
1261 | 0 | if (dst) { |
1262 | 0 | fr_strerror_printf("The log destination '%s' is already logging to file %s", |
1263 | 0 | dst->name, log->file); |
1264 | 0 | goto error; |
1265 | 0 | } |
1266 | | |
1267 | 0 | if (fr_log_init_file(log, log->file) < 0) goto error; |
1268 | | |
1269 | 0 | talloc_set_destructor(log, _log_free); |
1270 | 0 | break; |
1271 | | |
1272 | 0 | case L_DST_NULL: |
1273 | 0 | break; |
1274 | | |
1275 | 0 | default: |
1276 | 0 | talloc_const_free(log_destination); |
1277 | 0 | log_destination = NULL; |
1278 | |
|
1279 | 0 | fr_assert(0); |
1280 | 0 | break; |
1281 | 0 | } |
1282 | | |
1283 | 0 | if (log_timestamp_is_set) { |
1284 | 0 | log->timestamp = log->timestamp ? L_TIMESTAMP_ON : L_TIMESTAMP_OFF; |
1285 | 0 | } else { |
1286 | 0 | log->timestamp = L_TIMESTAMP_AUTO; |
1287 | 0 | } |
1288 | |
|
1289 | 0 | log_register_dst(name, log, cs); |
1290 | |
|
1291 | 0 | return 0; |
1292 | 0 | } |
1293 | | |
1294 | | static fr_cmp_ret_t _log_track_name_cmp(void const *two, void const *one) |
1295 | 0 | { |
1296 | 0 | fr_log_track_t const *a = one; |
1297 | 0 | fr_log_track_t const *b = two; |
1298 | |
|
1299 | 0 | return CMP(strcmp(a->name, b->name), 0); |
1300 | 0 | } |
1301 | | |
1302 | | static fr_cmp_ret_t _log_track_filename_cmp(void const *two, void const *one) |
1303 | 0 | { |
1304 | 0 | fr_log_track_t const *a = one; |
1305 | 0 | fr_log_track_t const *b = two; |
1306 | |
|
1307 | 0 | fr_assert(a->log); |
1308 | 0 | fr_assert(a->log->dst == L_DST_FILES); |
1309 | |
|
1310 | 0 | fr_assert(b->log); |
1311 | 0 | fr_assert(b->log->dst == L_DST_FILES); |
1312 | |
|
1313 | 0 | return CMP(strcmp(a->log->file, b->log->file), 0); |
1314 | 0 | } |
1315 | | |
1316 | | /** Initialises the server logging functionality, and the underlying libfreeradius log |
1317 | | * |
1318 | | * @note Call log free when the server is done to fix any spurious memory leaks. |
1319 | | * |
1320 | | * @param[in] log Logging parameters. |
1321 | | * @param[in] daemonize Changes what we do with stdout/stderr. |
1322 | | * @return |
1323 | | * - 0 on success. |
1324 | | * - -1 on failure. |
1325 | | */ |
1326 | | int log_global_init(fr_log_t *log, bool daemonize) |
1327 | 0 | { |
1328 | 0 | int ret; |
1329 | |
|
1330 | 0 | ret = fr_log_init_legacy(log, daemonize); |
1331 | 0 | if (ret < 0) return ret; |
1332 | | |
1333 | 0 | if (fr_dict_autoload(log_dict) < 0) { |
1334 | 0 | fr_perror("log_init"); |
1335 | 0 | return -1; |
1336 | 0 | } |
1337 | | |
1338 | 0 | if (fr_dict_attr_autoload(log_dict_attr) < 0) { |
1339 | 0 | fr_perror("log_init"); |
1340 | 0 | return -1; |
1341 | 0 | } |
1342 | | |
1343 | 0 | dst_tree = fr_rb_inline_alloc(NULL, fr_log_track_t, name_node, |
1344 | 0 | _log_track_name_cmp, NULL); |
1345 | 0 | if (!dst_tree) { |
1346 | 0 | fr_perror("log_init"); |
1347 | 0 | return -1; |
1348 | 0 | } |
1349 | | |
1350 | 0 | filename_tree = fr_rb_inline_alloc(NULL, fr_log_track_t, filename_node, |
1351 | 0 | _log_track_filename_cmp, NULL); |
1352 | 0 | if (!filename_tree) { |
1353 | 0 | fr_perror("log_init"); |
1354 | 0 | return -1; |
1355 | 0 | } |
1356 | | |
1357 | 0 | log_register_dst("default", log, NULL); |
1358 | |
|
1359 | 0 | return ret; |
1360 | 0 | } |
1361 | | |
1362 | | void log_global_free(void) |
1363 | 0 | { |
1364 | 0 | fr_dict_autofree(log_dict); |
1365 | 0 | TALLOC_FREE(src_tree); |
1366 | 0 | TALLOC_FREE(dst_tree); |
1367 | | TALLOC_FREE(filename_tree); |
1368 | 0 | } |