]>
Commit | Line | Data |
---|---|---|
19d3ee2f | 1 | /* $OpenBSD: log.c,v 1.61 2023/12/06 21:06:48 djm Exp $ */ |
e4340be5 DM |
2 | /* |
3 | * Author: Tatu Ylonen <ylo@cs.hut.fi> | |
4 | * Copyright (c) 1995 Tatu Ylonen <ylo@cs.hut.fi>, Espoo, Finland | |
5 | * All rights reserved | |
6 | * | |
7 | * As far as I am concerned, the code I have written for this software | |
8 | * can be used freely for any purpose. Any derived versions of this | |
9 | * software must be clearly marked as such, and if the derived work is | |
10 | * incompatible with the protocol description in the RFC file, it must be | |
11 | * called by a name other than "ssh" or "Secure Shell". | |
12 | */ | |
5ce662a9 | 13 | /* |
e4340be5 DM |
14 | * Copyright (c) 2000 Markus Friedl. All rights reserved. |
15 | * | |
16 | * Redistribution and use in source and binary forms, with or without | |
17 | * modification, are permitted provided that the following conditions | |
18 | * are met: | |
19 | * 1. Redistributions of source code must retain the above copyright | |
20 | * notice, this list of conditions and the following disclaimer. | |
21 | * 2. Redistributions in binary form must reproduce the above copyright | |
22 | * notice, this list of conditions and the following disclaimer in the | |
23 | * documentation and/or other materials provided with the distribution. | |
24 | * | |
25 | * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR | |
26 | * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES | |
27 | * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. | |
28 | * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, | |
29 | * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT | |
30 | * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, | |
31 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY | |
32 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | |
33 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF | |
34 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
5428f646 | 35 | */ |
5ce662a9 DM |
36 | |
37 | #include "includes.h" | |
5ce662a9 | 38 | |
d7834353 DM |
39 | #include <sys/types.h> |
40 | ||
03d4d7e6 | 41 | #include <fcntl.h> |
5d19626a | 42 | #include <stdarg.h> |
a7a73ee3 | 43 | #include <stdio.h> |
e7a1e5cf | 44 | #include <stdlib.h> |
e3476ed0 | 45 | #include <string.h> |
8a432f5f | 46 | #include <syslog.h> |
e6b3b610 | 47 | #include <unistd.h> |
36b78000 | 48 | #include <errno.h> |
63b4bcd0 | 49 | #if defined(HAVE_STRNVIS) && defined(HAVE_VIS_H) && !defined(BROKEN_STRNVIS) |
5c3a5584 DM |
50 | # include <vis.h> |
51 | #endif | |
8a432f5f | 52 | |
d7834353 | 53 | #include "log.h" |
752250ca | 54 | #include "match.h" |
194a1cb0 | 55 | |
8a432f5f BL |
56 | static LogLevel log_level = SYSLOG_LEVEL_INFO; |
57 | static int log_on_stderr = 1; | |
03d4d7e6 | 58 | static int log_stderr_fd = STDERR_FILENO; |
8a432f5f | 59 | static int log_facility = LOG_AUTH; |
ee22db7c | 60 | static const char *argv0; |
8f0bf237 DM |
61 | static log_handler_fn *log_handler; |
62 | static void *log_handler_ctx; | |
752250ca | 63 | static char **log_verbose; |
64 | static size_t nlog_verbose; | |
8a432f5f BL |
65 | |
66 | extern char *__progname; | |
67 | ||
23a7027e DM |
68 | #define LOG_SYSLOG_VIS (VIS_CSTYLE|VIS_NL|VIS_TAB|VIS_OCTAL) |
69 | #define LOG_STDERR_VIS (VIS_SAFE|VIS_OCTAL) | |
70 | ||
8a432f5f BL |
71 | /* textual representation of log-facilities/levels */ |
72 | ||
73 | static struct { | |
74 | const char *name; | |
75 | SyslogFacility val; | |
76 | } log_facilities[] = { | |
77 | { "DAEMON", SYSLOG_FACILITY_DAEMON }, | |
78 | { "USER", SYSLOG_FACILITY_USER }, | |
79 | { "AUTH", SYSLOG_FACILITY_AUTH }, | |
30246a8f DM |
80 | #ifdef LOG_AUTHPRIV |
81 | { "AUTHPRIV", SYSLOG_FACILITY_AUTHPRIV }, | |
82 | #endif | |
8a432f5f BL |
83 | { "LOCAL0", SYSLOG_FACILITY_LOCAL0 }, |
84 | { "LOCAL1", SYSLOG_FACILITY_LOCAL1 }, | |
85 | { "LOCAL2", SYSLOG_FACILITY_LOCAL2 }, | |
86 | { "LOCAL3", SYSLOG_FACILITY_LOCAL3 }, | |
87 | { "LOCAL4", SYSLOG_FACILITY_LOCAL4 }, | |
88 | { "LOCAL5", SYSLOG_FACILITY_LOCAL5 }, | |
89 | { "LOCAL6", SYSLOG_FACILITY_LOCAL6 }, | |
90 | { "LOCAL7", SYSLOG_FACILITY_LOCAL7 }, | |
fcd93204 | 91 | { NULL, SYSLOG_FACILITY_NOT_SET } |
8a432f5f BL |
92 | }; |
93 | ||
94 | static struct { | |
95 | const char *name; | |
96 | LogLevel val; | |
97 | } log_levels[] = | |
98 | { | |
99 | { "QUIET", SYSLOG_LEVEL_QUIET }, | |
100 | { "FATAL", SYSLOG_LEVEL_FATAL }, | |
101 | { "ERROR", SYSLOG_LEVEL_ERROR }, | |
102 | { "INFO", SYSLOG_LEVEL_INFO }, | |
103 | { "VERBOSE", SYSLOG_LEVEL_VERBOSE }, | |
104 | { "DEBUG", SYSLOG_LEVEL_DEBUG1 }, | |
105 | { "DEBUG1", SYSLOG_LEVEL_DEBUG1 }, | |
106 | { "DEBUG2", SYSLOG_LEVEL_DEBUG2 }, | |
107 | { "DEBUG3", SYSLOG_LEVEL_DEBUG3 }, | |
fcd93204 | 108 | { NULL, SYSLOG_LEVEL_NOT_SET } |
8a432f5f BL |
109 | }; |
110 | ||
5d14019b | 111 | LogLevel |
112 | log_level_get(void) | |
113 | { | |
114 | return log_level; | |
115 | } | |
116 | ||
8a432f5f BL |
117 | SyslogFacility |
118 | log_facility_number(char *name) | |
119 | { | |
120 | int i; | |
8e8ef2a3 | 121 | |
8a432f5f BL |
122 | if (name != NULL) |
123 | for (i = 0; log_facilities[i].name; i++) | |
124 | if (strcasecmp(log_facilities[i].name, name) == 0) | |
125 | return log_facilities[i].val; | |
fcd93204 | 126 | return SYSLOG_FACILITY_NOT_SET; |
8a432f5f BL |
127 | } |
128 | ||
e7140f20 DT |
129 | const char * |
130 | log_facility_name(SyslogFacility facility) | |
131 | { | |
132 | u_int i; | |
133 | ||
134 | for (i = 0; log_facilities[i].name; i++) | |
135 | if (log_facilities[i].val == facility) | |
136 | return log_facilities[i].name; | |
137 | return NULL; | |
138 | } | |
139 | ||
8a432f5f BL |
140 | LogLevel |
141 | log_level_number(char *name) | |
142 | { | |
143 | int i; | |
8e8ef2a3 | 144 | |
8a432f5f BL |
145 | if (name != NULL) |
146 | for (i = 0; log_levels[i].name; i++) | |
147 | if (strcasecmp(log_levels[i].name, name) == 0) | |
148 | return log_levels[i].val; | |
fcd93204 | 149 | return SYSLOG_LEVEL_NOT_SET; |
8a432f5f | 150 | } |
5ce662a9 | 151 | |
e7140f20 DT |
152 | const char * |
153 | log_level_name(LogLevel level) | |
154 | { | |
155 | u_int i; | |
156 | ||
157 | for (i = 0; log_levels[i].name != NULL; i++) | |
158 | if (log_levels[i].val == level) | |
159 | return log_levels[i].name; | |
160 | return NULL; | |
161 | } | |
162 | ||
e4340be5 | 163 | void |
752250ca | 164 | log_verbose_add(const char *s) |
e4340be5 | 165 | { |
752250ca | 166 | char **tmp; |
167 | ||
168 | /* Ignore failures here */ | |
169 | if ((tmp = recallocarray(log_verbose, nlog_verbose, nlog_verbose + 1, | |
170 | sizeof(*log_verbose))) != NULL) { | |
171 | log_verbose = tmp; | |
172 | if ((log_verbose[nlog_verbose] = strdup(s)) != NULL) | |
173 | nlog_verbose++; | |
174 | } | |
e4340be5 DM |
175 | } |
176 | ||
177 | void | |
752250ca | 178 | log_verbose_reset(void) |
e4340be5 | 179 | { |
752250ca | 180 | size_t i; |
8e8ef2a3 | 181 | |
752250ca | 182 | for (i = 0; i < nlog_verbose; i++) |
183 | free(log_verbose[i]); | |
184 | free(log_verbose); | |
185 | log_verbose = NULL; | |
186 | nlog_verbose = 0; | |
5ce662a9 DM |
187 | } |
188 | ||
8a432f5f BL |
189 | /* |
190 | * Initialize the log. | |
191 | */ | |
6162d121 | 192 | |
8a432f5f | 193 | void |
ee22db7c | 194 | log_init(const char *av0, LogLevel level, SyslogFacility facility, |
195 | int on_stderr) | |
6162d121 | 196 | { |
9b5495d2 DT |
197 | #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) |
198 | struct syslog_data sdata = SYSLOG_DATA_INIT; | |
199 | #endif | |
6162d121 | 200 | |
835903da DT |
201 | argv0 = av0; |
202 | ||
54cd41a4 | 203 | if (log_change_level(level) != 0) { |
edcd5762 | 204 | fprintf(stderr, "Unrecognized internal syslog level code %d\n", |
8a432f5f BL |
205 | (int) level); |
206 | exit(1); | |
207 | } | |
208 | ||
8f0bf237 DM |
209 | log_handler = NULL; |
210 | log_handler_ctx = NULL; | |
211 | ||
8a432f5f BL |
212 | log_on_stderr = on_stderr; |
213 | if (on_stderr) | |
214 | return; | |
215 | ||
216 | switch (facility) { | |
217 | case SYSLOG_FACILITY_DAEMON: | |
218 | log_facility = LOG_DAEMON; | |
219 | break; | |
220 | case SYSLOG_FACILITY_USER: | |
221 | log_facility = LOG_USER; | |
222 | break; | |
223 | case SYSLOG_FACILITY_AUTH: | |
224 | log_facility = LOG_AUTH; | |
225 | break; | |
30246a8f DM |
226 | #ifdef LOG_AUTHPRIV |
227 | case SYSLOG_FACILITY_AUTHPRIV: | |
228 | log_facility = LOG_AUTHPRIV; | |
229 | break; | |
8a432f5f BL |
230 | #endif |
231 | case SYSLOG_FACILITY_LOCAL0: | |
232 | log_facility = LOG_LOCAL0; | |
233 | break; | |
234 | case SYSLOG_FACILITY_LOCAL1: | |
235 | log_facility = LOG_LOCAL1; | |
236 | break; | |
237 | case SYSLOG_FACILITY_LOCAL2: | |
238 | log_facility = LOG_LOCAL2; | |
239 | break; | |
240 | case SYSLOG_FACILITY_LOCAL3: | |
241 | log_facility = LOG_LOCAL3; | |
242 | break; | |
243 | case SYSLOG_FACILITY_LOCAL4: | |
244 | log_facility = LOG_LOCAL4; | |
245 | break; | |
246 | case SYSLOG_FACILITY_LOCAL5: | |
247 | log_facility = LOG_LOCAL5; | |
248 | break; | |
249 | case SYSLOG_FACILITY_LOCAL6: | |
250 | log_facility = LOG_LOCAL6; | |
251 | break; | |
252 | case SYSLOG_FACILITY_LOCAL7: | |
253 | log_facility = LOG_LOCAL7; | |
254 | break; | |
255 | default: | |
256 | fprintf(stderr, | |
edcd5762 | 257 | "Unrecognized internal syslog facility code %d\n", |
8a432f5f BL |
258 | (int) facility); |
259 | exit(1); | |
260 | } | |
9b5495d2 DT |
261 | |
262 | /* | |
263 | * If an external library (eg libwrap) attempts to use syslog | |
264 | * immediately after reexec, syslog may be pointing to the wrong | |
265 | * facility, so we force an open/close of syslog here. | |
266 | */ | |
267 | #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) | |
268 | openlog_r(argv0 ? argv0 : __progname, LOG_PID, log_facility, &sdata); | |
269 | closelog_r(&sdata); | |
270 | #else | |
271 | openlog(argv0 ? argv0 : __progname, LOG_PID, log_facility); | |
272 | closelog(); | |
273 | #endif | |
6162d121 DM |
274 | } |
275 | ||
54cd41a4 | 276 | int |
50a48d02 DT |
277 | log_change_level(LogLevel new_log_level) |
278 | { | |
279 | /* no-op if log_init has not been called */ | |
280 | if (argv0 == NULL) | |
54cd41a4 | 281 | return 0; |
282 | ||
283 | switch (new_log_level) { | |
284 | case SYSLOG_LEVEL_QUIET: | |
285 | case SYSLOG_LEVEL_FATAL: | |
286 | case SYSLOG_LEVEL_ERROR: | |
287 | case SYSLOG_LEVEL_INFO: | |
288 | case SYSLOG_LEVEL_VERBOSE: | |
289 | case SYSLOG_LEVEL_DEBUG1: | |
290 | case SYSLOG_LEVEL_DEBUG2: | |
291 | case SYSLOG_LEVEL_DEBUG3: | |
292 | log_level = new_log_level; | |
293 | return 0; | |
294 | default: | |
295 | return -1; | |
296 | } | |
50a48d02 DT |
297 | } |
298 | ||
299 | int | |
300 | log_is_on_stderr(void) | |
301 | { | |
d2d6bf86 | 302 | return log_on_stderr && log_stderr_fd == STDERR_FILENO; |
50a48d02 DT |
303 | } |
304 | ||
03d4d7e6 DM |
305 | /* redirect what would usually get written to stderr to specified file */ |
306 | void | |
307 | log_redirect_stderr_to(const char *logfile) | |
308 | { | |
309 | int fd; | |
310 | ||
c8935081 | 311 | if (logfile == NULL) { |
312 | if (log_stderr_fd != STDERR_FILENO) { | |
313 | close(log_stderr_fd); | |
314 | log_stderr_fd = STDERR_FILENO; | |
315 | } | |
316 | return; | |
317 | } | |
318 | ||
03d4d7e6 DM |
319 | if ((fd = open(logfile, O_WRONLY|O_CREAT|O_APPEND, 0600)) == -1) { |
320 | fprintf(stderr, "Couldn't open logfile %s: %s\n", logfile, | |
31d8d231 | 321 | strerror(errno)); |
03d4d7e6 DM |
322 | exit(1); |
323 | } | |
324 | log_stderr_fd = fd; | |
325 | } | |
326 | ||
8a432f5f BL |
327 | #define MSGBUFSIZ 1024 |
328 | ||
8f0bf237 DM |
329 | void |
330 | set_log_handler(log_handler_fn *handler, void *ctx) | |
331 | { | |
332 | log_handler = handler; | |
333 | log_handler_ctx = ctx; | |
334 | } | |
335 | ||
752250ca | 336 | static void |
faf2b86a | 337 | do_log(LogLevel level, int force, const char *suffix, const char *fmt, |
338 | va_list args) | |
6162d121 | 339 | { |
051b0acb | 340 | #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) |
74a3442d DM |
341 | struct syslog_data sdata = SYSLOG_DATA_INIT; |
342 | #endif | |
8a432f5f BL |
343 | char msgbuf[MSGBUFSIZ]; |
344 | char fmtbuf[MSGBUFSIZ]; | |
345 | char *txt = NULL; | |
346 | int pri = LOG_INFO; | |
36b78000 | 347 | int saved_errno = errno; |
8f0bf237 | 348 | log_handler_fn *tmp_handler; |
e694f8ac | 349 | const char *progname = argv0 != NULL ? argv0 : __progname; |
8a432f5f | 350 | |
752250ca | 351 | if (!force && level > log_level) |
8a432f5f BL |
352 | return; |
353 | ||
354 | switch (level) { | |
355 | case SYSLOG_LEVEL_FATAL: | |
356 | if (!log_on_stderr) | |
357 | txt = "fatal"; | |
358 | pri = LOG_CRIT; | |
359 | break; | |
360 | case SYSLOG_LEVEL_ERROR: | |
361 | if (!log_on_stderr) | |
362 | txt = "error"; | |
363 | pri = LOG_ERR; | |
364 | break; | |
365 | case SYSLOG_LEVEL_INFO: | |
366 | pri = LOG_INFO; | |
367 | break; | |
368 | case SYSLOG_LEVEL_VERBOSE: | |
369 | pri = LOG_INFO; | |
370 | break; | |
371 | case SYSLOG_LEVEL_DEBUG1: | |
372 | txt = "debug1"; | |
373 | pri = LOG_DEBUG; | |
374 | break; | |
375 | case SYSLOG_LEVEL_DEBUG2: | |
376 | txt = "debug2"; | |
377 | pri = LOG_DEBUG; | |
378 | break; | |
379 | case SYSLOG_LEVEL_DEBUG3: | |
380 | txt = "debug3"; | |
381 | pri = LOG_DEBUG; | |
382 | break; | |
383 | default: | |
384 | txt = "internal error"; | |
385 | pri = LOG_ERR; | |
386 | break; | |
387 | } | |
8f0bf237 | 388 | if (txt != NULL && log_handler == NULL) { |
8a432f5f BL |
389 | snprintf(fmtbuf, sizeof(fmtbuf), "%s: %s", txt, fmt); |
390 | vsnprintf(msgbuf, sizeof(msgbuf), fmtbuf, args); | |
391 | } else { | |
392 | vsnprintf(msgbuf, sizeof(msgbuf), fmt, args); | |
393 | } | |
9e2c4f64 | 394 | if (suffix != NULL) { |
395 | snprintf(fmtbuf, sizeof(fmtbuf), "%s: %s", msgbuf, suffix); | |
396 | strlcpy(msgbuf, fmtbuf, sizeof(msgbuf)); | |
397 | } | |
23a7027e DM |
398 | strnvis(fmtbuf, msgbuf, sizeof(fmtbuf), |
399 | log_on_stderr ? LOG_STDERR_VIS : LOG_SYSLOG_VIS); | |
8f0bf237 DM |
400 | if (log_handler != NULL) { |
401 | /* Avoid recursion */ | |
402 | tmp_handler = log_handler; | |
403 | log_handler = NULL; | |
faf2b86a | 404 | tmp_handler(level, force, fmtbuf, log_handler_ctx); |
8f0bf237 DM |
405 | log_handler = tmp_handler; |
406 | } else if (log_on_stderr) { | |
e694f8ac | 407 | snprintf(msgbuf, sizeof msgbuf, "%s%s%.*s\r\n", |
408 | (log_on_stderr > 1) ? progname : "", | |
409 | (log_on_stderr > 1) ? ": " : "", | |
9747b9c7 | 410 | (int)sizeof msgbuf - 3, fmtbuf); |
dbee3082 | 411 | (void)write(log_stderr_fd, msgbuf, strlen(msgbuf)); |
8a432f5f | 412 | } else { |
051b0acb | 413 | #if defined(HAVE_OPENLOG_R) && defined(SYSLOG_DATA_INIT) |
e694f8ac | 414 | openlog_r(progname, LOG_PID, log_facility, &sdata); |
c11fe255 | 415 | syslog_r(pri, &sdata, "%.500s", fmtbuf); |
74a3442d DM |
416 | closelog_r(&sdata); |
417 | #else | |
e694f8ac | 418 | openlog(progname, LOG_PID, log_facility); |
b93addb6 | 419 | syslog(pri, "%.500s", fmtbuf); |
8a432f5f | 420 | closelog(); |
74a3442d | 421 | #endif |
8a432f5f | 422 | } |
36b78000 | 423 | errno = saved_errno; |
6162d121 | 424 | } |
752250ca | 425 | |
426 | void | |
427 | sshlog(const char *file, const char *func, int line, int showfunc, | |
9e2c4f64 | 428 | LogLevel level, const char *suffix, const char *fmt, ...) |
752250ca | 429 | { |
430 | va_list args; | |
431 | ||
432 | va_start(args, fmt); | |
9e2c4f64 | 433 | sshlogv(file, func, line, showfunc, level, suffix, fmt, args); |
752250ca | 434 | va_end(args); |
435 | } | |
436 | ||
437 | void | |
3554b4af | 438 | sshlogdie(const char *file, const char *func, int line, int showfunc, |
9e2c4f64 | 439 | LogLevel level, const char *suffix, const char *fmt, ...) |
752250ca | 440 | { |
441 | va_list args; | |
442 | ||
443 | va_start(args, fmt); | |
9e2c4f64 | 444 | sshlogv(file, func, line, showfunc, SYSLOG_LEVEL_INFO, |
445 | suffix, fmt, args); | |
752250ca | 446 | va_end(args); |
447 | cleanup_exit(255); | |
448 | } | |
449 | ||
450 | void | |
3554b4af | 451 | sshsigdie(const char *file, const char *func, int line, int showfunc, |
9e2c4f64 | 452 | LogLevel level, const char *suffix, const char *fmt, ...) |
752250ca | 453 | { |
454 | va_list args; | |
455 | ||
456 | va_start(args, fmt); | |
9e2c4f64 | 457 | sshlogv(file, func, line, showfunc, SYSLOG_LEVEL_FATAL, |
458 | suffix, fmt, args); | |
752250ca | 459 | va_end(args); |
460 | _exit(1); | |
461 | } | |
462 | ||
463 | void | |
464 | sshlogv(const char *file, const char *func, int line, int showfunc, | |
9e2c4f64 | 465 | LogLevel level, const char *suffix, const char *fmt, va_list args) |
752250ca | 466 | { |
467 | char tag[128], fmt2[MSGBUFSIZ + 128]; | |
468 | int forced = 0; | |
469 | const char *cp; | |
470 | size_t i; | |
471 | ||
19d3ee2f | 472 | /* short circuit processing early if we're not going to log anything */ |
473 | if (nlog_verbose == 0 && level > log_level) | |
474 | return; | |
475 | ||
d3cc4d65 | 476 | snprintf(tag, sizeof(tag), "%.48s:%.48s():%d (pid=%ld)", |
477 | (cp = strrchr(file, '/')) == NULL ? file : cp + 1, func, line, | |
478 | (long)getpid()); | |
752250ca | 479 | for (i = 0; i < nlog_verbose; i++) { |
480 | if (match_pattern_list(tag, log_verbose[i], 0) == 1) { | |
481 | forced = 1; | |
482 | break; | |
483 | } | |
484 | } | |
485 | ||
faf2b86a | 486 | if (forced) |
752250ca | 487 | snprintf(fmt2, sizeof(fmt2), "%s: %s", tag, fmt); |
488 | else if (showfunc) | |
489 | snprintf(fmt2, sizeof(fmt2), "%s: %s", func, fmt); | |
490 | else | |
491 | strlcpy(fmt2, fmt, sizeof(fmt2)); | |
492 | ||
faf2b86a | 493 | do_log(level, forced, suffix, fmt2, args); |
494 | } | |
495 | ||
496 | void | |
497 | sshlogdirect(LogLevel level, int forced, const char *fmt, ...) | |
498 | { | |
499 | va_list args; | |
500 | ||
501 | va_start(args, fmt); | |
502 | do_log(level, forced, NULL, fmt, args); | |
503 | va_end(args); | |
752250ca | 504 | } |