/* $NetBSD: syslogd.c,v 1.147 2024/11/09 16:31:31 jschauma Exp $ */ /* * Copyright (c) 1983, 1988, 1993, 1994 * The Regents of the University of California. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the University nor the names of its contributors * may be used to endorse or promote products derived from this software * without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ #include #ifndef lint __COPYRIGHT("@(#) Copyright (c) 1983, 1988, 1993, 1994\ The Regents of the University of California. All rights reserved."); #endif /* not lint */ #ifndef lint #if 0 static char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94"; #else __RCSID("$NetBSD: syslogd.c,v 1.147 2024/11/09 16:31:31 jschauma Exp $"); #endif #endif /* not lint */ /* * syslogd -- log system messages * * This program implements a system log. It takes a series of lines. * Each line may have a priority, signified as "" as * the first characters of the line. If this is * not present, a default priority is used. * * To kill syslogd, send a signal 15 (terminate). A signal 1 (hup) will * cause it to reread its configuration file. * * Defined Constants: * * MAXLINE -- the maximimum line length that can be handled. * DEFUPRI -- the default priority for user messages * DEFSPRI -- the default priority for kernel messages * * Author: Eric Allman * extensive changes by Ralph Campbell * more extensive changes by Eric Allman (again) * Extension to log by program name as well as facility and priority * by Peter da Silva. * -U and -v by Harlan Stenn. * Priority comparison code by Harlan Stenn. * TLS, syslog-protocol, and syslog-sign code by Martin Schuette. */ #define SYSLOG_NAMES #include #include #include "syslogd.h" #include "extern.h" #ifndef DISABLE_SIGN #include "sign.h" struct sign_global_t GlobalSign = { .rsid = 0, .sig2_delims = STAILQ_HEAD_INITIALIZER(GlobalSign.sig2_delims) }; #endif /* !DISABLE_SIGN */ #ifndef DISABLE_TLS #include "tls.h" #endif /* !DISABLE_TLS */ #ifdef LIBWRAP int allow_severity = LOG_AUTH|LOG_INFO; int deny_severity = LOG_AUTH|LOG_WARNING; #endif const char *ConfFile = _PATH_LOGCONF; char ctty[] = _PATH_CONSOLE; /* * Queue of about-to-be-dead processes we should watch out for. */ TAILQ_HEAD(, deadq_entry) deadq_head = TAILQ_HEAD_INITIALIZER(deadq_head); typedef struct deadq_entry { pid_t dq_pid; int dq_timeout; TAILQ_ENTRY(deadq_entry) dq_entries; } *dq_t; /* * The timeout to apply to processes waiting on the dead queue. Unit * of measure is "mark intervals", i.e. 20 minutes by default. * Processes on the dead queue will be terminated after that time. */ #define DQ_TIMO_INIT 2 #define RCVBUFLEN 16384 int buflen = RCVBUFLEN; /* * Intervals at which we flush out "message repeated" messages, * in seconds after previous message is logged. After each flush, * we move to the next interval until we reach the largest. */ int repeatinterval[] = { 30, 120, 600 }; /* # of secs before flush */ #define MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1) #define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) #define BACKOFF(f) { if ((size_t)(++(f)->f_repeatcount) > MAXREPEAT) \ (f)->f_repeatcount = MAXREPEAT; \ } /* values for f_type */ #define F_UNUSED 0 /* unused entry */ #define F_FILE 1 /* regular file */ #define F_TTY 2 /* terminal */ #define F_CONSOLE 3 /* console terminal */ #define F_FORW 4 /* remote machine */ #define F_USERS 5 /* list of users */ #define F_WALL 6 /* everyone logged on */ #define F_PIPE 7 /* pipe to program */ #define F_FIFO 8 /* mkfifo(2) file */ #define F_TLS 9 struct TypeInfo { const char *name; char *queue_length_string; const char *default_length_string; char *queue_size_string; const char *default_size_string; int64_t queue_length; int64_t queue_size; int max_msg_length; } TypeInfo[] = { /* numeric values are set in init() * -1 in length/size or max_msg_length means infinite */ {"UNUSED", NULL, "0", NULL, "0", 0, 0, 0}, {"FILE", NULL, "1024", NULL, "1M", 0, 0, 16384}, {"TTY", NULL, "0", NULL, "0", 0, 0, 1024}, {"CONSOLE", NULL, "0", NULL, "0", 0, 0, 1024}, {"FORW", NULL, "0", NULL, "1M", 0, 0, 16384}, {"USERS", NULL, "0", NULL, "0", 0, 0, 1024}, {"WALL", NULL, "0", NULL, "0", 0, 0, 1024}, {"PIPE", NULL, "1024", NULL, "1M", 0, 0, 16384}, {"FIFO", NULL, "1024", NULL, "1M", 0, 0, 16384}, #ifndef DISABLE_TLS {"TLS", NULL, "-1", NULL, "16M", 0, 0, 16384} #endif /* !DISABLE_TLS */ }; struct filed *Files = NULL; struct filed consfile; time_t now; int Debug = D_NONE; /* debug flag */ int daemonized = 0; /* we are not daemonized yet */ char *LocalFQDN = NULL; /* our FQDN */ char *oldLocalFQDN = NULL; /* our previous FQDN */ char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ struct socketEvent *finet; /* Internet datagram sockets and events */ int *funix; /* Unix domain datagram sockets */ #ifndef DISABLE_TLS struct socketEvent *TLS_Listen_Set; /* TLS/TCP sockets and events */ #endif /* !DISABLE_TLS */ int Initialized = 0; /* set when we have initialized ourselves */ int ShuttingDown; /* set when we die() */ int MarkInterval = 20 * 60; /* interval between marks in seconds */ int MarkSeq = 0; /* mark sequence number */ int SecureMode = 0; /* listen only on unix domain socks */ int UseNameService = 1; /* make domain name queries */ int NumForwards = 0; /* number of forwarding actions in conf file */ char **LogPaths; /* array of pathnames to read messages from */ int NoRepeat = 0; /* disable "repeated"; log always */ int RemoteAddDate = 0; /* always add date to messages from network */ int SyncKernel = 0; /* write kernel messages synchronously */ int UniquePriority = 0; /* only log specified priority */ int LogFacPri = 0; /* put facility and priority in log messages: */ /* 0=no, 1=numeric, 2=names */ int LogOverflow = 1; /* 0=no, any other value = yes */ bool BSDOutputFormat = true; /* if true emit traditional BSD Syslog lines, * otherwise new syslog-protocol lines * * Open Issue: having a global flag is the * easiest solution. If we get a more detailed * config file this could/should be changed * into a destination-specific flag. * Most output code should be ready to handle * this, it will only break some syslog-sign * configurations (e.g. with SG="0"). */ bool KernXlat = true; /* translate kern.* -> user.* */ char appname[] = "syslogd";/* the APPNAME for own messages */ char *include_pid; /* include PID in own messages */ char include_pid_buf[11]; /* init and setup */ void usage(void) __attribute__((__noreturn__)); void set_debug(const char *); void logpath_add(char ***, int *, int *, const char *); void logpath_fileadd(char ***, int *, int *, const char *); void init(int fd, short event, void *ev); /* SIGHUP kevent dispatch routine */ struct socketEvent* socksetup(int, const char *); int getmsgbufsize(void); char *getLocalFQDN(void); void trim_anydomain(char *); /* pipe & subprocess handling */ int p_open(char *, pid_t *); void deadq_enter(pid_t, const char *); int deadq_remove(pid_t); void log_deadchild(pid_t, int, const char *); void reapchild(int fd, short event, void *ev); /* SIGCHLD kevent dispatch routine */ /* input message parsing & formatting */ const char *cvthname(struct sockaddr_storage *); void printsys(char *); struct buf_msg *printline_syslogprotocol(const char*, char*, int, int); struct buf_msg *printline_bsdsyslog(const char*, char*, int, int); struct buf_msg *printline_kernelprintf(const char*, char*, int, int); size_t check_timestamp(unsigned char *, char **, bool, bool); char *copy_utf8_ascii(char*, size_t); uint_fast32_t get_utf8_value(const char*); unsigned valid_utf8(const char *); static unsigned check_sd(char*); static unsigned check_msgid(char *); /* event handling */ static void dispatch_read_klog(int fd, short event, void *ev); static void dispatch_read_finet(int fd, short event, void *ev); static void dispatch_read_funix(int fd, short event, void *ev); static void domark(int fd, short event, void *ev); /* timer kevent dispatch routine */ /* log messages */ void logmsg_async(int, const char *, const char *, int); void logmsg(struct buf_msg *); int matches_spec(const char *, const char *, char *(*)(const char *, const char *)); void udp_send(struct filed *, char *, size_t); void wallmsg(struct filed *, struct iovec *, size_t); /* buffer & queue functions */ size_t message_queue_purge(struct filed *f, size_t, int); size_t message_allqueues_check(void); static struct buf_queue * find_qentry_to_delete(const struct buf_queue_head *, int, bool); struct buf_queue * message_queue_add(struct filed *, struct buf_msg *); size_t buf_queue_obj_size(struct buf_queue*); /* configuration & parsing */ void cfline(size_t, const char *, struct filed *, const char *, const char *); void read_config_file(FILE*, struct filed**); void store_sign_delim_sg2(char*); int decode(const char *, CODE *); bool copy_config_value(const char *, char **, const char **, const char *, int); bool copy_config_value_word(char **, const char **); /* config parsing */ #ifndef DISABLE_TLS void free_cred_SLIST(struct peer_cred_head *); static inline void free_incoming_tls_sockets(void); #endif /* !DISABLE_TLS */ static int writev1(int, struct iovec *, size_t); static void setsockbuf(int, const char *); /* for make_timestamp() */ char timestamp[MAX_TIMESTAMPLEN + 1]; /* * Global line buffer. Since we only process one event at a time, * a global one will do. But for klog, we use own buffer so that * partial line at the end of buffer can be deferred. */ char *linebuf, *klog_linebuf; size_t linebufsize, klog_linebufoff; static const char *bindhostname = NULL; #ifndef DISABLE_TLS struct TLS_Incoming TLS_Incoming_Head = \ SLIST_HEAD_INITIALIZER(TLS_Incoming_Head); extern char *SSL_ERRCODE[]; struct tls_global_options_t tls_opt; #endif /* !DISABLE_TLS */ int main(int argc, char *argv[]) { int ch, j, fklog; int funixsize = 0, funixmaxsize = 0; struct sockaddr_un sunx; char **pp; struct event *ev; uid_t uid = 0; gid_t gid = 0; char *user = NULL; char *group = NULL; const char *root = "/"; char *endp; struct group *gr; struct passwd *pw; unsigned long l; char pfpath[PATH_MAX]; /* should we set LC_TIME="C" to ensure correct timestamps&parsing? */ (void)setlocale(LC_ALL, ""); while ((ch = getopt(argc, argv, "b:B:d::knsSf:m:o:p:P:ru:g:t:TUvX")) != -1) switch(ch) { case 'b': bindhostname = optarg; break; case 'B': buflen = atoi(optarg); if (buflen < RCVBUFLEN) buflen = RCVBUFLEN; break; case 'd': /* debug */ if (optarg != NULL) { /* * getopt passes as optarg everything * after 'd' in -darg, manually accept * -d=arg too. */ if (optarg[0] == '=') ++optarg; } else if (optind < argc) { /* * :: treats "-d ..." as missing * optarg, so look ahead manually and * pick up the next arg if it looks * like one. */ if (argv[optind][0] != '-') { optarg = argv[optind]; ++optind; } } set_debug(optarg); break; case 'f': /* configuration file */ ConfFile = optarg; break; case 'g': group = optarg; if (*group == '\0') usage(); break; case 'k': /* pass-through (remote) kern.* */ KernXlat = false; break; case 'm': /* mark interval */ MarkInterval = atoi(optarg) * 60; break; case 'n': /* turn off DNS queries */ UseNameService = 0; break; case 'o': /* message format */ #define EQ(a) (strncmp(optarg, # a, sizeof(# a) - 1) == 0) if (EQ(bsd) || EQ(rfc3264)) BSDOutputFormat = true; else if (EQ(syslog) || EQ(rfc5424)) BSDOutputFormat = false; else usage(); /* TODO: implement additional output option "osyslog" * for old syslogd behaviour as introduced after * FreeBSD PR#bin/7055. */ break; case 'p': /* path */ logpath_add(&LogPaths, &funixsize, &funixmaxsize, optarg); break; case 'P': /* file of paths */ logpath_fileadd(&LogPaths, &funixsize, &funixmaxsize, optarg); break; case 'r': /* disable "repeated" compression */ NoRepeat++; break; case 's': /* no network listen mode */ SecureMode++; break; case 'S': SyncKernel = 1; break; case 't': root = optarg; if (*root == '\0') usage(); break; case 'T': RemoteAddDate = 1; break; case 'u': user = optarg; if (*user == '\0') usage(); break; case 'U': /* only log specified priority */ UniquePriority = 1; break; case 'v': /* log facility and priority */ if (LogFacPri < 2) LogFacPri++; break; case 'X': LogOverflow = 0; break; default: usage(); } if ((argc -= optind) != 0) usage(); setlinebuf(stdout); tzset(); /* init TZ information for localtime. */ if (user != NULL) { if (isdigit((unsigned char)*user)) { errno = 0; endp = NULL; l = strtoul(user, &endp, 0); if (errno || *endp != '\0') goto getuser; uid = (uid_t)l; if (uid != l) {/* TODO: never executed */ errno = 0; logerror("UID out of range"); die(0, 0, NULL); } } else { getuser: if ((pw = getpwnam(user)) != NULL) { uid = pw->pw_uid; } else { errno = 0; logerror("Cannot find user `%s'", user); die(0, 0, NULL); } } } if (group != NULL) { if (isdigit((unsigned char)*group)) { errno = 0; endp = NULL; l = strtoul(group, &endp, 0); if (errno || *endp != '\0') goto getgroup; gid = (gid_t)l; if (gid != l) {/* TODO: never executed */ errno = 0; logerror("GID out of range"); die(0, 0, NULL); } } else { getgroup: if ((gr = getgrnam(group)) != NULL) { gid = gr->gr_gid; } else { errno = 0; logerror("Cannot find group `%s'", group); die(0, 0, NULL); } } } if (access(root, F_OK | R_OK)) { logerror("Cannot access `%s'", root); die(0, 0, NULL); } consfile.f_type = F_CONSOLE; (void)strlcpy(consfile.f_un.f_fname, ctty, sizeof(consfile.f_un.f_fname)); linebufsize = getmsgbufsize(); if (linebufsize < MAXLINE) linebufsize = MAXLINE; linebufsize++; if (!(linebuf = malloc(linebufsize))) { logerror("Couldn't allocate buffer"); die(0, 0, NULL); } if (!(klog_linebuf = malloc(linebufsize))) { logerror("Couldn't allocate buffer for klog"); die(0, 0, NULL); } #ifndef SUN_LEN #define SUN_LEN(unp) (strlen((unp)->sun_path) + 2) #endif if (funixsize == 0) logpath_add(&LogPaths, &funixsize, &funixmaxsize, _PATH_LOG); funix = malloc(sizeof(*funix) * funixsize); if (funix == NULL) { logerror("Couldn't allocate funix descriptors"); die(0, 0, NULL); } for (j = 0, pp = LogPaths; *pp; pp++, j++) { DPRINTF(D_NET, "Making unix dgram socket `%s'\n", *pp); unlink(*pp); memset(&sunx, 0, sizeof(sunx)); sunx.sun_family = AF_LOCAL; (void)strncpy(sunx.sun_path, *pp, sizeof(sunx.sun_path)); funix[j] = socket(AF_LOCAL, SOCK_DGRAM, 0); if (funix[j] < 0 || bind(funix[j], (struct sockaddr *)&sunx, SUN_LEN(&sunx)) < 0 || chmod(*pp, 0666) < 0) { logerror("Cannot create `%s'", *pp); die(0, 0, NULL); } setsockbuf(funix[j], *pp); DPRINTF(D_NET, "Listening on unix dgram socket `%s'\n", *pp); } if ((fklog = open(_PATH_KLOG, O_RDONLY, 0)) < 0) { DPRINTF(D_FILE, "Can't open `%s' (%d)\n", _PATH_KLOG, errno); } else { DPRINTF(D_FILE, "Listening on kernel log `%s' with fd %d\n", _PATH_KLOG, fklog); } #if (!defined(DISABLE_TLS) && !defined(DISABLE_SIGN)) /* basic OpenSSL init */ SSL_load_error_strings(); (void) SSL_library_init(); OpenSSL_add_all_digests(); /* OpenSSL PRNG needs /dev/urandom, thus initialize before chroot() */ if (!RAND_status()) { errno = 0; logerror("Unable to initialize OpenSSL PRNG"); } else { DPRINTF(D_TLS, "Initializing PRNG\n"); } #endif /* (!defined(DISABLE_TLS) && !defined(DISABLE_SIGN)) */ #ifndef DISABLE_SIGN /* initialize rsid -- we will use that later to determine * whether sign_global_init() was already called */ GlobalSign.rsid = 0; #endif /* !DISABLE_SIGN */ #if (IETF_NUM_PRIVALUES != (LOG_NFACILITIES<<3)) logerror("Warning: system defines %d priority values, but " "syslog-protocol/syslog-sign specify %d values", LOG_NFACILITIES, IETF_NUM_PRIVALUES>>3); #endif #ifdef __NetBSD_Version__ if ((uid != 0) || (gid != 0)) { /* Create the pidfile here so we can chown it to the target * user/group and possibly report any error before daemonizing. * We then call pidfile(3) again to write the actual * daemon pid below. * * Note: this will likely leave the truncated pidfile in * place upon exit, since the effective user is unlikely * to have write permissions to _PATH_VARRUN. */ if (pidfile(NULL)) { logerror("Failed to create pidfile"); die(0, 0, NULL); } j = sizeof(pfpath); if (snprintf(pfpath, j, "%s%s.pid", _PATH_VARRUN, getprogname()) >= j) { logerror("Pidfile path `%s' too long.", pfpath); die(0, 0, NULL); } if (chown(pfpath, uid, gid) < 0) { logerror("Failed to chown pidfile `%s` to `%d:%d`", pfpath, uid, gid); die(0, 0, NULL); } } #endif /* __NetBSD_Version__ */ /* * All files are open, we can drop privileges and chroot. */ DPRINTF(D_MISC, "Attempt to chroot to `%s'\n", root); if (chroot(root) == -1) { logerror("Failed to chroot to `%s'", root); die(0, 0, NULL); } DPRINTF(D_MISC, "Attempt to set GID/EGID to `%d'\n", gid); if (setgid(gid) || setegid(gid)) { logerror("Failed to set gid to `%d'", gid); die(0, 0, NULL); } DPRINTF(D_MISC, "Attempt to set UID/EUID to `%d'\n", uid); if (setuid(uid) || seteuid(uid)) { logerror("Failed to set uid to `%d'", uid); die(0, 0, NULL); } /* * We cannot detach from the terminal before we are sure we won't * have a fatal error, because error message would not go to the * terminal and would not be logged because syslogd dies. * All die() calls are behind us, we can call daemon(). */ if (!Debug) { (void)daemon(0, 0); daemonized = 1; /* Tuck my process id away, if I'm not in debug mode. */ #ifdef __NetBSD_Version__ pidfile(NULL); #endif /* __NetBSD_Version__ */ } include_pid = include_pid_buf; snprintf(include_pid_buf, sizeof(include_pid_buf), "%d", getpid()); /* * Create the global kernel event descriptor. * * NOTE: We MUST do this after daemon(), because the kqueue() * API dictates that kqueue descriptors are not inherited * across forks (lame!). */ (void)event_init(); /* * We must read the configuration file for the first time * after the kqueue descriptor is created, because we install * events during this process. */ init(0, 0, NULL); /* * Always exit on SIGTERM. Also exit on SIGINT and SIGQUIT * if we're debugging. */ (void)signal(SIGTERM, SIG_IGN); (void)signal(SIGINT, SIG_IGN); (void)signal(SIGQUIT, SIG_IGN); ev = allocev(); signal_set(ev, SIGTERM, die, ev); EVENT_ADD(ev); if (Debug) { ev = allocev(); signal_set(ev, SIGINT, die, ev); EVENT_ADD(ev); ev = allocev(); signal_set(ev, SIGQUIT, die, ev); EVENT_ADD(ev); } ev = allocev(); signal_set(ev, SIGCHLD, reapchild, ev); EVENT_ADD(ev); ev = allocev(); schedule_event(&ev, &((struct timeval){TIMERINTVL, 0}), domark, ev); (void)signal(SIGPIPE, SIG_IGN); /* We'll catch EPIPE instead. */ /* Re-read configuration on SIGHUP. */ (void) signal(SIGHUP, SIG_IGN); ev = allocev(); signal_set(ev, SIGHUP, init, ev); EVENT_ADD(ev); #ifndef DISABLE_TLS ev = allocev(); signal_set(ev, SIGUSR1, dispatch_force_tls_reconnect, ev); EVENT_ADD(ev); #endif /* !DISABLE_TLS */ if (fklog >= 0) { ev = allocev(); DPRINTF(D_EVENT, "register klog for fd %d with ev@%p\n", fklog, ev); event_set(ev, fklog, EV_READ | EV_PERSIST, dispatch_read_klog, ev); EVENT_ADD(ev); } for (j = 0, pp = LogPaths; *pp; pp++, j++) { ev = allocev(); event_set(ev, funix[j], EV_READ | EV_PERSIST, dispatch_read_funix, ev); EVENT_ADD(ev); } DPRINTF(D_MISC, "Off & running....\n"); j = event_dispatch(); /* normal termination via die(), reaching this is an error */ DPRINTF(D_MISC, "event_dispatch() returned %d\n", j); die(0, 0, NULL); /*NOTREACHED*/ return 0; } void usage(void) { (void)fprintf(stderr, "usage: %s [-dknrSsTUvX] [-B buffer_length] [-b bind_address]\n" "\t[-f config_file] [-g group]\n" "\t[-m mark_interval] [-P file_list] [-p log_socket\n" "\t[-p log_socket2 ...]] [-t chroot_dir] [-u user]\n", getprogname()); exit(1); } static void setsockbuf(int fd, const char *name) { int curbuflen; socklen_t socklen = sizeof(buflen); if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &curbuflen, &socklen) == -1) { logerror("getsockopt: SO_RCVBUF: `%s'", name); return; } if (curbuflen >= buflen) return; if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buflen, socklen) == -1) { logerror("setsockopt: SO_RCVBUF: `%s'", name); return; } } /* * Dispatch routine for reading /dev/klog * * Note: slightly different semantic in dispatch_read functions: * - read_klog() might give multiple messages in linebuf and * leaves the task of splitting them to printsys() * - all other read functions receive one message and * then call printline() with one buffer. */ static void dispatch_read_klog(int fd, short event, void *ev) { ssize_t rv; size_t resid = linebufsize - klog_linebufoff; DPRINTF((D_CALL|D_EVENT), "Kernel log active (%d, %d, %p)" " with linebuf@%p, length %zu)\n", fd, event, ev, klog_linebuf, linebufsize); rv = read(fd, &klog_linebuf[klog_linebufoff], resid - 1); if (rv > 0) { klog_linebuf[klog_linebufoff + rv] = '\0'; printsys(klog_linebuf); } else if (rv < 0 && errno != EINTR && (errno != ENOBUFS || LogOverflow)) { /* * /dev/klog has croaked. Disable the event * so it won't bother us again. */ logerror("klog failed"); event_del(ev); } } /* * Dispatch routine for reading Unix domain sockets. */ static void dispatch_read_funix(int fd, short event, void *ev) { struct sockaddr_un myname, fromunix; ssize_t rv; socklen_t sunlen; sunlen = sizeof(myname); if (getsockname(fd, (struct sockaddr *)&myname, &sunlen) != 0) { /* * This should never happen, so ensure that it doesn't * happen again. */ logerror("getsockname() unix failed"); event_del(ev); return; } #define SUN_PATHLEN(su) \ ((su)->sun_len - (sizeof(*(su)) - sizeof((su)->sun_path))) DPRINTF((D_CALL|D_EVENT|D_NET), "Unix socket (%.*s) active (%d, %d %p)" " with linebuf@%p, size %zu)\n", (int)SUN_PATHLEN(&myname), myname.sun_path, fd, event, ev, linebuf, linebufsize-1); sunlen = sizeof(fromunix); rv = recvfrom(fd, linebuf, linebufsize-1, 0, (struct sockaddr *)&fromunix, &sunlen); if (rv > 0) { linebuf[rv] = '\0'; printline(LocalFQDN, linebuf, 0); } else if (rv < 0 && errno != EINTR && (errno != ENOBUFS || LogOverflow)) { logerror("recvfrom() unix `%.*s'", (int)SUN_PATHLEN(&myname), myname.sun_path); } } /* * Dispatch routine for reading Internet sockets. */ static void dispatch_read_finet(int fd, short event, void *ev) { #ifdef LIBWRAP struct request_info req; #endif struct sockaddr_storage frominet; ssize_t rv; socklen_t len; int reject = 0; DPRINTF((D_CALL|D_EVENT|D_NET), "inet socket active (%d, %d %p) " " with linebuf@%p, size %zu)\n", fd, event, ev, linebuf, linebufsize-1); #ifdef LIBWRAP request_init(&req, RQ_DAEMON, appname, RQ_FILE, fd, NULL); fromhost(&req); reject = !hosts_access(&req); if (reject) DPRINTF(D_NET, "access denied\n"); #endif len = sizeof(frominet); rv = recvfrom(fd, linebuf, linebufsize-1, 0, (struct sockaddr *)&frominet, &len); if (rv == 0 || (rv < 0 && (errno == EINTR || (errno == ENOBUFS && LogOverflow == 0)))) return; else if (rv < 0) { logerror("recvfrom inet"); return; } linebuf[rv] = '\0'; if (!reject) printline(cvthname(&frominet), linebuf, RemoteAddDate ? ADDDATE : 0); } /* * given a pointer to an array of char *'s, a pointer to its current * size and current allocated max size, and a new char * to add, add * it, update everything as necessary, possibly allocating a new array */ void logpath_add(char ***lp, int *szp, int *maxszp, const char *new) { char **nlp; int newmaxsz; DPRINTF(D_FILE, "Adding `%s' to the %p logpath list\n", new, *lp); if (*szp == *maxszp) { if (*maxszp == 0) { newmaxsz = 4; /* start of with enough for now */ *lp = NULL; } else newmaxsz = *maxszp * 2; nlp = realloc(*lp, sizeof(char *) * (newmaxsz + 1)); if (nlp == NULL) { logerror("Couldn't allocate line buffer"); die(0, 0, NULL); } *lp = nlp; *maxszp = newmaxsz; } if (((*lp)[(*szp)++] = strdup(new)) == NULL) { logerror("Couldn't allocate logpath"); die(0, 0, NULL); } (*lp)[(*szp)] = NULL; /* always keep it NULL terminated */ } /* do a file of log sockets */ void logpath_fileadd(char ***lp, int *szp, int *maxszp, const char *file) { FILE *fp; char *line; size_t len; fp = fopen(file, "r"); if (fp == NULL) { logerror("Could not open socket file list `%s'", file); die(0, 0, NULL); } while ((line = fgetln(fp, &len)) != NULL) { line[len - 1] = 0; logpath_add(lp, szp, maxszp, line); } fclose(fp); } /* * checks UTF-8 codepoint * returns either its length in bytes or 0 if *input is invalid */ unsigned valid_utf8(const char *c) { unsigned rc, nb; /* first byte gives sequence length */ if ((*c & 0x80) == 0x00) return 1; /* 0bbbbbbb -- ASCII */ else if ((*c & 0xc0) == 0x80) return 0; /* 10bbbbbb -- trailing byte */ else if ((*c & 0xe0) == 0xc0) nb = 2; /* 110bbbbb */ else if ((*c & 0xf0) == 0xe0) nb = 3; /* 1110bbbb */ else if ((*c & 0xf8) == 0xf0) nb = 4; /* 11110bbb */ else return 0; /* UTF-8 allows only up to 4 bytes */ /* catch overlong encodings */ if ((*c & 0xfe) == 0xc0) return 0; /* 1100000b ... */ else if (((*c & 0xff) == 0xe0) && ((*(c+1) & 0xe0) == 0x80)) return 0; /* 11100000 100bbbbb ... */ else if (((*c & 0xff) == 0xf0) && ((*(c+1) & 0xf0) == 0x80)) return 0; /* 11110000 1000bbbb ... ... */ /* and also filter UTF-16 surrogates (=invalid in UTF-8) */ if (((*c & 0xff) == 0xed) && ((*(c+1) & 0xe0) == 0xa0)) return 0; /* 11101101 101bbbbb ... */ rc = nb; /* check trailing bytes */ switch (nb) { default: return 0; case 4: if ((*(c+3) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ case 3: if ((*(c+2) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ case 2: if ((*(c+1) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ } return rc; } #define UTF8CHARMAX 4 /* * read UTF-8 value * returns a the codepoint number */ uint_fast32_t get_utf8_value(const char *c) { uint_fast32_t sum; unsigned nb, i; /* first byte gives sequence length */ if ((*c & 0x80) == 0x00) return *c;/* 0bbbbbbb -- ASCII */ else if ((*c & 0xc0) == 0x80) return 0; /* 10bbbbbb -- trailing byte */ else if ((*c & 0xe0) == 0xc0) { /* 110bbbbb */ nb = 2; sum = (*c & ~0xe0) & 0xff; } else if ((*c & 0xf0) == 0xe0) { /* 1110bbbb */ nb = 3; sum = (*c & ~0xf0) & 0xff; } else if ((*c & 0xf8) == 0xf0) { /* 11110bbb */ nb = 4; sum = (*c & ~0xf8) & 0xff; } else return 0; /* UTF-8 allows only up to 4 bytes */ /* check trailing bytes -- 10bbbbbb */ i = 1; while (i < nb) { sum <<= 6; sum |= ((*(c+i) & ~0xc0) & 0xff); i++; } return sum; } /* note previous versions transscribe * control characters, e.g. \007 --> "^G" * did anyone rely on that? * * this new version works on only one buffer and * replaces control characters with a space */ #define NEXTFIELD(ptr) if (*(p) == ' ') (p)++; /* SP */ \ else { \ DPRINTF(D_DATA, "format error\n"); \ if (*(p) == '\0') start = (p); \ goto all_syslog_msg; \ } #define FORCE2ASCII(c) ((iscntrl((unsigned char)(c)) && (c) != '\t') \ ? ((c) == '\n' ? ' ' : '?') \ : (c) & 0177) /* following syslog-protocol */ #define printusascii(ch) (ch >= 33 && ch <= 126) #define sdname(ch) (ch != '=' && ch != ' ' \ && ch != ']' && ch != '"' \ && printusascii(ch)) /* checks whether the first word of string p can be interpreted as * a syslog-protocol MSGID and if so returns its length. * * otherwise returns 0 */ static unsigned check_msgid(char *p) { char *q = p; /* consider the NILVALUE to be valid */ if (*q == '-' && *(q+1) == ' ') return 1; for (;;) { if (*q == ' ') return q - p; else if (*q == '\0' || !printusascii(*q) || q - p >= MSGID_MAX) return 0; else q++; } } /* * returns number of chars found in SD at beginning of string p * thus returns 0 if no valid SD is found * * if ascii == true then substitute all non-ASCII chars * otherwise use syslog-protocol rules to allow UTF-8 in values * note: one pass for filtering and scanning, so a found SD * is always filtered, but an invalid one could be partially * filtered up to the format error. */ static unsigned check_sd(char* p) { char *q = p; bool esc = false; /* consider the NILVALUE to be valid */ if (*q == '-' && (*(q+1) == ' ' || *(q+1) == '\0')) return 1; for(;;) { /* SD-ELEMENT */ if (*q++ != '[') return 0; /* SD-ID */ if (!sdname(*q)) return 0; while (sdname(*q)) { *q = FORCE2ASCII(*q); q++; } for(;;) { /* SD-PARAM */ if (*q == ']') { q++; if (*q == ' ' || *q == '\0') return q - p; else if (*q == '[') break; } else if (*q++ != ' ') return 0; /* PARAM-NAME */ if (!sdname(*q)) return 0; while (sdname(*q)) { *q = FORCE2ASCII(*q); q++; } if (*q++ != '=') return 0; if (*q++ != '"') return 0; for(;;) { /* PARAM-VALUE */ if (esc) { esc = false; if (*q == '\\' || *q == '"' || *q == ']') { q++; continue; } /* no else because invalid * escape sequences are accepted */ } else if (*q == '"') break; else if (*q == '\0' || *q == ']') return 0; else if (*q == '\\') esc = true; else { int i; i = valid_utf8(q); if (i == 0) *q = '?'; else if (i == 1) *q = FORCE2ASCII(*q); else /* multi byte char */ q += (i-1); } q++; } q++; } } } struct buf_msg * printline_syslogprotocol(const char *hname, char *msg, int flags, int pri) { struct buf_msg *buffer; char *p, *start; unsigned sdlen = 0, i = 0; bool utf8allowed = false; /* for some fields */ DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_syslogprotocol(" "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); buffer = buf_msg_new(0); p = msg; p += check_timestamp((unsigned char*) p, &buffer->timestamp, true, !BSDOutputFormat); DPRINTF(D_DATA, "Got timestamp \"%s\"\n", buffer->timestamp); if (flags & ADDDATE) { FREEPTR(buffer->timestamp); buffer->timestamp = make_timestamp(NULL, !BSDOutputFormat, 0); } start = p; NEXTFIELD(p); /* extract host */ for (start = p;; p++) { if ((*p == ' ' || *p == '\0') && start == p-1 && *(p-1) == '-') { /* NILVALUE */ break; } else if ((*p == ' ' || *p == '\0') && (start != p-1 || *(p-1) != '-')) { buffer->host = strndup(start, p - start); break; } else { *p = FORCE2ASCII(*p); } } /* p @ SP after host */ DPRINTF(D_DATA, "Got host \"%s\"\n", buffer->host); /* extract app-name */ NEXTFIELD(p); for (start = p;; p++) { if ((*p == ' ' || *p == '\0') && start == p-1 && *(p-1) == '-') { /* NILVALUE */ break; } else if ((*p == ' ' || *p == '\0') && (start != p-1 || *(p-1) != '-')) { buffer->prog = strndup(start, p - start); break; } else { *p = FORCE2ASCII(*p); } } DPRINTF(D_DATA, "Got prog \"%s\"\n", buffer->prog); /* extract procid */ NEXTFIELD(p); for (start = p;; p++) { if ((*p == ' ' || *p == '\0') && start == p-1 && *(p-1) == '-') { /* NILVALUE */ break; } else if ((*p == ' ' || *p == '\0') && (start != p-1 || *(p-1) != '-')) { buffer->pid = strndup(start, p - start); start = p; break; } else { *p = FORCE2ASCII(*p); } } DPRINTF(D_DATA, "Got pid \"%s\"\n", buffer->pid); /* extract msgid */ NEXTFIELD(p); for (start = p;; p++) { if ((*p == ' ' || *p == '\0') && start == p-1 && *(p-1) == '-') { /* NILVALUE */ start = p+1; break; } else if ((*p == ' ' || *p == '\0') && (start != p-1 || *(p-1) != '-')) { buffer->msgid = strndup(start, p - start); start = p+1; break; } else { *p = FORCE2ASCII(*p); } } DPRINTF(D_DATA, "Got msgid \"%s\"\n", buffer->msgid); /* extract SD */ NEXTFIELD(p); start = p; sdlen = check_sd(p); DPRINTF(D_DATA, "check_sd(\"%s\") returned %d\n", p, sdlen); if (sdlen == 1 && *p == '-') { /* NILVALUE */ p++; } else if (sdlen > 1) { buffer->sd = strndup(p, sdlen); p += sdlen; } else { DPRINTF(D_DATA, "format error\n"); } if (*p == '\0') start = p; else if (*p == ' ') start = ++p; /* SP */ DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); /* and now the message itself * note: move back to last start to check for BOM */ all_syslog_msg: p = start; /* check for UTF-8-BOM */ if (IS_BOM(p)) { DPRINTF(D_DATA, "UTF-8 BOM\n"); utf8allowed = true; p += 3; } if (*p != '\0' && !utf8allowed) { size_t msglen; msglen = strlen(p); assert(!buffer->msg); buffer->msg = copy_utf8_ascii(p, msglen); buffer->msgorig = buffer->msg; buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; } else if (*p != '\0' && utf8allowed) { while (*p != '\0') { i = valid_utf8(p); if (i == 0) *p++ = '?'; else if (i == 1) *p = FORCE2ASCII(*p); p += i; } assert(p != start); assert(!buffer->msg); buffer->msg = strndup(start, p - start); buffer->msgorig = buffer->msg; buffer->msglen = buffer->msgsize = 1 + p - start; } DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); buffer->recvhost = strdup(hname); buffer->pri = pri; buffer->flags = flags; return buffer; } /* copies an input into a new ASCII buffer * ASCII controls are converted to format "^X" * multi-byte UTF-8 chars are converted to format "" */ #define INIT_BUFSIZE 512 char * copy_utf8_ascii(char *p, size_t p_len) { size_t idst = 0, isrc = 0, dstsize = INIT_BUFSIZE, i; char *dst, *tmp_dst; MALLOC(dst, dstsize); while (isrc < p_len) { if (dstsize < idst + 10) { /* check for enough space for \0 and a UTF-8 * conversion; longest possible is */ tmp_dst = realloc(dst, dstsize + INIT_BUFSIZE); if (!tmp_dst) break; dst = tmp_dst; dstsize += INIT_BUFSIZE; } i = valid_utf8(&p[isrc]); if (i == 0) { /* invalid encoding */ dst[idst++] = '?'; isrc++; } else if (i == 1) { /* check printable */ if (iscntrl((unsigned char)p[isrc]) && p[isrc] != '\t') { if (p[isrc] == '\n') { dst[idst++] = ' '; isrc++; } else { dst[idst++] = '^'; dst[idst++] = p[isrc++] ^ 0100; } } else dst[idst++] = p[isrc++]; } else { /* convert UTF-8 to ASCII */ dst[idst++] = '<'; idst += snprintf(&dst[idst], dstsize - idst, "U+%x", get_utf8_value(&p[isrc])); isrc += i; dst[idst++] = '>'; } } dst[idst] = '\0'; /* shrink buffer to right size */ tmp_dst = realloc(dst, idst+1); if (tmp_dst) return tmp_dst; else return dst; } struct buf_msg * printline_bsdsyslog(const char *hname, char *msg, int flags, int pri) { struct buf_msg *buffer; char *p, *start; unsigned msgidlen = 0, sdlen = 0; DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_bsdsyslog(" "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); buffer = buf_msg_new(0); p = msg; p += check_timestamp((unsigned char*) p, &buffer->timestamp, false, !BSDOutputFormat); DPRINTF(D_DATA, "Got timestamp \"%s\"\n", buffer->timestamp); if (flags & ADDDATE || !buffer->timestamp) { FREEPTR(buffer->timestamp); buffer->timestamp = make_timestamp(NULL, !BSDOutputFormat, 0); } if (*p == ' ') p++; /* SP */ else goto all_bsd_msg; /* in any error case we skip header parsing and * treat all following data as message content */ /* extract host */ for (start = p;; p++) { if (*p == ' ' || *p == '\0') { buffer->host = strndup(start, p - start); break; } else if (*p == '[' || (*p == ':' && (*(p+1) == ' ' || *(p+1) == '\0'))) { /* no host in message */ buffer->host = strdup(hname); buffer->prog = strndup(start, p - start); break; } else { *p = FORCE2ASCII(*p); } } DPRINTF(D_DATA, "Got host \"%s\"\n", buffer->host); /* p @ SP after host, or @ :/[ after prog */ /* extract program */ if (!buffer->prog) { if (*p == ' ') p++; /* SP */ else goto all_bsd_msg; for (start = p;; p++) { if (*p == ' ' || *p == '\0') { /* error */ goto all_bsd_msg; } else if (*p == '[' || (*p == ':' && (*(p+1) == ' ' || *(p+1) == '\0'))) { buffer->prog = strndup(start, p - start); break; } else { *p = FORCE2ASCII(*p); } } } DPRINTF(D_DATA, "Got prog \"%s\"\n", buffer->prog); start = p; /* p @ :/[ after prog */ if (*p == '[') { p++; if (*p == ' ') p++; /* SP */ for (start = p;; p++) { if (*p == ' ' || *p == '\0') { /* error */ goto all_bsd_msg; } else if (*p == ']') { buffer->pid = strndup(start, p - start); break; } else { *p = FORCE2ASCII(*p); } } } DPRINTF(D_DATA, "Got pid \"%s\"\n", buffer->pid); if (*p == ']') p++; if (*p == ':') p++; if (*p == ' ') p++; /* p @ msgid, @ opening [ of SD or @ first byte of message * accept either case and try to detect MSGID and SD fields * * only limitation: we do not accept UTF-8 data in * BSD Syslog messages -- so all SD values are ASCII-filtered * * I have found one scenario with 'unexpected' behaviour: * if there is only a SD intended, but a) it is short enough * to be a MSGID and b) the first word of the message can also * be parsed as an SD. * example: * "<35>Jul 6 12:39:08 tag[123]: [exampleSDID@0] - hello" * --> parsed as * MSGID = "[exampleSDID@0]" * SD = "-" * MSG = "hello" */ start = p; msgidlen = check_msgid(p); if (msgidlen) /* check for SD in 2nd field */ sdlen = check_sd(p+msgidlen+1); if (msgidlen && sdlen) { /* MSGID in 1st and SD in 2nd field * now check for NILVALUEs and copy */ if (msgidlen == 1 && *p == '-') { p++; /* - */ p++; /* SP */ DPRINTF(D_DATA, "Got MSGID \"-\"\n"); } else { /* only has ASCII chars after check_msgid() */ buffer->msgid = strndup(p, msgidlen); p += msgidlen; p++; /* SP */ DPRINTF(D_DATA, "Got MSGID \"%s\"\n", buffer->msgid); } } else { /* either no msgid or no SD in 2nd field * --> check 1st field for SD */ DPRINTF(D_DATA, "No MSGID\n"); sdlen = check_sd(p); } if (sdlen == 0) { DPRINTF(D_DATA, "No SD\n"); } else if (sdlen > 1) { buffer->sd = copy_utf8_ascii(p, sdlen); DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); } else if (sdlen == 1 && *p == '-') { p++; DPRINTF(D_DATA, "Got SD \"-\"\n"); } else { DPRINTF(D_DATA, "Error\n"); } if (*p == ' ') p++; start = p; /* and now the message itself * note: do not reset start, because we might come here * by goto and want to have the incomplete field as part * of the msg */ all_bsd_msg: if (*p != '\0') { size_t msglen = strlen(p); buffer->msg = copy_utf8_ascii(p, msglen); buffer->msgorig = buffer->msg; buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; } DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); buffer->recvhost = strdup(hname); buffer->pri = pri; buffer->flags = flags | BSDSYSLOG; return buffer; } struct buf_msg * printline_kernelprintf(const char *hname, char *msg, int flags, int pri) { struct buf_msg *buffer; char *p; unsigned sdlen = 0; DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_kernelprintf(" "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); buffer = buf_msg_new(0); buffer->timestamp = make_timestamp(NULL, !BSDOutputFormat, 0); buffer->pri = pri; buffer->flags = flags; /* assume there is no MSGID but there might be SD */ p = msg; sdlen = check_sd(p); if (sdlen == 0) { DPRINTF(D_DATA, "No SD\n"); } else if (sdlen > 1) { buffer->sd = copy_utf8_ascii(p, sdlen); DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); } else if (sdlen == 1 && *p == '-') { p++; DPRINTF(D_DATA, "Got SD \"-\"\n"); } else { DPRINTF(D_DATA, "Error\n"); } if (*p == ' ') p++; if (*p != '\0') { size_t msglen = strlen(p); buffer->msg = copy_utf8_ascii(p, msglen); buffer->msgorig = buffer->msg; buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; } DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); return buffer; } /* * Take a raw input line, read priority and version, call the * right message parsing function, then call logmsg(). */ void printline(const char *hname, char *msg, int flags) { struct buf_msg *buffer; int pri; char *p, *q; long n; bool bsdsyslog = true; DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline(\"%s\", \"%s\", %d)\n", hname, msg, flags); /* test for special codes */ pri = DEFUPRI; p = msg; if (*p == '<') { errno = 0; n = strtol(p + 1, &q, 10); if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) { p = q + 1; pri = (int)n; /* check for syslog-protocol version */ if (*p == '1' && p[1] == ' ') { p += 2; /* skip version and space */ bsdsyslog = false; } else { bsdsyslog = true; } } } if (pri & ~(LOG_FACMASK|LOG_PRIMASK)) pri = DEFUPRI; /* * Don't (usually) allow users to log kernel messages. * NOTE: Since LOG_KERN == 0, this will also match * messages with no facility specified. */ if ((pri & LOG_FACMASK) == LOG_KERN && KernXlat) pri = LOG_USER | LOG_PRI(pri); if (bsdsyslog) { buffer = printline_bsdsyslog(hname, p, flags, pri); } else { buffer = printline_syslogprotocol(hname, p, flags, pri); } logmsg(buffer); DELREF(buffer); } /* * Take a raw input line from /dev/klog, split and format similar to syslog(). */ void printsys(char *msg) { int n, is_printf, pri, flags; char *p, *q; struct buf_msg *buffer; klog_linebufoff = 0; for (p = msg; *p != '\0'; ) { bool bsdsyslog = true; is_printf = 1; flags = ISKERNEL | ADDDATE | BSDSYSLOG; if (SyncKernel) flags |= SYNC_FILE; if (is_printf) /* kernel printf's come out on console */ flags |= IGN_CONS; pri = DEFSPRI; if (*p == '<') { errno = 0; n = (int)strtol(p + 1, &q, 10); if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) { p = q + 1; is_printf = 0; pri = n; if (*p == '1') { /* syslog-protocol version */ p += 2; /* skip version and space */ bsdsyslog = false; } else { bsdsyslog = true; } } } for (q = p; *q != '\0' && *q != '\n'; q++) /* look for end of line; no further checks. * trust the kernel to send ASCII only */; if (*q != '\0') *q++ = '\0'; else { memcpy(linebuf, p, klog_linebufoff = q - p); break; } if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) pri = DEFSPRI; /* allow all kinds of input from kernel */ if (is_printf) buffer = printline_kernelprintf( LocalFQDN, p, flags, pri); else { if (bsdsyslog) buffer = printline_bsdsyslog( LocalFQDN, p, flags, pri); else buffer = printline_syslogprotocol( LocalFQDN, p, flags, pri); } /* set fields left open */ if (!buffer->prog) buffer->prog = strdup(_PATH_UNIX); if (!buffer->host) buffer->host = LocalFQDN; if (!buffer->recvhost) buffer->recvhost = LocalFQDN; logmsg(buffer); DELREF(buffer); p = q; } } /* * Check to see if `name' matches the provided specification, using the * specified strstr function. */ int matches_spec(const char *name, const char *spec, char *(*check)(const char *, const char *)) { const char *s; const char *cursor; char prev, next; size_t len; if (name[0] == '\0') return 0; if (strchr(name, ',')) /* sanity */ return 0; len = strlen(name); cursor = spec; while ((s = (*check)(cursor, name)) != NULL) { prev = s == spec ? ',' : *(s - 1); cursor = s + len; next = *cursor; if (prev == ',' && (next == '\0' || next == ',')) return 1; } return 0; } /* * wrapper with old function signature, * keeps calling code shorter and hides buffer allocation */ void logmsg_async(int pri, const char *sd, const char *msg, int flags) { struct buf_msg *buffer; size_t msglen; DPRINTF((D_CALL|D_DATA), "logmsg_async(%d, \"%s\", \"%s\", %d)\n", pri, sd, msg, flags); if (msg) { msglen = strlen(msg); msglen++; /* adds \0 */ buffer = buf_msg_new(msglen); buffer->msglen = strlcpy(buffer->msg, msg, msglen) + 1; } else { buffer = buf_msg_new(0); } if (sd) buffer->sd = strdup(sd); buffer->timestamp = make_timestamp(NULL, !BSDOutputFormat, 0); buffer->prog = appname; buffer->pid = include_pid; buffer->recvhost = buffer->host = LocalFQDN; buffer->pri = pri; buffer->flags = flags; logmsg(buffer); DELREF(buffer); } /* read timestamp in from_buf, convert into a timestamp in to_buf * * returns length of timestamp found in from_buf (= number of bytes consumed) */ size_t check_timestamp(unsigned char *from_buf, char **to_buf, bool from_iso, bool to_iso) { unsigned char *q; int p; bool found_ts = false; DPRINTF((D_CALL|D_DATA), "check_timestamp(%p = \"%s\", from_iso=%d, " "to_iso=%d)\n", from_buf, from_buf, from_iso, to_iso); if (!from_buf) return 0; /* * Check to see if msg looks non-standard. * looks at every char because we do not have a msg length yet */ /* detailed checking adapted from Albert Mietus' sl_timestamp.c */ if (from_iso) { if (from_buf[4] == '-' && from_buf[7] == '-' && from_buf[10] == 'T' && from_buf[13] == ':' && from_buf[16] == ':' && isdigit(from_buf[0]) && isdigit(from_buf[1]) && isdigit(from_buf[2]) && isdigit(from_buf[3]) /* YYYY */ && isdigit(from_buf[5]) && isdigit(from_buf[6]) && isdigit(from_buf[8]) && isdigit(from_buf[9]) /* mm dd */ && isdigit(from_buf[11]) && isdigit(from_buf[12]) /* HH */ && isdigit(from_buf[14]) && isdigit(from_buf[15]) /* MM */ && isdigit(from_buf[17]) && isdigit(from_buf[18]) /* SS */ ) { /* time-secfrac */ if (from_buf[19] == '.') for (p=20; isdigit(from_buf[p]); p++) /* NOP*/; else p = 19; /* time-offset */ if (from_buf[p] == 'Z' || ((from_buf[p] == '+' || from_buf[p] == '-') && from_buf[p+3] == ':' && isdigit(from_buf[p+1]) && isdigit(from_buf[p+2]) && isdigit(from_buf[p+4]) && isdigit(from_buf[p+5]) )) found_ts = true; } } else { if (from_buf[3] == ' ' && from_buf[6] == ' ' && from_buf[9] == ':' && from_buf[12] == ':' && (from_buf[4] == ' ' || isdigit(from_buf[4])) && isdigit(from_buf[5]) /* dd */ && isdigit(from_buf[7]) && isdigit(from_buf[8]) /* HH */ && isdigit(from_buf[10]) && isdigit(from_buf[11]) /* MM */ && isdigit(from_buf[13]) && isdigit(from_buf[14]) /* SS */ && isupper(from_buf[0]) && islower(from_buf[1]) /* month */ && islower(from_buf[2])) found_ts = true; } if (!found_ts) { if (from_buf[0] == '-' && from_buf[1] == ' ') { /* NILVALUE */ if (to_iso) { /* with ISO = syslog-protocol output leave * it as is, because it is better to have * no timestamp than a wrong one. */ *to_buf = strdup("-"); } else { /* with BSD Syslog the field is required * so replace it with current time */ *to_buf = make_timestamp(NULL, false, 0); } return 2; } *to_buf = make_timestamp(NULL, false, 0); return 0; } if (!from_iso && !to_iso) { /* copy BSD timestamp */ DPRINTF(D_CALL, "check_timestamp(): copy BSD timestamp\n"); *to_buf = strndup((char *)from_buf, BSD_TIMESTAMPLEN); return BSD_TIMESTAMPLEN; } else if (from_iso && to_iso) { /* copy ISO timestamp */ DPRINTF(D_CALL, "check_timestamp(): copy ISO timestamp\n"); if (!(q = (unsigned char *) strchr((char *)from_buf, ' '))) q = from_buf + strlen((char *)from_buf); *to_buf = strndup((char *)from_buf, q - from_buf); return q - from_buf; } else if (from_iso && !to_iso) { /* convert ISO->BSD */ struct tm parsed; time_t timeval; char tsbuf[MAX_TIMESTAMPLEN]; int i = 0, j; DPRINTF(D_CALL, "check_timestamp(): convert ISO->BSD\n"); for(i = 0; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' && from_buf[i] != '.' && from_buf[i] != ' '; i++) tsbuf[i] = from_buf[i]; /* copy date & time */ j = i; for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' && from_buf[i] != '+' && from_buf[i] != '-' && from_buf[i] != 'Z' && from_buf[i] != ' '; i++) ; /* skip fraction digits */ for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' && from_buf[i] != ':' && from_buf[i] != ' ' ; i++, j++) tsbuf[j] = from_buf[i]; /* copy TZ */ if (from_buf[i] == ':') i++; /* skip colon */ for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' && from_buf[i] != ' ' ; i++, j++) tsbuf[j] = from_buf[i]; /* copy TZ */ (void)memset(&parsed, 0, sizeof(parsed)); (void)strptime(tsbuf, "%FT%T%z", &parsed); parsed.tm_isdst = -1; timeval = mktime(&parsed); *to_buf = make_timestamp(&timeval, false, BSD_TIMESTAMPLEN); return i; } else if (!from_iso && to_iso) { /* convert BSD->ISO */ struct tm parsed; struct tm *current; time_t timeval; (void)memset(&parsed, 0, sizeof(parsed)); parsed.tm_isdst = -1; DPRINTF(D_CALL, "check_timestamp(): convert BSD->ISO\n"); strptime((char *)from_buf, "%b %d %T", &parsed); current = gmtime(&now); /* use current year and timezone */ parsed.tm_isdst = current->tm_isdst; parsed.tm_gmtoff = current->tm_gmtoff; parsed.tm_year = current->tm_year; if (current->tm_mon == 0 && parsed.tm_mon == 11) parsed.tm_year--; timeval = mktime(&parsed); *to_buf = make_timestamp(&timeval, true, MAX_TIMESTAMPLEN - 1); return BSD_TIMESTAMPLEN; } else { DPRINTF(D_MISC, "Executing unreachable code in check_timestamp()\n"); return 0; } } /* * Log a message to the appropriate log files, users, etc. based on * the priority. */ void logmsg(struct buf_msg *buffer) { struct filed *f; int fac, omask, prilev; DPRINTF((D_CALL|D_BUFFER), "logmsg: buffer@%p, pri 0%o/%d, flags 0x%x," " timestamp \"%s\", from \"%s\", sd \"%s\", msg \"%s\"\n", buffer, buffer->pri, buffer->pri, buffer->flags, buffer->timestamp, buffer->recvhost, buffer->sd, buffer->msg); omask = sigblock(sigmask(SIGHUP)|sigmask(SIGALRM)); /* sanity check */ assert(buffer->refcount == 1); assert(buffer->msglen <= buffer->msgsize); assert(buffer->msgorig <= buffer->msg); assert((buffer->msg && buffer->msglen == strlen(buffer->msg)+1) || (!buffer->msg && !buffer->msglen)); if (!buffer->msg && !buffer->sd && !buffer->msgid) DPRINTF(D_BUFFER, "Empty message?\n"); /* extract facility and priority level */ if (buffer->flags & MARK) fac = LOG_NFACILITIES; else fac = LOG_FAC(buffer->pri); prilev = LOG_PRI(buffer->pri); /* log the message to the particular outputs */ if (!Initialized) { f = &consfile; f->f_file = open(ctty, O_WRONLY | O_NDELAY, 0); if (f->f_file >= 0) { DELREF(f->f_prevmsg); f->f_prevmsg = NEWREF(buffer); fprintlog(f, NEWREF(buffer), NULL); DELREF(buffer); (void)close(f->f_file); } (void)sigsetmask(omask); return; } for (f = Files; f; f = f->f_next) { char *h; /* host to use for comparing */ /* skip messages that are incorrect priority */ if (!MATCH_PRI(f, fac, prilev) || f->f_pmask[fac] == INTERNAL_NOPRI) continue; /* skip messages with the incorrect host name */ /* compare with host (which is supposedly more correct), */ /* but fallback to recvhost if host is NULL */ h = (buffer->host != NULL) ? buffer->host : buffer->recvhost; if (f->f_host != NULL && h != NULL) { char shost[MAXHOSTNAMELEN + 1]; if (BSDOutputFormat) { (void)strlcpy(shost, h, sizeof(shost)); trim_anydomain(shost); h = shost; } switch (f->f_host[0]) { case '+': if (! matches_spec(h, f->f_host + 1, strcasestr)) continue; break; case '-': if (matches_spec(h, f->f_host + 1, strcasestr)) continue; break; } } /* skip messages with the incorrect program name */ if (f->f_program != NULL && buffer->prog != NULL) { switch (f->f_program[0]) { case '+': if (!matches_spec(buffer->prog, f->f_program + 1, strstr)) continue; break; case '-': if (matches_spec(buffer->prog, f->f_program + 1, strstr)) continue; break; default: if (!matches_spec(buffer->prog, f->f_program, strstr)) continue; break; } } if (f->f_type == F_CONSOLE && (buffer->flags & IGN_CONS)) continue; /* don't output marks to recently written files */ if ((buffer->flags & MARK) && (now - f->f_time) < MarkInterval / 2) continue; /* * suppress duplicate lines to this file unless NoRepeat */ #define MSG_FIELD_EQ(x) ((!buffer->x && !f->f_prevmsg->x) || \ (buffer->x && f->f_prevmsg->x && !strcmp(buffer->x, f->f_prevmsg->x))) if ((buffer->flags & MARK) == 0 && f->f_prevmsg && buffer->msglen == f->f_prevmsg->msglen && !NoRepeat && MSG_FIELD_EQ(host) && MSG_FIELD_EQ(sd) && MSG_FIELD_EQ(msg) ) { f->f_prevcount++; DPRINTF(D_DATA, "Msg repeated %d times, %ld sec of %d\n", f->f_prevcount, (long)(now - f->f_time), repeatinterval[f->f_repeatcount]); /* * If domark would have logged this by now, * flush it now (so we don't hold isolated messages), * but back off so we'll flush less often * in the future. */ if (now > REPEATTIME(f)) { fprintlog(f, NEWREF(buffer), NULL); DELREF(buffer); BACKOFF(f); } } else { /* new line, save it */ if (f->f_prevcount) fprintlog(f, NULL, NULL); f->f_repeatcount = 0; DELREF(f->f_prevmsg); f->f_prevmsg = NEWREF(buffer); fprintlog(f, NEWREF(buffer), NULL); DELREF(buffer); } } (void)sigsetmask(omask); } /* * format one buffer into output format given by flag BSDOutputFormat * line is allocated and has to be free()d by caller * size_t pointers are optional, if not NULL then they will return * different lengths used for formatting and output */ #define OUT(x) ((x)?(x):"-") bool format_buffer(struct buf_msg *buffer, char **line, size_t *ptr_linelen, size_t *ptr_msglen, size_t *ptr_tlsprefixlen, size_t *ptr_prilen) { #define FPBUFSIZE 30 static char ascii_empty[] = ""; char fp_buf[FPBUFSIZE] = "\0"; char *hostname, *shorthostname = NULL; char *ascii_sd = ascii_empty; char *ascii_msg = ascii_empty; size_t linelen, msglen, tlsprefixlen, prilen, j; DPRINTF(D_CALL, "format_buffer(%p)\n", buffer); if (!buffer) return false; /* All buffer fields are set with strdup(). To avoid problems * on memory exhaustion we allow them to be empty and replace * the essential fields with already allocated generic values. */ if (!buffer->timestamp) buffer->timestamp = timestamp; if (!buffer->host && !buffer->recvhost) buffer->host = LocalFQDN; if (LogFacPri) { const char *f_s = NULL, *p_s = NULL; int fac = buffer->pri & LOG_FACMASK; int pri = LOG_PRI(buffer->pri); char f_n[5], p_n[5]; if (LogFacPri > 1) { CODE *c; for (c = facilitynames; c->c_name != NULL; c++) { if (c->c_val == fac) { f_s = c->c_name; break; } } for (c = prioritynames; c->c_name != NULL; c++) { if (c->c_val == pri) { p_s = c->c_name; break; } } } if (f_s == NULL) { snprintf(f_n, sizeof(f_n), "%d", LOG_FAC(fac)); f_s = f_n; } if (p_s == NULL) { snprintf(p_n, sizeof(p_n), "%d", pri); p_s = p_n; } snprintf(fp_buf, sizeof(fp_buf), "<%s.%s>", f_s, p_s); } /* hostname or FQDN */ hostname = (buffer->host ? buffer->host : buffer->recvhost); if (BSDOutputFormat && (shorthostname = strdup(hostname))) { /* if the previous BSD output format with "host [recvhost]:" * gets implemented, this is the right place to distinguish * between buffer->host and buffer->recvhost */ trim_anydomain(shorthostname); hostname = shorthostname; } /* new message formatting: * instead of using iov always assemble one complete TLS-ready line * with length and priority (depending on BSDOutputFormat either in * BSD Syslog or syslog-protocol format) * * additionally save the length of the prefixes, * so UDP destinations can skip the length prefix and * file/pipe/wall destinations can omit length and priority */ /* first determine required space */ if (BSDOutputFormat) { /* only output ASCII chars */ if (buffer->sd) ascii_sd = copy_utf8_ascii(buffer->sd, strlen(buffer->sd)); if (buffer->msg) { if (IS_BOM(buffer->msg)) ascii_msg = copy_utf8_ascii(buffer->msg, buffer->msglen - 1); else /* assume already converted at input */ ascii_msg = buffer->msg; } msglen = snprintf(NULL, 0, "<%d>%s%.15s %s %s%s%s%s: %s%s%s", buffer->pri, fp_buf, buffer->timestamp, hostname, OUT(buffer->prog), buffer->pid ? "[" : "", buffer->pid ? buffer->pid : "", buffer->pid ? "]" : "", ascii_sd, (buffer->sd && buffer->msg ? " ": ""), ascii_msg); } else msglen = snprintf(NULL, 0, "<%d>1 %s%s %s %s %s %s %s%s%s", buffer->pri, fp_buf, buffer->timestamp, hostname, OUT(buffer->prog), OUT(buffer->pid), OUT(buffer->msgid), OUT(buffer->sd), (buffer->msg ? " ": ""), (buffer->msg ? buffer->msg: "")); /* add space for length prefix */ tlsprefixlen = 0; for (j = msglen; j; j /= 10) tlsprefixlen++; /* one more for the space */ tlsprefixlen++; prilen = snprintf(NULL, 0, "<%d>", buffer->pri); if (!BSDOutputFormat) prilen += 2; /* version char and space */ MALLOC(*line, msglen + tlsprefixlen + 1); if (BSDOutputFormat) linelen = snprintf(*line, msglen + tlsprefixlen + 1, "%zu <%d>%s%.15s %s %s%s%s%s: %s%s%s", msglen, buffer->pri, fp_buf, buffer->timestamp, hostname, OUT(buffer->prog), (buffer->pid ? "[" : ""), (buffer->pid ? buffer->pid : ""), (buffer->pid ? "]" : ""), ascii_sd, (buffer->sd && buffer->msg ? " ": ""), ascii_msg); else linelen = snprintf(*line, msglen + tlsprefixlen + 1, "%zu <%d>1 %s%s %s %s %s %s %s%s%s", msglen, buffer->pri, fp_buf, buffer->timestamp, hostname, OUT(buffer->prog), OUT(buffer->pid), OUT(buffer->msgid), OUT(buffer->sd), (buffer->msg ? " ": ""), (buffer->msg ? buffer->msg: "")); DPRINTF(D_DATA, "formatted %zu octets to: '%.*s' (linelen %zu, " "msglen %zu, tlsprefixlen %zu, prilen %zu)\n", linelen, (int)linelen, *line, linelen, msglen, tlsprefixlen, prilen); FREEPTR(shorthostname); if (ascii_sd != ascii_empty) FREEPTR(ascii_sd); if (ascii_msg != ascii_empty && ascii_msg != buffer->msg) FREEPTR(ascii_msg); if (ptr_linelen) *ptr_linelen = linelen; if (ptr_msglen) *ptr_msglen = msglen; if (ptr_tlsprefixlen) *ptr_tlsprefixlen = tlsprefixlen; if (ptr_prilen) *ptr_prilen = prilen; return true; } /* * if qentry == NULL: new message, if temporarily undeliverable it will be enqueued * if qentry != NULL: a temporarily undeliverable message will not be enqueued, * but after delivery be removed from the queue */ void fprintlog(struct filed *f, struct buf_msg *passedbuffer, struct buf_queue *qentry) { static char crnl[] = "\r\n"; struct buf_msg *buffer = passedbuffer; struct iovec iov[4]; struct iovec *v = iov; bool error = false; int e = 0, len = 0; size_t msglen, linelen, tlsprefixlen, prilen; char *p, *line = NULL, *lineptr = NULL; #ifndef DISABLE_SIGN bool newhash = false; #endif #define REPBUFSIZE 80 char greetings[200]; #define ADDEV() do { v++; assert((size_t)(v - iov) < A_CNT(iov)); } while(/*CONSTCOND*/0) DPRINTF(D_CALL, "fprintlog(%p, %p, %p)\n", f, buffer, qentry); f->f_time = now; /* increase refcount here and lower again at return. * this enables the buffer in the else branch to be freed * --> every branch needs one NEWREF() or buf_msg_new()! */ if (buffer) { (void)NEWREF(buffer); } else { if (f->f_prevcount > 1) { /* possible syslog-sign incompatibility: * assume destinations f1 and f2 share one SG and * get the same message sequence. * * now both f1 and f2 generate "repeated" messages * "repeated" messages are different due to different * timestamps * the SG will get hashes for the two "repeated" messages * * now both f1 and f2 are just fine, but a verification * will report that each 'lost' a message, i.e. the * other's "repeated" message * * conditions for 'safe configurations': * - use NoRepeat option, * - use SG 3, or * - have exactly one destination for every PRI */ buffer = buf_msg_new(REPBUFSIZE); buffer->msglen = snprintf(buffer->msg, REPBUFSIZE, "last message repeated %d times", f->f_prevcount); buffer->timestamp = make_timestamp(NULL, !BSDOutputFormat, 0); buffer->pri = f->f_prevmsg->pri; buffer->host = LocalFQDN; buffer->prog = appname; buffer->pid = include_pid; } else { buffer = NEWREF(f->f_prevmsg); } } /* no syslog-sign messages to tty/console/... */ if ((buffer->flags & SIGN_MSG) && ((f->f_type == F_UNUSED) || (f->f_type == F_TTY) || (f->f_type == F_CONSOLE) || (f->f_type == F_USERS) || (f->f_type == F_WALL) || (f->f_type == F_FIFO))) { DELREF(buffer); return; } /* buffering works only for few types */ if (qentry && (f->f_type != F_TLS) && (f->f_type != F_PIPE) && (f->f_type != F_FILE) && (f->f_type != F_FIFO)) { errno = 0; logerror("Warning: unexpected message type %d in buffer", f->f_type); DELREF(buffer); return; } if (!format_buffer(buffer, &line, &linelen, &msglen, &tlsprefixlen, &prilen)) { DPRINTF(D_CALL, "format_buffer() failed, skip message\n"); DELREF(buffer); return; } /* assert maximum message length */ if (TypeInfo[f->f_type].max_msg_length != -1 && (size_t)TypeInfo[f->f_type].max_msg_length < linelen - tlsprefixlen - prilen) { linelen = TypeInfo[f->f_type].max_msg_length + tlsprefixlen + prilen; DPRINTF(D_DATA, "truncating oversized message to %zu octets\n", linelen); } #ifndef DISABLE_SIGN /* keep state between appending the hash (before buffer is sent) * and possibly sending a SB (after buffer is sent): */ /* get hash */ if (!(buffer->flags & SIGN_MSG) && !qentry) { char *hash = NULL; struct signature_group_t *sg; if ((sg = sign_get_sg(buffer->pri, f)) != NULL) { if (sign_msg_hash(line + tlsprefixlen, &hash)) newhash = sign_append_hash(hash, sg); else DPRINTF(D_SIGN, "Unable to hash line \"%s\"\n", line); } } #endif /* !DISABLE_SIGN */ /* set start and length of buffer and/or fill iovec */ switch (f->f_type) { case F_UNUSED: /* nothing */ break; case F_TLS: /* nothing, as TLS uses whole buffer to send */ lineptr = line; len = linelen; break; case F_FORW: lineptr = line + tlsprefixlen; len = linelen - tlsprefixlen; break; case F_PIPE: case F_FIFO: case F_FILE: /* fallthrough */ if (f->f_flags & FFLAG_FULL) { v->iov_base = line + tlsprefixlen; v->iov_len = linelen - tlsprefixlen; } else { v->iov_base = line + tlsprefixlen + prilen; v->iov_len = linelen - tlsprefixlen - prilen; } ADDEV(); v->iov_base = &crnl[1]; v->iov_len = 1; ADDEV(); break; case F_CONSOLE: case F_TTY: /* filter non-ASCII */ p = line; while (*p) { *p = FORCE2ASCII(*p); p++; } v->iov_base = line + tlsprefixlen + prilen; v->iov_len = linelen - tlsprefixlen - prilen; ADDEV(); v->iov_base = crnl; v->iov_len = 2; ADDEV(); break; case F_WALL: v->iov_base = greetings; v->iov_len = snprintf(greetings, sizeof(greetings), "\r\n\7Message from syslogd@%s at %s ...\r\n", (buffer->host ? buffer->host : buffer->recvhost), buffer->timestamp); ADDEV(); /* FALLTHROUGH */ case F_USERS: /* fallthrough */ /* filter non-ASCII */ p = line; while (*p) { *p = FORCE2ASCII(*p); p++; } v->iov_base = line + tlsprefixlen + prilen; v->iov_len = linelen - tlsprefixlen - prilen; ADDEV(); v->iov_base = &crnl[1]; v->iov_len = 1; ADDEV(); break; } /* send */ switch (f->f_type) { case F_UNUSED: DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name); break; case F_FORW: DPRINTF(D_MISC, "Logging to %s %s\n", TypeInfo[f->f_type].name, f->f_un.f_forw.f_hname); udp_send(f, lineptr, len); break; #ifndef DISABLE_TLS case F_TLS: DPRINTF(D_MISC, "Logging to %s %s\n", TypeInfo[f->f_type].name, f->f_un.f_tls.tls_conn->hostname); /* make sure every message gets queued once * it will be removed when sendmsg is sent and free()d */ if (!qentry) qentry = message_queue_add(f, NEWREF(buffer)); (void)tls_send(f, lineptr, len, qentry); break; #endif /* !DISABLE_TLS */ case F_PIPE: DPRINTF(D_MISC, "Logging to %s %s\n", TypeInfo[f->f_type].name, f->f_un.f_pipe.f_pname); if (f->f_un.f_pipe.f_pid == 0) { /* (re-)open */ if ((f->f_file = p_open(f->f_un.f_pipe.f_pname, &f->f_un.f_pipe.f_pid)) < 0) { f->f_type = F_UNUSED; logerror("%s", f->f_un.f_pipe.f_pname); message_queue_freeall(f); break; } else if (!qentry) /* prevent recursion */ SEND_QUEUE(f); } if (writev(f->f_file, iov, v - iov) < 0) { e = errno; if (f->f_un.f_pipe.f_pid > 0) { (void) close(f->f_file); deadq_enter(f->f_un.f_pipe.f_pid, f->f_un.f_pipe.f_pname); } f->f_un.f_pipe.f_pid = 0; /* * If the error was EPIPE, then what is likely * has happened is we have a command that is * designed to take a single message line and * then exit, but we tried to feed it another * one before we reaped the child and thus * reset our state. * * Well, now we've reset our state, so try opening * the pipe and sending the message again if EPIPE * was the error. */ if (e == EPIPE) { if ((f->f_file = p_open(f->f_un.f_pipe.f_pname, &f->f_un.f_pipe.f_pid)) < 0) { f->f_type = F_UNUSED; logerror("%s", f->f_un.f_pipe.f_pname); message_queue_freeall(f); break; } if (writev(f->f_file, iov, v - iov) < 0) { e = errno; if (f->f_un.f_pipe.f_pid > 0) { (void) close(f->f_file); deadq_enter(f->f_un.f_pipe.f_pid, f->f_un.f_pipe.f_pname); } f->f_un.f_pipe.f_pid = 0; error = true; /* enqueue on return */ } else e = 0; } if (e != 0 && !error) { errno = e; logerror("%s", f->f_un.f_pipe.f_pname); } } if (e == 0 && qentry) { /* sent buffered msg */ message_queue_remove(f, qentry); } break; case F_CONSOLE: if (buffer->flags & IGN_CONS) { DPRINTF(D_MISC, "Logging to %s (ignored)\n", TypeInfo[f->f_type].name); break; } /* FALLTHROUGH */ case F_TTY: case F_FILE: DPRINTF(D_MISC, "Logging to %s %s\n", TypeInfo[f->f_type].name, f->f_un.f_fname); again: if ((f->f_type == F_FILE ? writev(f->f_file, iov, v - iov) : writev1(f->f_file, iov, v - iov)) < 0) { e = errno; if (f->f_type == F_FILE && e == ENOSPC) { int lasterror = f->f_lasterror; f->f_lasterror = e; if (lasterror != e) logerror("%s", f->f_un.f_fname); error = true; /* enqueue on return */ } (void)close(f->f_file); /* * Check for errors on TTY's due to loss of tty */ if ((e == EIO || e == EBADF) && f->f_type != F_FILE) { f->f_file = open(f->f_un.f_fname, O_WRONLY|O_APPEND|O_NONBLOCK, 0); if (f->f_file < 0) { f->f_type = F_UNUSED; logerror("%s", f->f_un.f_fname); message_queue_freeall(f); } else goto again; } else { f->f_type = F_UNUSED; errno = e; f->f_lasterror = e; logerror("%s", f->f_un.f_fname); message_queue_freeall(f); } } else { f->f_lasterror = 0; if ((buffer->flags & SYNC_FILE) && (f->f_flags & FFLAG_SYNC)) (void)fsync(f->f_file); /* Problem with files: We cannot check beforehand if * they would be writeable and call send_queue() first. * So we call send_queue() after a successful write, * which means the first message will be out of order. */ if (!qentry) /* prevent recursion */ SEND_QUEUE(f); else if (qentry) /* sent buffered msg */ message_queue_remove(f, qentry); } break; case F_FIFO: DPRINTF(D_MISC, "Logging to %s %s\n", TypeInfo[f->f_type].name, f->f_un.f_fname); if (f->f_file < 0) { f->f_file = open(f->f_un.f_fname, O_WRONLY|O_NONBLOCK, 0); e = errno; if (f->f_file < 0 && e == ENXIO) { /* Drop messages with no reader */ if (qentry) message_queue_remove(f, qentry); break; } } if (f->f_file >= 0 && writev(f->f_file, iov, v - iov) < 0) { e = errno; /* Enqueue if the fifo buffer is full */ if (e == EAGAIN) { if (f->f_lasterror != e) logerror("%s", f->f_un.f_fname); f->f_lasterror = e; error = true; /* enqueue on return */ break; } close(f->f_file); f->f_file = -1; /* Drop messages with no reader */ if (e == EPIPE) { if (qentry) message_queue_remove(f, qentry); break; } } if (f->f_file < 0) { f->f_type = F_UNUSED; errno = e; f->f_lasterror = e; logerror("%s", f->f_un.f_fname); message_queue_freeall(f); break; } f->f_lasterror = 0; if (!qentry) /* prevent recursion (see comment for F_FILE) */ SEND_QUEUE(f); if (qentry) /* sent buffered msg */ message_queue_remove(f, qentry); break; case F_USERS: case F_WALL: DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name); wallmsg(f, iov, v - iov); break; } f->f_prevcount = 0; if (error && !qentry) message_queue_add(f, NEWREF(buffer)); #ifndef DISABLE_SIGN if (newhash) { struct signature_group_t *sg; sg = sign_get_sg(buffer->pri, f); (void)sign_send_signature_block(sg, false); } #endif /* !DISABLE_SIGN */ /* this belongs to the ad-hoc buffer at the first if(buffer) */ DELREF(buffer); /* TLS frees on its own */ if (f->f_type != F_TLS) FREEPTR(line); } /* send one line by UDP */ void udp_send(struct filed *f, char *line, size_t len) { int lsent, fail, retry, j; struct addrinfo *r; DPRINTF((D_NET|D_CALL), "udp_send(f=%p, line=\"%s\", " "len=%zu) to dest.\n", f, line, len); if (!finet) return; lsent = -1; fail = 0; assert(f->f_type == F_FORW); for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) { retry = 0; for (j = 0; j < finet->fd; j++) { if (finet[j+1].af != r->ai_family) continue; sendagain: lsent = sendto(finet[j+1].fd, line, len, 0, r->ai_addr, r->ai_addrlen); if (lsent == -1) { switch (errno) { case ENOBUFS: /* wait/retry/drop */ if (++retry < 5) { usleep(1000); goto sendagain; } break; case EHOSTDOWN: case EHOSTUNREACH: case ENETDOWN: /* drop */ break; default: /* busted */ fail++; break; } } else if ((size_t)lsent == len) break; } if ((size_t)lsent != len && fail) { f->f_type = F_UNUSED; logerror("sendto() failed"); } } } /* * WALLMSG -- Write a message to the world at large * * Write the specified message to either the entire * world, or a list of approved users. */ void wallmsg(struct filed *f, struct iovec *iov, size_t iovcnt) { #ifdef __NetBSD_Version__ static int reenter; /* avoid calling ourselves */ int i; char *p; struct utmpentry *ep; if (reenter++) return; (void)getutentries(NULL, &ep); /* NOSTRICT */ for (; ep; ep = ep->next) { if (f->f_type == F_WALL) { if ((p = ttymsg(iov, iovcnt, ep->line, TTYMSGTIME)) != NULL) { errno = 0; /* already in msg */ logerror("%s", p); } continue; } /* should we send the message to this user? */ for (i = 0; i < MAXUNAMES; i++) { if (!f->f_un.f_uname[i][0]) break; if (strcmp(f->f_un.f_uname[i], ep->name) == 0) { struct stat st; char tty[MAXPATHLEN]; snprintf(tty, sizeof(tty), "%s/%s", _PATH_DEV, ep->line); if (stat(tty, &st) != -1 && (st.st_mode & S_IWGRP) == 0) break; if ((p = ttymsg(iov, iovcnt, ep->line, TTYMSGTIME)) != NULL) { errno = 0; /* already in msg */ logerror("%s", p); } break; } } } reenter = 0; #endif /* __NetBSD_Version__ */ } void /*ARGSUSED*/ reapchild(int fd, short event, void *ev) { int status; pid_t pid; struct filed *f; while ((pid = wait3(&status, WNOHANG, NULL)) > 0) { if (!Initialized || ShuttingDown) { /* * Be silent while we are initializing or * shutting down. */ continue; } if (deadq_remove(pid)) continue; /* Now, look in the list of active processes. */ for (f = Files; f != NULL; f = f->f_next) { if (f->f_type == F_PIPE && f->f_un.f_pipe.f_pid == pid) { (void) close(f->f_file); f->f_un.f_pipe.f_pid = 0; log_deadchild(pid, status, f->f_un.f_pipe.f_pname); break; } } } } /* * Return a printable representation of a host address (FQDN if available) */ const char * cvthname(struct sockaddr_storage *f) { int error; int niflag = NI_DGRAM; static char host[NI_MAXHOST], ip[NI_MAXHOST]; error = getnameinfo((struct sockaddr*)f, ((struct sockaddr*)f)->sa_len, ip, sizeof ip, NULL, 0, NI_NUMERICHOST|niflag); DPRINTF(D_CALL, "cvthname(%s)\n", ip); if (error) { DPRINTF(D_NET, "Malformed from address %s\n", gai_strerror(error)); return "???"; } if (!UseNameService) return ip; error = getnameinfo((struct sockaddr*)f, ((struct sockaddr*)f)->sa_len, host, sizeof host, NULL, 0, niflag); if (error) { DPRINTF(D_NET, "Host name for your address (%s) unknown\n", ip); return ip; } return host; } void trim_anydomain(char *host) { bool onlydigits = true; int i; if (!BSDOutputFormat) return; /* if non-digits found, then assume hostname and cut at first dot (this * case also covers IPv6 addresses which should not contain dots), * if only digits then assume IPv4 address and do not cut at all */ for (i = 0; host[i]; i++) { if (host[i] == '.' && !onlydigits) host[i] = '\0'; else if (!isdigit((unsigned char)host[i]) && host[i] != '.') onlydigits = false; } } static void /*ARGSUSED*/ domark(int fd, short event, void *ev) { struct event *ev_pass = (struct event *)ev; struct filed *f; dq_t q, nextq; sigset_t newmask, omask; schedule_event(&ev_pass, &((struct timeval){TIMERINTVL, 0}), domark, ev_pass); DPRINTF((D_CALL|D_EVENT), "domark()\n"); BLOCK_SIGNALS(omask, newmask); now = time(NULL); MarkSeq += TIMERINTVL; if (MarkSeq >= MarkInterval) { logmsg_async(LOG_INFO, NULL, "-- MARK --", ADDDATE|MARK); MarkSeq = 0; } for (f = Files; f; f = f->f_next) { if (f->f_prevcount && now >= REPEATTIME(f)) { DPRINTF(D_DATA, "Flush %s: repeated %d times, %d sec.\n", TypeInfo[f->f_type].name, f->f_prevcount, repeatinterval[f->f_repeatcount]); fprintlog(f, NULL, NULL); BACKOFF(f); } } message_allqueues_check(); RESTORE_SIGNALS(omask); /* Walk the dead queue, and see if we should signal somebody. */ for (q = TAILQ_FIRST(&deadq_head); q != NULL; q = nextq) { nextq = TAILQ_NEXT(q, dq_entries); switch (q->dq_timeout) { case 0: /* Already signalled once, try harder now. */ if (kill(q->dq_pid, SIGKILL) != 0) (void) deadq_remove(q->dq_pid); break; case 1: /* * Timed out on the dead queue, send terminate * signal. Note that we leave the removal from * the dead queue to reapchild(), which will * also log the event (unless the process * didn't even really exist, in case we simply * drop it from the dead queue). */ if (kill(q->dq_pid, SIGTERM) != 0) { (void) deadq_remove(q->dq_pid); break; } /* FALLTHROUGH */ default: q->dq_timeout--; } } #ifndef DISABLE_SIGN if (GlobalSign.rsid) { /* check if initialized */ struct signature_group_t *sg; STAILQ_FOREACH(sg, &GlobalSign.SigGroups, entries) { sign_send_certificate_block(sg); } } #endif /* !DISABLE_SIGN */ } /* * Print syslogd errors some place. */ void logerror(const char *fmt, ...) { static int logerror_running; va_list ap; char tmpbuf[BUFSIZ]; char buf[BUFSIZ]; char *outbuf; /* If there's an error while trying to log an error, give up. */ if (logerror_running) return; logerror_running = 1; va_start(ap, fmt); (void)vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, ap); va_end(ap); if (errno) { (void)snprintf(buf, sizeof(buf), "%s: %s", tmpbuf, strerror(errno)); outbuf = buf; } else { (void)snprintf(buf, sizeof(buf), "%s", tmpbuf); outbuf = tmpbuf; } if (daemonized) logmsg_async(LOG_SYSLOG|LOG_ERR, NULL, outbuf, ADDDATE); if (!daemonized && Debug) DPRINTF(D_MISC, "%s\n", outbuf); if (!daemonized && !Debug) printf("%s: %s\n", getprogname(), outbuf); logerror_running = 0; } /* * Print syslogd info some place. */ void loginfo(const char *fmt, ...) { va_list ap; char buf[BUFSIZ]; va_start(ap, fmt); (void)vsnprintf(buf, sizeof(buf), fmt, ap); va_end(ap); DPRINTF(D_MISC, "%s\n", buf); logmsg_async(LOG_SYSLOG|LOG_INFO, NULL, buf, ADDDATE); } #ifndef DISABLE_TLS static inline void free_incoming_tls_sockets(void) { struct TLS_Incoming_Conn *tls_in; int i; /* * close all listening and connected TLS sockets */ if (TLS_Listen_Set) for (i = 0; i < TLS_Listen_Set->fd; i++) { if (close(TLS_Listen_Set[i+1].fd) == -1) logerror("close() failed"); DEL_EVENT(TLS_Listen_Set[i+1].ev); FREEPTR(TLS_Listen_Set[i+1].ev); } FREEPTR(TLS_Listen_Set); /* close/free incoming TLS connections */ while (!SLIST_EMPTY(&TLS_Incoming_Head)) { tls_in = SLIST_FIRST(&TLS_Incoming_Head); SLIST_REMOVE_HEAD(&TLS_Incoming_Head, entries); FREEPTR(tls_in->inbuf); free_tls_conn(tls_in->tls_conn); free(tls_in); } } #endif /* !DISABLE_TLS */ void /*ARGSUSED*/ die(int fd, short event, void *ev) { struct filed *f, *next; char **p; sigset_t newmask, omask; int i; size_t j; ShuttingDown = 1; /* Don't log SIGCHLDs. */ /* prevent recursive signals */ BLOCK_SIGNALS(omask, newmask); errno = 0; if (ev != NULL) logerror("Exiting on signal %d", fd); else logerror("Fatal error, exiting"); /* * flush any pending output */ for (f = Files; f != NULL; f = f->f_next) { /* flush any pending output */ if (f->f_prevcount) fprintlog(f, NULL, NULL); SEND_QUEUE(f); } #ifndef DISABLE_TLS free_incoming_tls_sockets(); #endif /* !DISABLE_TLS */ #ifndef DISABLE_SIGN sign_global_free(); #endif /* !DISABLE_SIGN */ /* * Close all open log files. */ for (f = Files; f != NULL; f = next) { message_queue_freeall(f); switch (f->f_type) { case F_FILE: case F_TTY: case F_CONSOLE: case F_FIFO: if (f->f_file >= 0) (void)close(f->f_file); break; case F_PIPE: if (f->f_un.f_pipe.f_pid > 0) { (void)close(f->f_file); } f->f_un.f_pipe.f_pid = 0; break; case F_FORW: if (f->f_un.f_forw.f_addr) freeaddrinfo(f->f_un.f_forw.f_addr); break; #ifndef DISABLE_TLS case F_TLS: free_tls_conn(f->f_un.f_tls.tls_conn); break; #endif /* !DISABLE_TLS */ } next = f->f_next; DELREF(f->f_prevmsg); FREEPTR(f->f_program); FREEPTR(f->f_host); DEL_EVENT(f->f_sq_event); free((char *)f); } /* * Close all open UDP sockets */ if (finet) { for (i = 0; i < finet->fd; i++) { (void)close(finet[i+1].fd); DEL_EVENT(finet[i+1].ev); FREEPTR(finet[i+1].ev); } FREEPTR(finet); } /* free config options */ for (j = 0; j < A_CNT(TypeInfo); j++) { FREEPTR(TypeInfo[j].queue_length_string); FREEPTR(TypeInfo[j].queue_size_string); } #ifndef DISABLE_TLS FREEPTR(tls_opt.CAdir); FREEPTR(tls_opt.CAfile); FREEPTR(tls_opt.keyfile); FREEPTR(tls_opt.certfile); FREEPTR(tls_opt.x509verify); FREEPTR(tls_opt.bindhost); FREEPTR(tls_opt.bindport); FREEPTR(tls_opt.server); FREEPTR(tls_opt.gen_cert); free_cred_SLIST(&tls_opt.cert_head); free_cred_SLIST(&tls_opt.fprint_head); FREE_SSL_CTX(tls_opt.global_TLS_CTX); #endif /* !DISABLE_TLS */ FREEPTR(funix); for (p = LogPaths; p && *p; p++) unlink(*p); exit(0); } #ifndef DISABLE_SIGN /* * get one "sign_delim_sg2" item, convert and store in ordered queue */ void store_sign_delim_sg2(char *tmp_buf) { struct string_queue *sqentry, *sqe1, *sqe2; if(!(sqentry = malloc(sizeof(*sqentry)))) { logerror("Unable to allocate memory"); return; } /*LINTED constcond/null effect */ assert(sizeof(int64_t) == sizeof(uint_fast64_t)); if (dehumanize_number(tmp_buf, (int64_t*) &(sqentry->key)) == -1 || sqentry->key > (LOG_NFACILITIES<<3)) { DPRINTF(D_PARSE, "invalid sign_delim_sg2: %s\n", tmp_buf); free(sqentry); FREEPTR(tmp_buf); return; } sqentry->data = tmp_buf; if (STAILQ_EMPTY(&GlobalSign.sig2_delims)) { STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims, sqentry, entries); return; } /* keep delimiters sorted */ sqe1 = sqe2 = STAILQ_FIRST(&GlobalSign.sig2_delims); if (sqe1->key > sqentry->key) { STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims, sqentry, entries); return; } while ((sqe1 = sqe2) && (sqe2 = STAILQ_NEXT(sqe1, entries))) { if (sqe2->key > sqentry->key) { break; } else if (sqe2->key == sqentry->key) { DPRINTF(D_PARSE, "duplicate sign_delim_sg2: %s\n", tmp_buf); FREEPTR(sqentry); FREEPTR(tmp_buf); return; } } STAILQ_INSERT_AFTER(&GlobalSign.sig2_delims, sqe1, sqentry, entries); } #endif /* !DISABLE_SIGN */ /* * read syslog.conf */ void read_config_file(FILE *cf, struct filed **f_ptr) { size_t linenum = 0; size_t i; struct filed *f, **nextp; char cline[LINE_MAX]; char prog[NAME_MAX + 1]; char host[MAXHOSTNAMELEN]; const char *p; char *q; bool found_keyword; #ifndef DISABLE_TLS struct peer_cred *cred = NULL; struct peer_cred_head *credhead = NULL; #endif /* !DISABLE_TLS */ #ifndef DISABLE_SIGN char *sign_sg_str = NULL; #endif /* !DISABLE_SIGN */ #if (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN)) char *tmp_buf = NULL; #endif /* (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN)) */ /* central list of recognized configuration keywords * and an address for their values as strings */ const struct config_keywords { const char *keyword; char **variable; } config_keywords[] = { #ifndef DISABLE_TLS /* TLS settings */ {"tls_ca", &tls_opt.CAfile}, {"tls_cadir", &tls_opt.CAdir}, {"tls_cert", &tls_opt.certfile}, {"tls_key", &tls_opt.keyfile}, {"tls_verify", &tls_opt.x509verify}, {"tls_bindport", &tls_opt.bindport}, {"tls_bindhost", &tls_opt.bindhost}, {"tls_server", &tls_opt.server}, {"tls_gen_cert", &tls_opt.gen_cert}, /* special cases in parsing */ {"tls_allow_fingerprints",&tmp_buf}, {"tls_allow_clientcerts", &tmp_buf}, /* buffer settings */ {"tls_queue_length", &TypeInfo[F_TLS].queue_length_string}, {"tls_queue_size", &TypeInfo[F_TLS].queue_size_string}, #endif /* !DISABLE_TLS */ {"file_queue_length", &TypeInfo[F_FILE].queue_length_string}, {"pipe_queue_length", &TypeInfo[F_PIPE].queue_length_string}, {"fifo_queue_length", &TypeInfo[F_FIFO].queue_length_string}, {"file_queue_size", &TypeInfo[F_FILE].queue_size_string}, {"pipe_queue_size", &TypeInfo[F_PIPE].queue_size_string}, {"fifo_queue_size", &TypeInfo[F_FIFO].queue_size_string}, #ifndef DISABLE_SIGN /* syslog-sign setting */ {"sign_sg", &sign_sg_str}, /* also special case in parsing */ {"sign_delim_sg2", &tmp_buf}, #endif /* !DISABLE_SIGN */ }; DPRINTF(D_CALL, "read_config_file()\n"); /* free all previous config options */ for (i = 0; i < A_CNT(TypeInfo); i++) { if (TypeInfo[i].queue_length_string && TypeInfo[i].queue_length_string != TypeInfo[i].default_length_string) { FREEPTR(TypeInfo[i].queue_length_string); TypeInfo[i].queue_length_string = strdup(TypeInfo[i].default_length_string); } if (TypeInfo[i].queue_size_string && TypeInfo[i].queue_size_string != TypeInfo[i].default_size_string) { FREEPTR(TypeInfo[i].queue_size_string); TypeInfo[i].queue_size_string = strdup(TypeInfo[i].default_size_string); } } for (i = 0; i < A_CNT(config_keywords); i++) FREEPTR(*config_keywords[i].variable); /* * global settings */ while (fgets(cline, sizeof(cline), cf) != NULL) { linenum++; for (p = cline; isspace((unsigned char)*p); ++p) continue; if ((*p == '\0') || (*p == '#')) continue; for (i = 0; i < A_CNT(config_keywords); i++) { if (copy_config_value(config_keywords[i].keyword, config_keywords[i].variable, &p, ConfFile, linenum)) { DPRINTF((D_PARSE|D_MEM), "found option %s, saved @%p\n", config_keywords[i].keyword, *config_keywords[i].variable); #ifndef DISABLE_SIGN if (!strcmp("sign_delim_sg2", config_keywords[i].keyword)) do { store_sign_delim_sg2(tmp_buf); } while (copy_config_value_word( &tmp_buf, &p)); #endif /* !DISABLE_SIGN */ #ifndef DISABLE_TLS /* special cases with multiple parameters */ if (!strcmp("tls_allow_fingerprints", config_keywords[i].keyword)) credhead = &tls_opt.fprint_head; else if (!strcmp("tls_allow_clientcerts", config_keywords[i].keyword)) credhead = &tls_opt.cert_head; if (credhead) do { if(!(cred = malloc(sizeof(*cred)))) { logerror("Unable to " "allocate memory"); break; } cred->data = tmp_buf; tmp_buf = NULL; SLIST_INSERT_HEAD(credhead, cred, entries); } while /* additional values? */ (copy_config_value_word(&tmp_buf, &p)); credhead = NULL; break; #endif /* !DISABLE_TLS */ } } } /* convert strings to integer values */ for (i = 0; i < A_CNT(TypeInfo); i++) { if (!TypeInfo[i].queue_length_string || dehumanize_number(TypeInfo[i].queue_length_string, &TypeInfo[i].queue_length) == -1) if (dehumanize_number(TypeInfo[i].default_length_string, &TypeInfo[i].queue_length) == -1) abort(); if (!TypeInfo[i].queue_size_string || dehumanize_number(TypeInfo[i].queue_size_string, &TypeInfo[i].queue_size) == -1) if (dehumanize_number(TypeInfo[i].default_size_string, &TypeInfo[i].queue_size) == -1) abort(); } #ifndef DISABLE_SIGN if (sign_sg_str) { if (sign_sg_str[1] == '\0' && (sign_sg_str[0] == '0' || sign_sg_str[0] == '1' || sign_sg_str[0] == '2' || sign_sg_str[0] == '3')) GlobalSign.sg = sign_sg_str[0] - '0'; else { GlobalSign.sg = SIGN_SG; DPRINTF(D_MISC, "Invalid sign_sg value `%s', " "use default value `%d'\n", sign_sg_str, GlobalSign.sg); } } else /* disable syslog-sign */ GlobalSign.sg = -1; #endif /* !DISABLE_SIGN */ rewind(cf); linenum = 0; /* * Foreach line in the conf table, open that file. */ f = NULL; nextp = &f; strcpy(prog, "*"); strcpy(host, "*"); while (fgets(cline, sizeof(cline), cf) != NULL) { linenum++; found_keyword = false; /* * check for end-of-section, comments, strip off trailing * spaces and newline character. #!prog is treated specially: * following lines apply only to that program. */ for (p = cline; isspace((unsigned char)*p); ++p) continue; if (*p == '\0') continue; if (*p == '#') { p++; if (*p != '!' && *p != '+' && *p != '-') continue; } for (i = 0; i < A_CNT(config_keywords); i++) { if (!strncasecmp(p, config_keywords[i].keyword, strlen(config_keywords[i].keyword))) { DPRINTF(D_PARSE, "skip cline %zu with keyword %s\n", linenum, config_keywords[i].keyword); found_keyword = true; } } if (found_keyword) continue; if (*p == '+' || *p == '-') { host[0] = *p++; while (isspace((unsigned char)*p)) p++; if (*p == '\0' || *p == '*') { strcpy(host, "*"); continue; } /* the +hostname expression will continue * to use the LocalHostName, not the FQDN */ for (i = 1; i < MAXHOSTNAMELEN - 1; i++) { if (*p == '@') { (void)strncpy(&host[i], LocalHostName, sizeof(host) - 1 - i); host[sizeof(host) - 1] = '\0'; i = strlen(host) - 1; p++; continue; } if (!isalnum((unsigned char)*p) && *p != '.' && *p != '-' && *p != ',') break; host[i] = *p++; } host[i] = '\0'; continue; } if (*p == '!') { p++; while (isspace((unsigned char)*p)) p++; if (*p == '\0' || *p == '*') { strcpy(prog, "*"); continue; } for (i = 0; i < NAME_MAX; i++) { if (!isprint((unsigned char)p[i])) break; prog[i] = p[i]; } prog[i] = '\0'; continue; } for (q = strchr(cline, '\0'); isspace((unsigned char)*--q);) continue; *++q = '\0'; if ((f = calloc(1, sizeof(*f))) == NULL) { logerror("alloc failed"); die(0, 0, NULL); } if (!*f_ptr) *f_ptr = f; /* return first node */ *nextp = f; nextp = &f->f_next; cfline(linenum, cline, f, prog, host); } } /* * INIT -- Initialize syslogd from configuration table */ void /*ARGSUSED*/ init(int fd, short event, void *ev) { FILE *cf; int i; struct filed *f, *newf, **nextp, *f2; char *p; sigset_t newmask, omask; #ifndef DISABLE_TLS char *tls_status_msg = NULL; struct peer_cred *cred = NULL; #endif /* !DISABLE_TLS */ /* prevent recursive signals */ BLOCK_SIGNALS(omask, newmask); DPRINTF((D_EVENT|D_CALL), "init\n"); /* * be careful about dependencies and order of actions: * 1. flush buffer queues * 2. flush -sign SBs * 3. flush/delete buffer queue again, in case an SB got there * 4. close files/connections */ /* * flush any pending output */ for (f = Files; f != NULL; f = f->f_next) { /* flush any pending output */ if (f->f_prevcount) fprintlog(f, NULL, NULL); SEND_QUEUE(f); } /* some actions only on SIGHUP and not on first start */ if (Initialized) { #ifndef DISABLE_SIGN sign_global_free(); #endif /* !DISABLE_SIGN */ #ifndef DISABLE_TLS free_incoming_tls_sockets(); #endif /* !DISABLE_TLS */ Initialized = 0; } /* * Close all open log files. */ for (f = Files; f != NULL; f = f->f_next) { switch (f->f_type) { case F_FILE: case F_TTY: case F_CONSOLE: (void)close(f->f_file); break; case F_PIPE: if (f->f_un.f_pipe.f_pid > 0) { (void)close(f->f_file); deadq_enter(f->f_un.f_pipe.f_pid, f->f_un.f_pipe.f_pname); } f->f_un.f_pipe.f_pid = 0; break; case F_FORW: if (f->f_un.f_forw.f_addr) freeaddrinfo(f->f_un.f_forw.f_addr); break; #ifndef DISABLE_TLS case F_TLS: free_tls_sslptr(f->f_un.f_tls.tls_conn); break; #endif /* !DISABLE_TLS */ } } /* * Close all open UDP sockets */ if (finet) { for (i = 0; i < finet->fd; i++) { if (close(finet[i+1].fd) < 0) { logerror("close() failed"); die(0, 0, NULL); } DEL_EVENT(finet[i+1].ev); FREEPTR(finet[i+1].ev); } FREEPTR(finet); } /* get FQDN and hostname/domain */ FREEPTR(oldLocalFQDN); oldLocalFQDN = LocalFQDN; LocalFQDN = getLocalFQDN(); if ((p = strchr(LocalFQDN, '.')) != NULL) (void)strlcpy(LocalHostName, LocalFQDN, 1+p-LocalFQDN); else (void)strlcpy(LocalHostName, LocalFQDN, sizeof(LocalHostName)); /* * Reset counter of forwarding actions */ NumForwards=0; /* new destination list to replace Files */ newf = NULL; nextp = &newf; /* open the configuration file */ if ((cf = fopen(ConfFile, "r")) == NULL) { DPRINTF(D_FILE, "Cannot open `%s'\n", ConfFile); *nextp = (struct filed *)calloc(1, sizeof(*f)); cfline(0, "*.ERR\t/dev/console", *nextp, "*", "*"); (*nextp)->f_next = (struct filed *)calloc(1, sizeof(*f)); cfline(0, "*.PANIC\t*", (*nextp)->f_next, "*", "*"); Initialized = 1; RESTORE_SIGNALS(omask); return; } #ifndef DISABLE_TLS /* init with new TLS_CTX * as far as I see one cannot change the cert/key of an existing CTX */ FREE_SSL_CTX(tls_opt.global_TLS_CTX); free_cred_SLIST(&tls_opt.cert_head); free_cred_SLIST(&tls_opt.fprint_head); #endif /* !DISABLE_TLS */ /* read and close configuration file */ read_config_file(cf, &newf); newf = *nextp; (void)fclose(cf); DPRINTF(D_MISC, "read_config_file() returned newf=%p\n", newf); #define MOVE_QUEUE(dst, src) do { \ struct buf_queue *buf; \ STAILQ_CONCAT(&dst->f_qhead, &src->f_qhead); \ STAILQ_FOREACH(buf, &dst->f_qhead, entries) { \ dst->f_qelements++; \ dst->f_qsize += buf_queue_obj_size(buf); \ } \ src->f_qsize = 0; \ src->f_qelements = 0; \ } while (0) /* * Free old log files. */ for (f = Files; f != NULL;) { struct filed *ftmp; /* check if a new logfile is equal, if so pass the queue */ for (f2 = newf; f2 != NULL; f2 = f2->f_next) { if (f->f_type == f2->f_type && ((f->f_type == F_PIPE && !strcmp(f->f_un.f_pipe.f_pname, f2->f_un.f_pipe.f_pname)) #ifndef DISABLE_TLS || (f->f_type == F_TLS && !strcmp(f->f_un.f_tls.tls_conn->hostname, f2->f_un.f_tls.tls_conn->hostname) && !strcmp(f->f_un.f_tls.tls_conn->port, f2->f_un.f_tls.tls_conn->port)) #endif /* !DISABLE_TLS */ || (f->f_type == F_FORW && !strcmp(f->f_un.f_forw.f_hname, f2->f_un.f_forw.f_hname)))) { DPRINTF(D_BUFFER, "move queue from f@%p " "to f2@%p\n", f, f2); MOVE_QUEUE(f2, f); } } message_queue_freeall(f); DELREF(f->f_prevmsg); #ifndef DISABLE_TLS if (f->f_type == F_TLS) free_tls_conn(f->f_un.f_tls.tls_conn); #endif /* !DISABLE_TLS */ FREEPTR(f->f_program); FREEPTR(f->f_host); DEL_EVENT(f->f_sq_event); ftmp = f->f_next; free((char *)f); f = ftmp; } Files = newf; Initialized = 1; if (Debug) { for (f = Files; f; f = f->f_next) { for (i = 0; i <= LOG_NFACILITIES; i++) if (f->f_pmask[i] == INTERNAL_NOPRI) printf("X "); else printf("%d ", f->f_pmask[i]); printf("%s: ", TypeInfo[f->f_type].name); switch (f->f_type) { case F_FILE: case F_TTY: case F_CONSOLE: case F_FIFO: printf("%s", f->f_un.f_fname); break; case F_FORW: printf("%s", f->f_un.f_forw.f_hname); break; #ifndef DISABLE_TLS case F_TLS: printf("[%s]", f->f_un.f_tls.tls_conn->hostname); break; #endif /* !DISABLE_TLS */ case F_PIPE: printf("%s", f->f_un.f_pipe.f_pname); break; case F_USERS: for (i = 0; i < MAXUNAMES && *f->f_un.f_uname[i]; i++) printf("%s, ", f->f_un.f_uname[i]); break; } if (f->f_program != NULL) printf(" (%s)", f->f_program); printf("\n"); } } finet = socksetup(PF_UNSPEC, bindhostname); if (finet) { if (SecureMode) { for (i = 0; i < finet->fd; i++) { if (shutdown(finet[i+1].fd, SHUT_RD) < 0) { logerror("shutdown() failed"); die(0, 0, NULL); } } } else DPRINTF(D_NET, "Listening on inet and/or inet6 socket\n"); DPRINTF(D_NET, "Sending on inet and/or inet6 socket\n"); } #ifndef DISABLE_TLS /* TLS setup -- after all local destinations opened */ DPRINTF(D_PARSE, "Parsed options: tls_ca: %s, tls_cadir: %s, " "tls_cert: %s, tls_key: %s, tls_verify: %s, " "bind: %s:%s, max. queue_lengths: %" PRId64 ", %" PRId64 ", %" PRId64 ", " "max. queue_sizes: %" PRId64 ", %" PRId64 ", %" PRId64 "\n", tls_opt.CAfile, tls_opt.CAdir, tls_opt.certfile, tls_opt.keyfile, tls_opt.x509verify, tls_opt.bindhost, tls_opt.bindport, TypeInfo[F_TLS].queue_length, TypeInfo[F_FILE].queue_length, TypeInfo[F_PIPE].queue_length, TypeInfo[F_TLS].queue_size, TypeInfo[F_FILE].queue_size, TypeInfo[F_PIPE].queue_size); SLIST_FOREACH(cred, &tls_opt.cert_head, entries) { DPRINTF(D_PARSE, "Accepting peer certificate " "from file: \"%s\"\n", cred->data); } SLIST_FOREACH(cred, &tls_opt.fprint_head, entries) { DPRINTF(D_PARSE, "Accepting peer certificate with " "fingerprint: \"%s\"\n", cred->data); } /* Note: The order of initialization is important because syslog-sign * should use the TLS cert for signing. -- So we check first if TLS * will be used and initialize it before starting -sign. * * This means that if we are a client without TLS destinations TLS * will not be initialized and syslog-sign will generate a new key. * -- Even if the user has set a usable tls_cert. * Is this the expected behaviour? The alternative would be to always * initialize the TLS structures, even if they will not be needed * (or only needed to read the DSA key for -sign). */ /* Initialize TLS only if used */ if (tls_opt.server) tls_status_msg = init_global_TLS_CTX(); else for (f = Files; f; f = f->f_next) { if (f->f_type != F_TLS) continue; tls_status_msg = init_global_TLS_CTX(); break; } #endif /* !DISABLE_TLS */ #ifndef DISABLE_SIGN /* only initialize -sign if actually used */ if (GlobalSign.sg == 0 || GlobalSign.sg == 1 || GlobalSign.sg == 2) (void)sign_global_init(Files); else if (GlobalSign.sg == 3) for (f = Files; f; f = f->f_next) if (f->f_flags & FFLAG_SIGN) { (void)sign_global_init(Files); break; } #endif /* !DISABLE_SIGN */ #ifndef DISABLE_TLS if (tls_status_msg) { loginfo("%s", tls_status_msg); free(tls_status_msg); } DPRINTF((D_NET|D_TLS), "Preparing sockets for TLS\n"); TLS_Listen_Set = socksetup_tls(PF_UNSPEC, tls_opt.bindhost, tls_opt.bindport); for (f = Files; f; f = f->f_next) { if (f->f_type != F_TLS) continue; if (!tls_connect(f->f_un.f_tls.tls_conn)) { logerror("Unable to connect to TLS server %s", f->f_un.f_tls.tls_conn->hostname); /* Reconnect after x seconds */ schedule_event(&f->f_un.f_tls.tls_conn->event, &((struct timeval){TLS_RECONNECT_SEC, 0}), tls_reconnect, f->f_un.f_tls.tls_conn); } } #endif /* !DISABLE_TLS */ loginfo("restart"); /* * Log a change in hostname, but only on a restart (we detect this * by checking to see if we're passed a kevent). */ if (oldLocalFQDN && strcmp(oldLocalFQDN, LocalFQDN) != 0) loginfo("host name changed, \"%s\" to \"%s\"", oldLocalFQDN, LocalFQDN); RESTORE_SIGNALS(omask); } /* * Crack a configuration file line */ void cfline(size_t linenum, const char *line, struct filed *f, const char *prog, const char *host) { struct addrinfo hints, *res; int error, i, pri, syncfile; const char *p, *q; char *bp; char buf[MAXLINE]; struct stat sb; DPRINTF((D_CALL|D_PARSE), "cfline(%zu, \"%s\", f, \"%s\", \"%s\")\n", linenum, line, prog, host); errno = 0; /* keep strerror() stuff out of logerror messages */ /* clear out file entry */ memset(f, 0, sizeof(*f)); for (i = 0; i <= LOG_NFACILITIES; i++) f->f_pmask[i] = INTERNAL_NOPRI; STAILQ_INIT(&f->f_qhead); /* * There should not be any space before the log facility. * Check this is okay, complain and fix if it is not. */ q = line; if (isblank((unsigned char)*line)) { errno = 0; logerror("Warning: `%s' space or tab before the log facility", line); /* Fix: strip all spaces/tabs before the log facility */ while (*q++ && isblank((unsigned char)*q)) /* skip blanks */; line = q; } /* * q is now at the first char of the log facility * There should be at least one tab after the log facility * Check this is okay, and complain and fix if it is not. */ q = line + strlen(line); while (!isblank((unsigned char)*q) && (q != line)) q--; if ((q == line) && strlen(line)) { /* No tabs or space in a non empty line: complain */ errno = 0; logerror( "Error: `%s' log facility or log target missing", line); return; } /* save host name, if any */ if (*host == '*') f->f_host = NULL; else { f->f_host = strdup(host); trim_anydomain(&f->f_host[1]); /* skip +/- at beginning */ } /* save program name, if any */ if (*prog == '*') f->f_program = NULL; else f->f_program = strdup(prog); /* scan through the list of selectors */ for (p = line; *p && !isblank((unsigned char)*p);) { int pri_done, pri_cmp, pri_invert; /* find the end of this facility name list */ for (q = p; *q && !isblank((unsigned char)*q) && *q++ != '.'; ) continue; /* get the priority comparison */ pri_cmp = 0; pri_done = 0; pri_invert = 0; if (*q == '!') { pri_invert = 1; q++; } while (! pri_done) { switch (*q) { case '<': pri_cmp = PRI_LT; q++; break; case '=': pri_cmp = PRI_EQ; q++; break; case '>': pri_cmp = PRI_GT; q++; break; default: pri_done = 1; break; } } /* collect priority name */ for (bp = buf; *q && !strchr("\t ,;", *q); ) *bp++ = *q++; *bp = '\0'; /* skip cruft */ while (strchr(",;", *q)) q++; /* decode priority name */ if (*buf == '*') { pri = LOG_PRIMASK + 1; pri_cmp = PRI_LT | PRI_EQ | PRI_GT; } else { pri = decode(buf, prioritynames); if (pri < 0) { errno = 0; logerror("Unknown priority name `%s'", buf); return; } } if (pri_cmp == 0) pri_cmp = UniquePriority ? PRI_EQ : PRI_EQ | PRI_GT; if (pri_invert) pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT; /* scan facilities */ while (*p && !strchr("\t .;", *p)) { for (bp = buf; *p && !strchr("\t ,;.", *p); ) *bp++ = *p++; *bp = '\0'; if (*buf == '*') for (i = 0; i < LOG_NFACILITIES; i++) { f->f_pmask[i] = pri; f->f_pcmp[i] = pri_cmp; } else { i = decode(buf, facilitynames); if (i < 0) { errno = 0; logerror("Unknown facility name `%s'", buf); return; } f->f_pmask[i >> 3] = pri; f->f_pcmp[i >> 3] = pri_cmp; } while (*p == ',' || *p == ' ') p++; } p = q; } /* skip to action part */ while (isblank((unsigned char)*p)) p++; /* * should this be "#ifndef DISABLE_SIGN" or is it a general option? * '+' before file destination: write with PRI field for later * verification */ if (*p == '+') { f->f_flags |= FFLAG_FULL; p++; } if (*p == '-') { syncfile = 0; p++; } else syncfile = 1; switch (*p) { case '@': #ifndef DISABLE_SIGN if (GlobalSign.sg == 3) f->f_flags |= FFLAG_SIGN; #endif /* !DISABLE_SIGN */ #ifndef DISABLE_TLS if (*(p+1) == '[') { /* TLS destination */ if (!parse_tls_destination(p, f, linenum)) { logerror("Unable to parse action %s", p); break; } f->f_type = F_TLS; break; } #endif /* !DISABLE_TLS */ (void)strlcpy(f->f_un.f_forw.f_hname, ++p, sizeof(f->f_un.f_forw.f_hname)); memset(&hints, 0, sizeof(hints)); hints.ai_family = AF_UNSPEC; hints.ai_socktype = SOCK_DGRAM; hints.ai_protocol = 0; error = getaddrinfo(f->f_un.f_forw.f_hname, "syslog", &hints, &res); if (error) { errno = 0; logerror("%s", gai_strerror(error)); break; } f->f_un.f_forw.f_addr = res; f->f_type = F_FORW; NumForwards++; break; case '/': #ifndef DISABLE_SIGN if (GlobalSign.sg == 3) f->f_flags |= FFLAG_SIGN; #endif /* !DISABLE_SIGN */ (void)strlcpy(f->f_un.f_fname, p, sizeof(f->f_un.f_fname)); if ((f->f_file = open(p, O_WRONLY|O_APPEND|O_NONBLOCK, 0)) < 0) { f->f_type = F_UNUSED; logerror("%s", p); break; } if (!fstat(f->f_file, &sb) && S_ISFIFO(sb.st_mode)) { f->f_file = -1; f->f_type = F_FIFO; break; } if (isatty(f->f_file)) { f->f_type = F_TTY; if (strcmp(p, ctty) == 0) f->f_type = F_CONSOLE; } else f->f_type = F_FILE; if (syncfile) f->f_flags |= FFLAG_SYNC; break; case '|': #ifndef DISABLE_SIGN if (GlobalSign.sg == 3) f->f_flags |= FFLAG_SIGN; #endif f->f_un.f_pipe.f_pid = 0; (void) strlcpy(f->f_un.f_pipe.f_pname, p + 1, sizeof(f->f_un.f_pipe.f_pname)); f->f_type = F_PIPE; break; case '*': f->f_type = F_WALL; break; default: for (i = 0; i < MAXUNAMES && *p; i++) { for (q = p; *q && *q != ','; ) q++; (void)strncpy(f->f_un.f_uname[i], p, UT_NAMESIZE); if ((q - p) > UT_NAMESIZE) f->f_un.f_uname[i][UT_NAMESIZE] = '\0'; else f->f_un.f_uname[i][q - p] = '\0'; while (*q == ',' || *q == ' ') q++; p = q; } f->f_type = F_USERS; break; } } /* * Decode a symbolic name to a numeric value */ int decode(const char *name, CODE *codetab) { CODE *c; char *p, buf[40]; if (isdigit((unsigned char)*name)) return atoi(name); for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) { if (isupper((unsigned char)*name)) *p = tolower((unsigned char)*name); else *p = *name; } *p = '\0'; for (c = codetab; c->c_name; c++) if (!strcmp(buf, c->c_name)) return c->c_val; return -1; } /* * Retrieve the size of the kernel message buffer, via sysctl. */ int getmsgbufsize(void) { #ifdef __NetBSD_Version__ int msgbufsize, mib[2]; size_t size; mib[0] = CTL_KERN; mib[1] = KERN_MSGBUFSIZE; size = sizeof msgbufsize; if (sysctl(mib, 2, &msgbufsize, &size, NULL, 0) == -1) { DPRINTF(D_MISC, "Couldn't get kern.msgbufsize\n"); return 0; } return msgbufsize; #else return MAXLINE; #endif /* __NetBSD_Version__ */ } /* * Retrieve the hostname, via sysctl. */ char * getLocalFQDN(void) { int mib[2]; char *hostname; size_t len; mib[0] = CTL_KERN; mib[1] = KERN_HOSTNAME; sysctl(mib, 2, NULL, &len, NULL, 0); if (!(hostname = malloc(len))) { logerror("Unable to allocate memory"); die(0,0,NULL); } else if (sysctl(mib, 2, hostname, &len, NULL, 0) == -1) { DPRINTF(D_MISC, "Couldn't get kern.hostname\n"); (void)gethostname(hostname, sizeof(len)); } return hostname; } struct socketEvent * socksetup(int af, const char *hostname) { struct addrinfo hints, *res, *r; int error, maxs; int on = 1; struct socketEvent *s, *socks; if(SecureMode && !NumForwards) return NULL; memset(&hints, 0, sizeof(hints)); hints.ai_flags = AI_PASSIVE; hints.ai_family = af; hints.ai_socktype = SOCK_DGRAM; error = getaddrinfo(hostname, "syslog", &hints, &res); if (error) { errno = 0; logerror("%s", gai_strerror(error)); die(0, 0, NULL); } /* Count max number of sockets we may open */ for (maxs = 0, r = res; r; r = r->ai_next, maxs++) continue; socks = calloc(maxs+1, sizeof(*socks)); if (!socks) { logerror("Couldn't allocate memory for sockets"); die(0, 0, NULL); } socks->fd = 0; /* num of sockets counter at start of array */ s = socks + 1; for (r = res; r; r = r->ai_next) { s->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); if (s->fd < 0) { logerror("socket() failed"); continue; } s->af = r->ai_family; if (r->ai_family == AF_INET6 && setsockopt(s->fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof(on)) < 0) { logerror("setsockopt(IPV6_V6ONLY) failed"); close(s->fd); continue; } if (!SecureMode) { if (bind(s->fd, r->ai_addr, r->ai_addrlen) < 0) { logerror("bind() failed"); close(s->fd); continue; } s->ev = allocev(); event_set(s->ev, s->fd, EV_READ | EV_PERSIST, dispatch_read_finet, s->ev); if (event_add(s->ev, NULL) == -1) { DPRINTF((D_EVENT|D_NET), "Failure in event_add()\n"); } else { DPRINTF((D_EVENT|D_NET), "Listen on UDP port " "(event@%p)\n", s->ev); } } socks->fd++; /* num counter */ s++; } if (res) freeaddrinfo(res); if (socks->fd == 0) { free (socks); if(Debug) return NULL; else die(0, 0, NULL); } return socks; } /* * Fairly similar to popen(3), but returns an open descriptor, as opposed * to a FILE *. */ int p_open(char *prog, pid_t *rpid) { static char sh[] = "sh", mc[] = "-c"; int pfd[2], nulldesc, i; pid_t pid; char *argv[4]; /* sh -c cmd NULL */ if (pipe(pfd) == -1) return -1; if ((nulldesc = open(_PATH_DEVNULL, O_RDWR)) == -1) { /* We are royally screwed anyway. */ return -1; } switch ((pid = fork())) { case -1: (void) close(nulldesc); return -1; case 0: argv[0] = sh; argv[1] = mc; argv[2] = prog; argv[3] = NULL; (void) setsid(); /* avoid catching SIGHUPs. */ /* * Reset ignored signals to their default behavior. */ (void)signal(SIGTERM, SIG_DFL); (void)signal(SIGINT, SIG_DFL); (void)signal(SIGQUIT, SIG_DFL); (void)signal(SIGPIPE, SIG_DFL); (void)signal(SIGHUP, SIG_DFL); dup2(pfd[0], STDIN_FILENO); dup2(nulldesc, STDOUT_FILENO); dup2(nulldesc, STDERR_FILENO); for (i = getdtablesize(); i > 2; i--) (void) close(i); (void) execvp(_PATH_BSHELL, argv); _exit(255); } (void) close(nulldesc); (void) close(pfd[0]); /* * Avoid blocking on a hung pipe. With O_NONBLOCK, we are * supposed to get an EWOULDBLOCK on writev(2), which is * caught by the logic above anyway, which will in turn * close the pipe, and fork a new logging subprocess if * necessary. The stale subprocess will be killed some * time later unless it terminated itself due to closing * its input pipe. */ if (fcntl(pfd[1], F_SETFL, O_NONBLOCK) == -1) { /* This is bad. */ logerror("Warning: cannot change pipe to pid %d to " "non-blocking.", (int) pid); } *rpid = pid; return pfd[1]; } void deadq_enter(pid_t pid, const char *name) { dq_t p; int status; /* * Be paranoid: if we can't signal the process, don't enter it * into the dead queue (perhaps it's already dead). If possible, * we try to fetch and log the child's status. */ if (kill(pid, 0) != 0) { if (waitpid(pid, &status, WNOHANG) > 0) log_deadchild(pid, status, name); return; } p = malloc(sizeof(*p)); if (p == NULL) { logerror("panic: out of memory!"); exit(1); } p->dq_pid = pid; p->dq_timeout = DQ_TIMO_INIT; TAILQ_INSERT_TAIL(&deadq_head, p, dq_entries); } int deadq_remove(pid_t pid) { dq_t q; for (q = TAILQ_FIRST(&deadq_head); q != NULL; q = TAILQ_NEXT(q, dq_entries)) { if (q->dq_pid == pid) { TAILQ_REMOVE(&deadq_head, q, dq_entries); free(q); return 1; } } return 0; } void log_deadchild(pid_t pid, int status, const char *name) { int code; char buf[256]; const char *reason; /* Keep strerror() struff out of logerror messages. */ errno = 0; if (WIFSIGNALED(status)) { reason = "due to signal"; code = WTERMSIG(status); } else { reason = "with status"; code = WEXITSTATUS(status); if (code == 0) return; } (void) snprintf(buf, sizeof(buf), "Logging subprocess %d (%s) exited %s %d.", pid, name, reason, code); logerror("%s", buf); } struct event * allocev(void) { struct event *ev; if (!(ev = calloc(1, sizeof(*ev)))) logerror("Unable to allocate memory"); return ev; } /* *ev is allocated if necessary */ void schedule_event(struct event **ev, struct timeval *tv, void (*cb)(int, short, void *), void *arg) { if (!*ev && !(*ev = allocev())) { return; } event_set(*ev, 0, 0, cb, arg); DPRINTF(D_EVENT, "event_add(%s@%p)\n", "schedule_ev", *ev); \ if (event_add(*ev, tv) == -1) { DPRINTF(D_EVENT, "Failure in event_add()\n"); } } #ifndef DISABLE_TLS /* abbreviation for freeing credential lists */ void free_cred_SLIST(struct peer_cred_head *head) { struct peer_cred *cred; while (!SLIST_EMPTY(head)) { cred = SLIST_FIRST(head); SLIST_REMOVE_HEAD(head, entries); FREEPTR(cred->data); free(cred); } } #endif /* !DISABLE_TLS */ /* * send message queue after reconnect */ /*ARGSUSED*/ void send_queue(int fd, short event, void *arg) { struct filed *f = (struct filed *) arg; struct buf_queue *qentry; #define SQ_CHUNK_SIZE 250 size_t cnt = 0; #ifndef DISABLE_TLS if (f->f_type == F_TLS) { /* use a flag to prevent recursive calls to send_queue() */ if (f->f_un.f_tls.tls_conn->send_queue) return; else f->f_un.f_tls.tls_conn->send_queue = true; } DPRINTF((D_DATA|D_CALL), "send_queue(f@%p with %zu msgs, " "cnt@%p = %zu)\n", f, f->f_qelements, &cnt, cnt); #endif /* !DISABLE_TLS */ while ((qentry = STAILQ_FIRST(&f->f_qhead))) { #ifndef DISABLE_TLS /* send_queue() might be called with an unconnected destination * from init() or die() or one message might take longer, * leaving the connection in state ST_WAITING and thus not * ready for the next message. * this check is a shortcut to skip these unnecessary calls */ if (f->f_type == F_TLS && f->f_un.f_tls.tls_conn->state != ST_TLS_EST) { DPRINTF(D_TLS, "abort send_queue(cnt@%p = %zu) " "on TLS connection in state %d\n", &cnt, cnt, f->f_un.f_tls.tls_conn->state); return; } #endif /* !DISABLE_TLS */ fprintlog(f, qentry->msg, qentry); /* Sending a long queue can take some time during which * SIGHUP and SIGALRM are blocked and no events are handled. * To avoid that we only send SQ_CHUNK_SIZE messages at once * and then reschedule ourselves to continue. Thus the control * will return first from all signal-protected functions so a * possible SIGHUP/SIGALRM is handled and then back to the * main loop which can handle possible input. */ if (++cnt >= SQ_CHUNK_SIZE) { if (!f->f_sq_event) { /* alloc on demand */ f->f_sq_event = allocev(); event_set(f->f_sq_event, 0, 0, send_queue, f); } if (event_add(f->f_sq_event, &((struct timeval){0, 1})) == -1) { DPRINTF(D_EVENT, "Failure in event_add()\n"); } break; } } #ifndef DISABLE_TLS if (f->f_type == F_TLS) f->f_un.f_tls.tls_conn->send_queue = false; #endif } /* * finds the next queue element to delete * * has stateful behaviour, before using it call once with reset = true * after that every call will return one next queue elemen to delete, * depending on strategy either the oldest or the one with the lowest priority */ static struct buf_queue * find_qentry_to_delete(const struct buf_queue_head *head, int strategy, bool reset) { static int pri; static struct buf_queue *qentry_static; struct buf_queue *qentry_tmp; if (reset || STAILQ_EMPTY(head)) { pri = LOG_DEBUG; qentry_static = STAILQ_FIRST(head); return NULL; } /* find elements to delete */ if (strategy == PURGE_BY_PRIORITY) { qentry_tmp = qentry_static; if (!qentry_tmp) return NULL; while ((qentry_tmp = STAILQ_NEXT(qentry_tmp, entries)) != NULL) { if (LOG_PRI(qentry_tmp->msg->pri) == pri) { /* save the successor, because qentry_tmp * is probably deleted by the caller */ qentry_static = STAILQ_NEXT(qentry_tmp, entries); return qentry_tmp; } } /* nothing found in while loop --> next pri */ if (--pri) return find_qentry_to_delete(head, strategy, false); else return NULL; } else /* strategy == PURGE_OLDEST or other value */ { qentry_tmp = qentry_static; qentry_static = STAILQ_NEXT(qentry_tmp, entries); return qentry_tmp; /* is NULL on empty queue */ } } /* note on TAILQ: newest message added at TAIL, * oldest to be removed is FIRST */ /* * checks length of a destination's message queue * if del_entries == 0 then assert queue length is * less or equal to configured number of queue elements * otherwise del_entries tells how many entries to delete * * returns the number of removed queue elements * (which not necessarily means free'd messages) * * strategy PURGE_OLDEST to delete oldest entry, e.g. after it was resent * strategy PURGE_BY_PRIORITY to delete messages with lowest priority first, * this is much slower but might be desirable when unsent messages have * to be deleted, e.g. in call from domark() */ size_t message_queue_purge(struct filed *f, size_t del_entries, int strategy) { size_t removed = 0; struct buf_queue *qentry = NULL; DPRINTF((D_CALL|D_BUFFER), "purge_message_queue(%p, %zu, %d) with " "f_qelements=%zu and f_qsize=%zu\n", f, del_entries, strategy, f->f_qelements, f->f_qsize); /* reset state */ (void)find_qentry_to_delete(&f->f_qhead, strategy, true); while (removed < del_entries || (TypeInfo[f->f_type].queue_length != -1 && (size_t)TypeInfo[f->f_type].queue_length <= f->f_qelements) || (TypeInfo[f->f_type].queue_size != -1 && (size_t)TypeInfo[f->f_type].queue_size <= f->f_qsize)) { qentry = find_qentry_to_delete(&f->f_qhead, strategy, 0); if (message_queue_remove(f, qentry)) removed++; else break; } return removed; } /* run message_queue_purge() for all destinations to free memory */ size_t message_allqueues_purge(void) { size_t sum = 0; struct filed *f; for (f = Files; f; f = f->f_next) sum += message_queue_purge(f, f->f_qelements/10, PURGE_BY_PRIORITY); DPRINTF(D_BUFFER, "message_allqueues_purge(): removed %zu buffer entries\n", sum); return sum; } /* run message_queue_purge() for all destinations to check limits */ size_t message_allqueues_check(void) { size_t sum = 0; struct filed *f; for (f = Files; f; f = f->f_next) sum += message_queue_purge(f, 0, PURGE_BY_PRIORITY); DPRINTF(D_BUFFER, "message_allqueues_check(): removed %zu buffer entries\n", sum); return sum; } struct buf_msg * buf_msg_new(const size_t len) { struct buf_msg *newbuf; CALLOC(newbuf, sizeof(*newbuf)); if (len) { /* len = 0 is valid */ MALLOC(newbuf->msg, len); newbuf->msgorig = newbuf->msg; newbuf->msgsize = len; } return NEWREF(newbuf); } void buf_msg_free(struct buf_msg *buf) { if (!buf) return; buf->refcount--; if (buf->refcount == 0) { FREEPTR(buf->timestamp); /* small optimizations: the host/recvhost may point to the * global HostName/FQDN. of course this must not be free()d * same goes for appname and include_pid */ if (buf->recvhost != buf->host && buf->recvhost != LocalHostName && buf->recvhost != LocalFQDN && buf->recvhost != oldLocalFQDN) FREEPTR(buf->recvhost); if (buf->host != LocalHostName && buf->host != LocalFQDN && buf->host != oldLocalFQDN) FREEPTR(buf->host); if (buf->prog != appname) FREEPTR(buf->prog); if (buf->pid != include_pid) FREEPTR(buf->pid); FREEPTR(buf->msgid); FREEPTR(buf->sd); FREEPTR(buf->msgorig); /* instead of msg */ FREEPTR(buf); } } size_t buf_queue_obj_size(struct buf_queue *qentry) { size_t sum = 0; if (!qentry) return 0; sum += sizeof(*qentry) + sizeof(*qentry->msg) + qentry->msg->msgsize + SAFEstrlen(qentry->msg->timestamp)+1 + SAFEstrlen(qentry->msg->msgid)+1; if (qentry->msg->prog && qentry->msg->prog != include_pid) sum += strlen(qentry->msg->prog)+1; if (qentry->msg->pid && qentry->msg->pid != appname) sum += strlen(qentry->msg->pid)+1; if (qentry->msg->recvhost && qentry->msg->recvhost != LocalHostName && qentry->msg->recvhost != LocalFQDN && qentry->msg->recvhost != oldLocalFQDN) sum += strlen(qentry->msg->recvhost)+1; if (qentry->msg->host && qentry->msg->host != LocalHostName && qentry->msg->host != LocalFQDN && qentry->msg->host != oldLocalFQDN) sum += strlen(qentry->msg->host)+1; return sum; } bool message_queue_remove(struct filed *f, struct buf_queue *qentry) { if (!f || !qentry || !qentry->msg) return false; assert(!STAILQ_EMPTY(&f->f_qhead)); STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries); f->f_qelements--; f->f_qsize -= buf_queue_obj_size(qentry); DPRINTF(D_BUFFER, "msg @%p removed from queue @%p, new qlen = %zu\n", qentry->msg, f, f->f_qelements); DELREF(qentry->msg); FREEPTR(qentry); return true; } /* * returns *qentry on success and NULL on error */ struct buf_queue * message_queue_add(struct filed *f, struct buf_msg *buffer) { struct buf_queue *qentry; /* check on every call or only every n-th time? */ message_queue_purge(f, 0, PURGE_BY_PRIORITY); while (!(qentry = malloc(sizeof(*qentry))) && message_queue_purge(f, 1, PURGE_OLDEST)) continue; if (!qentry) { logerror("Unable to allocate memory"); DPRINTF(D_BUFFER, "queue empty, no memory, msg dropped\n"); return NULL; } else { qentry->msg = buffer; f->f_qelements++; f->f_qsize += buf_queue_obj_size(qentry); STAILQ_INSERT_TAIL(&f->f_qhead, qentry, entries); DPRINTF(D_BUFFER, "msg @%p queued @%p, qlen = %zu\n", buffer, f, f->f_qelements); return qentry; } } void message_queue_freeall(struct filed *f) { struct buf_queue *qentry; if (!f) return; DPRINTF(D_MEM, "message_queue_freeall(f@%p) with f_qhead@%p\n", f, &f->f_qhead); while (!STAILQ_EMPTY(&f->f_qhead)) { qentry = STAILQ_FIRST(&f->f_qhead); STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries); DELREF(qentry->msg); FREEPTR(qentry); } f->f_qelements = 0; f->f_qsize = 0; } #ifndef DISABLE_TLS /* utility function for tls_reconnect() */ struct filed * get_f_by_conninfo(struct tls_conn_settings *conn_info) { struct filed *f; for (f = Files; f; f = f->f_next) { if ((f->f_type == F_TLS) && f->f_un.f_tls.tls_conn == conn_info) return f; } DPRINTF(D_TLS, "get_f_by_conninfo() called on invalid conn_info\n"); return NULL; } /* * Called on signal. * Lets the admin reconnect without waiting for the reconnect timer expires. */ /*ARGSUSED*/ void dispatch_force_tls_reconnect(int fd, short event, void *ev) { struct filed *f; DPRINTF((D_TLS|D_CALL|D_EVENT), "dispatch_force_tls_reconnect()\n"); for (f = Files; f; f = f->f_next) { if (f->f_type == F_TLS && f->f_un.f_tls.tls_conn->state == ST_NONE) tls_reconnect(fd, event, f->f_un.f_tls.tls_conn); } } #endif /* !DISABLE_TLS */ /* * return a timestamp in a static buffer, * either format the timestamp given by parameter in_now * or use the current time if in_now is NULL. */ char * make_timestamp(time_t *in_now, bool iso, size_t tlen) { int frac_digits = 6; struct timeval tv; time_t mytime; struct tm ltime; int len = 0; int tzlen = 0; /* uses global var: time_t now; */ if (in_now) { mytime = *in_now; } else { gettimeofday(&tv, NULL); mytime = now = tv.tv_sec; } if (!iso) { strlcpy(timestamp, ctime(&mytime) + 4, sizeof(timestamp)); timestamp[BSD_TIMESTAMPLEN] = '\0'; } else { localtime_r(&mytime, <ime); len += strftime(timestamp, sizeof(timestamp), "%FT%T", <ime); snprintf(×tamp[len], frac_digits + 2, ".%.*jd", frac_digits, (intmax_t)tv.tv_usec); len += frac_digits + 1; tzlen = strftime(×tamp[len], sizeof(timestamp) - len, "%z", <ime); len += tzlen; if (tzlen == 5) { /* strftime gives "+0200", but we need "+02:00" */ timestamp[len + 2] = '\0'; timestamp[len + 1] = timestamp[len]; timestamp[len] = timestamp[len - 1]; timestamp[len - 1] = timestamp[len - 2]; timestamp[len - 2] = ':'; } } switch (tlen) { case (size_t)-1: return timestamp; case 0: return strdup(timestamp); default: return strndup(timestamp, tlen); } } /* auxiliary code to allocate memory and copy a string */ bool copy_string(char **mem, const char *p, const char *q) { const size_t len = 1 + q - p; if (!(*mem = malloc(len))) { logerror("Unable to allocate memory for config"); return false; } strlcpy(*mem, p, len); return true; } /* keyword has to end with ", everything until next " is copied */ bool copy_config_value_quoted(const char *keyword, char **mem, const char **p) { const char *q; if (strncasecmp(*p, keyword, strlen(keyword))) return false; q = *p += strlen(keyword); if (!(q = strchr(*p, '"'))) { errno = 0; logerror("unterminated \"\n"); return false; } if (!(copy_string(mem, *p, q))) return false; *p = ++q; return true; } /* for config file: * following = required but whitespace allowed, quotes optional * if numeric, then conversion to integer and no memory allocation */ bool copy_config_value(const char *keyword, char **mem, const char **p, const char *file, int line) { if (strncasecmp(*p, keyword, strlen(keyword))) return false; *p += strlen(keyword); while (isspace((unsigned char)**p)) *p += 1; if (**p != '=') { errno = 0; logerror("expected \"=\" in file %s, line %d", file, line); return false; } *p += 1; return copy_config_value_word(mem, p); } /* copy next parameter from a config line */ bool copy_config_value_word(char **mem, const char **p) { const char *q; while (isspace((unsigned char)**p)) *p += 1; if (**p == '"') return copy_config_value_quoted("\"", mem, p); /* without quotes: find next whitespace or end of line */ (void)((q = strchr(*p, ' ')) || (q = strchr(*p, '\t')) || (q = strchr(*p, '\n')) || (q = strchr(*p, '\0'))); if (q-*p == 0 || !(copy_string(mem, *p, q))) return false; *p = ++q; return true; } static int writev1(int fd, struct iovec *iov, size_t count) { ssize_t nw = 0, tot = 0; size_t ntries = 5; if (count == 0) return 0; while (ntries--) { switch ((nw = writev(fd, iov, count))) { case -1: if (errno == EAGAIN || errno == EWOULDBLOCK) { struct pollfd pfd; pfd.fd = fd; pfd.events = POLLOUT; pfd.revents = 0; (void)poll(&pfd, 1, 500); continue; } return -1; case 0: return 0; default: tot += nw; while (nw > 0) { if (iov->iov_len > (size_t)nw) { iov->iov_len -= nw; iov->iov_base = (char *)iov->iov_base + nw; break; } else { if (--count == 0) return tot; nw -= iov->iov_len; iov++; } } } } return tot == 0 ? nw : tot; } #ifdef NDEBUG /* * -d also controls daemoniziation, so it makes sense even with * NDEBUG, but if the user also tries to specify the logging details * with an argument, warn them it's not compiled into this binary. */ void set_debug(const char *level) { Debug = D_DEFAULT; if (level == NULL) return; /* don't bother parsing the argument */ fprintf(stderr, "%s: debug logging is not compiled\n", getprogname()); } #else /* !NDEBUG */ void set_debug(const char *level) { if (level == NULL) { Debug = D_DEFAULT; /* compat */ return; } /* skip initial whitespace for consistency with strto*l */ while (isspace((unsigned char)*level)) ++level; /* accept ~num to mean "all except num" */ bool invert = level[0] == '~'; if (invert) ++level; errno = 0; char *endp = NULL; unsigned long bits = strtoul(level, &endp, 0); if (errno || endp == level || *endp != '\0') { fprintf(stderr, "%s: bad argument to -d\n", getprogname()); usage(); } if (invert) bits = ~bits; Debug = bits & D_ALL; /* * make it possible to use -d to stay in the foreground but * suppress all dbprintf output (there better be free bits in * typeof(Debug) that are not in D_ALL). */ if (Debug == 0) Debug = ~D_ALL; } #endif /* !NDEBUG */ #ifndef NDEBUG void dbprintf(const char *fname, const char *funname, size_t lnum, const char *fmt, ...) { va_list ap; char *ts; ts = make_timestamp(NULL, true, (size_t)-1); printf("%s:%s:%s:%.4zu\t", ts, fname, funname, lnum); va_start(ap, fmt); vprintf(fmt, ap); va_end(ap); } #endif