summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--usr.sbin/syslogd/syslog.conf.538
-rw-r--r--usr.sbin/syslogd/syslogd.c256
2 files changed, 287 insertions, 7 deletions
diff --git a/usr.sbin/syslogd/syslog.conf.5 b/usr.sbin/syslogd/syslog.conf.5
index 59112a3..b11aae4 100644
--- a/usr.sbin/syslogd/syslog.conf.5
+++ b/usr.sbin/syslogd/syslog.conf.5
@@ -182,7 +182,7 @@ The
field of each line specifies the action to be taken when the
.Em selector
field selects a message.
-There are four forms:
+There are five forms:
.Bl -bullet
.It
A pathname (beginning with a leading slash).
@@ -199,6 +199,39 @@ if they are logged in.
.It
An asterisk.
Selected messages are written to all logged-in users.
+.It
+A vertical bar (``|''), followed by a command to pipe the selected
+messages to. The command is passed to a
+.Pa /bin/sh
+for evaluation, so usual shell metacharacters or input/output
+redirection can occur. (Note however that redirecting
+.Xr stdio 3
+buffered output from the invoked command can cause additional delays,
+or even lost output data in case a logging subprocess exited with a
+signal.) The command itself runs with
+.Em stdout
+and
+.Em stderr
+redirected to
+.Pa /dev/null .
+Upon receipt of a
+.Dv SIGHUP ,
+.Nm syslogd
+will close the pipe to the process. If the process didn't exit
+voluntarily, it will be send a
+.Dv SIGTERM
+signal after a grace period of up to 40 seconds.
+.Pp
+The command will only be started once data arrive that should be piped
+to it. If it exited later, it will be restarted as necessary.
+.Pp
+Unless the command is a full pipeline, it's probably useful to
+start the command with
+.Em exec
+so that the invoking shell process does not wait for the command to
+cmoplete. Warning: the process is started under the UID invoking
+.Xr syslogd 8 ,
+normally superuser.
.El
.Pp
Blank lines and lines whose first non-blank character is a hash (``#'')
@@ -235,6 +268,9 @@ mail.* /var/log/maillog
# special file.
uucp,news.crit /var/log/spoolerr
+# Pipe all authentication messages to a filter.
+auth.* |exec /usr/local/sbin/authfilter
+
# Save ftpd transactions along with mail and news
!ftpd
*.* /var/log/spoolerr
diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c
index 4f5146d..4927792 100644
--- a/usr.sbin/syslogd/syslogd.c
+++ b/usr.sbin/syslogd/syslogd.c
@@ -79,6 +79,7 @@ static const char rcsid[] =
#include <sys/wait.h>
#include <sys/socket.h>
#include <sys/msgbuf.h>
+#include <sys/queue.h>
#include <sys/uio.h>
#include <sys/un.h>
#include <sys/time.h>
@@ -144,6 +145,10 @@ struct filed {
struct sockaddr_in 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 */
@@ -155,6 +160,30 @@ struct filed {
};
/*
+ * Queue of about-to-be dead processes we should watch out for.
+ */
+
+TAILQ_HEAD(stailhead, deadq_entry) deadq_head;
+struct stailhead *deadq_headp;
+
+struct deadq_entry {
+ pid_t dq_pid;
+ int dq_timeout;
+ TAILQ_ENTRY(deadq_entry) dq_entries;
+};
+
+/*
+ * 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
+
+typedef struct deadq_entry *dq_t;
+
+
+/*
* 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.
@@ -174,10 +203,11 @@ int repeatinterval[] = { 30, 120, 600 }; /* # of secs before flush */
#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;
@@ -199,6 +229,7 @@ char bootfile[MAXLINE+1]; /* booted kernel file */
void cfline __P((char *, struct filed *, char *));
char *cvthname __P((struct sockaddr_in *));
+void deadq_enter __P((pid_t));
int decode __P((const char *, CODE *));
void die __P((int));
void domark __P((int));
@@ -208,6 +239,7 @@ void logerror __P((const char *));
void logmsg __P((int, char *, char *, int));
void printline __P((char *, char *));
void printsys __P((char *));
+int p_open __P((char *, pid_t *));
void reapchild __P((int));
char *ttymsg __P((struct iovec *, int, char *, int));
void usage __P((void));
@@ -274,6 +306,7 @@ main(argc, argv)
(void)signal(SIGQUIT, Debug ? die : SIG_IGN);
(void)signal(SIGCHLD, reapchild);
(void)signal(SIGALRM, domark);
+ (void)signal(SIGPIPE, SIG_IGN); /* We'll catch EPIPE instead. */
(void)alarm(TIMERINTVL);
#ifndef SUN_LEN
@@ -299,6 +332,8 @@ main(argc, argv)
else
finet = -1;
+ TAILQ_INIT(&deadq_head);
+
inetm = 0;
if (finet >= 0) {
struct servent *sp;
@@ -638,6 +673,7 @@ fprintlog(f, flags, msg)
int l;
char line[MAXLINE + 1], repbuf[80], greetings[200];
char *msgret;
+ dq_t q;
v = iov;
if (f->f_type == F_WALL) {
@@ -717,6 +753,29 @@ fprintlog(f, flags, msg)
(void)fsync(f->f_file);
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_pid = 0;
+ errno = e;
+ logerror(f->f_un.f_pipe.f_pname);
+ }
+ break;
+
case F_CONSOLE:
if (flags & IGN_CONS) {
dprintf(" (ignored)\n");
@@ -808,10 +867,52 @@ void
reapchild(signo)
int signo;
{
- int status;
+ int status, code;
+ pid_t pid;
+ struct filed *f;
+ char buf[256];
+ const char *reason;
+ dq_t q;
+
+ while ((pid = wait3(&status, WNOHANG, (struct rusage *)NULL)) > 0) {
+ if (!Initialized)
+ /* Don't tell while we are initting. */
+ continue;
- while (wait3(&status, WNOHANG, (struct rusage *)NULL) > 0)
- ;
+ /* First, look if it's a process from the dead queue. */
+ 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);
+ goto oncemore;
+ }
+
+ /* Now, look in list of active processes. */
+ for (f = Files; f; f = f->f_next)
+ if (f->f_type == F_PIPE &&
+ f->f_un.f_pipe.f_pid == pid) {
+ (void)close(f->f_file);
+
+ errno = 0; /* Keep strerror() stuff out of logerror messages. */
+ f->f_un.f_pipe.f_pid = 0;
+ if (WIFSIGNALED(status)) {
+ reason = "due to signal";
+ code = WTERMSIG(status);
+ } else {
+ reason = "with status";
+ code = WEXITSTATUS(status);
+ if (code == 0)
+ goto oncemore; /* Exited OK. */
+ }
+ (void)snprintf(buf, sizeof buf,
+ "Logging subprocess %d (%s) exited %s %d.",
+ pid, f->f_un.f_pipe.f_pname,
+ reason, code);
+ logerror(buf);
+ break;
+ }
+ oncemore:
+ }
}
/*
@@ -847,6 +948,7 @@ domark(signo)
int signo;
{
struct filed *f;
+ dq_t q;
now = time((time_t *)NULL);
MarkSeq += TIMERINTVL;
@@ -864,6 +966,29 @@ domark(signo)
BACKOFF(f);
}
}
+
+ /* Walk the dead queue, and see if we should signal somebody. */
+ for (q = TAILQ_FIRST(&deadq_head); q != NULL; q = TAILQ_NEXT(q, dq_entries))
+ switch (q->dq_timeout) {
+ case 0:
+ /* Already signalled once, try harder now. */
+ kill(q->dq_pid, SIGKILL);
+ break;
+
+ case 1:
+ /*
+ * Timed out on dead queue, send terminate
+ * signal. Note that we leave the removal
+ * from the dead queue to reapchild(), which
+ * will also log the event.
+ */
+ kill(q->dq_pid, SIGTERM);
+ /* FALLTROUGH */
+
+ default:
+ q->dq_timeout--;
+ }
+
(void)alarm(TIMERINTVL);
}
@@ -874,7 +999,7 @@ void
logerror(type)
const char *type;
{
- char buf[100];
+ char buf[512];
if (errno)
(void)snprintf(buf,
@@ -893,10 +1018,13 @@ die(signo)
struct filed *f;
char buf[100];
+ Initialized = 0; /* 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);
}
if (signo) {
dprintf("syslogd: exiting on signal %d\n", signo);
@@ -941,6 +1069,12 @@ init(signo)
case F_TTY:
(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_pid = 0;
+ break;
}
next = f->f_next;
if(f->f_program) free(f->f_program);
@@ -1031,6 +1165,10 @@ init(signo)
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]);
@@ -1177,6 +1315,12 @@ cfline(line, f, prog)
}
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;
@@ -1299,3 +1443,103 @@ timedout(sig)
else
exit(0);
}
+
+/*
+ * Fairly similar to popen(3), but returns an open descriptor, as
+ * opposed to a FILE *.
+ */
+int
+p_open(prog, pid)
+ 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;
+
+ mask = sigmask(SIGALRM) | sigmask(SIGHUP);
+ sigprocmask(SIG_BLOCK, &omask, &mask);
+ switch ((*pid = fork())) {
+ case -1:
+ sigprocmask(SIG_SETMASK, 0, &omask);
+ 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
+ * behaviour to standard values.
+ */
+ signal(SIGALRM, SIG_IGN);
+ signal(SIGHUP, SIG_IGN);
+ sigprocmask(SIG_SETMASK, 0, &omask);
+ 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, 0, &omask);
+ close(nulldesc);
+ 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 (so we
+ * get rid of really dead puppies).
+ */
+ if (fcntl(pfd[1], F_SETFL, O_NONBLOCK) == -1) {
+ /* This is bad. */
+ (void)snprintf(errmsg, sizeof errmsg,
+ "Warning: cannot change pipe to PID %d to "
+ "non-blocking behaviour.",
+ (int)*pid);
+ logerror(errmsg);
+ }
+ return pfd[1];
+}
+
+void
+deadq_enter(pid)
+ pid_t pid;
+{
+ dq_t p;
+
+ p = malloc(sizeof(struct deadq_entry));
+ if (p == 0) {
+ errno = 0;
+ logerror("panic: out of virtual memory!");
+ exit(1);
+ }
+
+ p->dq_pid = pid;
+ p->dq_timeout = DQ_TIMO_INIT;
+ TAILQ_INSERT_TAIL(&deadq_head, p, dq_entries);
+}
OpenPOWER on IntegriCloud