diff --git a/usr.sbin/syslogd/Makefile b/usr.sbin/syslogd/Makefile --- a/usr.sbin/syslogd/Makefile +++ b/usr.sbin/syslogd/Makefile @@ -3,17 +3,18 @@ .include -.PATH: ${SRCTOP}/usr.bin/wall - -PACKAGE= syslogd +PACKAGE= syslogd +SYSLOGD_DPACAKGE= syslogd CONFGROUPS= CONFS SYSLOGD_D CONFS= syslog.conf PROG= syslogd MAN= syslog.conf.5 syslogd.8 -SRCS= syslogd.c ttymsg.c -SYSLOGD_DPACAKGE= syslogd +SRCS= syslogd.c \ + syslogd_config.c +.PATH: ${SRCTOP}/usr.bin/wall +SRCS+= ttymsg.c LIBADD= util .if ${MK_INET_SUPPORT} != "no" diff --git a/usr.sbin/syslogd/syslogd.h b/usr.sbin/syslogd/syslogd.h --- a/usr.sbin/syslogd/syslogd.h +++ b/usr.sbin/syslogd/syslogd.h @@ -109,6 +109,13 @@ #define HAS_INET6 false #endif +extern bool Debug; +extern bool RFC3164OutputFormat; +extern bool UniquePriority; +extern char LocalHostName[MAXHOSTNAMELEN]; +extern int family; +extern int logflags; + /* * Network addresses that are allowed to log to us. */ @@ -262,6 +269,7 @@ u_int f_repeatcount; /* number of "repeated" msgs */ STAILQ_ENTRY(filed) next; /* next in linked list */ }; +extern STAILQ_HEAD(fileds, filed) fhead; /* * Queue of about-to-be dead processes we should watch out for. @@ -279,4 +287,8 @@ */ #define DQ_TIMO_INIT 2 +void logerror(const char *); +void parseconfigfile(FILE *); +void readconfigfile(const char *); + #endif /* !_SYSLOGD_H_ */ diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c --- a/usr.sbin/syslogd/syslogd.c +++ b/usr.sbin/syslogd/syslogd.c @@ -142,12 +142,10 @@ static const char *ConfFile = _PATH_LOGCONF; static const char *PidFile = _PATH_LOGPID; -static const char include_str[] = "include"; -static const char include_ext[] = ".conf"; static STAILQ_HEAD(, allowedpeer) aphead = STAILQ_HEAD_INITIALIZER(aphead); static STAILQ_HEAD(, socklist) shead = STAILQ_HEAD_INITIALIZER(shead); -static STAILQ_HEAD(, filed) fhead = STAILQ_HEAD_INITIALIZER(fhead); +struct fileds fhead = STAILQ_HEAD_INITIALIZER(fhead); static TAILQ_HEAD(, deadq_entry) deadq_head = TAILQ_HEAD_INITIALIZER(deadq_head); /* @@ -168,10 +166,10 @@ static struct filed consfile; /* Console */ static int nulldesc; /* /dev/null descriptor */ -static bool Debug; /* debug flag */ +bool Debug; /* debug flag */ static bool Foreground = false; /* Run in foreground, instead of daemonizing */ static bool resolve = true; /* resolve hostname */ -static char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ +char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ static const char *LocalDomain; /* our local domain name */ static bool Initialized; /* set when we have initialized ourselves */ static int MarkInterval = 20 * 60; /* interval between marks in seconds */ @@ -179,35 +177,33 @@ static bool NoBind; /* don't bind() as suggested by RFC 3164 */ static int SecureMode; /* when true, receive only unix domain socks */ static int MaxForwardLen = 1024; /* max length of forwared message */ -static int family = PF_UNSPEC; /* protocol family */ +int family = PF_UNSPEC; /* protocol family */ static bool mask_C1 = true; /* mask characters from 0x80 - 0x9F */ static bool send_to_all; /* send message to all IPv4/IPv6 addresses */ static bool use_bootfile; /* log entire bootfile for every kern msg */ static int no_compress; /* don't compress messages (1=pipes, 2=all) */ -static int logflags = O_WRONLY|O_APPEND; /* flags used to open log files */ +int logflags = O_WRONLY|O_APPEND; /* flags used to open log files */ static char bootfile[MAXPATHLEN]; /* booted kernel file */ static bool RemoteAddDate; /* Always set the date on remote messages */ static bool RemoteHostname; /* Log remote hostname from the message */ -static bool UniquePriority; /* Only log specified priority? */ +bool UniquePriority; /* Only log specified priority? */ static int LogFacPri; /* Put facility and priority in log message: */ /* 0=no, 1=numeric, 2=names */ static bool KeepKernFac; /* Keep remotely logged kernel facility */ static bool needdofsync = false; /* Are any file(s) waiting to be fsynced? */ static struct pidfh *pfh; -static bool RFC3164OutputFormat = true; /* Use legacy format by default. */ +bool RFC3164OutputFormat = true; /* Use legacy format by default. */ struct iovlist; static bool allowaddr(char *); static void addsock(const char *, const char *, mode_t); -static void cfline(const char *, const char *, const char *, const char *); static const char *cvthname(struct sockaddr *); static void deadq_enter(int); static void deadq_remove(struct deadq_entry *); -static int decode(const char *, const CODE *); static void die(int) __dead2; static void dofsync(void); static void fprintlog_first(struct filed *, const char *, const char *, @@ -215,7 +211,6 @@ static void fprintlog_write(struct filed *, struct iovlist *, int); static void fprintlog_successive(struct filed *, int); static void init(bool); -static void logerror(const char *); static void logmsg(int, const struct logtime *, const char *, const char *, const char *, const char *, const char *, const char *, int); static void log_deadchild(pid_t, int, const char *); @@ -226,7 +221,6 @@ static int skip_message(const char *, const char *, int); static int evaluate_prop_filter(const struct prop_filter *filter, const char *value); -static struct prop_filter *prop_filter_compile(char *); static void parsemsg(const char *, char *); static void printsys(char *); static int p_open(const char *, pid_t *); @@ -2177,7 +2171,7 @@ /* * Print syslogd errors some place. */ -static void +void logerror(const char *msg) { char buf[512]; @@ -2235,177 +2229,6 @@ exit(1); } -static int -configfiles(const struct dirent *dp) -{ - const char *p; - size_t ext_len; - - if (dp->d_name[0] == '.') - return (0); - - ext_len = sizeof(include_ext) -1; - - if (dp->d_namlen <= ext_len) - return (0); - - p = &dp->d_name[dp->d_namlen - ext_len]; - if (strcmp(p, include_ext) != 0) - return (0); - - return (1); -} - -static void -parseconfigfile(FILE *cf) -{ - FILE *cf2; - struct dirent **ent; - char cline[LINE_MAX]; - char host[MAXHOSTNAMELEN]; - char prog[LINE_MAX]; - char file[MAXPATHLEN]; - char pfilter[LINE_MAX]; - char *p, *tmp; - int i, nents; - size_t include_len; - - /* - * Foreach line in the conf table, open that file. - */ - include_len = sizeof(include_str) - 1; - (void)strlcpy(host, "*", sizeof(host)); - (void)strlcpy(prog, "*", sizeof(prog)); - (void)strlcpy(pfilter, "*", sizeof(pfilter)); - while (fgets(cline, sizeof(cline), cf) != NULL) { - /* - * 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(*p); ++p) - continue; - if (*p == '\0') - continue; - if (strncmp(p, include_str, include_len) == 0 && - isspace(p[include_len])) { - p += include_len; - while (isspace(*p)) - p++; - tmp = p; - while (*tmp != '\0' && !isspace(*tmp)) - tmp++; - *tmp = '\0'; - dprintf("Trying to include files in '%s'\n", p); - nents = scandir(p, &ent, configfiles, alphasort); - if (nents == -1) { - dprintf("Unable to open '%s': %s\n", p, - strerror(errno)); - continue; - } - for (i = 0; i < nents; i++) { - if (snprintf(file, sizeof(file), "%s/%s", p, - ent[i]->d_name) >= (int)sizeof(file)) { - dprintf("ignoring path too long: " - "'%s/%s'\n", p, ent[i]->d_name); - free(ent[i]); - continue; - } - free(ent[i]); - cf2 = fopen(file, "r"); - if (cf2 == NULL) - continue; - dprintf("reading %s\n", file); - parseconfigfile(cf2); - (void)fclose(cf2); - } - free(ent); - continue; - } - if (*p == '#') { - p++; - if (*p == '\0' || strchr("!+-:", *p) == NULL) - continue; - } - if (*p == '+' || *p == '-') { - host[0] = *p++; - while (isspace(*p)) - p++; - if (*p == '\0' || *p == '*') { - (void)strlcpy(host, "*", sizeof(host)); - continue; - } - if (*p == '@') - p = LocalHostName; - for (i = 1; i < MAXHOSTNAMELEN - 1; i++) { - if (!isalnum(*p) && *p != '.' && *p != '-' - && *p != ',' && *p != ':' && *p != '%') - break; - host[i] = *p++; - } - host[i] = '\0'; - continue; - } - if (*p == '!') { - p++; - while (isspace(*p)) - p++; - if (*p == '\0' || *p == '*') { - (void)strlcpy(prog, "*", sizeof(prog)); - continue; - } - for (i = 0; i < LINE_MAX - 1; i++) { - if (!isprint(p[i]) || isspace(p[i])) - break; - prog[i] = p[i]; - } - prog[i] = '\0'; - continue; - } - if (*p == ':') { - p++; - while (isspace(*p)) - p++; - if (*p == '\0' || *p == '*') { - (void)strlcpy(pfilter, "*", sizeof(pfilter)); - continue; - } - (void)strlcpy(pfilter, p, sizeof(pfilter)); - continue; - } - for (p = cline + 1; *p != '\0'; p++) { - if (*p != '#') - continue; - if (*(p - 1) == '\\') { - strcpy(p - 1, p); - p--; - continue; - } - *p = '\0'; - break; - } - for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--) - cline[i] = '\0'; - cfline(cline, prog, host, pfilter); - } -} - -static void -readconfigfile(const char *path) -{ - FILE *cf; - - /* open the configuration file */ - if ((cf = fopen(path, "r")) != NULL) { - parseconfigfile(cf); - (void)fclose(cf); - } else { - dprintf("cannot open %s\n", ConfFile); - cfline("*.ERR\t/dev/console", "*", "*", "*"); - cfline("*.PANIC\t*", "*", "*", "*"); - } -} - /* * INIT -- Initialize syslogd from configuration table */ @@ -2586,489 +2409,6 @@ dprintf("%s\n", bootfileMsg); } } - -/* - * Compile property-based filter. - */ -static struct prop_filter * -prop_filter_compile(char *filter) -{ - struct prop_filter *pfilter; - char *filter_endpos, *p; - char **ap, *argv[2] = {NULL, NULL}; - int re_flags = REG_NOSUB; - int escaped; - - pfilter = calloc(1, sizeof(*pfilter)); - if (pfilter == NULL) { - logerror("pfilter calloc"); - exit(1); - } - if (*filter == '*') { - pfilter->prop_type = FILT_PROP_NOOP; - return (pfilter); - } - - /* - * Here's some filter examples mentioned in syslog.conf(5) - * 'msg, contains, ".*Deny.*"' - * 'programname, regex, "^bird6?$"' - * 'hostname, icase_ereregex, "^server-(dcA|podB)-rack1[0-9]{2}\\..*"' - */ - - /* - * Split filter into 3 parts: property name (argv[0]), - * cmp type (argv[1]) and lvalue for comparison (filter). - */ - for (ap = argv; (*ap = strsep(&filter, ", \t\n")) != NULL;) { - if (**ap != '\0') - if (++ap >= &argv[2]) - break; - } - - if (argv[0] == NULL || argv[1] == NULL) { - logerror("filter parse error"); - free(pfilter); - return (NULL); - } - - /* fill in prop_type */ - if (strcasecmp(argv[0], "msg") == 0) - pfilter->prop_type = FILT_PROP_MSG; - else if (strcasecmp(argv[0], "hostname") == 0) - pfilter->prop_type = FILT_PROP_HOSTNAME; - else if (strcasecmp(argv[0], "source") == 0) - pfilter->prop_type = FILT_PROP_HOSTNAME; - else if (strcasecmp(argv[0], "programname") == 0) - pfilter->prop_type = FILT_PROP_PROGNAME; - else { - logerror("unknown property"); - free(pfilter); - return (NULL); - } - - /* full in cmp_flags (i.e. !contains, icase_regex, etc.) */ - if (*argv[1] == '!') { - pfilter->cmp_flags |= FILT_FLAG_EXCLUDE; - argv[1]++; - } - if (strncasecmp(argv[1], "icase_", (sizeof("icase_") - 1)) == 0) { - pfilter->cmp_flags |= FILT_FLAG_ICASE; - argv[1] += sizeof("icase_") - 1; - } - - /* fill in cmp_type */ - if (strcasecmp(argv[1], "contains") == 0) - pfilter->cmp_type = FILT_CMP_CONTAINS; - else if (strcasecmp(argv[1], "isequal") == 0) - pfilter->cmp_type = FILT_CMP_EQUAL; - else if (strcasecmp(argv[1], "startswith") == 0) - pfilter->cmp_type = FILT_CMP_STARTS; - else if (strcasecmp(argv[1], "regex") == 0) - pfilter->cmp_type = FILT_CMP_REGEX; - else if (strcasecmp(argv[1], "ereregex") == 0) { - pfilter->cmp_type = FILT_CMP_REGEX; - re_flags |= REG_EXTENDED; - } else { - logerror("unknown cmp function"); - free(pfilter); - return (NULL); - } - - /* - * Handle filter value - */ - - /* ' ".*Deny.*"' */ - /* remove leading whitespace and check for '"' next character */ - filter += strspn(filter, ", \t\n"); - if (*filter != '"' || strlen(filter) < 3) { - logerror("property value parse error"); - free(pfilter); - return (NULL); - } - filter++; - - /* '.*Deny.*"' */ - /* process possible backslash (\") escaping */ - escaped = 0; - filter_endpos = filter; - for (p = filter; *p != '\0'; p++) { - if (*p == '\\' && !escaped) { - escaped = 1; - /* do not shift filter_endpos */ - continue; - } - if (*p == '"' && !escaped) { - p++; - break; - } - /* we've seen some esc symbols, need to compress the line */ - if (filter_endpos != p) - *filter_endpos = *p; - - filter_endpos++; - escaped = 0; - } - - *filter_endpos = '\0'; - /* '.*Deny.*' */ - - /* We should not have anything but whitespace left after closing '"' */ - if (*p != '\0' && strspn(p, " \t\n") != strlen(p)) { - logerror("property value parse error"); - free(pfilter); - return (NULL); - } - - if (pfilter->cmp_type == FILT_CMP_REGEX) { - pfilter->pflt_re = calloc(1, sizeof(*pfilter->pflt_re)); - if (pfilter->pflt_re == NULL) { - logerror("RE calloc() error"); - free(pfilter); - free(pfilter->pflt_re); - return (NULL); - } - if (pfilter->cmp_flags & FILT_FLAG_ICASE) - re_flags |= REG_ICASE; - if (regcomp(pfilter->pflt_re, filter, re_flags) != 0) { - logerror("RE compilation error"); - free(pfilter); - free(pfilter->pflt_re); - return (NULL); - } - } else { - pfilter->pflt_strval = strdup(filter); - pfilter->pflt_strlen = strlen(filter); - } - - return (pfilter); -} - -static const char * -parse_selector(const char *p, struct filed *f) -{ - int i, pri; - int pri_done = 0, pri_cmp = 0, pri_invert = 0; - char *bp, buf[LINE_MAX], ebuf[100]; - const char *q; - - /* find the end of this facility name list */ - for (q = p; *q && *q != '\t' && *q != ' ' && *q++ != '.';) - continue; - - /* get the priority comparison */ - 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++; - break; - } - } - - /* collect priority name */ - for (bp = buf; *q != '\0' && !strchr("\t,; ", *q); ) - *bp++ = *q++; - *bp = '\0'; - - /* skip cruft */ - while (strchr(",;", *q)) - q++; - - /* decode priority name */ - if (*buf == '*') { - pri = LOG_PRIMASK; - pri_cmp = PRI_LT | PRI_EQ | PRI_GT; - } else { - /* Ignore trailing spaces. */ - for (i = strlen(buf) - 1; i >= 0 && buf[i] == ' '; i--) - buf[i] = '\0'; - - pri = decode(buf, prioritynames); - if (pri < 0) { - errno = 0; - (void)snprintf(ebuf, sizeof(ebuf), - "unknown priority name \"%s\"", buf); - logerror(ebuf); - free(f); - return (NULL); - } - } - if (!pri_cmp) - pri_cmp = (UniquePriority) - ? (PRI_EQ) - : (PRI_EQ | PRI_GT) - ; - if (pri_invert) - pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT; - - /* scan facilities */ - while (*p != '\0' && !strchr("\t.; ", *p)) { - for (bp = buf; *p != '\0' && !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; - (void)snprintf(ebuf, sizeof(ebuf), - "unknown facility name \"%s\"", - buf); - logerror(ebuf); - free(f); - return (NULL); - } - f->f_pmask[i >> 3] = pri; - f->f_pcmp[i >> 3] = pri_cmp; - } - while (*p == ',' || *p == ' ') - p++; - } - return (q); -} - -static void -parse_action(const char *p, struct filed *f) -{ - struct addrinfo hints, *res; - int error, i; - const char *q; - bool syncfile; - - if (*p == '-') { - syncfile = false; - p++; - } else { - syncfile = true; - } - - switch (*p) { - case '@': - { - char *tp; - char endkey = ':'; - /* - * scan forward to see if there is a port defined. - * so we can't use strlcpy.. - */ - i = sizeof(f->fu_forw_hname); - tp = f->fu_forw_hname; - p++; - - /* - * an ipv6 address should start with a '[' in that case - * we should scan for a ']' - */ - if (*p == '[') { - p++; - endkey = ']'; - } - while (*p && (*p != endkey) && (i-- > 0)) { - *tp++ = *p++; - } - if (endkey == ']' && *p == endkey) - p++; - *tp = '\0'; - } - /* See if we copied a domain and have a port */ - if (*p == ':') - p++; - else - p = NULL; - - hints = (struct addrinfo){ - .ai_family = family, - .ai_socktype = SOCK_DGRAM - }; - error = getaddrinfo(f->fu_forw_hname, - p ? p : "syslog", &hints, &res); - if (error) { - logerror(gai_strerror(error)); - break; - } - f->fu_forw_addr = res; - f->f_type = F_FORW; - break; - - case '/': - if ((f->f_file = open(p, logflags, 0600)) < 0) { - f->f_type = F_UNUSED; - logerror(p); - break; - } - if (syncfile) - f->f_flags |= FFLAG_SYNC; - if (isatty(f->f_file)) { - if (strcmp(p, _PATH_CONSOLE) == 0) - f->f_type = F_CONSOLE; - else - f->f_type = F_TTY; - (void)strlcpy(f->fu_fname, p + sizeof(_PATH_DEV) - 1, - sizeof(f->fu_fname)); - } else { - (void)strlcpy(f->fu_fname, p, sizeof(f->fu_fname)); - f->f_type = F_FILE; - } - break; - - case '|': - f->fu_pipe_pd = -1; - (void)strlcpy(f->fu_pipe_pname, p + 1, - sizeof(f->fu_pipe_pname)); - f->f_type = F_PIPE; - break; - - case '*': - f->f_type = F_WALL; - break; - - default: - for (i = 0; i < MAXUNAMES && *p != '\0'; i++) { - for (q = p; *q != '\0' && *q != ','; ) - q++; - (void)strncpy(f->fu_uname[i], p, MAXLOGNAME - 1); - if ((q - p) >= MAXLOGNAME) - f->fu_uname[i][MAXLOGNAME - 1] = '\0'; - else - f->fu_uname[i][q - p] = '\0'; - while (*q == ',' || *q == ' ') - q++; - p = q; - } - f->f_type = F_USERS; - break; - } -} - -/* - * Crack a configuration file line - */ -static void -cfline(const char *line, const char *prog, const char *host, - const char *pfilter) -{ - struct filed *f; - int i; - const char *p; - char *pfilter_dup; - - dprintf("cfline(\"%s\", f, \"%s\", \"%s\", \"%s\")\n", line, prog, - host, pfilter); - - f = calloc(1, sizeof(*f)); - if (f == NULL) { - logerror("malloc"); - exit(1); - } - errno = 0; /* keep strerror() stuff out of logerror messages */ - - for (i = 0; i <= LOG_NFACILITIES; i++) - f->f_pmask[i] = INTERNAL_NOPRI; - - /* save hostname if any */ - if (host && *host == '*') - host = NULL; - if (host) { - int hl; - - f->f_host = strdup(host); - if (f->f_host == NULL) { - logerror("strdup"); - exit(1); - } - hl = strlen(f->f_host); - if (hl > 0 && f->f_host[hl-1] == '.') - f->f_host[--hl] = '\0'; - /* RFC 5424 prefers logging FQDNs. */ - if (RFC3164OutputFormat) - trimdomain(f->f_host, hl); - } - - /* save program name if any */ - if (prog && *prog == '*') - prog = NULL; - if (prog) { - f->f_program = strdup(prog); - if (f->f_program == NULL) { - logerror("strdup"); - exit(1); - } - } - - if (pfilter) { - pfilter_dup = strdup(pfilter); - if (pfilter_dup == NULL) { - logerror("strdup"); - exit(1); - } - f->f_prop_filter = prop_filter_compile(pfilter_dup); - if (f->f_prop_filter == NULL) { - logerror("filter compile error"); - exit(1); - } - free(pfilter_dup); - } - - /* scan through the list of selectors */ - for (p = line; *p != '\0' && *p != '\t' && *p != ' ';) - p = parse_selector(p, f); - - /* skip to action part */ - while (*p == '\t' || *p == ' ') - p++; - parse_action(p, f); - - STAILQ_INSERT_TAIL(&fhead, f, next); -} - -/* - * Decode a symbolic name to a numeric value - */ -static int -decode(const char *name, const CODE *codetab) -{ - const CODE *c; - char *p, buf[40]; - - if (isdigit(*name)) - return (atoi(name)); - - for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) { - if (isupper(*name)) - *p = tolower(*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); -} - static void markit(void) { diff --git a/usr.sbin/syslogd/syslogd_config.c b/usr.sbin/syslogd/syslogd_config.c new file mode 100644 --- /dev/null +++ b/usr.sbin/syslogd/syslogd_config.c @@ -0,0 +1,738 @@ +/*- + * SPDX-License-Identifier: BSD-3-Clause + * + * 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. + */ +/*- + * SPDX-License-Identifier: BSD-2-Clause + * + * Copyright (c) 2018 Prodrive Technologies, https://prodrive-technologies.com/ + * Author: Ed Schouten + * Copyright (c) 2023 The FreeBSD Foundation + * + * This software was developed by Jake Freeland + * under sponsorship from the FreeBSD Foundation. + * + * 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. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR 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 AUTHOR 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 +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "syslogd.h" + +#define dprintf if (Debug) printf + +static const char include_str[] = "include"; +static const char include_ext[] = ".conf"; + +/* + * Compile property-based filter. + */ +static struct prop_filter * +prop_filter_compile(char *filter) +{ + struct prop_filter *pfilter; + char *filter_endpos, *p; + char **ap, *argv[2] = {NULL, NULL}; + int re_flags = REG_NOSUB; + int escaped; + + pfilter = calloc(1, sizeof(*pfilter)); + if (pfilter == NULL) { + logerror("pfilter calloc"); + exit(1); + } + if (*filter == '*') { + pfilter->prop_type = FILT_PROP_NOOP; + return (pfilter); + } + + /* + * Here's some filter examples mentioned in syslog.conf(5) + * 'msg, contains, ".*Deny.*"' + * 'programname, regex, "^bird6?$"' + * 'hostname, icase_ereregex, "^server-(dcA|podB)-rack1[0-9]{2}\\..*"' + */ + + /* + * Split filter into 3 parts: property name (argv[0]), + * cmp type (argv[1]) and lvalue for comparison (filter). + */ + for (ap = argv; (*ap = strsep(&filter, ", \t\n")) != NULL;) { + if (**ap != '\0') + if (++ap >= &argv[2]) + break; + } + + if (argv[0] == NULL || argv[1] == NULL) { + logerror("filter parse error"); + free(pfilter); + return (NULL); + } + + /* fill in prop_type */ + if (strcasecmp(argv[0], "msg") == 0) + pfilter->prop_type = FILT_PROP_MSG; + else if (strcasecmp(argv[0], "hostname") == 0) + pfilter->prop_type = FILT_PROP_HOSTNAME; + else if (strcasecmp(argv[0], "source") == 0) + pfilter->prop_type = FILT_PROP_HOSTNAME; + else if (strcasecmp(argv[0], "programname") == 0) + pfilter->prop_type = FILT_PROP_PROGNAME; + else { + logerror("unknown property"); + free(pfilter); + return (NULL); + } + + /* full in cmp_flags (i.e. !contains, icase_regex, etc.) */ + if (*argv[1] == '!') { + pfilter->cmp_flags |= FILT_FLAG_EXCLUDE; + argv[1]++; + } + if (strncasecmp(argv[1], "icase_", (sizeof("icase_") - 1)) == 0) { + pfilter->cmp_flags |= FILT_FLAG_ICASE; + argv[1] += sizeof("icase_") - 1; + } + + /* fill in cmp_type */ + if (strcasecmp(argv[1], "contains") == 0) + pfilter->cmp_type = FILT_CMP_CONTAINS; + else if (strcasecmp(argv[1], "isequal") == 0) + pfilter->cmp_type = FILT_CMP_EQUAL; + else if (strcasecmp(argv[1], "startswith") == 0) + pfilter->cmp_type = FILT_CMP_STARTS; + else if (strcasecmp(argv[1], "regex") == 0) + pfilter->cmp_type = FILT_CMP_REGEX; + else if (strcasecmp(argv[1], "ereregex") == 0) { + pfilter->cmp_type = FILT_CMP_REGEX; + re_flags |= REG_EXTENDED; + } else { + logerror("unknown cmp function"); + free(pfilter); + return (NULL); + } + + /* + * Handle filter value + */ + + /* ' ".*Deny.*"' */ + /* remove leading whitespace and check for '"' next character */ + filter += strspn(filter, ", \t\n"); + if (*filter != '"' || strlen(filter) < 3) { + logerror("property value parse error"); + free(pfilter); + return (NULL); + } + filter++; + + /* '.*Deny.*"' */ + /* process possible backslash (\") escaping */ + escaped = 0; + filter_endpos = filter; + for (p = filter; *p != '\0'; p++) { + if (*p == '\\' && !escaped) { + escaped = 1; + /* do not shift filter_endpos */ + continue; + } + if (*p == '"' && !escaped) { + p++; + break; + } + /* we've seen some esc symbols, need to compress the line */ + if (filter_endpos != p) + *filter_endpos = *p; + + filter_endpos++; + escaped = 0; + } + + *filter_endpos = '\0'; + /* '.*Deny.*' */ + + /* We should not have anything but whitespace left after closing '"' */ + if (*p != '\0' && strspn(p, " \t\n") != strlen(p)) { + logerror("property value parse error"); + free(pfilter); + return (NULL); + } + + if (pfilter->cmp_type == FILT_CMP_REGEX) { + pfilter->pflt_re = calloc(1, sizeof(*pfilter->pflt_re)); + if (pfilter->pflt_re == NULL) { + logerror("RE calloc() error"); + free(pfilter); + free(pfilter->pflt_re); + return (NULL); + } + if (pfilter->cmp_flags & FILT_FLAG_ICASE) + re_flags |= REG_ICASE; + if (regcomp(pfilter->pflt_re, filter, re_flags) != 0) { + logerror("RE compilation error"); + free(pfilter); + free(pfilter->pflt_re); + return (NULL); + } + } else { + pfilter->pflt_strval = strdup(filter); + pfilter->pflt_strlen = strlen(filter); + } + + return (pfilter); +} + +/* + * Decode a symbolic name to a numeric value + */ +static int +decode(const char *name, const CODE *codetab) +{ + const CODE *c; + char *p, buf[40]; + + if (isdigit(*name)) + return (atoi(name)); + + for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) { + if (isupper(*name)) + *p = tolower(*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); +} + +static const char * +parse_selector(const char *p, struct filed *f) +{ + int i, pri; + int pri_done = 0, pri_cmp = 0, pri_invert = 0; + char *bp, buf[LINE_MAX], ebuf[100]; + const char *q; + + /* find the end of this facility name list */ + for (q = p; *q && *q != '\t' && *q != ' ' && *q++ != '.';) + continue; + + /* get the priority comparison */ + 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++; + break; + } + } + + /* collect priority name */ + for (bp = buf; *q != '\0' && !strchr("\t,; ", *q); ) + *bp++ = *q++; + *bp = '\0'; + + /* skip cruft */ + while (strchr(",;", *q)) + q++; + + /* decode priority name */ + if (*buf == '*') { + pri = LOG_PRIMASK; + pri_cmp = PRI_LT | PRI_EQ | PRI_GT; + } else { + /* Ignore trailing spaces. */ + for (i = strlen(buf) - 1; i >= 0 && buf[i] == ' '; i--) + buf[i] = '\0'; + + pri = decode(buf, prioritynames); + if (pri < 0) { + errno = 0; + (void)snprintf(ebuf, sizeof(ebuf), + "unknown priority name \"%s\"", buf); + logerror(ebuf); + free(f); + return (NULL); + } + } + if (!pri_cmp) + pri_cmp = (UniquePriority) + ? (PRI_EQ) + : (PRI_EQ | PRI_GT) + ; + if (pri_invert) + pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT; + + /* scan facilities */ + while (*p != '\0' && !strchr("\t.; ", *p)) { + for (bp = buf; *p != '\0' && !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; + (void)snprintf(ebuf, sizeof(ebuf), + "unknown facility name \"%s\"", + buf); + logerror(ebuf); + free(f); + return (NULL); + } + f->f_pmask[i >> 3] = pri; + f->f_pcmp[i >> 3] = pri_cmp; + } + while (*p == ',' || *p == ' ') + p++; + } + return (q); +} + +static void +parse_action(const char *p, struct filed *f) +{ + struct addrinfo hints, *res; + int error, i; + const char *q; + bool syncfile; + + if (*p == '-') { + syncfile = false; + p++; + } else { + syncfile = true; + } + + switch (*p) { + case '@': + { + char *tp; + char endkey = ':'; + /* + * scan forward to see if there is a port defined. + * so we can't use strlcpy.. + */ + i = sizeof(f->fu_forw_hname); + tp = f->fu_forw_hname; + p++; + + /* + * an ipv6 address should start with a '[' in that case + * we should scan for a ']' + */ + if (*p == '[') { + p++; + endkey = ']'; + } + while (*p && (*p != endkey) && (i-- > 0)) { + *tp++ = *p++; + } + if (endkey == ']' && *p == endkey) + p++; + *tp = '\0'; + } + /* See if we copied a domain and have a port */ + if (*p == ':') + p++; + else + p = NULL; + + hints = (struct addrinfo){ + .ai_family = family, + .ai_socktype = SOCK_DGRAM + }; + error = getaddrinfo(f->fu_forw_hname, + p ? p : "syslog", &hints, &res); + if (error) { + logerror(gai_strerror(error)); + break; + } + f->fu_forw_addr = res; + f->f_type = F_FORW; + break; + + case '/': + if ((f->f_file = open(p, logflags, 0600)) < 0) { + f->f_type = F_UNUSED; + logerror(p); + break; + } + if (syncfile) + f->f_flags |= FFLAG_SYNC; + if (isatty(f->f_file)) { + if (strcmp(p, _PATH_CONSOLE) == 0) + f->f_type = F_CONSOLE; + else + f->f_type = F_TTY; + (void)strlcpy(f->fu_fname, p + sizeof(_PATH_DEV) - 1, + sizeof(f->fu_fname)); + } else { + (void)strlcpy(f->fu_fname, p, sizeof(f->fu_fname)); + f->f_type = F_FILE; + } + break; + + case '|': + f->fu_pipe_pd = -1; + (void)strlcpy(f->fu_pipe_pname, p + 1, + sizeof(f->fu_pipe_pname)); + f->f_type = F_PIPE; + break; + + case '*': + f->f_type = F_WALL; + break; + + default: + for (i = 0; i < MAXUNAMES && *p != '\0'; i++) { + for (q = p; *q != '\0' && *q != ','; ) + q++; + (void)strncpy(f->fu_uname[i], p, MAXLOGNAME - 1); + if ((q - p) >= MAXLOGNAME) + f->fu_uname[i][MAXLOGNAME - 1] = '\0'; + else + f->fu_uname[i][q - p] = '\0'; + while (*q == ',' || *q == ' ') + q++; + p = q; + } + f->f_type = F_USERS; + break; + } +} + +/* + * Crack a configuration file line + */ +static void +cfline(const char *line, const char *prog, const char *host, + const char *pfilter) +{ + struct filed *f; + int i; + const char *p; + char *pfilter_dup; + + dprintf("cfline(\"%s\", f, \"%s\", \"%s\", \"%s\")\n", line, prog, + host, pfilter); + + f = calloc(1, sizeof(*f)); + if (f == NULL) { + logerror("malloc"); + exit(1); + } + errno = 0; /* keep strerror() stuff out of logerror messages */ + + for (i = 0; i <= LOG_NFACILITIES; i++) + f->f_pmask[i] = INTERNAL_NOPRI; + + /* save hostname if any */ + if (host && *host == '*') + host = NULL; + if (host) { + int hl; + + f->f_host = strdup(host); + if (f->f_host == NULL) { + logerror("strdup"); + exit(1); + } + hl = strlen(f->f_host); + if (hl > 0 && f->f_host[hl-1] == '.') + f->f_host[--hl] = '\0'; + /* RFC 5424 prefers logging FQDNs. */ + if (RFC3164OutputFormat) + trimdomain(f->f_host, hl); + } + + /* save program name if any */ + if (prog && *prog == '*') + prog = NULL; + if (prog) { + f->f_program = strdup(prog); + if (f->f_program == NULL) { + logerror("strdup"); + exit(1); + } + } + + if (pfilter) { + pfilter_dup = strdup(pfilter); + if (pfilter_dup == NULL) { + logerror("strdup"); + exit(1); + } + f->f_prop_filter = prop_filter_compile(pfilter_dup); + if (f->f_prop_filter == NULL) { + logerror("filter compile error"); + exit(1); + } + free(pfilter_dup); + } + + /* scan through the list of selectors */ + for (p = line; *p != '\0' && *p != '\t' && *p != ' ';) + p = parse_selector(p, f); + + /* skip to action part */ + while (*p == '\t' || *p == ' ') + p++; + parse_action(p, f); + + STAILQ_INSERT_TAIL(&fhead, f, next); +} + +static int +configfiles(const struct dirent *dp) +{ + const char *p; + size_t ext_len; + + if (dp->d_name[0] == '.') + return (0); + + ext_len = sizeof(include_ext) -1; + + if (dp->d_namlen <= ext_len) + return (0); + + p = &dp->d_name[dp->d_namlen - ext_len]; + if (strcmp(p, include_ext) != 0) + return (0); + + return (1); +} + +void +parseconfigfile(FILE *cf) +{ + FILE *cf2; + struct dirent **ent; + char cline[LINE_MAX]; + char host[MAXHOSTNAMELEN]; + char prog[LINE_MAX]; + char file[MAXPATHLEN]; + char pfilter[LINE_MAX]; + char *p, *tmp; + int i, nents; + size_t include_len; + + /* + * Foreach line in the conf table, open that file. + */ + include_len = sizeof(include_str) - 1; + (void)strlcpy(host, "*", sizeof(host)); + (void)strlcpy(prog, "*", sizeof(prog)); + (void)strlcpy(pfilter, "*", sizeof(pfilter)); + while (fgets(cline, sizeof(cline), cf) != NULL) { + /* + * 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(*p); ++p) + continue; + if (*p == '\0') + continue; + if (strncmp(p, include_str, include_len) == 0 && + isspace(p[include_len])) { + p += include_len; + while (isspace(*p)) + p++; + tmp = p; + while (*tmp != '\0' && !isspace(*tmp)) + tmp++; + *tmp = '\0'; + dprintf("Trying to include files in '%s'\n", p); + nents = scandir(p, &ent, configfiles, alphasort); + if (nents == -1) { + dprintf("Unable to open '%s': %s\n", p, + strerror(errno)); + continue; + } + for (i = 0; i < nents; i++) { + if (snprintf(file, sizeof(file), "%s/%s", p, + ent[i]->d_name) >= (int)sizeof(file)) { + dprintf("ignoring path too long: " + "'%s/%s'\n", p, ent[i]->d_name); + free(ent[i]); + continue; + } + free(ent[i]); + cf2 = fopen(file, "r"); + if (cf2 == NULL) + continue; + dprintf("reading %s\n", file); + parseconfigfile(cf2); + (void)fclose(cf2); + } + free(ent); + continue; + } + if (*p == '#') { + p++; + if (*p == '\0' || strchr("!+-:", *p) == NULL) + continue; + } + if (*p == '+' || *p == '-') { + host[0] = *p++; + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '*') { + (void)strlcpy(host, "*", sizeof(host)); + continue; + } + if (*p == '@') + p = LocalHostName; + for (i = 1; i < MAXHOSTNAMELEN - 1; i++) { + if (!isalnum(*p) && *p != '.' && *p != '-' + && *p != ',' && *p != ':' && *p != '%') + break; + host[i] = *p++; + } + host[i] = '\0'; + continue; + } + if (*p == '!') { + p++; + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '*') { + (void)strlcpy(prog, "*", sizeof(prog)); + continue; + } + for (i = 0; i < LINE_MAX - 1; i++) { + if (!isprint(p[i]) || isspace(p[i])) + break; + prog[i] = p[i]; + } + prog[i] = '\0'; + continue; + } + if (*p == ':') { + p++; + while (isspace(*p)) + p++; + if (*p == '\0' || *p == '*') { + (void)strlcpy(pfilter, "*", sizeof(pfilter)); + continue; + } + (void)strlcpy(pfilter, p, sizeof(pfilter)); + continue; + } + for (p = cline + 1; *p != '\0'; p++) { + if (*p != '#') + continue; + if (*(p - 1) == '\\') { + strcpy(p - 1, p); + p--; + continue; + } + *p = '\0'; + break; + } + for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--) + cline[i] = '\0'; + cfline(cline, prog, host, pfilter); + } +} + +void +readconfigfile(const char *path) +{ + FILE *cf; + + /* open the configuration file */ + if ((cf = fopen(path, "r")) != NULL) { + parseconfigfile(cf); + (void)fclose(cf); + } else { + dprintf("cannot open %s\n", path); + cfline("*.ERR\t/dev/console", "*", "*", "*"); + cfline("*.PANIC\t*", "*", "*", "*"); + } +}