Changeset View
Changeset View
Standalone View
Standalone View
usr.sbin/syslogd/syslogd.c
Context not available. | |||||
#include <sysexits.h> | #include <sysexits.h> | ||||
#include <unistd.h> | #include <unistd.h> | ||||
#include <utmpx.h> | #include <utmpx.h> | ||||
#include <regex.h> | |||||
#include "pathnames.h" | #include "pathnames.h" | ||||
#include "ttymsg.h" | #include "ttymsg.h" | ||||
Context not available. | |||||
#define MARK 0x008 /* this message is a mark */ | #define MARK 0x008 /* this message is a mark */ | ||||
#define ISKERNEL 0x010 /* kernel generated message */ | #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 | * This structure represents the files that will have log | ||||
* copies printed. | * copies printed. | ||||
Context not available. | |||||
#define PRI_EQ 0x2 | #define PRI_EQ 0x2 | ||||
#define PRI_GT 0x4 | #define PRI_GT 0x4 | ||||
char *f_program; /* program this applies to */ | char *f_program; /* program this applies to */ | ||||
struct prop_filter *f_prop_filter; /* property-based filter */ | |||||
union { | union { | ||||
char f_uname[MAXUNAMES][MAXLOGNAME]; | char f_uname[MAXUNAMES][MAXLOGNAME]; | ||||
struct { | struct { | ||||
Context not available. | |||||
static int addfile(struct filed *); | static int addfile(struct filed *); | ||||
static int addpeer(struct peer *); | static int addpeer(struct peer *); | ||||
static int addsock(struct sockaddr *, socklen_t, struct socklist *); | 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 const char *cvthname(struct sockaddr *); | ||||
static void deadq_enter(pid_t, const char *); | static void deadq_enter(pid_t, const char *); | ||||
static int deadq_remove(struct deadq_entry *); | static int deadq_remove(struct deadq_entry *); | ||||
Context not available. | |||||
return !exclude; | 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) | |||||
melifaro: Why not enforcing caller to provide the message length? Also: why `<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 */ | |||||
melifaroUnsubmitted Done Inline Actions/* NOTREACHED */ ? melifaro: `/* NOTREACHED */` ? | |||||
return (-1); | |||||
} | |||||
/* | /* | ||||
* Log a message to the appropriate log files, users, etc. based on | * Log a message to the appropriate log files, users, etc. based on | ||||
* the priority. | * the priority. | ||||
Context not available. | |||||
logmsg(int pri, const char *msg, const char *from, int flags) | logmsg(int pri, const char *msg, const char *from, int flags) | ||||
{ | { | ||||
struct filed *f; | struct filed *f; | ||||
int i, fac, msglen, prilev; | int i, fac, msglen, prilev, filterret; | ||||
const char *timestamp; | const char *timestamp; | ||||
char prog[NAME_MAX+1]; | char prog[NAME_MAX+1]; | ||||
char buf[MAXLINE+1]; | char buf[MAXLINE+1]; | ||||
Context not available. | |||||
if (skip_message(prog, f->f_program, 1)) | if (skip_message(prog, f->f_program, 1)) | ||||
continue; | 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 */ | /* skip message to console if it has already been printed */ | ||||
if (f->f_type == F_CONSOLE && (flags & IGN_CONS)) | if (f->f_type == F_CONSOLE && (flags & IGN_CONS)) | ||||
continue; | continue; | ||||
Context not available. | |||||
char host[MAXHOSTNAMELEN]; | char host[MAXHOSTNAMELEN]; | ||||
char prog[LINE_MAX]; | char prog[LINE_MAX]; | ||||
char file[MAXPATHLEN]; | char file[MAXPATHLEN]; | ||||
char pfilter[LINE_MAX]; | |||||
char *p, *tmp; | char *p, *tmp; | ||||
int i, nents; | int i, nents; | ||||
size_t include_len; | size_t include_len; | ||||
Context not available. | |||||
include_len = sizeof(include_str) -1; | include_len = sizeof(include_str) -1; | ||||
(void)strlcpy(host, "*", sizeof(host)); | (void)strlcpy(host, "*", sizeof(host)); | ||||
(void)strlcpy(prog, "*", sizeof(prog)); | (void)strlcpy(prog, "*", sizeof(prog)); | ||||
(void)strlcpy(pfilter, "*", sizeof(pfilter)); | |||||
while (fgets(cline, sizeof(cline), cf) != NULL) { | while (fgets(cline, sizeof(cline), cf) != NULL) { | ||||
/* | /* | ||||
* check for end-of-section, comments, strip off trailing | * check for end-of-section, comments, strip off trailing | ||||
Context not available. | |||||
} | } | ||||
if (*p == '#') { | if (*p == '#') { | ||||
p++; | p++; | ||||
if (*p != '!' && *p != '+' && *p != '-') | if (*p == '\0' || strchr("!+-:", *p) == NULL) | ||||
continue; | continue; | ||||
} | } | ||||
if (*p == '+' || *p == '-') { | if (*p == '+' || *p == '-') { | ||||
Context not available. | |||||
prog[i] = 0; | prog[i] = 0; | ||||
continue; | 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++) { | for (p = cline + 1; *p != '\0'; p++) { | ||||
if (*p != '#') | if (*p != '#') | ||||
continue; | continue; | ||||
Context not available. | |||||
} | } | ||||
for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--) | for (i = strlen(cline) - 1; i >= 0 && isspace(cline[i]); i--) | ||||
cline[i] = '\0'; | cline[i] = '\0'; | ||||
f = cfline(cline, prog, host); | f = cfline(cline, prog, host, pfilter); | ||||
if (f != NULL) | if (f != NULL) | ||||
addfile(f); | addfile(f); | ||||
} | } | ||||
Context not available. | |||||
STAILQ_REMOVE_HEAD(&fhead, next); | STAILQ_REMOVE_HEAD(&fhead, next); | ||||
free(f->f_program); | free(f->f_program); | ||||
free(f->f_host); | 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); | free(f); | ||||
} | } | ||||
/* open the configuration file */ | /* open the configuration file */ | ||||
if ((cf = fopen(ConfFile, "r")) == NULL) { | if ((cf = fopen(ConfFile, "r")) == NULL) { | ||||
dprintf("cannot open %s\n", ConfFile); | dprintf("cannot open %s\n", ConfFile); | ||||
f = cfline("*.ERR\t/dev/console", "*", "*"); | f = cfline("*.ERR\t/dev/console", "*", "*", "*"); | ||||
if (f != NULL) | if (f != NULL) | ||||
addfile(f); | addfile(f); | ||||
f = cfline("*.PANIC\t*", "*", "*"); | f = cfline("*.PANIC\t*", "*", "*", "*"); | ||||
if (f != NULL) | if (f != NULL) | ||||
addfile(f); | addfile(f); | ||||
Initialized = 1; | Initialized = 1; | ||||
Context not available. | |||||
} | } | ||||
} | } | ||||
#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); | |||||
melifaroUnsubmitted Done Inline ActionsYou can consider doing strdup() in the caller, simplifying the error handling here. melifaro: You can consider doing `strdup()` in the caller, simplifying the error handling here. | |||||
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) | |||||
melifaroUnsubmitted Done Inline ActionsGiven the filter line already contains everything before first ",", what is the point of doing strNcasecmp()? melifaro: Given the filter line already contains everything before first ",", what is the point of doing… | |||||
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) { | |||||
melifaroUnsubmitted Done Inline Actions*argv[1] == '!' ? melifaro: `*argv[1] == '!'` ? | |||||
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 */ | |||||
melifaroUnsubmitted Done Inline ActionsProviding some example(s) here would help going through the code. melifaro: Providing some example(s) here would help going through the code. | |||||
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 | * Crack a configuration file line | ||||
*/ | */ | ||||
static struct filed * | 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 filed *f; | ||||
struct addrinfo hints, *res; | struct addrinfo hints, *res; | ||||
Context not available. | |||||
char *bp; | char *bp; | ||||
char buf[MAXLINE], ebuf[100]; | 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)); | f = calloc(1, sizeof(*f)); | ||||
if (f == NULL) { | if (f == NULL) { | ||||
Context not available. | |||||
} | } | ||||
} | } | ||||
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 */ | /* scan through the list of selectors */ | ||||
for (p = line; *p && *p != '\t' && *p != ' ';) { | for (p = line; *p && *p != '\t' && *p != ' ';) { | ||||
int pri_done; | int pri_done; | ||||
Context not available. |
Why not enforcing caller to provide the message length? Also: why <0?