Index: usr.sbin/syslogd/syslog.conf.5 =================================================================== --- usr.sbin/syslogd/syslog.conf.5 +++ usr.sbin/syslogd/syslog.conf.5 @@ -44,9 +44,10 @@ program. It consists of blocks of lines separated by -.Em program -and +.Em program , .Em hostname +or +.Em property-based filter specifications (separations appear alone on their lines), with each line containing two fields: the .Em selector @@ -154,14 +155,16 @@ library routine. .Pp Each block of lines is separated from the previous block by a -.Em program -or +.Em program , .Em hostname +or +.Em property-based filter specification. A block will only log messages corresponding to the most recent -.Em program -and +.Em program , .Em hostname +and +.Em property-based filter specifications given. Thus, with a block which selects .Ql ppp @@ -236,11 +239,24 @@ values may be specified for hostname specifications. .Pp A -.Em program +.Em property-based filter +specification is a line beginning with +.Ql #: or +.Ql \&: +and the following blocks will be applied only when filter value +matches given filter propertie's value. See +.Sx PROPERTY-BASED FILTERS +section for more details. +.Pp +A +.Em program , .Em hostname -specification may be reset by giving the program or hostname as -.Ql * . +or +.Em property-based filter +specification may be reset by giving +.Ql * +as an argument. .Pp See .Xr syslog 3 @@ -434,6 +450,78 @@ is removed and .Ql # is treated as an ordinary character. +.Sh PROPERTY-BASED FILTERS +.Em program , +.Em hostname +specifications performs exact match filtering against explicit field only. +.Em Property-based filters +feature substring and regular expressions (see +.Xr re_format 7 ) +matching against various message attributes. +Filter specification starts with +.Ql #: +or +.Ql \&: +followed by three comma-separated fields +.Em property , operator , \&"value\&" . +Value must be double-quoted. A double quote and backslash must be escaped by +a blackslash. +.Pp +Following +.Em properties +are supported as test value: +.Pp +.Bl -bullet -compact +.It +.Ql msg +- body of the message received. +.It +.Ql programname +- program name sent the message +.It +.Ql hostname +- hostname of message's originator +.It +.Ql source +- an alias for hostname +.El +.Pp +Operator specifies a comparison function between +.Em propertie's + value against filter's value. +Possible operators: +.Pp +.Bl -bullet -compact +.It +.Ql contains +- true if filter value is found as a substring of +.Em property +.It +.Ql isequal +- true if filter value is equal to +.Em property +.It +.Ql startswith +- true if property starts with filter value +.It +.Ql regex +- true if property matches basic regular expression defined in filter value +.It +.Ql ereregex +- true if property matches extended regular expression defined in filter value +.El +.Pp +Operator may be prefixed by +.Pp +.Bl -bullet -compact +.It +.Ql \&! +- to invert compare logic +.It +.Ql icase_ +- to make comparison function case insensitive +.El +.Pp .Sh IMPLEMENTATION NOTES The .Dq kern @@ -503,6 +591,21 @@ # Log ipfw messages without syncing after every message. !ipfw *.* -/var/log/ipfw + +# Log ipfw messages with "Deny" in the message body. +:msg, contains, ".*Deny.*" +*.* /var/log/ipfw.deny + +# Reset program name filtering +!* + +# Log messages from bird or bird6 into one file +:processname, regex, "^bird6?$" +*.* /var/log/bird-all.log + +# Log messages from servers in racks 10-19 in multiple locations, case insensitive +:hostname, icase_ereregex, "^server-(dcA|podB|cdn)-rack1[0-9]{2}\\..*" +*.* /var/log/racks10..19.log .Ed .Sh SEE ALSO .Xr syslog 3 , Index: usr.sbin/syslogd/syslogd.c =================================================================== --- usr.sbin/syslogd/syslogd.c +++ usr.sbin/syslogd/syslogd.c @@ -113,6 +113,7 @@ #include #include #include +#include #include "pathnames.h" #include "ttymsg.h" @@ -175,6 +176,37 @@ #define MARK 0x008 /* this message is a mark */ #define ISKERNEL 0x010 /* kernel generated message */ +/* + * This structure hold property-based filter + */ + +struct prop_filter { + uint8_t prop_type; +#define PROP_TYPE_NOOP 0 +#define PROP_TYPE_MSG 1 +#define PROP_TYPE_HOSTNAME 2 +#define PROP_TYPE_PROGNAME 3 + + uint8_t cmp_type; +#define PROP_CMP_CONTAINS 1 +#define PROP_CMP_EQUAL 2 +#define PROP_CMP_STARTS 3 +#define PROP_CMP_REGEX 4 + + uint16_t cmp_flags; +#define PROP_FLAG_EXCLUDE (1 << 0) +#define PROP_FLAG_ICASE (1 << 1) + + union { + char *p_strval; + regex_t *p_re; + } pflt_uniptr; +#define pflt_strval pflt_uniptr.p_strval +#define pflt_re pflt_uniptr.p_re + + size_t pflt_strlen; +}; + /* * This structure represents the files that will have log * copies printed. @@ -194,6 +226,7 @@ #define PRI_EQ 0x2 #define PRI_GT 0x4 char *f_program; /* program this applies to */ + struct prop_filter *f_prop_filter; /* property-based filter */ union { char f_uname[MAXUNAMES][MAXLOGNAME]; struct { @@ -337,7 +370,8 @@ static int addfile(struct filed *); static int addpeer(struct peer *); static int addsock(struct sockaddr *, socklen_t, struct socklist *); -static struct filed *cfline(const char *, const char *, const char *); +static struct filed *cfline(const char *, const char *, const char *, + const char *); static const char *cvthname(struct sockaddr *); static void deadq_enter(pid_t, const char *); static int deadq_remove(struct deadq_entry *); @@ -1026,6 +1060,71 @@ return !exclude; } +/* + * Match a set of message properties against a filter. + * Return a non-0 value if the message must be ignored + * based on the filter. + */ +static int +evaluate_prop_filter(const struct prop_filter *filter, const char *value, + size_t valuelen) +{ + const char *s = NULL; + const int exclude = ((filter->cmp_flags & PROP_FLAG_EXCLUDE) > 0); + + if (value == NULL) + return (-1); + + if (filter->cmp_type == PROP_CMP_REGEX) { + if (regexec(filter->pflt_re, value, 0, NULL, 0) == 0) + return (exclude); + else + return (!exclude); + } + + if (valuelen < 0) + valuelen = strlen(value); + + /* a shortcut for equal with different length is always false */ + if (filter->cmp_type == PROP_CMP_EQUAL + && valuelen != filter->pflt_strlen) + return (!exclude); + + if (filter->cmp_flags & PROP_FLAG_ICASE) + s = strcasestr (value, filter->pflt_strval); + else + s = strstr (value, filter->pflt_strval); + + /* + * PROP_CMP_CONTAINS true if s + * PROP_CMP_STARTS true if s && s == value + * PROP_CMP_EQUAL true if s && s == value && + * valuelen == filter->pflt_strlen + * (and length match is checked + * already) + */ + + switch (filter->cmp_type) { + case PROP_CMP_STARTS: + case PROP_CMP_EQUAL: + if (s != value) + return (!exclude); + /* FALLTHROUGH */ + case PROP_CMP_CONTAINS: + if (s) + return (exclude); + else + return (!exclude); + break; + default: + /* unknown cmp_type */ + break; + } + + /* should not be reachable, discard the message */ + return (-1); +} + /* * Log a message to the appropriate log files, users, etc. based on * the priority. @@ -1034,7 +1133,7 @@ logmsg(int pri, const char *msg, const char *from, int flags) { struct filed *f; - int i, fac, msglen, prilev; + int i, fac, msglen, prilev, filterret; const char *timestamp; char prog[NAME_MAX+1]; char buf[MAXLINE+1]; @@ -1129,6 +1228,28 @@ if (skip_message(prog, f->f_program, 1)) continue; + if (f->f_prop_filter + && f->f_prop_filter->prop_type != PROP_TYPE_NOOP) { + switch (f->f_prop_filter->prop_type) { + case PROP_TYPE_MSG: + filterret = evaluate_prop_filter( + f->f_prop_filter, msg, msglen); + break; + case PROP_TYPE_HOSTNAME: + filterret = evaluate_prop_filter( + f->f_prop_filter, from, -1); + break; + case PROP_TYPE_PROGNAME: + filterret = evaluate_prop_filter( + f->f_prop_filter, prog, -1); + break; + default: + filterret = -1; + } + if (filterret) + continue; + } + /* skip message to console if it has already been printed */ if (f->f_type == F_CONSOLE && (flags & IGN_CONS)) continue; @@ -1706,6 +1827,7 @@ char host[MAXHOSTNAMELEN]; char prog[LINE_MAX]; char file[MAXPATHLEN]; + char pfilter[LINE_MAX]; char *p, *tmp; int i, nents; size_t include_len; @@ -1716,6 +1838,7 @@ 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 @@ -1764,7 +1887,7 @@ } if (*p == '#') { p++; - if (*p != '!' && *p != '+' && *p != '-') + if (*p == '\0' || strchr("!+-:", *p) == NULL) continue; } if (*p == '+' || *p == '-') { @@ -1801,6 +1924,17 @@ prog[i] = 0; continue; } + if (*p == ':') { + p++; + while (isspace(*p)) + p++; + if ((!*p) || (*p == '*')) { + (void)strlcpy(pfilter, "*", sizeof(pfilter)); + continue; + } + (void)strlcpy(pfilter, p, sizeof(pfilter)); + continue; + } for (p = cline + 1; *p != '\0'; p++) { if (*p != '#') continue; @@ -1814,7 +1948,7 @@ } for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--) cline[i] = '\0'; - f = cfline(cline, prog, host); + f = cfline(cline, prog, host, pfilter); if (f != NULL) addfile(f); } @@ -1905,16 +2039,30 @@ STAILQ_REMOVE_HEAD(&fhead, next); free(f->f_program); free(f->f_host); + if (f->f_prop_filter) { + switch (f->f_prop_filter->cmp_type) { + case PROP_CMP_REGEX: + regfree(f->f_prop_filter->pflt_re); + free(f->f_prop_filter->pflt_re); + break; + case PROP_CMP_CONTAINS: + case PROP_CMP_EQUAL: + case PROP_CMP_STARTS: + free(f->f_prop_filter->pflt_strval); + break; + } + free(f->f_prop_filter); + } free(f); } /* open the configuration file */ if ((cf = fopen(ConfFile, "r")) == NULL) { dprintf("cannot open %s\n", ConfFile); - f = cfline("*.ERR\t/dev/console", "*", "*"); + f = cfline("*.ERR\t/dev/console", "*", "*", "*"); if (f != NULL) addfile(f); - f = cfline("*.PANIC\t*", "*", "*"); + f = cfline("*.PANIC\t*", "*", "*", "*"); if (f != NULL) addfile(f); Initialized = 1; @@ -2010,11 +2158,163 @@ } } +#define CONST_STRNCASECMP(a, b) strncasecmp((a), (b), (sizeof(b))) +#define CONST_STRNCASECMPPREF(a, b) strncasecmp((a), (b), (sizeof(b) - 1)) +/* + * Compile property-based filter. + * Returns 0 on success, -1 otherwise. + */ +static int +prop_filter_compile(struct prop_filter *pfilter, const char *filterstr) +{ + char *filter, *filterp, *filter_endpos, *p; + char **ap, *argv[2]; + int re_flags = REG_NOSUB; + int escaped; + + filterp = filter = strdup(filterstr); + if (filter == NULL) { + logerror("strdup"); + return (-1); + } + + bzero(pfilter, sizeof(struct prop_filter)); + bzero(argv, sizeof(argv)); + + /* + * Split filter into 3 parts: property name, cmp type + * and value to compare against which stays in 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(filterp); + return (-1); + } + + /* fill in prop_type */ + if (CONST_STRNCASECMP(argv[0], "msg") == 0) + pfilter->prop_type = PROP_TYPE_MSG; + else if(CONST_STRNCASECMP(argv[0], "hostname") == 0) + pfilter->prop_type = PROP_TYPE_HOSTNAME; + else if(CONST_STRNCASECMP(argv[0], "source") == 0) + pfilter->prop_type = PROP_TYPE_HOSTNAME; + else if(CONST_STRNCASECMP(argv[0], "programname") == 0) + pfilter->prop_type = PROP_TYPE_PROGNAME; + else { + logerror("unknown property"); + free(filterp); + return (-1); + } + + /* full in cmp_flags */ + if (CONST_STRNCASECMPPREF(argv[1], "!") == 0) { + pfilter->cmp_flags |= PROP_FLAG_EXCLUDE; + argv[1]++; + } + if (CONST_STRNCASECMPPREF(argv[1], "icase_") == 0) { + pfilter->cmp_flags |= PROP_FLAG_ICASE; + argv[1] += sizeof("icase_") - 1; + } + + /* fill in cmp_type */ + if (CONST_STRNCASECMP(argv[1], "contains") == 0) + pfilter->cmp_type = PROP_CMP_CONTAINS; + else if (CONST_STRNCASECMP(argv[1], "isequal") == 0) + pfilter->cmp_type = PROP_CMP_EQUAL; + else if (CONST_STRNCASECMP(argv[1], "startswith") == 0) + pfilter->cmp_type = PROP_CMP_STARTS; + else if (CONST_STRNCASECMP(argv[1], "regex") == 0) + pfilter->cmp_type = PROP_CMP_REGEX; + else if (CONST_STRNCASECMP(argv[1], "ereregex") == 0) { + pfilter->cmp_type = PROP_CMP_REGEX; + re_flags |= REG_EXTENDED; + } else { + logerror("unknown cmp function"); + free(filterp); + return (-1); + } + + /* + * Handle filter value + */ + + /* remove leading whitespace and check for " next character */ + filter += strspn(filter, ", \t\n"); + if (*filter != '"' || strlen(filter) < 3) { + logerror("property value parse error"); + free(filterp); + return (-1); + } + filter++; + + /* process possible '"' 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'; + + /* do we have anything but whitespace left? */ + if (*p != '\0' && strspn(p, " \t\n") != strlen(p)) { + logerror("property value parse error"); + free(filterp); + return (-1); + } + + if (pfilter->cmp_type == PROP_CMP_REGEX) { + pfilter->pflt_re = calloc(1, sizeof(*pfilter->pflt_re)); + if (pfilter->pflt_re == NULL) { + logerror("RE calloc() error"); + free(filterp); + free(pfilter->pflt_re); + return (-1); + } + if (pfilter->cmp_flags & PROP_FLAG_ICASE) + re_flags |= REG_ICASE; + if (regcomp(pfilter->pflt_re, filter, re_flags) != 0) { + logerror("RE compilation error"); + free(filterp); + free(pfilter->pflt_re); + return (-1); + } + } else { + pfilter->pflt_strval = strdup(filter); + pfilter->pflt_strlen = strlen(filter); + } + + free(filterp); + return (0); + +} + /* * Crack a configuration file line */ static struct filed * -cfline(const char *line, const char *prog, const char *host) +cfline(const char *line, const char *prog, const char *host, + const char *pfilter) { struct filed *f; struct addrinfo hints, *res; @@ -2023,7 +2323,8 @@ char *bp; char buf[MAXLINE], ebuf[100]; - dprintf("cfline(\"%s\", f, \"%s\", \"%s\")\n", line, prog, host); + dprintf("cfline(\"%s\", f, \"%s\", \"%s\", \"%s\")\n", line, prog, + host, pfilter); f = calloc(1, sizeof(*f)); if (f == NULL) { @@ -2063,6 +2364,22 @@ } } + if (pfilter) { + f->f_prop_filter = calloc(1, sizeof(*(f->f_prop_filter))); + if (f->f_prop_filter == NULL) { + logerror("pfilter calloc"); + exit(1); + } + if (*pfilter == '*') + f->f_prop_filter->prop_type = PROP_TYPE_NOOP; + else { + if (prop_filter_compile(f->f_prop_filter, pfilter)) { + logerror("filter compile error"); + exit(1); + } + } + } + /* scan through the list of selectors */ for (p = line; *p && *p != '\t' && *p != ' ';) { int pri_done;