Please note that diffs are not public domain; they are subject to the copyright notices on the relevant files. =================================================================== RCS file: /ftp/cvs/cvsroot/src/usr.sbin/syslogd/syslogd.c,v rcsdiff: /ftp/cvs/cvsroot/src/usr.sbin/syslogd/syslogd.c,v: warning: Unknown phrases like `commitid ...;' are present. retrieving revision 1.69 retrieving revision 1.69.2.13 diff -u -p -r1.69 -r1.69.2.13 --- src/usr.sbin/syslogd/syslogd.c 2004/10/30 15:53:25 1.69 +++ src/usr.sbin/syslogd/syslogd.c 2004/11/17 01:29:48 1.69.2.13 @@ -1,4 +1,4 @@ -/* $NetBSD: syslogd.c,v 1.69 2004/10/30 15:53:25 dsl Exp $ */ +/* $NetBSD: syslogd.c,v 1.69.2.13 2004/11/17 01:29:48 thorpej Exp $ */ /* * Copyright (c) 1983, 1988, 1993, 1994 @@ -39,7 +39,7 @@ __COPYRIGHT("@(#) Copyright (c) 1983, 19 #if 0 static char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94"; #else -__RCSID("$NetBSD: syslogd.c,v 1.69 2004/10/30 15:53:25 dsl Exp $"); +__RCSID("$NetBSD: syslogd.c,v 1.69.2.13 2004/11/17 01:29:48 thorpej Exp $"); #endif #endif /* not lint */ @@ -63,6 +63,8 @@ __RCSID("$NetBSD: syslogd.c,v 1.69 2004/ * 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. */ #define MAXLINE 1024 /* maximum line length */ @@ -78,6 +80,7 @@ __RCSID("$NetBSD: syslogd.c,v 1.69 2004/ #include #include #include +#include #include @@ -138,18 +141,24 @@ struct filed { short f_type; /* entry type, see below */ short f_file; /* file descriptor */ time_t f_time; /* time this was last written */ + char *f_host; /* host from which to record */ u_char f_pmask[LOG_NFACILITIES+1]; /* priority mask */ + char *f_program; /* program this applies to */ union { char f_uname[MAXUNAMES][UT_NAMESIZE+1]; struct { - char f_hname[MAXHOSTNAMELEN+1]; + char f_hname[MAXHOSTNAMELEN]; struct addrinfo *f_addr; } f_forw; /* forwarding address */ char f_fname[MAXPATHLEN]; + struct { + char f_pname[MAXPATHLEN]; + pid_t f_pid; + } f_pipe; } f_un; char f_prevline[MAXSVLINE]; /* last message logged */ char f_lasttime[16]; /* time of last occurrence */ - char f_prevhost[MAXHOSTNAMELEN+1]; /* host from which recd. */ + char f_prevhost[MAXHOSTNAMELEN]; /* host from which recd. */ int f_prevpri; /* pri of f_prevline */ int f_prevlen; /* length of f_prevline */ int f_prevcount; /* repetition cnt of prevline */ @@ -157,6 +166,24 @@ struct filed { }; /* + * 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 + +/* * 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. @@ -176,10 +203,11 @@ int repeatinterval[] = { 30, 120, 600 }; #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 */ -char *TypeNames[7] = { +char *TypeNames[8] = { "UNUSED", "FILE", "TTY", "CONSOLE", - "FORW", "USERS", "WALL" + "FORW", "USERS", "WALL", "PIPE" }; struct filed *Files; @@ -187,10 +215,11 @@ struct filed consfile; int Debug; /* debug flag */ int daemonized = 0; /* we are not daemonized yet */ -char LocalHostName[MAXHOSTNAMELEN+1]; /* our hostname */ +char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ char *LocalDomain; /* our local domain name */ int *finet = NULL; /* Internet datagram sockets */ -int Initialized = 0; /* set when we have initialized ourselves */ +int Initialized; /* 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 */ @@ -201,8 +230,10 @@ int NoRepeat = 0; /* disable "repeated" int SyncKernel = 0; /* write kernel messages synchronously */ volatile sig_atomic_t gothup = 0; /* got SIGHUP */ -void cfline(char *, struct filed *); +void cfline(char *, struct filed *, char *, char *); char *cvthname(struct sockaddr_storage *); +void deadq_enter(pid_t, const char *); +int deadq_remove(pid_t); int decode(const char *, CODE *); void die(int); void domark(int); @@ -212,8 +243,12 @@ int* socksetup(int); void init(void); void logerror(const char *, ...); void logmsg(int, char *, char *, int); +void log_deadchild(pid_t, int, const char *); +int matches_spec(const char *, const char *, + char *(*)(const char *, const char *)); void printline(char *, char *); void printsys(char *); +int p_open(char *, pid_t *); void sighup(int); void reapchild(int); void usage(void); @@ -230,6 +265,8 @@ main(int argc, char *argv[]) int funixsize = 0, funixmaxsize = 0; struct sockaddr_un sunx, fromunix; struct sockaddr_storage frominet; + struct sigaction sact; + sigset_t mask; char *p, *line, **pp; struct pollfd *readfds; uid_t uid = 0; @@ -375,8 +412,20 @@ getgroup: (void)signal(SIGTERM, die); (void)signal(SIGINT, Debug ? die : SIG_IGN); (void)signal(SIGQUIT, Debug ? die : SIG_IGN); - (void)signal(SIGCHLD, reapchild); + /* + * We don't want the SIGCHLD and SIGHUP handlers to interfere + * with each other; they are likely candidates for being called + * simultaneously (SIGHUP closes pipe descriptor, process dies, + * SIGCHLD happens). + */ + sigemptyset(&mask); + sigaddset(&mask, SIGHUP); + sact.sa_handler = reapchild; + sact.sa_mask = mask; + sact.sa_flags = SA_RESTART; + (void)sigaction(SIGCHLD, &sact, NULL); (void)signal(SIGALRM, domark); + (void)signal(SIGPIPE, SIG_IGN); /* We'll catch EPIPE instead. */ #ifndef SUN_LEN #define SUN_LEN(unp) (strlen((unp)->sun_path) + 2) @@ -415,7 +464,13 @@ getgroup: dprintf("Off & running....\n"); - (void)signal(SIGHUP, sighup); + /* prevent SIGHUP and SIGCHLD handlers from running together */ + sigemptyset(&mask); + sigaddset(&mask, SIGCHLD); + sact.sa_handler = sighup; + sact.sa_mask = mask; + sact.sa_flags = SA_RESTART; + (void)sigaction(SIGHUP, &sact, NULL); /* setup pollfd set. */ readfds = (struct pollfd *)malloc(sizeof(struct pollfd) * @@ -720,6 +775,28 @@ printsys(char *msg) time_t now; /* + * 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; + char prev, next; + + if ((s = (*check)(spec, name)) != NULL) { + prev = s == spec ? ',' : *(s - 1); + next = *(s + strlen(name)); + + if (prev == ',' && (next == '\0' || next == ',')) + return (1); + } + + return (0); +} + +/* * Log a message to the appropriate log files, users, etc. based on * the priority. */ @@ -729,6 +806,8 @@ logmsg(int pri, char *msg, char *from, i struct filed *f; int fac, msglen, omask, prilev; char *timestamp; + char prog[NAME_MAX + 1]; + int i; dprintf("logmsg: pri 0%o, flags 0x%x, from %s, msg %s\n", pri, flags, from, msg); @@ -752,6 +831,12 @@ logmsg(int pri, char *msg, char *from, i msglen -= 16; } + /* skip leading whitespace */ + while (isspace((unsigned char)*msg)) { + msg++; + msglen--; + } + /* extract facility and priority level */ if (flags & MARK) fac = LOG_NFACILITIES; @@ -759,6 +844,15 @@ logmsg(int pri, char *msg, char *from, i fac = LOG_FAC(pri); prilev = LOG_PRI(pri); + /* extract program name */ + for (i = 0; i < NAME_MAX; i++) { + if (!isprint((unsigned char)msg[i]) || + msg[i] == ':' || msg[i] == '[') + break; + prog[i] = msg[i]; + } + prog[i] = '\0'; + /* log the message to the particular outputs */ if (!Initialized) { f = &consfile; @@ -777,6 +871,43 @@ logmsg(int pri, char *msg, char *from, i f->f_pmask[fac] == INTERNAL_NOPRI) continue; + /* skip messages with the incorrect host name */ + if (f->f_host != NULL) { + switch (f->f_host[0]) { + case '+': + if (! matches_spec(from, f->f_host + 1, + strcasestr)) + continue; + break; + case '-': + if (matches_spec(from, f->f_host + 1, + strcasestr)) + continue; + break; + } + } + + /* skip messages with the incorrect program name */ + if (f->f_program != NULL) { + switch (f->f_program[0]) { + case '+': + if (! matches_spec(prog, f->f_program + 1, + strstr)) + continue; + break; + case '-': + if (matches_spec(prog, f->f_program + 1, + strstr)) + continue; + break; + default: + if (! matches_spec(prog, f->f_program, + strstr)) + continue; + break; + } + } + if (f->f_type == F_CONSOLE && (flags & IGN_CONS)) continue; @@ -790,7 +921,7 @@ logmsg(int pri, char *msg, char *from, i if ((flags & MARK) == 0 && msglen == f->f_prevlen && !NoRepeat && !strcmp(msg, f->f_prevline) && - !strcmp(from, f->f_prevhost)) { + !strcasecmp(from, f->f_prevhost)) { (void)strncpy(f->f_lasttime, timestamp, 15); f->f_prevcount++; dprintf("Msg repeated %d times, %ld sec of %d\n", @@ -891,7 +1022,7 @@ fprintlog(struct filed *f, int flags, ch * check for local vs remote messages * (from FreeBSD PR#bin/7055) */ - if (strcmp(f->f_prevhost, LocalHostName)) { + if (strcasecmp(f->f_prevhost, LocalHostName)) { l = snprintf(line, sizeof(line) - 1, "<%d>%.15s [%s]: %s", f->f_prevpri, (char *) iov[0].iov_base, @@ -927,6 +1058,30 @@ fprintlog(struct filed *f, int flags, ch } break; + case F_PIPE: + dprintf(" %s\n", f->f_un.f_pipe.f_pname); + v->iov_base = "\n"; + v->iov_len = 1; + if (f->f_un.f_pipe.f_pid == 0) { + 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(f->f_un.f_pipe.f_pname); + break; + } + } + if (writev(f->f_file, iov, 6) < 0) { + int e = errno; + (void) close(f->f_file); + if (f->f_un.f_pipe.f_pid > 0) + deadq_enter(f->f_un.f_pipe.f_pid, + f->f_un.f_pipe.f_pname); + f->f_un.f_pipe.f_pid = 0; + errno = e; + logerror(f->f_un.f_pipe.f_pname); + } + break; + case F_CONSOLE: if (flags & IGN_CONS) { dprintf(" (ignored)\n"); @@ -1039,10 +1194,36 @@ sighup(int signo) void reapchild(int signo) { - union wait status; + 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)) + goto oncemore; - while (wait3((int *)&status, WNOHANG, (struct rusage *)NULL) > 0) - ; + /* 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; + } + } + oncemore: + continue; + } } /* @@ -1075,7 +1256,7 @@ cvthname(struct sockaddr_storage *f) dprintf("Host name for your address (%s) unknown\n", ip); return (ip); } - if ((p = strchr(host, '.')) && strcmp(p + 1, LocalDomain) == 0) + if ((p = strchr(host, '.')) && strcasecmp(p + 1, LocalDomain) == 0) *p = '\0'; return (host); } @@ -1084,6 +1265,7 @@ void domark(int signo) { struct filed *f; + dq_t q, nextq; now = time((time_t *)NULL); MarkSeq += TIMERINTVL; @@ -1101,6 +1283,37 @@ domark(int signo) BACKOFF(f); } } + + /* 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--; + } + } + (void)alarm(TIMERINTVL); } @@ -1142,10 +1355,13 @@ die(int signo) struct filed *f; char **p; + ShuttingDown = 1; /* Don't log SIGCHLDs. */ for (f = Files; f != NULL; f = f->f_next) { /* flush any pending output */ if (f->f_prevcount) fprintlog(f, 0, (char *)NULL); + if (f->f_type == F_PIPE) + (void) close(f->f_file); } errno = 0; if (signo) @@ -1168,6 +1384,8 @@ init(void) struct filed *f, *next, **nextp; char *p; char cline[LINE_MAX]; + char prog[NAME_MAX + 1]; + char host[MAXHOSTNAMELEN]; dprintf("init\n"); @@ -1186,12 +1404,23 @@ init(void) case F_CONSOLE: (void)close(f->f_file); break; + case F_PIPE: + (void)close(f->f_file); + if (f->f_un.f_pipe.f_pid > 0) + 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; } next = f->f_next; + if (f->f_program != NULL) + free(f->f_program); + if (f->f_host != NULL) + free(f->f_host); free((char *)f); } Files = NULL; @@ -1220,9 +1449,9 @@ init(void) if ((cf = fopen(ConfFile, "r")) == NULL) { dprintf("Cannot open `%s'\n", ConfFile); *nextp = (struct filed *)calloc(1, sizeof(*f)); - cfline("*.ERR\t/dev/console", *nextp); + cfline("*.ERR\t/dev/console", *nextp, "*", "*"); (*nextp)->f_next = (struct filed *)calloc(1, sizeof(*f)); - cfline("*.PANIC\t*", (*nextp)->f_next); + cfline("*.PANIC\t*", (*nextp)->f_next, "*", "*"); Initialized = 1; return; } @@ -1231,22 +1460,65 @@ init(void) * Foreach line in the conf table, open that file. */ f = NULL; + strcpy(prog, "*"); + strcpy(host, "*"); while (fgets(cline, sizeof(cline), cf) != NULL) { /* * check for end-of-section, comments, strip off trailing - * spaces and newline character. + * 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' || *p == '#') + if (*p == '\0') + continue; + if (*p == '#') { + p++; + if (*p != '!' && *p != '+' && *p != '-') + continue; + } + if (*p == '+' || *p == '-') { + host[0] = *p++; + while (isspace((unsigned char)*p)) + p++; + if (*p == '\0' || *p == '*') { + strcpy(host, "*"); + continue; + } + if (*p == '@') + p = LocalHostName; + for (i = 1; i < MAXHOSTNAMELEN - 1; i++) { + 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 (p = strchr(cline, '\0'); isspace((unsigned char)*--p);) continue; *++p = '\0'; f = (struct filed *)calloc(1, sizeof(*f)); *nextp = f; nextp = &f->f_next; - cfline(cline, f); + cfline(cline, f, prog, host); } /* close the configuration file */ @@ -1273,12 +1545,18 @@ init(void) printf("%s", f->f_un.f_forw.f_hname); break; + 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"); } } @@ -1305,7 +1583,7 @@ init(void) * Crack a configuration file line */ void -cfline(char *line, struct filed *f) +cfline(char *line, struct filed *f, char *prog, char *host) { struct addrinfo hints, *res; int error, i, pri; @@ -1313,7 +1591,7 @@ cfline(char *line, struct filed *f) char buf[MAXLINE]; int sp_err; - dprintf("cfline(%s)\n", line); + dprintf("cfline(\"%s\", f, \"%s\", \"%s\")\n", line, prog, host); errno = 0; /* keep strerror() stuff out of logerror messages */ @@ -1374,6 +1652,18 @@ cfline(char *line, struct filed *f) *q='\t'; } + /* save host name, if any */ + if (*host == '*') + f->f_host = NULL; + else + f->f_host = strdup(host); + + /* 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 && *p != '\t';) { @@ -1467,6 +1757,12 @@ cfline(char *line, struct filed *f) f->f_type = F_CONSOLE; break; + case '|': + f->f_un.f_pipe.f_pid = 0; + (void) strcpy(f->f_un.f_pipe.f_pname, p + 1); + f->f_type = F_PIPE; + break; + case '*': f->f_type = F_WALL; break; @@ -1601,3 +1897,155 @@ socksetup(int af) return(socks); } + +/* + * Fairly similar to popen(3), but returns an open descriptor, as opposed + * to a FILE *. + */ +int +p_open(char *prog, pid_t *pid) +{ + int pfd[2], nulldesc, i; + sigset_t omask, mask; + char *argv[4]; /* sh -c cmd NULL */ + char errmsg[200]; + + if (pipe(pfd) == -1) + return (-1); + if ((nulldesc = open(_PATH_DEVNULL, O_RDWR)) == -1) { + /* We are royally screwed anyway. */ + return (-1); + } + + sigemptyset(&mask); + sigaddset(&mask, SIGALRM); + sigaddset(&mask, SIGHUP); + sigprocmask(SIG_BLOCK, &mask, &omask); + switch ((*pid = fork())) { + case -1: + sigprocmask(SIG_SETMASK, &omask, NULL); + (void) close(nulldesc); + return (-1); + + case 0: + argv[0] = "sh"; + argv[1] = "-c"; + argv[2] = prog; + argv[3] = NULL; + + alarm(0); + (void) setsid(); /* avoid catching SIGHUPs. */ + + /* + * Throw away pending signals, and reset signal + * behavior to standard values. + */ + signal(SIGALRM, SIG_IGN); + signal(SIGHUP, SIG_IGN); + sigprocmask(SIG_SETMASK, &omask, NULL); + signal(SIGPIPE, SIG_DFL); + signal(SIGQUIT, SIG_DFL); + signal(SIGALRM, SIG_DFL); + 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); + } + + sigprocmask(SIG_SETMASK, &omask, NULL); + (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. */ + (void) snprintf(errmsg, sizeof(errmsg), + "Warning: cannot change pipd to pid %d to " + "non-blocking.", (int) *pid); + logerror(errmsg); + } + 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) { + errno = 0; + 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(buf); +}