/* * ProFTPD - FTP server daemon * Copyright (c) 1997, 1998 Public Flood Software * Copyright (c) 1999, 2000 MacGyver aka Habeeb J. Dihu * Copyright (c) 2001-2020 The ProFTPD Project team * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Suite 500, Boston, MA 02110-1335, USA. * * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu * and other respective copyright holders give permission to link this program * with OpenSSL, and distribute the resulting executable, without including * the source code for OpenSSL in the source distribution. */ /* ProFTPD logging support. */ #include "conf.h" #ifdef HAVE_EXECINFO_H # include #endif #define LOGBUFFER_SIZE (PR_TUNABLE_PATH_MAX * 4) /* From src/main.c */ extern unsigned char is_master; static int syslog_open = FALSE; static int syslog_discard = FALSE; static unsigned long log_opts = PR_LOG_OPT_DEFAULT; static int logstderr = TRUE; static int debug_level = DEBUG0; static int default_level = PR_LOG_NOTICE; static int facility = LOG_DAEMON; static int set_facility = -1; static char systemlog_fn[PR_TUNABLE_PATH_MAX] = {'\0'}; static char systemlog_host[256] = {'\0'}; static int systemlog_fd = -1; static const char *trace_channel = "log"; int syslog_sockfd = -1; #ifdef PR_USE_NONBLOCKING_LOG_OPEN static int fd_set_block(int fd) { int flags, res; flags = fcntl(fd, F_GETFL); res = fcntl(fd, F_SETFL, flags & (U32BITS ^ O_NONBLOCK)); return res; } #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ int pr_log_openfile(const char *log_file, int *log_fd, mode_t log_mode) { int res; pool *tmp_pool = NULL; char *ptr = NULL, *lf; unsigned char have_stat = FALSE, *allow_log_symlinks = NULL; struct stat st; /* Sanity check */ if (log_file == NULL || log_fd == NULL) { errno = EINVAL; return -1; } /* Make a temporary copy of log_file in case it's a constant */ tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "log_openfile() tmp pool"); lf = pstrdup(tmp_pool, log_file); ptr = strrchr(lf, '/'); if (ptr == NULL) { pr_log_debug(DEBUG0, "inappropriate log file: %s", lf); destroy_pool(tmp_pool); errno = EINVAL; return -1; } /* Set the path separator to zero, in order to obtain the directory * name, so that checks of the directory may be made. */ if (ptr != lf) { *ptr = '\0'; } if (stat(lf, &st) < 0) { int xerrno = errno; pr_log_debug(DEBUG0, "error: unable to stat() %s: %s", lf, strerror(errno)); destroy_pool(tmp_pool); errno = xerrno; return -1; } /* The path must be in a valid directory */ if (!S_ISDIR(st.st_mode)) { pr_log_debug(DEBUG0, "error: %s is not a directory", lf); destroy_pool(tmp_pool); errno = ENOTDIR; return -1; } /* Do not log to world-writable directories */ if (st.st_mode & S_IWOTH) { pr_log_pri(PR_LOG_NOTICE, "error: %s is a world-writable directory", lf); destroy_pool(tmp_pool); return PR_LOG_WRITABLE_DIR; } /* Restore the path separator so that checks on the file itself may be * done. */ if (ptr != lf) { *ptr = '/'; } allow_log_symlinks = get_param_ptr(main_server->conf, "AllowLogSymlinks", FALSE); if (allow_log_symlinks == NULL || *allow_log_symlinks == FALSE) { int flags = O_APPEND|O_CREAT|O_WRONLY; #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Use the O_NONBLOCK flag when opening log files, as they might be * FIFOs whose other end is not currently running; we do not want to * block indefinitely in such cases. */ flags |= O_NONBLOCK; #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ #ifdef O_NOFOLLOW /* On systems that support the O_NOFOLLOW flag (e.g. Linux and FreeBSD), * use it so that the path being opened, if it is a symlink, is not * followed. */ flags |= O_NOFOLLOW; #elif defined(SOLARIS2) /* Solaris doesn't support the O_NOFOLLOW flag. Instead, in their * wisdom (hah!), Solaris decided that if the given path is a symlink * and the flags O_CREAT and O_EXCL are set, the link is not followed. * Right. The problem here is the case where the path is not a symlink; * using O_CREAT|O_EXCL will then cause the open() to fail if the * file already exists. */ flags |= O_EXCL; #endif /* O_NOFOLLOW or SOLARIS2 */ *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { if (errno != EEXIST) { destroy_pool(tmp_pool); /* More portability fun: Linux likes to report ELOOP if O_NOFOLLOW * is used to open a symlink file; FreeBSD likes to return EMLINK. * Both would lead to rather misleading error messages being * logged. Catch these errnos, and return the value that properly * informs the caller that the given path was an illegal symlink. */ switch (errno) { #ifdef ELOOP case ELOOP: return PR_LOG_SYMLINK; #endif /* ELOOP */ #ifdef EMLINK case EMLINK: return PR_LOG_SYMLINK; #endif /* EMLINK */ } return -1; } else { #if defined(SOLARIS2) /* On Solaris, because of the stupid multiplexing of O_CREAT and * O_EXCL to get open() not to follow a symlink, it's possible that * the path already exists. Now, we'll try to open() without * O_EXCL, then lstat() the path to see if this pre-existing file is * a symlink or a regular file. * * Note that because this check cannot be done atomically on Solaris, * the possibility of a race condition/symlink attack still exists. * Solaris doesn't provide a good way around this situation. */ flags &= ~O_EXCL; *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { destroy_pool(tmp_pool); return -1; } /* The race condition on Solaris is here, between the open() call * above and the lstat() call below... */ if (lstat(lf, &st) != -1) have_stat = TRUE; #else destroy_pool(tmp_pool); return -1; #endif /* SOLARIS2 */ } } /* Stat the file using the descriptor, not the path */ if (!have_stat && fstat(*log_fd, &st) != -1) { have_stat = TRUE; } if (!have_stat || S_ISLNK(st.st_mode)) { pr_log_debug(DEBUG0, !have_stat ? "error: unable to stat %s" : "error: %s is a symbolic link", lf); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); return PR_LOG_SYMLINK; } } else { int flags = O_CREAT|O_APPEND|O_WRONLY; #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Use the O_NONBLOCK flag when opening log files, as they might be * FIFOs whose other end is not currently running; we do not want to * block indefinitely in such cases. */ flags |= O_NONBLOCK; #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ *log_fd = open(lf, flags, log_mode); if (*log_fd < 0) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } } /* Make sure we're dealing with an expected file type (i.e. NOT a * directory). */ if (fstat(*log_fd, &st) < 0) { int xerrno = errno; pr_log_debug(DEBUG0, "error: unable to stat %s (fd %d): %s", lf, *log_fd, strerror(xerrno)); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); errno = xerrno; return -1; } if (S_ISDIR(st.st_mode)) { int xerrno = EISDIR; pr_log_debug(DEBUG0, "error: unable to use %s: %s", lf, strerror(xerrno)); close(*log_fd); *log_fd = -1; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Find a usable fd for the just-opened log fd. */ if (*log_fd <= STDERR_FILENO) { res = pr_fs_get_usable_fd(*log_fd); if (res < 0) { pr_log_debug(DEBUG0, "warning: unable to find good fd for logfd %d: %s", *log_fd, strerror(errno)); } else { close(*log_fd); *log_fd = res; } } if (fcntl(*log_fd, F_SETFD, FD_CLOEXEC) < 0) { pr_log_pri(PR_LOG_WARNING, "unable to set CLO_EXEC on log fd %d: %s", *log_fd, strerror(errno)); } /* Advise the platform that we will be treating this log file as * write-only data. */ pr_fs_fadvise(*log_fd, 0, 0, PR_FS_FADVISE_DONTNEED); #ifdef PR_USE_NONBLOCKING_LOG_OPEN /* Return the fd to blocking mode. */ (void) fd_set_block(*log_fd); #endif /* PR_USE_NONBLOCKING_LOG_OPEN */ destroy_pool(tmp_pool); return 0; } int pr_log_vwritefile(int logfd, const char *ident, const char *fmt, va_list msg) { pool *tmp_pool; char buf[LOGBUFFER_SIZE] = {'\0'}; struct timeval now; struct tm *tm = NULL; size_t buflen, len; unsigned long millis; if (logfd < 0) { errno = EINVAL; return -1; } tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "Log message pool"); gettimeofday(&now, NULL); tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); if (tm == NULL) { int xerrno = errno; destroy_pool(tmp_pool); errno = xerrno; return -1; } /* Prepend the timestamp */ len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); buflen = len; buf[sizeof(buf)-1] = '\0'; /* Convert microsecs to millisecs. */ millis = now.tv_usec / 1000; len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); buflen += len; /* Prepend a small header */ len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u]: ", ident, (unsigned int) (session.pid ? session.pid : getpid())); buflen += len; buf[sizeof(buf)-1] = '\0'; /* Affix the message */ len = pr_vsnprintf(buf + buflen, sizeof(buf) - buflen - 1, fmt, msg); buflen += len; buf[sizeof(buf)-1] = '\0'; if (buflen < (sizeof(buf) - 1)) { buf[buflen++] = '\n'; } else { buf[sizeof(buf)-5] = '.'; buf[sizeof(buf)-4] = '.'; buf[sizeof(buf)-3] = '.'; buf[sizeof(buf)-2] = '\n'; buflen = sizeof(buf)-1; } pr_log_event_generate(PR_LOG_TYPE_UNSPEC, logfd, -1, buf, buflen); destroy_pool(tmp_pool); while (write(logfd, buf, buflen) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } return -1; } return 0; } int pr_log_writefile(int logfd, const char *ident, const char *fmt, ...) { va_list msg; int res; if (logfd < 0) { errno = EINVAL; return -1; } va_start(msg, fmt); res = pr_log_vwritefile(logfd, ident, fmt, msg); va_end(msg); return res; } int log_opensyslog(const char *fn) { int res = 0; if (set_facility != -1) facility = set_facility; if (fn) { memset(systemlog_fn, '\0', sizeof(systemlog_fn)); sstrncpy(systemlog_fn, fn, sizeof(systemlog_fn)); } if (!*systemlog_fn) { /* The child may have inherited a valid socket from the parent. */ pr_closelog(syslog_sockfd); syslog_sockfd = pr_openlog("proftpd", LOG_NDELAY|LOG_PID, facility); if (syslog_sockfd < 0) { int xerrno = errno; (void) pr_trace_msg(trace_channel, 1, "error opening syslog fd: %s", strerror(xerrno)); errno = xerrno; return -1; } /* Find a usable fd for the just-opened socket fd. */ if (syslog_sockfd <= STDERR_FILENO) { res = pr_fs_get_usable_fd(syslog_sockfd); if (res > 0) { (void) close(syslog_sockfd); syslog_sockfd = res; } } (void) fcntl(syslog_sockfd, F_SETFD, FD_CLOEXEC); systemlog_fd = -1; } else if ((res = pr_log_openfile(systemlog_fn, &systemlog_fd, PR_LOG_SYSTEM_MODE)) < 0) { memset(systemlog_fn, '\0', sizeof(systemlog_fn)); return res; } syslog_open = TRUE; return 0; } void log_closesyslog(void) { (void) close(systemlog_fd); systemlog_fd = -1; (void) pr_closelog(syslog_sockfd); syslog_sockfd = -1; syslog_open = FALSE; } int log_getfacility(void) { return set_facility; } void log_setfacility(int f) { set_facility = f; } void log_discard(void) { syslog_discard = TRUE; } static void log_write(int priority, int f, char *s, int discard) { int max_priority = 0, *ptr = NULL; char serverinfo[PR_TUNABLE_BUFFER_SIZE] = {'\0'}; memset(serverinfo, '\0', sizeof(serverinfo)); if (main_server != NULL && main_server->ServerFQDN) { const pr_netaddr_t *remote_addr; const char *remote_name; size_t buflen = 0; remote_addr = pr_netaddr_get_sess_remote_addr(); remote_name = pr_netaddr_get_sess_remote_name(); if (log_opts & PR_LOG_OPT_USE_VHOST) { size_t len; len = pr_snprintf(serverinfo, sizeof(serverinfo)-1, "%s", main_server->ServerFQDN); buflen += len; } serverinfo[sizeof(serverinfo)-1] = '\0'; if (remote_addr != NULL && remote_name != NULL) { pr_snprintf(serverinfo + buflen, sizeof(serverinfo) - buflen, "%s(%s[%s])", (log_opts & PR_LOG_OPT_USE_VHOST) ? " " : "", remote_name, pr_netaddr_get_ipstr(remote_addr)); serverinfo[sizeof(serverinfo)-1] = '\0'; } } if (!discard && (logstderr || !main_server)) { char buf[LOGBUFFER_SIZE] = {'\0'}; size_t buflen = 0, len = 0; pid_t log_pid; const char *process_label = "proftpd"; if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { pool *tmp_pool; struct timeval now; struct tm *tm = NULL; unsigned long millis; tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "Log message pool"); gettimeofday(&now, NULL); tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); if (tm == NULL) { destroy_pool(tmp_pool); return; } len = strftime(buf, sizeof(buf)-1, "%Y-%m-%d %H:%M:%S", tm); buflen = len; buf[sizeof(buf)-1] = '\0'; destroy_pool(tmp_pool); /* Convert microsecs to millisecs. */ millis = now.tv_usec / 1000; len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); buflen += len; } buf[sizeof(buf)-1] = '\0'; if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", systemlog_host); buflen += len; } log_pid = session.pid ? session.pid : getpid(); if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { if (is_master) { process_label = "daemon"; } else { process_label = "session"; } } if (*serverinfo) { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, s); } else { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); } buflen += len; buf[sizeof(buf)-1] = '\0'; pr_log_event_generate(PR_LOG_TYPE_SYSTEMLOG, STDERR_FILENO, priority, buf, buflen); fprintf(stderr, "%s", buf); fflush(stderr); return; } if (syslog_discard) { /* Only return now if we don't have any log listeners. */ if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { return; } } if (main_server != NULL) { ptr = get_param_ptr(main_server->conf, "SyslogLevel", FALSE); } if (ptr != NULL) { max_priority = *ptr; } else { /* Default SyslogLevel is NOTICE. Note, however, that for backward * compatibility of debugging, if the DebugLevel is set higher * than DEBUG0, we will automatically ASSUME that the admin wants * the syslog level to be e.g. DEBUG. */ max_priority = default_level; if (debug_level != DEBUG0) { max_priority = PR_LOG_DEBUG; } } if (priority > max_priority) { /* Only return now if we don't have any log listeners. */ if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { return; } } if (systemlog_fd != -1) { char buf[LOGBUFFER_SIZE] = {'\0'}; size_t buflen = 0, len = 0; pid_t log_pid; const char *process_label = "proftpd"; if (log_opts & PR_LOG_OPT_USE_TIMESTAMP) { pool *tmp_pool; struct timeval now; struct tm *tm; unsigned long millis; tmp_pool = make_sub_pool(permanent_pool); pr_pool_tag(tmp_pool, "Log message pool"); gettimeofday(&now, NULL); tm = pr_localtime(tmp_pool, (const time_t *) &(now.tv_sec)); if (tm == NULL) { destroy_pool(tmp_pool); return; } len = strftime(buf, sizeof(buf), "%Y-%m-%d %H:%M:%S", tm); buflen = len; buf[sizeof(buf) - 1] = '\0'; destroy_pool(tmp_pool); /* Convert microsecs to millisecs. */ millis = now.tv_usec / 1000; len = pr_snprintf(buf + buflen, sizeof(buf) - len, ",%03lu ", millis); buflen += len; } buf[sizeof(buf) - 1] = '\0'; if (log_opts & PR_LOG_OPT_USE_HOSTNAME) { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s ", systemlog_host); buflen += len; } log_pid = session.pid ? session.pid : getpid(); if (log_opts & PR_LOG_OPT_USE_ROLE_BASED_PROCESS_LABELS) { if (is_master) { process_label = "daemon"; } else { process_label = "session"; } } if (*serverinfo) { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u] %s: %s\n", process_label, (unsigned int) log_pid, serverinfo, s); } else { len = pr_snprintf(buf + buflen, sizeof(buf) - buflen, "%s[%u]: %s\n", process_label, (unsigned int) log_pid, s); } buflen += len; buf[sizeof(buf)-1] = '\0'; pr_log_event_generate(PR_LOG_TYPE_SYSTEMLOG, systemlog_fd, priority, buf, buflen); /* Now we need to enforce the discard, syslog_discard and SyslogLevel * filtering. */ if (discard) { return; } if (syslog_discard) { return; } if (priority > max_priority) { return; } while (write(systemlog_fd, buf, buflen) < 0) { if (errno == EINTR) { pr_signals_handle(); continue; } break; } return; } pr_log_event_generate(PR_LOG_TYPE_SYSLOG, syslog_sockfd, priority, s, strlen(s)); if (set_facility != -1) { f = set_facility; } if (!syslog_open) { syslog_sockfd = pr_openlog("proftpd", LOG_NDELAY|LOG_PID, f); if (syslog_sockfd < 0) { (void) pr_trace_msg(trace_channel, 1, "error opening syslog fd: %s", strerror(errno)); return; } syslog_open = TRUE; } else if (f != facility) { /* If this message is to be sent to a different log facility than a * default one (or the facility configured via SyslogFacility), then * OR in the facility with the priority value, as per the syslog(3) * docs. */ priority |= f; } if (*serverinfo) { pr_syslog(syslog_sockfd, priority, "%s - %s\n", serverinfo, s); } else { pr_syslog(syslog_sockfd, priority, "%s\n", s); } } void pr_log_pri(int priority, const char *fmt, ...) { char buf[LOGBUFFER_SIZE] = {'\0'}; va_list msg; va_start(msg, fmt); pr_vsnprintf(buf, sizeof(buf), fmt, msg); va_end(msg); /* Always make sure the buffer is NUL-terminated. */ buf[sizeof(buf) - 1] = '\0'; log_write(priority, facility, buf, FALSE); } /* Like pr_log_pri(), but sends the log entry in the LOG_AUTHPRIV * facility (presumably it doesn't need to be seen by everyone). */ void pr_log_auth(int priority, const char *fmt, ...) { char buf[LOGBUFFER_SIZE] = {'\0'}; va_list msg; va_start(msg, fmt); pr_vsnprintf(buf, sizeof(buf), fmt, msg); va_end(msg); /* Always make sure the buffer is NUL-terminated. */ buf[sizeof(buf) - 1] = '\0'; log_write(priority, LOG_AUTHPRIV, buf, FALSE); } /* Disable logging to stderr, should be done right before forking * or disassociation from controlling tty. After disabling stderr * logging, all messages go to syslog. */ void log_stderr(int bool) { logstderr = bool; } /* Set the debug logging level; see log.h for constants. Higher * numbers mean print more, DEBUG0 (0) == print no debugging log * (default) */ int pr_log_setdebuglevel(int level) { int old_level = debug_level; debug_level = level; return old_level; } /* Set the default logging level; see log.h for constants. */ int pr_log_setdefaultlevel(int level) { int old_level = default_level; default_level = level; return old_level; } int pr_log_set_options(unsigned long opts) { log_opts = opts; return 0; } /* Convert a string into the matching syslog level value. Return -1 * if no matching level is found. */ int pr_log_str2sysloglevel(const char *name) { if (strncasecmp(name, "emerg", 6) == 0) { return PR_LOG_EMERG; } else if (strncasecmp(name, "alert", 6) == 0) { return PR_LOG_ALERT; } else if (strncasecmp(name, "crit", 5) == 0) { return PR_LOG_CRIT; } else if (strncasecmp(name, "error", 6) == 0) { return PR_LOG_ERR; } else if (strncasecmp(name, "warn", 5) == 0) { return PR_LOG_WARNING; } else if (strncasecmp(name, "notice", 7) == 0) { return PR_LOG_NOTICE; } else if (strncasecmp(name, "info", 5) == 0) { return PR_LOG_INFO; } else if (strncasecmp(name, "debug", 6) == 0) { return PR_LOG_DEBUG; } errno = ENOENT; return -1; } void pr_log_debug(int level, const char *fmt, ...) { char buf[LOGBUFFER_SIZE] = {'\0'}; va_list msg; int discard = FALSE; if (debug_level < level) { discard = TRUE; if (pr_log_event_listening(PR_LOG_TYPE_SYSLOG) <= 0 && pr_log_event_listening(PR_LOG_TYPE_SYSTEMLOG) <= 0) { return; } } if (fmt == NULL) return; memset(buf, '\0', sizeof(buf)); va_start(msg, fmt); pr_vsnprintf(buf, sizeof(buf), fmt, msg); va_end(msg); /* Always make sure the buffer is NUL-terminated. */ buf[sizeof(buf) - 1] = '\0'; log_write(PR_LOG_DEBUG, facility, buf, discard); } static const char *get_log_event_name(unsigned int log_type) { const char *event_name = NULL; switch (log_type) { case PR_LOG_TYPE_UNSPEC: event_name = PR_LOG_NAME_UNSPEC; break; case PR_LOG_TYPE_XFERLOG: event_name = PR_LOG_NAME_XFERLOG; break; case PR_LOG_TYPE_SYSLOG: event_name = PR_LOG_NAME_SYSLOG; break; case PR_LOG_TYPE_SYSTEMLOG: event_name = PR_LOG_NAME_SYSTEMLOG; break; case PR_LOG_TYPE_EXTLOG: event_name = PR_LOG_NAME_EXTLOG; break; case PR_LOG_TYPE_TRACELOG: event_name = PR_LOG_NAME_TRACELOG; break; default: errno = EINVAL; return NULL; } return event_name; } int pr_log_event_generate(unsigned int log_type, int log_fd, int log_level, const char *log_msg, size_t log_msglen) { const char *event_name; pr_log_event_t le; if (log_msg == NULL || log_msglen == 0) { errno = EINVAL; return -1; } if (pr_log_event_listening(log_type) <= 0) { errno = ENOENT; return -1; } event_name = get_log_event_name(log_type); memset(&le, 0, sizeof(le)); le.log_type = log_type; le.log_fd = log_fd; le.log_level = log_level; le.log_msg = log_msg; le.log_msglen = log_msglen; pr_event_generate(event_name, &le); return 0; } int pr_log_event_listening(unsigned int log_type) { const char *event_name; int res; event_name = get_log_event_name(log_type); if (event_name == NULL) { return FALSE; } res = pr_event_listening(event_name); if (res <= 0) { return FALSE; } return TRUE; } void pr_log_stacktrace(int log_fd, const char *name) { #if defined(HAVE_EXECINFO_H) && \ defined(HAVE_BACKTRACE) && \ defined(HAVE_BACKTRACE_SYMBOLS) void *trace[PR_TUNABLE_CALLER_DEPTH]; int tracesz, use_fd = TRUE; if (log_fd < 0 || name == NULL) { use_fd = FALSE; } if (use_fd) { (void) pr_log_writefile(log_fd, name, "%s", "-----BEGIN STACK TRACE-----"); } else { (void) pr_log_pri(PR_LOG_WARNING, "-----BEGIN STACK TRACE-----"); } tracesz = backtrace(trace, PR_TUNABLE_CALLER_DEPTH); if (tracesz < 0) { if (use_fd) { (void) pr_log_writefile(log_fd, name, "backtrace(3) error: %s", strerror(errno)); } else { (void) pr_log_pri(PR_LOG_WARNING, "backtrace(3) error: %s", strerror(errno)); } } else { char **strings; strings = backtrace_symbols(trace, tracesz); if (strings != NULL) { register int i; for (i = 1; i < tracesz; i++) { if (use_fd) { (void) pr_log_writefile(log_fd, name, "[%d] %s", i-1, strings[i]); } else { (void) pr_log_pri(PR_LOG_WARNING, "[%d] %s", i-1, strings[i]); } } /* Prevent memory leaks. */ free(strings); } else { if (use_fd) { (void) pr_log_writefile(log_fd, name, "error obtaining backtrace symbols: %s", strerror(errno)); } else { (void) pr_log_pri(PR_LOG_WARNING, "error obtaining backtrace symbols: %s", strerror(errno)); } } } if (use_fd) { (void) pr_log_writefile(log_fd, name, "%s", "-----END STACK TRACE-----"); } else { (void) pr_log_pri(PR_LOG_WARNING, "%s", "-----END STACK TRACE-----"); } #endif } void init_log(void) { char buf[256]; memset(buf, '\0', sizeof(buf)); if (gethostname(buf, sizeof(buf)) < 0) { sstrncpy(buf, "localhost", sizeof(buf)); } sstrncpy(systemlog_host, (char *) pr_netaddr_validate_dns_str(buf), sizeof(systemlog_host)); memset(systemlog_fn, '\0', sizeof(systemlog_fn)); log_closesyslog(); }