Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * ProFTPD - FTP server daemon |
3 | | * Copyright (c) 1997, 1998 Public Flood Software |
4 | | * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu <macgyver@tos.net> |
5 | | * Copyright (c) 2001-2023 The ProFTPD Project team |
6 | | * |
7 | | * This program is free software; you can redistribute it and/or modify |
8 | | * it under the terms of the GNU General Public License as published by |
9 | | * the Free Software Foundation; either version 2 of the License, or |
10 | | * (at your option) any later version. |
11 | | * |
12 | | * This program is distributed in the hope that it will be useful, |
13 | | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
14 | | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
15 | | * GNU General Public License for more details. |
16 | | * |
17 | | * You should have received a copy of the GNU General Public License |
18 | | * along with this program; if not, write to the Free Software |
19 | | * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. |
20 | | * |
21 | | * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu |
22 | | * and other respective copyright holders give permission to link this program |
23 | | * with OpenSSL, and distribute the resulting executable, without including |
24 | | * the source code for OpenSSL in the source distribution. |
25 | | */ |
26 | | |
27 | | /* ProFTPD logging support. */ |
28 | | |
29 | | #include "conf.h" |
30 | | |
31 | | #ifdef HAVE_EXECINFO_H |
32 | | # include <execinfo.h> |
33 | | #endif |
34 | | |
35 | | #define LOGBUFFER_SIZE (PR_TUNABLE_PATH_MAX * 4) |
36 | | |
37 | | /* From src/main.c */ |
38 | | extern unsigned char is_master; |
39 | | |
40 | | static int syslog_open = FALSE; |
41 | | static int syslog_discard = FALSE; |
42 | | static unsigned long log_opts = PR_LOG_OPT_DEFAULT; |
43 | | static int logstderr = TRUE; |
44 | | static int debug_level = DEBUG0; |
45 | | static int default_level = PR_LOG_NOTICE; |
46 | | static int facility = LOG_DAEMON; |
47 | | static int set_facility = -1; |
48 | | static char systemlog_fn[PR_TUNABLE_PATH_MAX] = {'\0'}; |
49 | | static char systemlog_host[256] = {'\0'}; |
50 | | static int systemlog_fd = -1; |
51 | | |
52 | | static const char *trace_channel = "log"; |
53 | | |
54 | | int syslog_sockfd = -1; |
55 | | |
56 | | #ifdef PR_USE_NONBLOCKING_LOG_OPEN |
57 | 0 | static int fd_set_block(int fd) { |
58 | 0 | int flags, res; |
59 | |
|
60 | 0 | flags = fcntl(fd, F_GETFL); |
61 | 0 | res = fcntl(fd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK)); |
62 | |
|
63 | 0 | return res; |
64 | 0 | } |
65 | | #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ |
66 | | |
67 | 0 | int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) { |
68 | 0 | pool *tmp_pool = NULL; |
69 | 0 | char *ptr = NULL, *lf; |
70 | 0 | unsigned char have_stat = FALSE, *allow_log_symlinks = NULL; |
71 | 0 | struct stat st; |
72 | | |
73 | | /* Sanity check */ |
74 | 0 | if (log_file == NULL || |
75 | 0 | log_fd == NULL) { |
76 | 0 | errno = EINVAL; |
77 | 0 | return -1; |
78 | 0 | } |
79 | | |
80 | | /* Make a temporary copy of log_file in case it's a constant */ |
81 | 0 | tmp_pool = make_sub_pool(permanent_pool); |
82 | 0 | pr_pool_tag(tmp_pool, "log_openfile() tmp pool"); |
83 | 0 | lf = pstrdup(tmp_pool, log_file); |
84 | |
|
85 | 0 | ptr = strrchr(lf, '/'); |
86 | 0 | if (ptr == NULL) { |
87 | 0 | pr_log_debug(DEBUG0, "inappropriate log file: %s", lf); |
88 | 0 | destroy_pool(tmp_pool); |
89 | |
|
90 | 0 | errno = EINVAL; |
91 | 0 | return -1; |
92 | 0 | } |
93 | | |
94 | | /* Set the path separator to zero, in order to obtain the directory |
95 | | * name, so that checks of the directory may be made. |
96 | | */ |
97 | 0 | if (ptr != lf) { |
98 | 0 | *ptr = '\0'; |
99 | 0 | } |
100 | |
|
101 | 0 | if (stat(lf, &st) < 0) { |
102 | 0 | int xerrno = errno; |
103 | 0 | pr_log_debug(DEBUG0, "error: unable to stat() %s: %s", lf, |
104 | 0 | strerror(errno)); |
105 | 0 | destroy_pool(tmp_pool); |
106 | |
|
107 | 0 | errno = xerrno; |
108 | 0 | return -1; |
109 | 0 | } |
110 | | |
111 | | /* The path must be in a valid directory */ |
112 | 0 | if (!S_ISDIR(st.st_mode)) { |
113 | 0 | pr_log_debug(DEBUG0, "error: %s is not a directory", lf); |
114 | 0 | destroy_pool(tmp_pool); |
115 | |
|
116 | 0 | errno = ENOTDIR; |
117 | 0 | return -1; |
118 | 0 | } |
119 | | |
120 | | /* Do not log to world-writable directories */ |
121 | 0 | if (st.st_mode & S_IWOTH) { |
122 | 0 | pr_log_pri(PR_LOG_NOTICE, "error: %s is a world-writable directory", lf); |
123 | 0 | destroy_pool(tmp_pool); |
124 | 0 | return PR_LOG_WRITABLE_DIR; |
125 | 0 | } |
126 | | |
127 | | /* Restore the path separator so that checks on the file itself may be |
128 | | * done. |
129 | | */ |
130 | 0 | if (ptr != lf) { |
131 | 0 | *ptr = '/'; |
132 | 0 | } |
133 | |
|
134 | 0 | allow_log_symlinks = get_param_ptr(main_server->conf, "AllowLogSymlinks", |
135 | 0 | FALSE); |
136 | |
|
137 | 0 | if (allow_log_symlinks == NULL || |
138 | 0 | *allow_log_symlinks == FALSE) { |
139 | 0 | int flags = O_APPEND|O_CREAT|O_WRONLY; |
140 | |
|
141 | 0 | #ifdef PR_USE_NONBLOCKING_LOG_OPEN |
142 | | /* Use the O_NONBLOCK flag when opening log files, as they might be |
143 | | * FIFOs whose other end is not currently running; we do not want to |
144 | | * block indefinitely in such cases. |
145 | | */ |
146 | 0 | flags |= O_NONBLOCK; |
147 | 0 | #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ |
148 | |
|
149 | 0 | #ifdef O_NOFOLLOW |
150 | | /* On systems that support the O_NOFOLLOW flag (e.g. Linux and FreeBSD), |
151 | | * use it so that the path being opened, if it is a symlink, is not |
152 | | * followed. |
153 | | */ |
154 | 0 | flags |= O_NOFOLLOW; |
155 | |
|
156 | | #elif defined(SOLARIS2) |
157 | | /* Solaris doesn't support the O_NOFOLLOW flag. Instead, in their |
158 | | * wisdom (hah!), Solaris decided that if the given path is a symlink |
159 | | * and the flags O_CREAT and O_EXCL are set, the link is not followed. |
160 | | * Right. The problem here is the case where the path is not a symlink; |
161 | | * using O_CREAT|O_EXCL will then cause the open() to fail if the |
162 | | * file already exists. |
163 | | */ |
164 | | flags |= O_EXCL; |
165 | | #endif /* O_NOFOLLOW or SOLARIS2 */ |
166 | |
|
167 | 0 | *log_fd = open(lf, flags, log_mode); |
168 | 0 | if (*log_fd < 0) { |
169 | |
|
170 | 0 | if (errno != EEXIST) { |
171 | 0 | destroy_pool(tmp_pool); |
172 | | |
173 | | /* More portability fun: Linux likes to report ELOOP if O_NOFOLLOW |
174 | | * is used to open a symlink file; FreeBSD likes to return EMLINK. |
175 | | * Both would lead to rather misleading error messages being |
176 | | * logged. Catch these errnos, and return the value that properly |
177 | | * informs the caller that the given path was an illegal symlink. |
178 | | */ |
179 | |
|
180 | 0 | switch (errno) { |
181 | 0 | #ifdef ELOOP |
182 | 0 | case ELOOP: |
183 | 0 | return PR_LOG_SYMLINK; |
184 | 0 | #endif /* ELOOP */ |
185 | | |
186 | 0 | #ifdef EMLINK |
187 | 0 | case EMLINK: |
188 | 0 | return PR_LOG_SYMLINK; |
189 | 0 | #endif /* EMLINK */ |
190 | 0 | } |
191 | | |
192 | 0 | return -1; |
193 | |
|
194 | 0 | } else { |
195 | | #if defined(SOLARIS2) |
196 | | /* On Solaris, because of the stupid multiplexing of O_CREAT and |
197 | | * O_EXCL to get open() not to follow a symlink, it's possible that |
198 | | * the path already exists. Now, we'll try to open() without |
199 | | * O_EXCL, then lstat() the path to see if this pre-existing file is |
200 | | * a symlink or a regular file. |
201 | | * |
202 | | * Note that because this check cannot be done atomically on Solaris, |
203 | | * the possibility of a race condition/symlink attack still exists. |
204 | | * Solaris doesn't provide a good way around this situation. |
205 | | */ |
206 | | flags &= ~O_EXCL; |
207 | | |
208 | | *log_fd = open(lf, flags, log_mode); |
209 | | if (*log_fd < 0) { |
210 | | destroy_pool(tmp_pool); |
211 | | return -1; |
212 | | } |
213 | | |
214 | | /* The race condition on Solaris is here, between the open() call |
215 | | * above and the lstat() call below... |
216 | | */ |
217 | | |
218 | | if (lstat(lf, &st) != -1) |
219 | | have_stat = TRUE; |
220 | | #else |
221 | 0 | destroy_pool(tmp_pool); |
222 | 0 | return -1; |
223 | 0 | #endif /* SOLARIS2 */ |
224 | 0 | } |
225 | 0 | } |
226 | | |
227 | | /* Stat the file using the descriptor, not the path */ |
228 | 0 | if (!have_stat && |
229 | 0 | fstat(*log_fd, &st) != -1) { |
230 | 0 | have_stat = TRUE; |
231 | 0 | } |
232 | |
|
233 | 0 | if (!have_stat || |
234 | 0 | S_ISLNK(st.st_mode)) { |
235 | 0 | pr_log_debug(DEBUG0, !have_stat ? "error: unable to stat %s" : |
236 | 0 | "error: %s is a symbolic link", lf); |
237 | |
|
238 | 0 | close(*log_fd); |
239 | 0 | *log_fd = -1; |
240 | 0 | destroy_pool(tmp_pool); |
241 | 0 | return PR_LOG_SYMLINK; |
242 | 0 | } |
243 | |
|
244 | 0 | } else { |
245 | 0 | int flags = O_CREAT|O_APPEND|O_WRONLY; |
246 | |
|
247 | 0 | #ifdef PR_USE_NONBLOCKING_LOG_OPEN |
248 | | /* Use the O_NONBLOCK flag when opening log files, as they might be |
249 | | * FIFOs whose other end is not currently running; we do not want to |
250 | | * block indefinitely in such cases. |
251 | | */ |
252 | 0 | flags |= O_NONBLOCK; |
253 | 0 | #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ |
254 | |
|
255 | 0 | *log_fd = open(lf, flags, log_mode); |
256 | 0 | if (*log_fd < 0) { |
257 | 0 | int xerrno = errno; |
258 | |
|
259 | 0 | destroy_pool(tmp_pool); |
260 | 0 | errno = xerrno; |
261 | 0 | return -1; |
262 | 0 | } |
263 | 0 | } |
264 | | |
265 | | /* Make sure we're dealing with an expected file type (i.e. NOT a |
266 | | * directory). |
267 | | */ |
268 | 0 | if (fstat(*log_fd, &st) < 0) { |
269 | 0 | int xerrno = errno; |
270 | |
|
271 | 0 | pr_log_debug(DEBUG0, "error: unable to stat %s (fd %d): %s", lf, *log_fd, |
272 | 0 | strerror(xerrno)); |
273 | |
|
274 | 0 | (void) close(*log_fd); |
275 | 0 | *log_fd = -1; |
276 | 0 | destroy_pool(tmp_pool); |
277 | |
|
278 | 0 | errno = xerrno; |
279 | 0 | return -1; |
280 | 0 | } |
281 | | |
282 | 0 | if (S_ISDIR(st.st_mode)) { |
283 | 0 | int xerrno = EISDIR; |
284 | |
|
285 | 0 | pr_log_debug(DEBUG0, "error: unable to use %s: %s", lf, strerror(xerrno)); |
286 | |
|
287 | 0 | (void) close(*log_fd); |
288 | 0 | *log_fd = -1; |
289 | 0 | destroy_pool(tmp_pool); |
290 | |
|
291 | 0 | errno = xerrno; |
292 | 0 | return -1; |
293 | 0 | } |
294 | | |
295 | | /* Find a usable fd for the just-opened log fd. */ |
296 | 0 | if (*log_fd <= STDERR_FILENO) { |
297 | 0 | int res; |
298 | |
|
299 | 0 | res = pr_fs_get_usable_fd(*log_fd); |
300 | 0 | if (res < 0) { |
301 | 0 | pr_log_debug(DEBUG0, "warning: unable to find good fd for logfd %d: %s", |
302 | 0 | *log_fd, strerror(errno)); |
303 | |
|
304 | 0 | } else { |
305 | 0 | close(*log_fd); |
306 | 0 | *log_fd = res; |
307 | 0 | } |
308 | 0 | } |
309 | |
|
310 | 0 | if (fcntl(*log_fd, F_SETFD, FD_CLOEXEC) < 0) { |
311 | 0 | pr_log_pri(PR_LOG_WARNING, "unable to set CLO_EXEC on log fd %d: %s", |
312 | 0 | *log_fd, strerror(errno)); |
313 | 0 | } |
314 | | |
315 | | /* Advise the platform that we will be treating this log file as |
316 | | * write-only data. |
317 | | */ |
318 | 0 | pr_fs_fadvise(*log_fd, 0, 0, PR_FS_FADVISE_DONTNEED); |
319 | |
|
320 | 0 | #ifdef PR_USE_NONBLOCKING_LOG_OPEN |
321 | | /* Return the fd to blocking mode. */ |
322 | 0 | (void) fd_set_block(*log_fd); |
323 | 0 | #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ |
324 | |
|
325 | 0 | destroy_pool(tmp_pool); |
326 | 0 | return 0; |
327 | 0 | } |
328 | | |
329 | | int pr_log_vwritefile(int logfd, const char *ident, const char *fmt, |
330 | 0 | va_list msg) { |
331 | 0 | pool *tmp_pool; |
332 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
333 | 0 | struct timeval now; |
334 | 0 | struct tm *tm = NULL; |
335 | 0 | size_t buflen, len; |
336 | 0 | unsigned long millis; |
337 | |
|
338 | 0 | if (logfd < 0 || |
339 | 0 | ident == NULL) { |
340 | 0 | errno = EINVAL; |
341 | 0 | return -1; |
342 | 0 | } |
343 | | |
344 | 0 | tmp_pool = make_sub_pool(permanent_pool); |
345 | 0 | pr_pool_tag(tmp_pool, "Log message pool"); |
346 | |
|
347 | 0 | gettimeofday(&now, NULL); |
348 | 0 | tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); |
349 | 0 | if (tm == NULL) { |
350 | 0 | int xerrno = errno; |
351 | |
|
352 | 0 | destroy_pool(tmp_pool); |
353 | 0 | errno = xerrno; |
354 | 0 | return -1; |
355 | 0 | } |
356 | | |
357 | | /* Prepend the timestamp */ |
358 | 0 | len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); |
359 | 0 | buflen = len; |
360 | 0 | buf[sizeof(buf)-1] = '\0'; |
361 | | |
362 | | /* Convert microsecs to millisecs. */ |
363 | 0 | millis = now.tv_usec / 1000; |
364 | |
|
365 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); |
366 | 0 | buflen += len; |
367 | | |
368 | | /* Prepend a small header */ |
369 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u]: ", ident, |
370 | 0 | (unsigned int) (session.pid ? session.pid : getpid())); |
371 | 0 | buflen += len; |
372 | 0 | buf[sizeof(buf)-1] = '\0'; |
373 | | |
374 | | /* Affix the message */ |
375 | 0 | len = pr_vsnprintf(buf + buflen, sizeof(buf) - buflen - 1, fmt, msg); |
376 | 0 | buflen += len; |
377 | 0 | buf[sizeof(buf)-1] = '\0'; |
378 | |
|
379 | 0 | if (buflen < (sizeof(buf) - 1)) { |
380 | 0 | buf[buflen++] = '\n'; |
381 | |
|
382 | 0 | } else { |
383 | 0 | buf[sizeof(buf)-5] = '.'; |
384 | 0 | buf[sizeof(buf)-4] = '.'; |
385 | 0 | buf[sizeof(buf)-3] = '.'; |
386 | 0 | buf[sizeof(buf)-2] = '\n'; |
387 | 0 | buflen = sizeof(buf)-1; |
388 | 0 | } |
389 | |
|
390 | 0 | pr_log_event_generate(PR_LOG_TYPE_UNSPEC, logfd, -1, buf, buflen); |
391 | 0 | destroy_pool(tmp_pool); |
392 | |
|
393 | 0 | while (write(logfd, buf, buflen) < 0) { |
394 | 0 | if (errno == EINTR) { |
395 | 0 | pr_signals_handle(); |
396 | 0 | continue; |
397 | 0 | } |
398 | | |
399 | 0 | return -1; |
400 | 0 | } |
401 | | |
402 | 0 | return 0; |
403 | 0 | } |
404 | | |
405 | 0 | int pr_log_writefile(int logfd, const char *ident, const char *fmt, ...) { |
406 | 0 | va_list msg; |
407 | 0 | int res; |
408 | |
|
409 | 0 | if (logfd < 0) { |
410 | 0 | errno = EINVAL; |
411 | 0 | return -1; |
412 | 0 | } |
413 | | |
414 | 0 | va_start(msg, fmt); |
415 | 0 | res = pr_log_vwritefile(logfd, ident, fmt, msg); |
416 | 0 | va_end(msg); |
417 | |
|
418 | 0 | return res; |
419 | 0 | } |
420 | | |
421 | 0 | int log_opensyslog(const char *fn) { |
422 | 0 | int res = 0; |
423 | |
|
424 | 0 | if (set_facility != -1) { |
425 | 0 | facility = set_facility; |
426 | 0 | } |
427 | |
|
428 | 0 | if (fn != NULL) { |
429 | 0 | memset(systemlog_fn, '\0', sizeof(systemlog_fn)); |
430 | 0 | sstrncpy(systemlog_fn, fn, sizeof(systemlog_fn)); |
431 | 0 | } |
432 | |
|
433 | 0 | if (!*systemlog_fn) { |
434 | | |
435 | | /* The child may have inherited a valid socket from the parent. */ |
436 | 0 | pr_closelog(syslog_sockfd); |
437 | |
|
438 | 0 | syslog_sockfd = pr_openlog("proftpd", LOG_NDELAY|LOG_PID, facility); |
439 | 0 | if (syslog_sockfd < 0) { |
440 | 0 | int xerrno = errno; |
441 | |
|
442 | 0 | (void) pr_trace_msg(trace_channel, 1, |
443 | 0 | "error opening syslog fd: %s", strerror(xerrno)); |
444 | 0 | errno = xerrno; |
445 | 0 | return -1; |
446 | 0 | } |
447 | | |
448 | | /* Find a usable fd for the just-opened socket fd. */ |
449 | 0 | if (syslog_sockfd <= STDERR_FILENO) { |
450 | 0 | res = pr_fs_get_usable_fd(syslog_sockfd); |
451 | 0 | if (res > 0) { |
452 | 0 | (void) close(syslog_sockfd); |
453 | 0 | syslog_sockfd = res; |
454 | 0 | } |
455 | 0 | } |
456 | |
|
457 | 0 | (void) fcntl(syslog_sockfd, F_SETFD, FD_CLOEXEC); |
458 | 0 | systemlog_fd = -1; |
459 | |
|
460 | 0 | } else if ((res = pr_log_openfile(systemlog_fn, &systemlog_fd, |
461 | 0 | PR_LOG_SYSTEM_MODE)) < 0) { |
462 | 0 | memset(systemlog_fn, '\0', sizeof(systemlog_fn)); |
463 | 0 | return res; |
464 | 0 | } |
465 | | |
466 | 0 | syslog_open = TRUE; |
467 | 0 | return 0; |
468 | 0 | } |
469 | | |
470 | 0 | void log_closesyslog(void) { |
471 | 0 | (void) close(systemlog_fd); |
472 | 0 | systemlog_fd = -1; |
473 | |
|
474 | 0 | (void) pr_closelog(syslog_sockfd); |
475 | 0 | syslog_sockfd = -1; |
476 | |
|
477 | 0 | syslog_open = FALSE; |
478 | 0 | } |
479 | | |
480 | 0 | int log_getfacility(void) { |
481 | 0 | return set_facility; |
482 | 0 | } |
483 | | |
484 | 0 | void log_setfacility(int f) { |
485 | 0 | set_facility = f; |
486 | 0 | } |
487 | | |
488 | 0 | void log_discard(void) { |
489 | 0 | syslog_discard = TRUE; |
490 | 0 | } |
491 | | |
492 | 0 | static size_t add_log_ts(pool *p, char *buf, size_t bufsz) { |
493 | 0 | struct timeval now; |
494 | 0 | struct tm *tm = NULL; |
495 | 0 | unsigned long millis; |
496 | 0 | size_t len; |
497 | |
|
498 | 0 | gettimeofday(&now, NULL); |
499 | 0 | tm = pr_localtime(p, (const time_t *) &(now.tv_sec)); |
500 | 0 | if (tm == NULL) { |
501 | 0 | return 0; |
502 | 0 | } |
503 | | |
504 | 0 | len = strftime(buf, bufsz-1, "%Y-%m-%d %H:%M:%S", tm); |
505 | 0 | buf[bufsz-1] = '\0'; |
506 | | |
507 | | /* Convert microsecs to millisecs. */ |
508 | 0 | millis = now.tv_usec / 1000; |
509 | |
|
510 | 0 | len += pr_snprintf(buf + len, bufsz - len, ",%03lu ", millis); |
511 | 0 | return len; |
512 | 0 | } |
513 | | |
514 | 0 | static void log_write(int priority, int f, char *s, int discard) { |
515 | 0 | int max_priority = 0, *ptr = NULL; |
516 | 0 | char serverinfo[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; |
517 | |
|
518 | 0 | memset(serverinfo, '\0', sizeof(serverinfo)); |
519 | |
|
520 | 0 | if (main_server != NULL && |
521 | 0 | main_server->ServerFQDN) { |
522 | 0 | const pr_netaddr_t *remote_addr; |
523 | 0 | const char *remote_name; |
524 | 0 | size_t buflen = 0; |
525 | |
|
526 | 0 | remote_addr = pr_netaddr_get_sess_remote_addr(); |
527 | 0 | remote_name = pr_netaddr_get_sess_remote_name(); |
528 | |
|
529 | 0 | if (log_opts & PR_LOG_OPT_USE_VHOST) { |
530 | 0 | size_t len; |
531 | |
|
532 | 0 | len = pr_snprintf(serverinfo, sizeof(serverinfo)-1, "%s", |
533 | 0 | main_server->ServerFQDN); |
534 | 0 | buflen += len; |
535 | 0 | } |
536 | |
|
537 | 0 | serverinfo[sizeof(serverinfo)-1] = '\0'; |
538 | |
|
539 | 0 | if (remote_addr != NULL && |
540 | 0 | remote_name != NULL) { |
541 | 0 | pr_snprintf(serverinfo + buflen, sizeof(serverinfo) - buflen, |
542 | 0 | "%s(%s[%s])", (log_opts & PR_LOG_OPT_USE_VHOST) ? " " : "", |
543 | 0 | remote_name, pr_netaddr_get_ipstr(remote_addr)); |
544 | 0 | serverinfo[sizeof(serverinfo)-1] = '\0'; |
545 | 0 | } |
546 | 0 | } |
547 | |
|
548 | 0 | if (!discard && |
549 | 0 | (logstderr || !main_server)) { |
550 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
551 | 0 | size_t buflen = 0, len = 0; |
552 | 0 | pid_t log_pid; |
553 | 0 | const char *process_label = "proftpd"; |
554 | |
|
555 | 0 | if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { |
556 | 0 | pool *tmp_pool; |
557 | |
|
558 | 0 | tmp_pool = make_sub_pool(permanent_pool); |
559 | 0 | pr_pool_tag(tmp_pool, "Log message pool"); |
560 | |
|
561 | 0 | len = add_log_ts(tmp_pool, buf, sizeof(buf)); |
562 | 0 | destroy_pool(tmp_pool); |
563 | |
|
564 | 0 | buflen += len; |
565 | 0 | } |
566 | |
|
567 | 0 | buf[sizeof(buf)-1] = '\0'; |
568 | |
|
569 | 0 | if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { |
570 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", |
571 | 0 | systemlog_host); |
572 | 0 | buflen += len; |
573 | 0 | } |
574 | |
|
575 | 0 | log_pid = session.pid ? session.pid : getpid(); |
576 | |
|
577 | 0 | if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { |
578 | 0 | if (is_master) { |
579 | 0 | process_label = "daemon"; |
580 | |
|
581 | 0 | } else { |
582 | 0 | process_label = "session"; |
583 | 0 | } |
584 | 0 | } |
585 | |
|
586 | 0 | if (*serverinfo) { |
587 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, |
588 | 0 | "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, |
589 | 0 | s); |
590 | |
|
591 | 0 | } else { |
592 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, |
593 | 0 | "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); |
594 | 0 | } |
595 | |
|
596 | 0 | buflen += len; |
597 | 0 | buf[sizeof(buf)-1] = '\0'; |
598 | |
|
599 | 0 | pr_log_event_generate(PR_LOG_TYPE_SYSTEMLOG, STDERR_FILENO, priority, |
600 | 0 | buf, buflen); |
601 | |
|
602 | 0 | fprintf(stderr, "%s", buf); |
603 | 0 | fflush(stderr); |
604 | 0 | return; |
605 | 0 | } |
606 | | |
607 | 0 | if (syslog_discard) { |
608 | | /* Only return now if we don't have any log listeners. */ |
609 | 0 | if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && |
610 | 0 | pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { |
611 | 0 | return; |
612 | 0 | } |
613 | 0 | } |
614 | | |
615 | 0 | if (main_server != NULL) { |
616 | 0 | ptr = get_param_ptr(main_server->conf, "SyslogLevel", FALSE); |
617 | 0 | } |
618 | |
|
619 | 0 | if (ptr != NULL) { |
620 | 0 | max_priority = *ptr; |
621 | |
|
622 | 0 | } else { |
623 | | /* Default SyslogLevel is NOTICE. Note, however, that for backward |
624 | | * compatibility of debugging, if the DebugLevel is set higher |
625 | | * than DEBUG0, we will automatically ASSUME that the admin wants |
626 | | * the syslog level to be e.g. DEBUG. |
627 | | */ |
628 | 0 | max_priority = default_level; |
629 | 0 | if (debug_level != DEBUG0) { |
630 | 0 | max_priority = PR_LOG_DEBUG; |
631 | 0 | } |
632 | 0 | } |
633 | |
|
634 | 0 | if (priority > max_priority) { |
635 | | /* Only return now if we don't have any log listeners. */ |
636 | 0 | if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && |
637 | 0 | pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { |
638 | 0 | return; |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | 0 | if (systemlog_fd != -1) { |
643 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
644 | 0 | size_t buflen = 0, len = 0; |
645 | 0 | pid_t log_pid; |
646 | 0 | const char *process_label = "proftpd"; |
647 | |
|
648 | 0 | if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { |
649 | 0 | pool *tmp_pool; |
650 | |
|
651 | 0 | tmp_pool = make_sub_pool(permanent_pool); |
652 | 0 | pr_pool_tag(tmp_pool, "Log message pool"); |
653 | |
|
654 | 0 | len = add_log_ts(tmp_pool, buf, sizeof(buf)); |
655 | 0 | destroy_pool(tmp_pool); |
656 | |
|
657 | 0 | buflen += len; |
658 | 0 | } |
659 | |
|
660 | 0 | buf[sizeof(buf) - 1] = '\0'; |
661 | |
|
662 | 0 | if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { |
663 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", |
664 | 0 | systemlog_host); |
665 | 0 | buflen += len; |
666 | 0 | } |
667 | |
|
668 | 0 | log_pid = session.pid ? session.pid : getpid(); |
669 | |
|
670 | 0 | if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { |
671 | 0 | if (is_master) { |
672 | 0 | process_label = "daemon"; |
673 | |
|
674 | 0 | } else { |
675 | 0 | process_label = "session"; |
676 | 0 | } |
677 | 0 | } |
678 | |
|
679 | 0 | if (*serverinfo) { |
680 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, |
681 | 0 | "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, |
682 | 0 | s); |
683 | |
|
684 | 0 | } else { |
685 | 0 | len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, |
686 | 0 | "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); |
687 | 0 | } |
688 | |
|
689 | 0 | buflen += len; |
690 | 0 | buf[sizeof(buf)-1] = '\0'; |
691 | |
|
692 | 0 | pr_log_event_generate(PR_LOG_TYPE_SYSTEMLOG, systemlog_fd, priority, |
693 | 0 | buf, buflen); |
694 | | |
695 | | /* Now we need to enforce the discard, syslog_discard and SyslogLevel |
696 | | * filtering. |
697 | | */ |
698 | 0 | if (discard) { |
699 | 0 | return; |
700 | 0 | } |
701 | | |
702 | 0 | if (syslog_discard) { |
703 | 0 | return; |
704 | 0 | } |
705 | | |
706 | 0 | if (priority > max_priority) { |
707 | 0 | return; |
708 | 0 | } |
709 | | |
710 | 0 | while (write(systemlog_fd, buf, buflen) < 0) { |
711 | 0 | if (errno == EINTR) { |
712 | 0 | pr_signals_handle(); |
713 | 0 | continue; |
714 | 0 | } |
715 | | |
716 | 0 | break; |
717 | 0 | } |
718 | |
|
719 | 0 | return; |
720 | 0 | } |
721 | | |
722 | 0 | pr_log_event_generate(PR_LOG_TYPE_SYSLOG, syslog_sockfd, priority, s, |
723 | 0 | strlen(s)); |
724 | |
|
725 | 0 | if (set_facility != -1) { |
726 | 0 | f = set_facility; |
727 | 0 | } |
728 | |
|
729 | 0 | if (!syslog_open) { |
730 | 0 | syslog_sockfd = pr_openlog("proftpd", LOG_NDELAY|LOG_PID, f); |
731 | 0 | if (syslog_sockfd < 0) { |
732 | 0 | (void) pr_trace_msg(trace_channel, 1, |
733 | 0 | "error opening syslog fd: %s", strerror(errno)); |
734 | 0 | return; |
735 | 0 | } |
736 | | |
737 | 0 | syslog_open = TRUE; |
738 | |
|
739 | 0 | } else if (f != facility) { |
740 | | /* If this message is to be sent to a different log facility than a |
741 | | * default one (or the facility configured via SyslogFacility), then |
742 | | * OR in the facility with the priority value, as per the syslog(3) |
743 | | * docs. |
744 | | */ |
745 | 0 | priority |= f; |
746 | 0 | } |
747 | | |
748 | 0 | if (*serverinfo) { |
749 | 0 | pr_syslog(syslog_sockfd, priority, "%s - %s\n", serverinfo, s); |
750 | |
|
751 | 0 | } else { |
752 | 0 | pr_syslog(syslog_sockfd, priority, "%s\n", s); |
753 | 0 | } |
754 | 0 | } |
755 | | |
756 | 0 | void pr_log_pri(int priority, const char *fmt, ...) { |
757 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
758 | 0 | va_list msg; |
759 | |
|
760 | 0 | va_start(msg, fmt); |
761 | 0 | pr_vsnprintf(buf, sizeof(buf), fmt, msg); |
762 | 0 | va_end(msg); |
763 | | |
764 | | /* Always make sure the buffer is NUL-terminated. */ |
765 | 0 | buf[sizeof(buf) - 1] = '\0'; |
766 | |
|
767 | 0 | log_write(priority, facility, buf, FALSE); |
768 | 0 | } |
769 | | |
770 | | /* Like pr_log_pri(), but sends the log entry in the LOG_AUTHPRIV |
771 | | * facility (presumably it doesn't need to be seen by everyone). |
772 | | */ |
773 | 0 | void pr_log_auth(int priority, const char *fmt, ...) { |
774 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
775 | 0 | va_list msg; |
776 | |
|
777 | 0 | va_start(msg, fmt); |
778 | 0 | pr_vsnprintf(buf, sizeof(buf), fmt, msg); |
779 | 0 | va_end(msg); |
780 | | |
781 | | /* Always make sure the buffer is NUL-terminated. */ |
782 | 0 | buf[sizeof(buf) - 1] = '\0'; |
783 | |
|
784 | 0 | log_write(priority, LOG_AUTHPRIV, buf, FALSE); |
785 | 0 | } |
786 | | |
787 | | /* Disable logging to stderr, should be done right before forking |
788 | | * or disassociation from controlling tty. After disabling stderr |
789 | | * logging, all messages go to syslog. |
790 | | */ |
791 | 0 | void log_stderr(int bool) { |
792 | 0 | logstderr = bool; |
793 | 0 | } |
794 | | |
795 | | /* Set the debug logging level; see log.h for constants. Higher |
796 | | * numbers mean print more, DEBUG0 (0) == print no debugging log |
797 | | * (default) |
798 | | */ |
799 | 0 | int pr_log_setdebuglevel(int level) { |
800 | 0 | int old_level = debug_level; |
801 | 0 | debug_level = level; |
802 | 0 | return old_level; |
803 | 0 | } |
804 | | |
805 | | /* Set the default logging level; see log.h for constants. */ |
806 | 0 | int pr_log_setdefaultlevel(int level) { |
807 | 0 | int old_level = default_level; |
808 | 0 | default_level = level; |
809 | 0 | return old_level; |
810 | 0 | } |
811 | | |
812 | 0 | int pr_log_set_options(unsigned long opts) { |
813 | 0 | log_opts = opts; |
814 | 0 | return 0; |
815 | 0 | } |
816 | | |
817 | | /* Convert a string into the matching syslog level value. Return -1 |
818 | | * if no matching level is found. |
819 | | */ |
820 | 0 | int pr_log_str2sysloglevel(const char *name) { |
821 | |
|
822 | 0 | if (strcasecmp(name, "emerg") == 0) { |
823 | 0 | return PR_LOG_EMERG; |
824 | 0 | } |
825 | | |
826 | 0 | if (strcasecmp(name, "alert") == 0) { |
827 | 0 | return PR_LOG_ALERT; |
828 | 0 | } |
829 | | |
830 | 0 | if (strcasecmp(name, "crit") == 0) { |
831 | 0 | return PR_LOG_CRIT; |
832 | 0 | } |
833 | | |
834 | 0 | if (strcasecmp(name, "error") == 0) { |
835 | 0 | return PR_LOG_ERR; |
836 | 0 | } |
837 | | |
838 | 0 | if (strcasecmp(name, "warn") == 0) { |
839 | 0 | return PR_LOG_WARNING; |
840 | 0 | } |
841 | | |
842 | 0 | if (strcasecmp(name, "notice") == 0) { |
843 | 0 | return PR_LOG_NOTICE; |
844 | 0 | } |
845 | | |
846 | 0 | if (strcasecmp(name, "info") == 0) { |
847 | 0 | return PR_LOG_INFO; |
848 | 0 | } |
849 | | |
850 | 0 | if (strcasecmp(name, "debug") == 0) { |
851 | 0 | return PR_LOG_DEBUG; |
852 | 0 | } |
853 | | |
854 | 0 | errno = ENOENT; |
855 | 0 | return -1; |
856 | 0 | } |
857 | | |
858 | 0 | void pr_log_debug(int level, const char *fmt, ...) { |
859 | 0 | char buf[LOGBUFFER_SIZE] = {'\0'}; |
860 | 0 | va_list msg; |
861 | 0 | int discard = FALSE; |
862 | |
|
863 | 0 | if (debug_level < level) { |
864 | 0 | discard = TRUE; |
865 | |
|
866 | 0 | if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && |
867 | 0 | pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { |
868 | 0 | return; |
869 | 0 | } |
870 | 0 | } |
871 | | |
872 | 0 | if (fmt == NULL) { |
873 | 0 | return; |
874 | 0 | } |
875 | | |
876 | 0 | memset(buf, '\0', sizeof(buf)); |
877 | 0 | va_start(msg, fmt); |
878 | 0 | pr_vsnprintf(buf, sizeof(buf), fmt, msg); |
879 | 0 | va_end(msg); |
880 | | |
881 | | /* Always make sure the buffer is NUL-terminated. */ |
882 | 0 | buf[sizeof(buf) - 1] = '\0'; |
883 | |
|
884 | 0 | log_write(PR_LOG_DEBUG, facility, buf, discard); |
885 | 0 | } |
886 | | |
887 | 1.42k | static const char *get_log_event_name(unsigned int log_type) { |
888 | 1.42k | const char *event_name = NULL; |
889 | | |
890 | 1.42k | switch (log_type) { |
891 | 0 | case PR_LOG_TYPE_UNSPEC: |
892 | 0 | event_name = PR_LOG_NAME_UNSPEC; |
893 | 0 | break; |
894 | | |
895 | 0 | case PR_LOG_TYPE_XFERLOG: |
896 | 0 | event_name = PR_LOG_NAME_XFERLOG; |
897 | 0 | break; |
898 | | |
899 | 0 | case PR_LOG_TYPE_SYSLOG: |
900 | 0 | event_name = PR_LOG_NAME_SYSLOG; |
901 | 0 | break; |
902 | | |
903 | 0 | case PR_LOG_TYPE_SYSTEMLOG: |
904 | 0 | event_name = PR_LOG_NAME_SYSTEMLOG; |
905 | 0 | break; |
906 | | |
907 | 0 | case PR_LOG_TYPE_EXTLOG: |
908 | 0 | event_name = PR_LOG_NAME_EXTLOG; |
909 | 0 | break; |
910 | | |
911 | 1.42k | case PR_LOG_TYPE_TRACELOG: |
912 | 1.42k | event_name = PR_LOG_NAME_TRACELOG; |
913 | 1.42k | break; |
914 | | |
915 | 0 | default: |
916 | 0 | errno = EINVAL; |
917 | 0 | return NULL; |
918 | 1.42k | } |
919 | | |
920 | 1.42k | return event_name; |
921 | 1.42k | } |
922 | | |
923 | | int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level, |
924 | 0 | const char *log_msg, size_t log_msglen) { |
925 | 0 | const char *event_name; |
926 | 0 | pr_log_event_t le; |
927 | |
|
928 | 0 | if (log_msg == NULL || |
929 | 0 | log_msglen == 0) { |
930 | 0 | errno = EINVAL; |
931 | 0 | return -1; |
932 | 0 | } |
933 | | |
934 | 0 | if (pr_log_event_listening(log_type) <= 0) { |
935 | 0 | errno = ENOENT; |
936 | 0 | return -1; |
937 | 0 | } |
938 | | |
939 | 0 | event_name = get_log_event_name(log_type); |
940 | |
|
941 | 0 | memset(&le, 0, sizeof(le)); |
942 | 0 | le.log_type = log_type; |
943 | 0 | le.log_fd = log_fd; |
944 | 0 | le.log_level = log_level; |
945 | 0 | le.log_msg = log_msg; |
946 | 0 | le.log_msglen = log_msglen; |
947 | |
|
948 | 0 | pr_event_generate(event_name, &le); |
949 | 0 | return 0; |
950 | 0 | } |
951 | | |
952 | 1.42k | int pr_log_event_listening(unsigned int log_type) { |
953 | 1.42k | const char *event_name; |
954 | 1.42k | int res; |
955 | | |
956 | 1.42k | event_name = get_log_event_name(log_type); |
957 | 1.42k | if (event_name == NULL) { |
958 | 0 | return FALSE; |
959 | 0 | } |
960 | | |
961 | 1.42k | res = pr_event_listening(event_name); |
962 | 1.42k | if (res <= 0) { |
963 | 1.42k | return FALSE; |
964 | 1.42k | } |
965 | | |
966 | 0 | return TRUE; |
967 | 1.42k | } |
968 | | |
969 | 0 | void pr_log_stacktrace(int log_fd, const char *name) { |
970 | 0 | #if defined(HAVE_EXECINFO_H) && \ |
971 | 0 | defined(HAVE_BACKTRACE) && \ |
972 | 0 | defined(HAVE_BACKTRACE_SYMBOLS) |
973 | 0 | void *trace[PR_TUNABLE_CALLER_DEPTH]; |
974 | 0 | int tracesz, use_fd = TRUE; |
975 | |
|
976 | 0 | if (log_fd < 0 || |
977 | 0 | name == NULL) { |
978 | 0 | use_fd = FALSE; |
979 | 0 | } |
980 | |
|
981 | 0 | if (use_fd) { |
982 | 0 | (void) pr_log_writefile(log_fd, name, "%s", "-----BEGIN STACK TRACE-----"); |
983 | |
|
984 | 0 | } else { |
985 | 0 | (void) pr_log_pri(PR_LOG_WARNING, "-----BEGIN STACK TRACE-----"); |
986 | 0 | } |
987 | |
|
988 | 0 | tracesz = backtrace(trace, PR_TUNABLE_CALLER_DEPTH); |
989 | 0 | if (tracesz < 0) { |
990 | 0 | if (use_fd) { |
991 | 0 | (void) pr_log_writefile(log_fd, name, "backtrace(3) error: %s", |
992 | 0 | strerror(errno)); |
993 | |
|
994 | 0 | } else { |
995 | 0 | (void) pr_log_pri(PR_LOG_WARNING, "backtrace(3) error: %s", |
996 | 0 | strerror(errno)); |
997 | 0 | } |
998 | |
|
999 | 0 | } else { |
1000 | 0 | char **strings; |
1001 | |
|
1002 | 0 | strings = backtrace_symbols(trace, tracesz); |
1003 | 0 | if (strings != NULL) { |
1004 | 0 | register int i; |
1005 | |
|
1006 | 0 | for (i = 1; i < tracesz; i++) { |
1007 | 0 | if (use_fd) { |
1008 | 0 | (void) pr_log_writefile(log_fd, name, "[%d] %s", i-1, strings[i]); |
1009 | |
|
1010 | 0 | } else { |
1011 | 0 | (void) pr_log_pri(PR_LOG_WARNING, "[%d] %s", i-1, strings[i]); |
1012 | 0 | } |
1013 | 0 | } |
1014 | | |
1015 | | /* Prevent memory leaks. */ |
1016 | 0 | free(strings); |
1017 | |
|
1018 | 0 | } else { |
1019 | 0 | if (use_fd) { |
1020 | 0 | (void) pr_log_writefile(log_fd, name, |
1021 | 0 | "error obtaining backtrace symbols: %s", strerror(errno)); |
1022 | |
|
1023 | 0 | } else { |
1024 | 0 | (void) pr_log_pri(PR_LOG_WARNING, |
1025 | 0 | "error obtaining backtrace symbols: %s", strerror(errno)); |
1026 | 0 | } |
1027 | 0 | } |
1028 | 0 | } |
1029 | |
|
1030 | 0 | if (use_fd) { |
1031 | 0 | (void) pr_log_writefile(log_fd, name, "%s", "-----END STACK TRACE-----"); |
1032 | |
|
1033 | 0 | } else { |
1034 | 0 | (void) pr_log_pri(PR_LOG_WARNING, "%s", "-----END STACK TRACE-----"); |
1035 | 0 | } |
1036 | 0 | #endif |
1037 | 0 | } |
1038 | | |
1039 | 0 | void init_log(void) { |
1040 | 0 | char buf[256]; |
1041 | |
|
1042 | 0 | memset(buf, '\0', sizeof(buf)); |
1043 | 0 | if (gethostname(buf, sizeof(buf)) < 0) { |
1044 | 0 | sstrncpy(buf, "localhost", sizeof(buf)); |
1045 | 0 | } |
1046 | |
|
1047 | 0 | sstrncpy(systemlog_host, (char *) pr_netaddr_validate_dns_str(buf), |
1048 | 0 | sizeof(systemlog_host)); |
1049 | 0 | memset(systemlog_fn, '\0', sizeof(systemlog_fn)); |
1050 | 0 | log_closesyslog(); |
1051 | 0 | } |