summaryrefslogtreecommitdiffstats
path: root/usr.sbin
diff options
context:
space:
mode:
authorjoerg <joerg@FreeBSD.org>1997-02-22 12:59:36 +0000
committerjoerg <joerg@FreeBSD.org>1997-02-22 12:59:36 +0000
commit3ff7a4f4f26e188127eac016ebebe2c854279ac6 (patch)
tree3427340bc2fa7ea0b5be0f87502c525151b6be1d /usr.sbin
parentcdca48ec7b9cb2ffc84e79e20555ef23cbeb7a36 (diff)
downloadFreeBSD-src-3ff7a4f4f26e188127eac016ebebe2c854279ac6.zip
FreeBSD-src-3ff7a4f4f26e188127eac016ebebe2c854279ac6.tar.gz
The "burn-in" phase has finished: this set of patches seems to run
stable now at a customer's site. Finally add the ability to syslogd to pipe particular messages through an arbitrary filtering command. Idea stolen from IRIX. This code is courtesy of the interface business GmbH, Dresden. Comment about whether to also merge this into 2.2 or not, please. Reviewed by: (long ago) peter
Diffstat (limited to 'usr.sbin')
-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