From: David van Moolenbroek Date: Fri, 24 Jul 2015 22:09:13 +0000 (+0000) Subject: Import NetBSD syslogd(8) X-Git-Url: http://zhaoyanbai.com/repos/%22http:/www.isc.org/icons/zpipe.c?a=commitdiff_plain;h=refs%2Fchanges%2F37%2F3037%2F2;p=minix.git Import NetBSD syslogd(8) The primary reason for the import is a likely GPL taint of the original MINIX3 syslogd. As a result, this import may still have some rough edges. Change-Id: I5c8d26eca10fc2dd50ecc9eab44a1d483cf068a9 --- diff --git a/distrib/sets/lists/minix/mi b/distrib/sets/lists/minix/mi index 01fa3d222..46a12c5ff 100644 --- a/distrib/sets/lists/minix/mi +++ b/distrib/sets/lists/minix/mi @@ -499,7 +499,7 @@ ./usr/bin/svrctl minix-sys ./usr/bin/swifi minix-sys ./usr/bin/synctree minix-sys -./usr/bin/syslogd minix-sys +./usr/bin/syslogd minix-sys obsolete ./usr/bin/sz minix-sys ./usr/bin/tail minix-sys ./usr/bin/tar minix-sys @@ -5310,6 +5310,7 @@ ./usr/sbin/postinstall minix-sys ./usr/sbin/pwd_mkdb minix-sys ./usr/sbin/rdate minix-sys +./usr/sbin/syslogd minix-sys ./usr/sbin/traceroute minix-sys ./usr/sbin/unlink minix-sys ./usr/sbin/user minix-sys diff --git a/etc/syslog.conf b/etc/syslog.conf index a2773a9d1..e2cf2d5bb 100644 --- a/etc/syslog.conf +++ b/etc/syslog.conf @@ -9,10 +9,10 @@ *.alert /dev/log ## High severity errors -*.alert;*.crit /var/log/syslog +*.alert /var/log/syslog ## Every other message (errors/warning and informational) -*.info;*.notice;*.warning;*.err /var/log/messages +*.info;mark.info /var/log/messages ## Debug informations (tracing programs) #*.debug /var/log/debug diff --git a/minix/commands/Makefile b/minix/commands/Makefile index ab521ff86..061230c7b 100644 --- a/minix/commands/Makefile +++ b/minix/commands/Makefile @@ -24,7 +24,7 @@ SUBDIR= add_route arp at backup btrace \ rotate rsh rshd service setup \ slip spell sprofalyze sprofdiff srccrc \ svclog svrctl swifi synctree sysenv \ - syslogd tcpd tcpdp tcpstat telnet \ + tcpd tcpdp tcpstat telnet \ telnetd term termcap tget time \ truncate udpstat umount \ update version vol \ diff --git a/minix/commands/syslogd/CHANGELOG b/minix/commands/syslogd/CHANGELOG deleted file mode 100644 index 9155a22d1..000000000 --- a/minix/commands/syslogd/CHANGELOG +++ /dev/null @@ -1,16 +0,0 @@ -Version 1.1 Oct. 28, 2000 - - first release for testing. - -Version 1.2 Jan. 23, 2001 - - Changed pidfile directory to '/usr/spool/locks' - so at boot old file is deleted. - - Changed the 'debug' variable to 'DbgOpt' so 'debug' - can be a preprocessor define from Makefile. - -Version 1.3 - - Changes for Minix 3.0 - - Changed pidfile to /var/run/syslogd.pid and added code - for setting pathname from Makefile - - Merged code from usyslogd.c to handle kernel messages. - - Reworked Makefile to make a correct installation - diff --git a/minix/commands/syslogd/Makefile b/minix/commands/syslogd/Makefile deleted file mode 100644 index 09e1ab69b..000000000 --- a/minix/commands/syslogd/Makefile +++ /dev/null @@ -1,12 +0,0 @@ -## -## @(#)Makefile 1.00 Jan. 11, 2000 -## -## Makefile for syslogd/klogd - -PROG= syslogd -PIDFILE= -DPIDFILE=\"/var/run/syslogd.pid\" -CPPFLAGS+= -Ddebug=0 ${PIDFILE} -BINMODE= 700 -MAN= - -.include diff --git a/minix/commands/syslogd/README b/minix/commands/syslogd/README deleted file mode 100644 index 8c34903ce..000000000 --- a/minix/commands/syslogd/README +++ /dev/null @@ -1,69 +0,0 @@ -This is just syslogd and the test programs now. syslog() is in libc 4.2 -syslogd has been changed to use /proc/kmsg for kernel messages. It also -avoids making any terminal it opens its controlling terminal. Otherwise -we have a dodgy race condition between processes connecting to terminals -which can result in the terminal having the wrong group at the wrong time. -The syslog() in libc 4.2 needs changing to use O_NOCTTY on its opens as -well. - -Mike Jagdis Internet: jaggy@purplet.demon.co.uk - FidoNet: Mike Jagdis, 2:252/305 - ---------------------------------------------------------------------------- - - Syslogd and Syslog.o - -These are the syslogd and syslog ported from 4.3BSD (that's the new one with -the *very* flexible config file). - -Syslogd is essentially unchanged from the 4.3BSD, with the exception that -*nothing* is ever logged to the console (BAD thing to do on a UNIXpc). You -can configure it (via /etc/syslog.conf) to log messages in different -logfiles (depending upon the sender's facility code and the priority), log -to users' terminals (same dependancies), and if things get real bad, it can -do a wall (write-all; same dependancies). - -Syslog is really only modified in that it uses UDP datagrams because I had -no luck at all using UNIX domain sockets on the 3B1. See syslog.h for -facility codes and priorities that can be used. - - -BUGS: -Messages from facilities LOG_KERN, LOG_USER, and LOG_PRT never can be -wall-ed, no matter how high the priority. I'm still trying to decide if -this is a bug or a feature. :-) - - -ALSO INCLUDED: - -Syslog_test, sendlog (to use from shell scripts) and logger (for use in -shell script also). - -NEEDED: - -The resolver routines in libresolv.a are not needed, but allow you to log to -hosts not present in /etc/hosts and to accept logging from same. - ------------------------------------------------------------------------ - -Fixed up UNIX domain socket code, added Linux specific code to read messages -from the kernel. - -The #ifdefs are now :- - -SYSLOG_INET listen on a UDP socket (syslogd) - log via UDP (syslog library call) - -SYSLOG_UNIXAF listen on a UNIX domain socker (syslogd) - log via UNIX domain (syslogd library) - -SYSLOG_KERNEL fork a second copy to read kernel messages using - syslog system call. - -syslogd should be built with one or more of these flags, libsyslog.a should -be built with SYSLOG_INET or SYSLOG_UNIXAF, SYSLOG_INET is used in preference -to SYSLOG_UNIXAF. - -readlog is a program which reads from the kernel and records the messages -it finds in syslogd via the normal library call interface, it can be run -instead of building syslogd with SYSLOG_KERNEL. diff --git a/minix/commands/syslogd/README.minix b/minix/commands/syslogd/README.minix deleted file mode 100644 index f7f8027d8..000000000 --- a/minix/commands/syslogd/README.minix +++ /dev/null @@ -1,26 +0,0 @@ - - - This is the porting to Minix of the "syslogd" facility - available on many other *nix systems. - - Since I' m still using an old 286 machine for my Minix - environment, I choosed to start from an old implementation - which has only the basic features. The result is a smaller - program, more suitable for 16 bits machines. - - The file syslog.c should be included in C compiler library - (libc.a) or directly linked with prorams requiring syslog. - - If you choose the former solution, you must recreate the - library. After having copied the file syslog.c to the - directory '/usr/src/libs/other' you have to modify the - Makefile in this directory adding syslog.c. - - Then issue a 'make' command in the '/usr/src/libs' and wait - a while. Then issue 'make install' to install the new object. - - NOTE: The network must be configured, up and running for - the package to work - - Giovanni Falzoni - diff --git a/minix/commands/syslogd/syslogd.c b/minix/commands/syslogd/syslogd.c deleted file mode 100644 index e16ede9cf..000000000 --- a/minix/commands/syslogd/syslogd.c +++ /dev/null @@ -1,971 +0,0 @@ -/* -** Copyright (c) 1983, 1988 -** The Regents of the University of California. All rights reserved. -** -** Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions -** are met: -** 1. Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** 2. Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in the -** documentation and/or other materials provided with the distribution. -** 3. All advertising materials mentioning features or use of this software -** must display the following acknowledgement: -** This product includes software developed by the University of -** California, Berkeley and its contributors. -** 4. Neither the name of the University nor the names of its contributors -** may be used to endorse or promote products derived from this software -** without specific prior written permission. -** -** THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND -** ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -** IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -** ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE -** FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -** DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS -** OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -** HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT -** LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY -** OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF -** SUCH DAMAGE. -** -** #ifndef lint -** char copyright2[] = -** "@(#) Copyright (c) 1983, 1988 Regents of the University of California.\n\ -** All rights reserved.\n"; -** #endif -** -** #ifndef lint -** static char sccsid[] = "@(#)syslogd.c 5.27 (Berkeley) 10/10/88"; -** #endif -** -** ----------------------------------------------------------------------- -** -** SYSLOGD -- log system messages -** This program implements a system log. -** It takes a series of lines and outputs them according to the setup -** defined in the configuration file. -** Each line may have a priority, signified as "" as -** the first characters of the line. If this is -** not present, a default priority is used. -** -** To kill syslogd, send a signal 15 (terminate). -** A signal 1 (hup) will cause it to reread its configuration file. -** -** Defined Constants: -** MAXLINE -- the maximimum line length that can be handled. -** MAXSVLINE -- the length of saved messages (for filtering) -** DEFUPRI -- the default priority for user messages -** DEFSPRI -- the default priority for kernel messages -** -** Author: Eric Allman -** extensive changes by Ralph Campbell -** more extensive changes by Eric Allman (again) -** changes by Steve Lord -** -** Extensive rewriting by G. Falzoni for porting to Minix -** -** $Log$ -** Revision 1.3 2006/04/04 14:22:40 beng -** Fix -** -** Revision 1.2 2006/04/04 14:18:16 beng -** Make syslogd work, even if it can only open klog and not udp or vice versa -** (but not neither) -** -** Revision 1.1 2006/04/03 13:07:42 beng -** Kick out usyslogd in favour of syslogd Giovanni's syslogd port -** -** Revision 1.3 2005/09/16 10:10:12 lsodgf0 -** Rework for Minix 3. Adds kernel logs from /dev/klogd -*/ - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#define SYSLOG_NAMES -#include -#define KLOGD 1 -/** Define following values to your requirements **/ -#define MAXLINE 512 /* maximum line length */ -#define MAXSVLINE 256 /* maximum saved line length */ - -#define DEFUPRI (LOG_USER|LOG_NOTICE) -#define DEFSPRI (LOG_KERN|LOG_CRIT) - -/* Flags to logmsg() */ -#define IGN_CONS 0x001 /* don't print on console */ -#define SYNC_FILE 0x002 /* do fsync on file after printing */ -#define ADDDATE 0x004 /* add a date to the message */ -#define MARK 0x008 /* this message is a mark */ - -#define CTTY "/dev/log" /* Minix log device (console) */ - -#define dprintf if(DbgOpt!=0)printf -#if debug == 0 -#define DEBUG(statement) -#else -#define DEBUG(statement) statement -#endif -#if !defined PIDFILE -#define PIDFILE "/var/run/syslogd.pid" -#endif - -#define UNAMESZ 8 /* length of a login name */ -#define MAXUNAMES 20 /* maximum number of user names */ -#define MAXFNAME 200 /* max file pathname length */ -#define MAXHOSTNAMELEN 64 /* max length of FQDN host name */ - -/* 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. */ -#define TIMERINTVL 30 /* interval for checking flush, mark */ -#define INTERVAL1 30 -#define INTERVAL2 60 -#define MAXREPEAT ((sizeof(repeatinterval)/sizeof(repeatinterval[0]))-1) -#define REPEATTIME(f) ((f)->f_time+repeatinterval[(f)->f_repeatcount]) -#define BACKOFF(f) {if(++(f)->f_repeatcount>MAXREPEAT)(f)->f_repeatcount=MAXREPEAT;} - -/* Values for f_type */ -#define F_UNUSED 0 /* unused entry */ -#define F_FILE 1 /* regular file */ -#define F_TTY 2 /* terminal */ -#define F_CONSOLE 3 /* console terminal */ -#define F_FORW 4 /* remote machine */ -#define F_USERS 5 /* list of users */ -#define F_WALL 6 /* everyone logged on */ - -#define max(a,b) ((a)>=(b)?(a):(b)) - -/* This structure represents the files that will have log copies printed */ -struct filed { - struct filed *f_next; /* next in linked list */ - short f_type; /* entry type, see below */ - short f_file; /* file descriptor */ - time_t f_time; /* time this was last written */ - char f_pmask[LOG_NFACILITIES + 1]; /* priority mask */ - union { - char f_uname[MAXUNAMES][UNAMESZ + 1]; - char f_fname[MAXFNAME]; - } 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. */ - int f_prevpri; /* pri of f_prevline */ - int f_prevlen; /* length of f_prevline */ - int f_prevcount; /* repetition cnt of prevline */ - int f_repeatcount; /* number of "repeated" msgs */ - int f_flags; /* store some additional flags */ -}; - -static const char *const TypeNames[] = -{ - "UNUSED", "FILE", "TTY", "CONSOLE", "FORW", "USERS", "WALL", NULL, -}; - -static struct filed *Files = NULL; -static struct filed consfile; -static int DbgOpt = 0; /* debug flag */ -static char LocalHostName[MAXHOSTNAMELEN + 1]; /* our hostname */ -static int Initialized = 0; /* set when we have initialized ourselves */ -static int MarkInterval = 20 * 60; /* interval between marks in seconds */ -static int MarkSeq = 0; /* mark sequence number */ -static time_t now; - -static const char *ConfFile = "/etc/syslog.conf"; -static const char *PidFile = PIDFILE; /* "/var/run/syslogd.pid" */ -static const char ctty[] = CTTY; - -static const char ProgName[] = "syslogd:"; -static const char version[] = "1.3 (Minix)"; -static const char usage[] = - /* */ "usage:\tsyslogd [-d] [-m markinterval] [-f conf-file]\n" - "\t\t[-p listeningport] [-v] [-?]\n" ; -static const int repeatinterval[] = - /* */ {INTERVAL1, INTERVAL2,}; /* # of secs before flush */ - -/* -** Name: void wallmsg(struct filed *fLog, char *message); -** Function: Write the specified message to either the entire -** world, or a list of approved users. -*/ -void wallmsg(struct filed * fLog, char *message) -{ - - return; -} - -/* -** Name: void fprintlog(struct filed *fLog, int flags, char *message); -** Function: -*/ -void fprintlog(struct filed * fLog, int flags, char *message) -{ - int len; - char line[MAXLINE + 1]; - char repbuf[80]; - - if (message == NULL) { - if (fLog->f_prevcount > 1) { - sprintf(repbuf, "last message repeated %d times", fLog->f_prevcount); - message = repbuf; - } else - message = fLog->f_prevline; - } - snprintf(line, sizeof(line), "%s %s %s", - fLog->f_lasttime, fLog->f_prevhost, message); - DEBUG(dprintf("Logging to %s", TypeNames[fLog->f_type]);) - fLog->f_time = now; - switch (fLog->f_type) { - case F_UNUSED: /* */ - DEBUG(dprintf("\n");) - break; - case F_CONSOLE: - if (flags & IGN_CONS) { - case F_FORW: /* */ - DEBUG(dprintf(" (ignored)\n");) - break; - } /* else Fall Through */ - case F_TTY: - case F_FILE: - DEBUG(dprintf(" %s\n", fLog->f_un.f_fname);) - strcat(line, fLog->f_type != F_FILE ? "\r\n" : "\n"); - len = strlen(line); - if (write(fLog->f_file, line, len) != len) { - /* Handle errors */ ; - } else if (flags & SYNC_FILE) - sync(); - break; - case F_USERS: - case F_WALL: - DEBUG(dprintf("\n");) - strcat(line, "\r\n"); - wallmsg(fLog, line); - break; - } - fLog->f_prevcount = 0; - return; -} - -/* -** Name: void logmsg(int pri, char *msg, char *from, int flags); -** Function: Log a message to the appropriate log files, users, etc. -** based on the priority. -*/ -void logmsg(int pri, char *msg, char *from, int flags) -{ - struct filed *f; - int fac, prilev; - int /*omask,*/ msglen; - char *timestamp; - - DEBUG(dprintf("logmsg: pri %o, flags %x, from %s, msg %s\n", pri, flags, from, msg);) -/* - omask = sigblock(__sigmask(SIGHUP) | __sigmask(SIGALRM)); -*/ - /* Check to see if msg looks non-standard. */ - msglen = strlen(msg); - if (msglen < 16 || msg[3] != ' ' || msg[6] != ' ' || - msg[9] != ':' || msg[12] != ':' || msg[15] != ' ') - flags |= ADDDATE; - - time(&now); - if (flags & ADDDATE) - timestamp = ctime(&now) + 4; - else { - timestamp = msg; - msg += 16; - msglen -= 16; - } - - /* Extract facility and priority level */ - fac = (flags & MARK) ? LOG_NFACILITIES : LOG_FAC(pri); - prilev = LOG_PRI(pri); - - /* Log the message to the particular outputs */ - if (!Initialized) { - /* Not yet initialized. Every message goes to console */ - f = &consfile; - f->f_file = open(ctty, O_WRONLY | O_NOCTTY); - if (f->f_file >= 0) { - if (!DbgOpt) setsid(); - fprintlog(f, flags, msg); - close(f->f_file); - } - } else { - for (f = Files; f; f = f->f_next) { - - /* Skip messages that are incorrect priority */ - if (f->f_pmask[fac] < prilev || f->f_pmask[fac] == INTERNAL_NOPRI) - continue; - - if (f->f_type == F_CONSOLE && (flags & IGN_CONS)) continue; - - /* Don't output marks to recently written files */ - if ((flags & MARK) && (now - f->f_time) < MarkInterval / 2) - continue; - - /* Suppress duplicate lines to this file */ - if ((flags & MARK) == 0 && msglen == f->f_prevlen && - !strcmp(msg, f->f_prevline) && - !strcmp(from, f->f_prevhost)) { - strncpy(f->f_lasttime, timestamp, 15); - f->f_prevcount += 1; - DEBUG(dprintf("msg repeated %d times, %d sec of %d\n", - f->f_prevcount, now - f->f_time, - repeatinterval[f->f_repeatcount]);) - /* If domark would have logged this by now, - * flush it now (so we don't hold isolated - * messages), but back off so we'll flush - * less often in the future. */ - if (now > REPEATTIME(f)) { - fprintlog(f, flags, (char *) NULL); - BACKOFF(f); - } - } else { - /* New line, save it */ - if (f->f_prevcount) fprintlog(f, 0, (char *) NULL); - f->f_repeatcount = 0; - strncpy(f->f_lasttime, timestamp, 15); - strncpy(f->f_prevhost, from, sizeof(f->f_prevhost)); - if (msglen < MAXSVLINE) { - f->f_prevlen = msglen; - f->f_prevpri = pri; - strcpy(f->f_prevline, msg); - fprintlog(f, flags, (char *) NULL); - } else { - f->f_prevline[0] = 0; - f->f_prevlen = 0; - fprintlog(f, flags, msg); - } - } - } - } - -/* - sigsetmask(omask); -*/ - return; -} - -/* -** Name: void logerror(char *type); -** Function: Prints syslogd errors in some place. -*/ -void logerror(char *type) -{ - char buf[100]; - - if (errno == 0) sprintf(buf, "%s %s", ProgName, type); - - sprintf(buf, "%s %s - %s", ProgName, type, strerror(errno)); - - errno = 0; - dprintf("%s\n", buf); - logmsg(LOG_SYSLOG | LOG_ERR, buf, LocalHostName, ADDDATE); - return; -} - -/* -** Name: void die(int sig); -** Function: Signal handler for kill signals. -*/ -void die(int sig) -{ - struct filed *f; - char buf[100]; - - for (f = Files; f != NULL; f = f->f_next) { - /* Flush any pending output */ - if (f->f_prevcount) fprintlog(f, 0, NULL); - } - if (sig >= 0) { - DEBUG(dprintf("%s exiting on signal %d\n", ProgName, sig);) - sprintf(buf, "exiting on signal %d", sig); - errno = 0; - logerror(buf); - } - unlink(PidFile); - exit(sig == (-1) ? EXIT_FAILURE : EXIT_SUCCESS); -} - -/* -** Name: void domark(int sig); -** Function: Signal handler for alarm. -** Used for messages filtering and mark facility. -*/ -void domark(int sig) -{ - struct filed *f; - - now = time(NULL); - MarkSeq += TIMERINTVL; - if (MarkSeq >= MarkInterval) { - logmsg(LOG_INFO, "-- MARK --", LocalHostName, ADDDATE | MARK); - MarkSeq = 0; - } - for (f = Files; f; f = f->f_next) { - if (f->f_prevcount && now >= REPEATTIME(f)) { - DEBUG(dprintf("flush %s: repeated %d times, %d sec.\n", - TypeNames[f->f_type], f->f_prevcount, - repeatinterval[f->f_repeatcount]);) - fprintlog(f, 0, NULL); - BACKOFF(f); - } - } - signal(SIGALRM, domark); - alarm(TIMERINTVL); - return; -} - -/* -** Name: int decode(char *name, struct _code *codetab); -** Function: Decode a symbolic name to a numeric value -*/ -int decode(char *name, const struct _code *codetab) -{ - const struct _code *c; - char *p; - char buf[40]; - - DEBUG(dprintf("symbolic name: %s", name);) - if (isdigit(*name)) return (atoi(name)); - - strcpy(buf, name); - for (p = buf; *p; p += 1) { - if (isupper(*p)) *p = tolower(*p); - } - for (c = codetab; c->c_name; c += 1) { - if (!strcmp(buf, c->c_name)) { - DEBUG(dprintf(" ==> %d\n", c->c_val);) - return (c->c_val); - } - } - return (-1); -} - -/* -** Name: void cfline(char *line, struct filed *f); -** Function: Parse a configuration file line -*/ -void cfline(char *line, struct filed * fLog) -{ - char *p, *q, *bp; - int ix, pri; - char buf[MAXLINE]; - char xbuf[200]; - - DEBUG(dprintf("cfline(%s)\n", line);) - - /* Keep sys_errlist stuff out of logerror messages */ - errno = 0; - - /* Clear out file entry */ - memset(fLog, 0, sizeof(*fLog)); - for (ix = 0; ix <= LOG_NFACILITIES; ix += 1) /* */ - fLog->f_pmask[ix] = INTERNAL_NOPRI; - - /* Scan through the list of selectors */ - for (p = line; *p && *p != '\t';) { - - /* Find the end of this facility name list */ - for (q = p; *q && *q != '\t' && *q++ != '.';) continue; - - /* Collect priority name */ - for (bp = buf; *q && !strchr("\t,;", *q);) *bp++ = *q++; - *bp = '\0'; - - /* Skip cruft */ - while (strchr(", ;", *q)) q++; - - /* Decode priority name */ - pri = decode(buf, prioritynames); - if (pri < 0) { - sprintf(xbuf, "unknown priority name \"%s\"", buf); - logerror(xbuf); - return; - } - - /* Scan facilities */ - while (*p && !strchr("\t.;", *p)) { - for (bp = buf; *p && !strchr("\t,;.", *p);) *bp++ = *p++; - *bp = '\0'; - if (*buf == '*') { - for (ix = 0; ix <= LOG_NFACILITIES; ix += 1) - if ((fLog->f_pmask[ix] < pri) || - (fLog->f_pmask[ix] == INTERNAL_NOPRI)) { - fLog->f_pmask[ix] = pri; - } - } else { - ix = decode(buf, facilitynames); - if (ix < 0) { - sprintf(xbuf, "unknown facility name \"%s\"", buf); - logerror(xbuf); - return; - } - if ((fLog->f_pmask[ix >> 3] < pri) || - (fLog->f_pmask[ix >> 3] == INTERNAL_NOPRI)) { - fLog->f_pmask[ix >> 3] = pri; - } - } - while (*p == ',' || *p == ' ') p++; - } - p = q; - } - - /* Skip to action part */ - while (*p == '\t' || *p == ' ') p++; - - DEBUG(dprintf("leading char in action: %c\n", *p);) - switch (*p) { - case '@': /* Logging to a remote host */ - break; /* NOT IMPLEMENTED */ - - case '/': /* Logging to a local file/device */ - strcpy(fLog->f_un.f_fname, p); - DEBUG(dprintf("filename: %s\n", p); /* ASP */) - if ((fLog->f_file = open(p, O_WRONLY | O_APPEND | O_CREAT | O_NOCTTY, 0644)) < 0) { - fLog->f_file = F_UNUSED; - sprintf(xbuf, "unknown file/device (%s)", p); - logerror(xbuf); - break; - } - if (isatty(fLog->f_file)) { - if (!DbgOpt) setsid(); - fLog->f_type = F_TTY; - } else - fLog->f_type = F_FILE; - if (strcmp(p, ctty) == 0) fLog->f_type = F_CONSOLE; - break; - - case '*': /* Logging to all users */ - DEBUG(dprintf("write-all\n");) - fLog->f_type = F_WALL; - break; - - default: /* Logging to selected users */ - DEBUG(dprintf("users: %s\n", p); /* ASP */) - for (ix = 0; ix < MAXUNAMES && *p; ix += 1) { - for (q = p; *q && *q != ',';) q += 1; - strncpy(fLog->f_un.f_uname[ix], p, UNAMESZ); - if ((q - p) > UNAMESZ) - fLog->f_un.f_uname[ix][UNAMESZ] = '\0'; - else - fLog->f_un.f_uname[ix][q - p] = '\0'; - while (*q == ',' || *q == ' ') q++; - p = q; - } - fLog->f_type = F_USERS; - break; - } -} - -/* -** Name: void printline(char *hname, char *msg); -** Function: Takes a raw input line, decodes the message and -** prints the message on the appropriate log files. -*/ -void printline(char *hname, char *msg) -{ - char line[MAXLINE + 1]; - char *p = msg, *q = line; - int ch, pri = DEFUPRI; - - /* Test for special codes */ - if (*p == '<') { - pri = 0; - while (isdigit(*++p)) { - if ((*p - '0') < 8) { - /* Only 3 bits allocated for pri -- ASP */ - pri = 10 * pri + (*p - '0'); - } else - pri = 10 * pri + 7; - } - if (*p == '>') ++p; - } - if (pri & ~(LOG_FACMASK | LOG_PRIMASK)) pri = DEFUPRI; - - /* Does not allow users to log kernel messages */ - if (LOG_FAC(pri) == LOG_KERN) pri = LOG_MAKEPRI(LOG_USER, LOG_PRI(pri)); - - /* Copies message to local buffer, translating control characters */ - while ((ch = *p++ & 0177) != '\0' && q < &line[sizeof(line) - 1]) { - if (ch == '\n') /* Removes newlines */ - *q++ = ' '; - else if (iscntrl(ch)) { /* Translates control characters */ - *q++ = '^'; - *q++ = ch ^ 0100; - } else - *q++ = ch; - } - *q = '\0'; - - logmsg(pri, line, hname, 0); - return; -} - -/* -** Name: void printkline(char *hname, char *msg); -** Function: Takes a raw input line from kernel and -** prints the message on the appropriate log files. -*/ -void printkline(char *hname, char *msg) -{ - char line[MAXLINE + 1]; - - /* Copies message to local buffer, adding source program tag */ - snprintf(line, sizeof(line), "kernel: %s", msg); - - logmsg(LOG_KERN | LOG_INFO, line, hname, ADDDATE); - return; -} - -/* -** Name: void init(int sig); -** Function: Initialize syslogd from configuration file. -** Used at startup or after a SIGHUP signal. -*/ -void init(int sig) -{ - FILE *cf; - struct filed *fLog, *next, **nextp; - char *p; - char cline[BUFSIZ]; - - DEBUG(dprintf("init\n");) - - /* Close all open log files. */ - Initialized = 0; - for (fLog = Files; fLog != NULL; fLog = next) { - - /* Flush any pending output */ - if (fLog->f_prevcount) fprintlog(fLog, 0, NULL); - - switch (fLog->f_type) { - case F_FILE: - case F_TTY: - case F_CONSOLE: close(fLog->f_file); break; - } - next = fLog->f_next; - free((char *) fLog); - } - Files = NULL; - nextp = &Files; - - /* Open the configuration file */ - if ((cf = fopen(ConfFile, "r")) != NULL) { - /* Foreach line in the configuration table, open that file. */ - fLog = NULL; - while (fgets(cline, sizeof(cline), cf) != NULL) { - /* Check for end-of-section, comments, strip off - * trailing spaces and newline character. */ - for (p = cline; isspace(*p); p += 1); - if (*p == '\0' || *p == '#') continue; - for (p = strchr(cline, '\0'); isspace(*--p);); - *++p = '\0'; - fLog = (struct filed *) calloc(1, sizeof(*fLog)); - *nextp = fLog; - nextp = &fLog->f_next; - cfline(cline, fLog); - } - - /* Close the configuration file */ - fclose(cf); - Initialized = 1; -DEBUG ( - if (DbgOpt) { - for (fLog = Files; fLog; fLog = fLog->f_next) { - int i; - for (i = 0; i <= LOG_NFACILITIES; i += 1) - if (fLog->f_pmask[i] == INTERNAL_NOPRI) - printf("X "); - else - printf("%d ", fLog->f_pmask[i]); - printf("%s: ", TypeNames[fLog->f_type]); - switch (fLog->f_type) { - case F_FILE: - case F_TTY: - case F_CONSOLE: - printf("%s", fLog->f_un.f_fname); - break; - case F_FORW: - break; - case F_USERS: - for (i = 0; i < MAXUNAMES && *fLog->f_un.f_uname[i]; i += 1) - printf("%s, ", fLog->f_un.f_uname[i]); - break; - } - printf("\n"); - } - } - ) - logmsg(LOG_SYSLOG | LOG_INFO, "syslogd: restart", LocalHostName, ADDDATE); - signal(SIGHUP, init); - DEBUG(dprintf("%s restarted\n", ProgName);) - } else { - DEBUG(dprintf("cannot open %s\n", ConfFile);) - *nextp = (struct filed *) calloc(1, sizeof(*fLog)); - cfline("*.ERR\t" CTTY, *nextp); - (*nextp)->f_next = (struct filed *) calloc(1, sizeof(*fLog)); - cfline("*.PANIC\t*", (*nextp)->f_next); - Initialized = 1; - } - return; -} - -/* -** Name: void daemonize(char *line); -** Function: Clone itself and becomes a daemon releasing unnecessay resources. -*/ -void daemonize(char *line) -{ - int lfd, len, pid; - - if ((lfd = open(PidFile, O_CREAT | O_RDWR, 0600)) > 0) { - len = read(lfd, line, 10); - line[len] = '\0'; - close(lfd); - if ((kill(len = atoi(line), 0) < 0 && errno == ESRCH) || len == 0) { - if (!DbgOpt) { - /* Parent ends and child becomes a daemon */ - if ((pid = fork()) > 0) { - /* Write process id. in pid file */ - lfd = open(PidFile, O_TRUNC | O_WRONLY); - len = sprintf(line, "%5d", pid); - write(lfd, line, len); - close(lfd); - - /* Wait for initialization to complete */ - exit(EXIT_SUCCESS); - } - sleep(1); - setsid(); /* Set as session leader */ - chdir("/"); /* Change to the root directory */ - /* Get rid of all open files */ - for (lfd = STDERR_FILENO + 1; lfd < OPEN_MAX; lfd += 1) - close(lfd); - } - } else { - fprintf(stderr, "\n%s already running\n", ProgName); - exit(EXIT_FAILURE); - } - } else { - fprintf(stderr, "\n%s can't open %s (%s)\n", ProgName, PidFile, strerror(errno)); - exit(EXIT_FAILURE); - } - return; -} - -int sockread(int *fd, char *buf, int maxlen) -{ - int len; - - /* Read a message from application programs */ - len = read(*fd, buf, maxlen); - if (len > 0) { /* Got a message */ - buf[len] = '\0'; - dprintf("got a message (%d, %#x)\n", *fd, len); - printline(LocalHostName, buf); - - } else if (len < 0) { /* Got an error or signal while reading */ - if (errno != EINTR) /* */ - { - logerror("Receive error from UDP channel"); - close(*fd); - *fd= -1; - } - - } else { /* (len == 0) Channel has been closed */ - logerror("network channel has closed"); - close(*fd); - die(-1); - } - - return len; -} - -/* -** Name: int main(int argc, char **argv); -** Function: Syslog daemon entry point -*/ -int main(int argc, char **argv) -{ - char *p, *udpdev, *eol; - int nfd, kfd, len, fdmax; - int ufd; - int ch, port = 0; - fd_set fdset; - struct nwio_udpopt udpopt; - struct servent *sp; - char line[MAXLINE + 1]; - - while ((ch = getopt(argc, argv, "df:m:p:v?")) != EOF) { - switch ((char) ch) { - case 'd': /* Debug */ - DbgOpt += 1; - break; - case 'f': /* Set configuration file */ - ConfFile = optarg; - break; - case 'm': /* Set mark interval */ - MarkInterval = atoi(optarg) * 60; - break; - case 'p': /* Set listening port */ - port = atoi(optarg); - break; - case 'v': /* Print version */ - fprintf(stderr, "%s version %s\n", ProgName, version); - return EXIT_FAILURE; - case '?': /* Help */ - default: - fprintf(stderr, usage); - return EXIT_FAILURE; - } - } - if (argc -= optind) { - fprintf(stderr, usage); - return EXIT_FAILURE; - } - - daemonize(line); - - /* Get the official name of local host. */ - gethostname(LocalHostName, sizeof(LocalHostName) - 1); - if ((p = strchr(LocalHostName, '.'))) *p = '\0'; - - udpdev = (p = getenv("UDP_DEVICE")) ? p : UDP_DEVICE; - sp = getservbyname("syslog", "udp"); - - signal(SIGTERM, die); - signal(SIGINT, DbgOpt ? die : SIG_IGN); - signal(SIGQUIT, DbgOpt ? die : SIG_IGN); - signal(SIGALRM, domark); - - alarm(TIMERINTVL); - - /* Open UDP device */ - nfd = open(udpdev, O_NONBLOCK | O_RDONLY); - - /* Configures the UDP device */ - udpopt.nwuo_flags = NWUO_SHARED | NWUO_LP_SET | NWUO_EN_LOC | - NWUO_DI_BROAD | NWUO_RP_SET | NWUO_RA_SET | - NWUO_RWDATONLY | NWUO_DI_IPOPT; - udpopt.nwuo_locport = udpopt.nwuo_remport = - port == 0 ? sp->s_port : htons(port); - udpopt.nwuo_remaddr = udpopt.nwuo_locaddr = htonl(0x7F000001L); - - if(nfd >= 0) { - while (ioctl(nfd, NWIOSUDPOPT, &udpopt) < 0 || - ioctl(nfd, NWIOGUDPOPT, &udpopt) < 0) { - if (errno == EAGAIN) { - sleep(1); - continue; - } - logerror("Set/Get UDP options failed"); - return EXIT_FAILURE; - } - } - - /* Open kernel log device */ - kfd = open("/dev/klog", O_NONBLOCK | O_RDONLY); - - if(kfd < 0 && nfd < 0) { - logerror("open /dev/klog and udp device failed - can't log anything"); - return EXIT_FAILURE; - } - - /* Open unix domain socket */ - - if((ufd = socket(PF_UNIX, SOCK_DGRAM, 0)) < 0) { - perror("unix socket"); - } else { - struct sockaddr_un uaddr; - memset(&uaddr, 0, sizeof(uaddr)); - strncpy(uaddr.sun_path, _PATH_LOG, sizeof(uaddr.sun_path) - 1); - uaddr.sun_family = AF_UNIX; - if(bind(ufd, (struct sockaddr *) &uaddr, sizeof(uaddr)) < 0) { - perror("unix socket bind"); - close(ufd); - ufd = -1; - } - } - - if(ufd < 0) exit(1); - - DEBUG(dprintf("unix domain socket = %d, at %s....\n", ufd, _PATH_LOG);) - - fdmax = max(max(nfd, kfd), ufd) + 1; - - DEBUG(dprintf("off & running....\n");) - - init(-1); /* Initilizes log data structures */ - - for (;;) { /* Main loop */ - - FD_ZERO(&fdset); /* Setup descriptors for select */ - if(nfd >= 0) FD_SET(nfd, &fdset); - if(kfd >= 0) FD_SET(kfd, &fdset); - if(ufd >= 0) FD_SET(ufd, &fdset); - - dprintf("select: nfd = %d, ufd = %d, fdmax = %d\n", nfd, ufd, fdmax); - - if (select(fdmax, &fdset, NULL, NULL, NULL) <= 0) { - sleep(1); - continue; - - } - - if (nfd >= 0 && FD_ISSET(nfd, &fdset)) { - dprintf("got nfd message (%d)\n", nfd); - len = sockread(&nfd, line, MAXLINE); - } - - if (ufd >= 0 && FD_ISSET(ufd, &fdset)) { - dprintf("got ufd message (%d)\n", ufd); - len = sockread(&ufd, line, MAXLINE); - } - - if (kfd >= 0 && FD_ISSET(kfd, &fdset)) { - static char linebuf[5*1024]; - - /* Read a message from kernel (klog) */ - len = read(kfd, linebuf, sizeof(linebuf)-2); - dprintf("got a message (%d, %#x)\n", kfd, len); - for (ch = 0; ch < len; ch += 1) - if (linebuf[ch] == '\0') linebuf[ch] = ' '; - if (linebuf[len - 1] == '\n') len -= 1; - linebuf[len] = '\n'; - linebuf[len + 1] = '\0'; - p = linebuf; - while((eol = strchr(p, '\n'))) { - *eol = '\0'; - printkline(LocalHostName, p); - p = eol+1; - } - } - } - /* Control never gets here */ -} - -/** syslogd.c **/ diff --git a/minix/man/man5/Makefile b/minix/man/man5/Makefile index a2bbfd4b9..71ca4d652 100644 --- a/minix/man/man5/Makefile +++ b/minix/man/man5/Makefile @@ -1,7 +1,7 @@ MAN= boot.cfg.5 configfile.5 crontab.5 dhcp.conf.5 dir.5 ethers.5 \ fstab.5 hosts.5 httpd.conf.5 http_status.5 keymap.5 \ passwd.5 resolv.conf.5 resolver.5 rhosts.5 statvfs.5 serv.access.5 \ - system.conf.5 syslog.conf.5 termcap.5 ttytab.5 TZ.5 utmp.5 \ + system.conf.5 termcap.5 ttytab.5 TZ.5 utmp.5 \ pkg_install.conf.5 pkg_summary.5 MLINKS += passwd.5 group.5 diff --git a/minix/man/man5/syslog.conf.5 b/minix/man/man5/syslog.conf.5 deleted file mode 100644 index 985c91270..000000000 --- a/minix/man/man5/syslog.conf.5 +++ /dev/null @@ -1,245 +0,0 @@ -.\" syslog.conf - syslogd(8) configuration file -.\" Copyright (c) 1995 Martin Schulze -.\" Modified for Minix porting by G. Falzoni -.\" -.\" This file is part of the sysklogd package, a kernel and system log daemon. -.\" -.\" This program is free software; you can redistribute it and/or modify -.\" it under the terms of the GNU General Public License as published by -.\" the Free Software Foundation; either version 2 of the License, or -.\" (at your option) any later version. -.\" -.\" This program is distributed in the hope that it will be useful, -.\" but WITHOUT ANY WARRANTY; without even the implied warranty of -.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -.\" GNU General Public License for more details. -.\" -.\" You should have received a copy of the GNU General Public License -.\" along with this program; if not, write to the Free Software -.\" Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. -.\" -.\" Local macros -.de Xr -.BR \\$1 (\\$2)\\$3 -.. -.de LB -.TP \\$1 -\\fB\\$2\\fR -\\$3 -.. -.de LI -.TP \\$1 -\\fI\\$2\\fR -\\$3 -.. -.de LR -.TP \\$1 -\\fR\\$2\\fR -\\$3 -.. -.\" end local macros -.TH SYSLOG.CONF 5 "1 January 1998" -.SH NAME -syslog.conf \- syslogd(8) configuration file -.SH DESCRIPTION -The -.I syslog.conf -file is the main configuration file for the -.Xr syslogd 8 -which logs system messages on *nix systems. This file specifies rules -for logging. For special features see the -.Xr syslogd 8 -manpage. -Every rule consists of two fields, a -.I selector -field and an -.I action -field. These two fields are separated by one or more tabs. -The selector field specifies a pattern of facilities and -priorities belonging to the specified action. -Lines starting with a hash mark (``#'') and empty lines are ignored. -.SH SELECTORS -The selector field itself again consists of two parts, a -.I facility -and a -.IR priority , -separated by a period (``.''). -Both parts are case insensitive and can also be specified as decimal -numbers, but don't do that, you have been warned. Both facilities and -priorities are described in -.BR syslog (3). -The names mentioned below correspond to the similar -.BR LOG_ -values -in -.IR /usr/include/syslog.h . -The -.I facility -is one of the following keywords: -.BR auth ", " authpriv ", " cron ", " daemon ", " kern ", " lpr ", " -.BR mail ", " mark ", " news ", " security " (same as " auth "), " -.BR syslog ", " user ", " uucp " and " local0 " through " local7 . -The keyword -.B security -should not be used anymore and -.B mark -is only for internal use and therefore should not be used in -applications. Anyway, you may want to specify and redirect these -messages here. The -.I facility -specifies the subsystem that produced the message, i.e. all mail -programs log with the mail facility -.BR "" ( LOG_MAIL ) -if they log using syslog. - -The -.I priority -is one of the following keywords, in ascending order: -.BR debug ", " info ", " notice ", " warning ", " warn " (same as " -.BR warning "), " err ", " error " (same as " err "), " crit ", " -.BR alert ", " emerg ", " panic " (same as " emerg ). -The keywords -.BR error ", " warn " and " panic -are deprecated and should not be used anymore. The -.I priority -defines the severity of the message -.PP -The behavior of the original BSD syslogd is that all messages of the -specified priority and higher are logged according to the given -action. This -.BR syslogd (8) -behaves the same, but has some extensions. -.PP -In addition to the above mentioned names the -.BR syslogd (8) -understands the following extensions: -.LB 5 "*" "An asterisk (``*'') before the period stands for all facilities. -.LB 5 "none" "The keyword none stands for no priority of the given facility. -.PP -Multiple selectors may be specified for a single -.I action -using the semicolon (``;'') separator. Remember that each selector in -the -.I selector -field is capable to overwrite the preceding ones. Using this -behavior you can exclude some priorities from the pattern. -.SH ACTIONS -The action field of a rule describes the abstract term -``logfile''. A ``logfile'' need not to be a real file, btw. The -.Xr syslogd 8 -provides the following actions. -.SS Regular File -Typically messages are logged to real files. The file has to be -specified with full pathname, beginning with a slash ``/''. -.SS Terminal and Console -If the file you specified is a tty, special tty-handling is done, same -with -.IR /dev/console . -.SS Remote Machine -This release does not implement -remote logging, i.e. the ability to send messages to a remote host running -.Xr syslogd 8 . -To forward messages to another host, prepend the hostname -with the at sign (``@''). -.SS List of Users -Usually critical messages are also directed to ``root'' on that -machine. You can specify a list of users that shall get the message by -simply writing the login. You may specify more than one user by -separating them with commas (``,''). If they're logged in they -get the message. Don't think a mail would be sent, that might be too -late. -.SS Everyone logged on -Emergency messages often go to all users currently online to notify -them that something strange is happening with the system. To specify -this -.IR wall (1)-feature -use an asterisk (``*''). -.SH EXAMPLES -Here are some example, partially taken from a real existing site and -configuration. Hopefully they rub out all questions to the -configuration, if not, drop me (Joey) a line. -.IP -.nf -# Store critical stuff in critical -# -*.crit /var/adm/critical -.fi -.LP -This will store all messages with the priority -.B crit -in the file -.IR /var/adm/critical . -.IP -.nf -# The tcp wrapper loggs with mail.info, we display -# all the connections on tty12 -# -mail.info /dev/tty12 -.fi -.LP -This directs all messages that uses -.BR mail.info " (in source " LOG_MAIL " | " LOG_INFO ) -to -.IR /dev/tty12 , -the 12th console. -.IP -.nf -# Log all mail.info and news.info messages to info -# -mail,news.info /var/adm/info -.fi -.LP -This will extract all messages that come either with -.BR mail.info " or with " news.info -and store them in the file -.IR /var/adm/info . -.IP -.nf -# Emergency messages will be displayed using wall -# -*.emerg * -.fi -.LP -This rule tells the -.B syslogd -to write all emergency messages to all currently logged in users. This -is the wall action. -.IP -.nf -# Messages of the priority alert will be directed -# to the operator -# -*.alert root,joey -.fi -.LP -This rule directs all messages with a priority of -.B alert -or higher to the terminals of the operator, i.e. of the users ``root'' -and ``joey'' if they're logged in. -.IP -.nf -*.* @finlandia -.fi -.LP -This rule would redirect all messages to a remote host called -finlandia. This is useful especially in a cluster of machines where -all syslog messages will be stored on only one machine. -.SH FILES -.I /etc/syslog.conf -Configuration file for -.B syslogd -.SH BUGS -The effects of multiple selectors are sometimes not intuitive. For -example ``mail.crit,*.err'' will select ``mail'' facility messages at -the level of ``err'' or higher, not at the level of ``crit'' or -higher. - -.SH SEE ALSO -.BR syslogd (8), -.BR logger (1), -.BR syslog (3) -.SH AUTHORS -The -.B syslogd -is taken from BSD sources, Greg Wettstein (greg@wind.rmcc.com) -performed the port to Linux, Martin Schulze (joey@linux.de) -made some bugfixes and added some new features. diff --git a/minix/man/man8/Makefile b/minix/man/man8/Makefile index ed7214cdc..2441f56cc 100644 --- a/minix/man/man8/Makefile +++ b/minix/man/man8/Makefile @@ -8,7 +8,7 @@ MAN= add_route.8 backup.8 boot.8 btrace.8 \ printroot.8 pr_routes.8 pwdauth.8 rarpd.8 \ readclock.8 repartition.8 \ rshd.8 screendump.8 serial-ip.8 \ - setup.8 shutdown.8 slip.8 srccrc.8 syslogd.8 tcpd.8 \ + setup.8 shutdown.8 slip.8 srccrc.8 tcpd.8 \ unix.8 update.8 usage.8 vbfs.8 MLINKS += httpd.8 in.httpd.8 diff --git a/minix/man/man8/syslogd.8 b/minix/man/man8/syslogd.8 deleted file mode 100644 index 1d569623a..000000000 --- a/minix/man/man8/syslogd.8 +++ /dev/null @@ -1,210 +0,0 @@ -.\" Copyright 1994 Dr. Greg Wettstein, Enjellic Systems Development. -.\" May be distributed under the GNU General Public License -.\" Sun Aug 30 11:35:55 MET: Martin Schulze: Updates -.\" -.\" from SYSKLOGD 8 "13 December 1995" "Version 1.3" "Linux System Administration" -.\" Modified for Minix porting by G. Falzoni -.\" -.\" Local macros -.de Xr -.BR \\$1 (\\$2)\\$3 -.. -.de LB -.TP \\$1 -\\fB\\$2\\fR -\\$3 -.. -.de LI -.TP \\$1 -\\fI\\$2\\fR -\\$3 -.. -.de LR -.TP \\$1 -\\fR\\$2\\fR -\\$3 -.. -.\" end local macros -.TH SYSLOGD 8 "Jan. 23, 2000" -.SH NAME -.PP -syslogd \- system logging daemon. -.SH SYNOPSIS -.PP -.B syslogd -.RB [ " \-d " ] -.RB [ " \-f " -.I config file -] -.RB [ " \-m " -.I interval -] -.RB [ " \-p" -.IB port -] -.RB [ " \-v " ] -.LP -.SH DESCRIPTION -.PP -System logging is provided by a version of -.BR syslogd (8) -derived from the -stock BSD sources. -.B Syslogd -provides the kind of logging that many modern programs use. Every logged -message contains at least a time, a hostname field and a -program name field, but that depends on how trusty the logging -program is. -.PP -While the syslogd sources have been heavily modified a couple of notes -are necessary. First of all there has been a systematic attempt to -insure that syslogd follows the default, standard BSD behavior. -The second important concept to note is that this version of syslogd -interacts transparently with the version of syslog found in the -standard libraries, so you must insure that the correct versions are installed. -.PP -The main configuration file -.I /etc/syslog.conf -or an alternative file, given with the -.B "\-f" -option, is read at startup. Any line that begins with the hash mark -(``#'') and empty lines are ignored. If an error occurs during parsing -the whole line is ignored. -.SH OPTIONS -.PP -.LB 9 "-d" "Turns on debug mode. -When using debug mode, the daemon will not proceed to -.BR fork (2) -to set itself in the background, but will stay in the -foreground and write much debug information on the current tty. See the -DEBUGGING section for more information. -.LB 9 "\-f config file" "Specify an alternative configuration file instead of -.IR /etc/syslog.conf "," -which is the default. -.LB 9 "\-m interval" "The syslogd logs a mark timestamp regularly. The default -.I interval -between two -.I \-\- MARK \-\- -lines is 20 minutes. This can be changed with this option. -.LB 9 "\-p port" "You can specify an alternative port instead of -.I syslog/udp -default service. -.LB 9 "\-v" "Print version and exit. -.SH SIGNALS -.PP -.B Syslogd -reacts to a set of signals. You may easily send a signal to it -using the following: -.IP -kill -SIGNAL `cat /usr/run/syslogd.pid` -.LB 9 SIGHUP "This lets syslogd perform a re-initialization. -All open files are closed, the configuration file (default -is '/etc/syslog.conf') will be reread. -.LB 9 SIGTERM "The syslogd will die. -.LB 9 "SIGINT SIGQUIT" "If debugging is enabled these are ignored, otherwise -syslogd will die. -.LB 9 SIGALRM "Every time syslogd receives this signal it will log -the mark line. Normally this is done by -.Xr alarm 2 . -.SH CONFIGURATION FILE SYNTAX DIFFERENCES -.PP -.B Syslogd -uses a slightly different syntax for its configuration file than -the original BSD sources. Originally all messages of a specific priority -and above were forwarded to the log file. -.PP -For example see the following sample file -.IP -## Sample syslog.conf - -## Emergency messages (system may be unusable) -.br -*.emerg * -.br -*.alert /dev/log - -## High severity errors -.br -*.alert;*.crit /usr/adm/syslog - -## every other message (errors/warning and informational) -.br -*.info;*.notice;*.warning;*.err /usr/adm/messages -.br -*.debug /usr/adm/debug - -.SH SUPPORT FOR REMOTE LOGGING -.PP -Not implemented. -.SH OUTPUT TO NAMED PIPES (FIFOs) -.PP -Not implemented. -.SH INSTALLATION CONCERNS -.PP -There is probably one important consideration when installing this -version of syslogd. This version of syslogd is dependent on proper -formatting of messages by the syslog function. -.PP -.B Syslogd -should be started by the rc sequence. -.SH DEBUGGING -.PP -When debugging is turned on using -.B "\-d" -option and syslogd is compiled with debug=1 then syslogd -will be very verbose by writing much of what it does on stdout. -Whenever -the configuration file is reread and re-parsed you'll see a tabular, -corresponding to the internal data structure. This tabular consists of -four fields: -.TP -.I number -This field contains a serial number starting by zero. This number -represents the position in the internal data structure (i.e. the -array). If one number is left out then there might be an error in the -corresponding line in -.IR /etc/syslog.conf . -.TP -.I pattern -This field is tricky and represents the internal structure -exactly. Every column stands for a facility (refer to -.BR syslog (3)). -As you can see, there are still some facilities left free for former -use, only the left most are used. Every field in a column represents -the priorities (refer to -.BR syslog (3)). -.TP -.I action -This field describes the particular action that takes place whenever a -message is received that matches the pattern. Refer to the -.BR syslog.conf (5) -manpage for all possible actions. -.TP -.I arguments -This field shows additional arguments to the actions in the last -field. For file-logging this is the filename for the logfile; for -user-logging this is a list of users; for remote logging this is the -hostname of the machine to log to; for console-logging this is the -used console; for tty-logging this is the specified tty; wall has no -additional arguments. -.PP -Note that if syslogd is compiled with debug=0 only a subset is printed. -.SH FILES -.PP -.LR 28 /etc/syslog.conf "Configuration file for syslogd. See -.Xr syslog.conf 5 -for exact information. -.LR 28 /dev/log "The log device (console) for Minix. -.LR 28 /usr/run/syslogd.pid "The file containing the process id of syslogd. -.SH BUGS -.PP -If an error occurs in one line the whole rule is ignored. -.B Syslogd -doesn't change the filemode of opened logfiles at any stage of -process. If a file is created it is world readable. If you want to -avoid this, you have to create it and change permissions on your own. -.SH SEE ALSO -.BR syslog.conf (5), -.BR logger (1), -.BR syslog (3). -.\" .BR services (5), diff --git a/usr.sbin/Makefile b/usr.sbin/Makefile index 93e7e707a..bd000bb5c 100644 --- a/usr.sbin/Makefile +++ b/usr.sbin/Makefile @@ -24,9 +24,9 @@ SUBDIR= \ \ rdate \ \ - traceroute \ - \ \ + syslogd \ + traceroute \ unlink user \ vipw vnconfig \ \ diff --git a/usr.sbin/syslogd/Makefile b/usr.sbin/syslogd/Makefile new file mode 100644 index 000000000..705895855 --- /dev/null +++ b/usr.sbin/syslogd/Makefile @@ -0,0 +1,38 @@ +# $NetBSD: Makefile,v 1.24 2012/06/06 00:33:45 christos Exp $ +# from: @(#)Makefile 8.1 (Berkeley) 6/6/93 +.include + +WARNS?=4 +USE_FORT?= yes # network server + +LINTFLAGS+=-X 132,247,135,259,117,298 + +PROG= syslogd +SRCS= syslogd.c utmpentry.c tls.c sign.c +MAN= syslogd.8 syslog.conf.5 +DPADD+=${LIBUTIL} ${LIBEVENT} +LDADD+=-lutil -levent +#make symlink to old socket location for transitional period +.if !defined(__MINIX) +SYMLINKS= /var/run/log /dev/log +.endif # !defined(__MINIX) +.PATH.c: ${NETBSDSRCDIR}/usr.bin/who +CPPFLAGS+=-I${NETBSDSRCDIR}/usr.bin/who -DSUPPORT_UTMPX -DSUPPORT_UTMP -Wredundant-decls + +.if (${USE_INET6} != "no") +CPPFLAGS+=-DINET6 +.endif + +.if !defined(__MINIX) +CPPFLAGS+=-DLIBWRAP +LDADD+= -lwrap +DPADD+= ${LIBWRAP} +.endif # !defined(__MINIX) + +.if ${MKCRYPTO} != "no" +LDADD+= -lssl -lcrypto +.else +CPPFLAGS+=-DDISABLE_TLS -DDISABLE_SIGN +.endif + +.include diff --git a/usr.sbin/syslogd/extern.h b/usr.sbin/syslogd/extern.h new file mode 100644 index 000000000..6fd57f056 --- /dev/null +++ b/usr.sbin/syslogd/extern.h @@ -0,0 +1,91 @@ +/* $NetBSD: extern.h,v 1.3 2010/06/09 21:55:42 riz Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * extern.h + * + * declarations for variables and functions from syslogd.c + * that are used in tls.c and sign.c + */ +#ifndef EXTERN_H_ +#define EXTERN_H_ + + +/* variables */ +extern int Debug; +extern struct tls_global_options_t tls_opt; +extern struct TLS_Incoming TLS_Incoming_Head; +extern struct sign_global_t GlobalSign; +extern char *linebuf; +extern size_t linebufsize; +extern int RemoteAddDate; + +extern bool BSDOutputFormat; +extern time_t now; +extern char timestamp[]; +extern char appname[]; +extern char *LocalFQDN; +extern char *include_pid; + +/* functions */ +extern void logerror(const char *, ...) + __attribute__((__format__(__printf__,1,2))); +extern void loginfo(const char *, ...) + __attribute__((__format__(__printf__,1,2))); +extern void printline(const char *, char *, int); +extern void die(int fd, short event, void *ev) + __attribute__((__noreturn__)); +extern struct event *allocev(void); +extern void send_queue(int __unused, short __unused, void *); +extern void schedule_event(struct event **, struct timeval *, + void (*)(int, short, void *), void *); +extern char *make_timestamp(time_t *, bool); +#ifndef DISABLE_TLS +extern struct filed *get_f_by_conninfo(struct tls_conn_settings *conn_info); +#endif +extern bool message_queue_remove(struct filed *, struct buf_queue *); +extern void buf_msg_free(struct buf_msg *msg); +extern void message_queue_freeall(struct filed *); +extern bool copy_string(char **, const char *, const char *); +extern bool copy_config_value_quoted(const char *, char **, const char **); +extern size_t message_allqueues_purge(void); +extern bool format_buffer(struct buf_msg*, char**, size_t*, size_t*, size_t*, + size_t*); +extern void fprintlog(struct filed *, struct buf_msg *, struct buf_queue *); +extern struct buf_msg *buf_msg_new(const size_t); + +#endif /*EXTERN_H_*/ diff --git a/usr.sbin/syslogd/howto.html b/usr.sbin/syslogd/howto.html new file mode 100644 index 000000000..ed3c6eb8c --- /dev/null +++ b/usr.sbin/syslogd/howto.html @@ -0,0 +1,89 @@ + + +NetBSD & Google's Summer of Code: Martin Schuette - Improve syslogd (syslogd) + + + +

Testing syslogd

+

Compiling

+

A (hopefully) stable version for testing is available by CVS and as a .tar.gz archive. It contains syslogd itself and all necessary files to build on NetBSD and FreeBSD.

+ +

To build just type make. Unless you have a complete NetBSD source tree -- then you can extract the files to /usr/src/usr.sbin/syslogd and replace the Makefile with Makefile.NetBSD and then type make. + +

Note on other BSDs

+

I also tested syslogd on FreeBSD. There are just a few issues/differences:

+
    +
  • You have to install libevent first
  • +
  • No pidfile is written
  • +
  • The code for wallmsg() is only copied, not tested
  • +
+

I assume the same holds for other BSDs but I have no live system to test them.

+ + +

Command line options

+

syslogd has to be run as root (because it uses chroot()). You should start it with option "-u username" to drop privileges.

+ +

By default messages are written in syslog Protocol format. To get the BSD Syslog output like from previous versions use the "-o" option.

+ +

syslog.conf

+ +

To use TLS some additional configuration is required.

+ +

X.509 certificates

+

Every syslogd using TLS needs an X.509 certificate. +The files containing the private key, certificate, and CA are configured with:

+
+tls_key="/etc/openssl/default.key"
+tls_cert="/etc/openssl/default.crt"
+tls_ca="/some/where/my.cacert"
+
+ +

If you do not already have a X.509 certificate then you can tell syslogd to generate one for you with

+
+tls_gen_cert=on
+
+ +

TLS client

+

To send messages with configure a TLS destination. Here are three examples with different additional options required for authentication

+
+# with CA
+*.*      @[logserver.example.org]:13245
+*.*      @[127.0.0.1]:13245(subject="logserver.example.org")
+# without CA
+*.*      @[127.0.0.1]:13245(fingerprint="SHA1:E4:E1:A6:1C:D4:31:D7:D4:9B:B8:DC:DF:DD:CE:30:71:46:00:92:C9")
+
+ +

If using a CA then it is checked whether the server's certificate matches the hostname or a given subject. Assuming the logserver's certificate has "logserver.example.org" as its commonName or as a subjectAltName/dnsName the first line is sufficient. (Once a standard portnumber has been assigned the port becomes optional.) If we do not want to rely on DNS and configure the destination with "127.0.0.1" then the subject comparison will fail. The alternatives are either to configure the subject as an option (as in the example above) or to generate a new certificate with the server's IP as a commonName or subjectAltName/ipAddress.

+ +

Without a CA the easiest way to authenticate the peer's certificate is its fingerprint as in the last line in the example. syslogd logs the fingerprints of all certificates it loads or tries to connect with, but it can also be read from the shell with "openssl x509 -in /etc/openssl/default.crt -noout -fingerprint".

+ +

TLS server

+

To enable TLS server mode use these lines.

+
+tls_server="on"
+tls_bindhost="127.0.0.1"
+tls_bindport="13245"
+
+

The bindhost is optional. The bindport is currently required (as long as there is no tcp port defined for the syslog service).

+ +

With a CA that is all -- there is no additional hostname check for clients. +Without a CA the server needs to be told which certificates to trust:

+
+tls_allow_fingerprints = MD5:00:A2:A7:02:CA:A0:0E:00:DC:F1:91:BE:6A:AA:FF:27 "SHA1:E4:E1:A6:1C:D4:31:D7:D4:9B:B8:DC:DF:DD:CE:30:71:46:00:92:C9"
+
+ +
+ + + +
+SourceForge.net Logo + + + + +
Martin Schütte <info@mschuette.name>
$Id: howto.html,v 1.1 2008/10/31 16:12:19 christos Exp $
+
+ + + diff --git a/usr.sbin/syslogd/index.html b/usr.sbin/syslogd/index.html new file mode 100644 index 000000000..2b8da9f9d --- /dev/null +++ b/usr.sbin/syslogd/index.html @@ -0,0 +1,161 @@ + + +NetBSD & Google's Summer of Code: Martin Schuette - Improve syslogd (syslogd) + + + +
+ + + + + + +
[NetBSD logo]   &   [Google logo]
+
+ +

NetBSD-SoC: Improve syslogd

+ +

What is it?

+ +

The syslog daemon handles most log messages of a unixoid system. It receives messages from shell-scripts, applications, daemons, the kernel, or by network and then writes them into logfiles, on user's consoles or forwards them to some other logserver -- all depending on its configuration and the message properties.

+ +

implemented the upcoming IETF +standards for NetBSD's syslog(3) +and syslogd(8): +

+
  • transport-tls defines the network protocol to send + syslog data over TLS (instead of UDP), thus providing a reliable and + authenticated transport. +
  • syslog-protocol defines a new layout for syslog +lines; the most important additions are full timestamps (with year and timezone) +and structured data with name=value pairs. This enables all programs to declare +semantic content (uid, client IP, return codes, etc), making automatic +log-monitoring (or at least parsing) much easier. +
  • syslog-sign defines signature messages to assert + authentication, integrity and correct sequencing of syslog messages. +

+ To my knowledge this is one of the first implementations of these + protocols. It will provide NetBSD (and hopefully the other BSDs as well) with + an advanced, reliable, and secure syslogd; thus saving admins the time and + effort to install custom logging solutions just to get secure transport to + their central logserver. +

+ +

Current Status

+

Functions

+

TLS

+

The TLS support is now working (tested with RSA and DSA keys). +It will read its configuration from syslog.conf, accept incoming TLS connections +to receive messages, establish connections to other TLS servers.

+

If a TLS server is temporarily not available then its messages will be buffered +and sent after reconnection.

+ +

syslog-protocol

+

A command line option determines whether syslogd output is in BSD Syslog or in syslog-protocol format. All received messages are converted accordingly.

+

I also modified syslog(3) in libc to send syslog-protocol messages.

+

While syslog(3) can only use the message field, a new syslogp(3) call is provided to add a MSGID and structured data to a message.

+ +

syslog-sign

+

syslogd(8) is now able to digitally sign messages with syslog-sign.

+ +

syslog.conf

+

I extended the traditional configuration file format to support additionally fields for TLS. +A syslog.conf for TLS currently looks like this:

+
+# TLS options
+tls_ca="/etc/my.cacert"
+tls_cert="/etc/localhost.crt"
+tls_key="/etc/localhost.key"
+tls_verify="off"
+tls_bindhost="127.0.0.1"
+tls_bindport="13245"
+tls_server=on
+
+# file destination
+*.*      /home/mschuett/test.log
+# UDP destination
+*.*      @192.168.178.5
+# TLS destination
+*.*      @[127.0.0.1]:5555(fingerprint="SHA1:E4:E1:A6:1C:D4:31:D7:D4:9B:B8:DC:DF:DD:CE:30:71:46:00:92:C9")
+
+ +

Source Code

+

To try syslogd fetch the latest .tar.gz archive (2008-08-18) (older versions: 2008-08-05, 2008-08-05).

+ +

The sources for syslogd, the libc functions, newsyslog, and logger are also available from the CVS on sourceforge.

+ +

For development I used an own SVN; a detailed timeline of code changes is available in the on my Trac.

+ +

The syslogd code needs and libevent. The only system-dependent function is wallmsg() to write messages to users's terminals.
+It was developed and tested on NetBSD and FreeBSD. I heard it does not compile on OpenBSD (I do not know about DragonflyBSD), probably due to different files under /usr/include. I would be interested if someone tried to compile on Linux; this will be some more work, because one will also need additional functions from BSDs libc that are not in glibc (most notably strlcat()).

+ +

Deliverables

+

+I got all my mandatory components: +

+
    +
  • Implement transport-tls in syslogd(8)
  • +
  • Implement syslog-protocol in syslogd(8)
  • +
  • Implement syslog-protocol in syslog(3)
  • +
  • Implement syslog-sign in syslogd(8)
  • +
+

+...and parts of my optional components: +

+
    +
  • interoperability with other implementations: so far I could only test TLS-transport with rsyslog
  • +
  • Extended API to use new functions: with syslogp() I wrote a new API; but it is not really the extended API I had in mind here.
  • +
+ +

Documentation

+ +

New manpages and description:

+ + +

Existing specifications and man-pages:

+ + +

IETF documents:

+ + +
+ + + + +
+SourceForge.net Logo + + + + +
Martin Schütte <info@mschuette.name>
$Id: index.html,v 1.1 2008/10/31 16:12:19 christos Exp $
+
+ + + diff --git a/usr.sbin/syslogd/pathnames.h b/usr.sbin/syslogd/pathnames.h new file mode 100644 index 000000000..cd8daff6e --- /dev/null +++ b/usr.sbin/syslogd/pathnames.h @@ -0,0 +1,37 @@ +/* $NetBSD: pathnames.h,v 1.6 2003/08/07 11:25:44 agc Exp $ */ + +/* + * Copyright (c) 1989, 1993 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * from: @(#)pathnames.h 8.1 (Berkeley) 6/6/93 + */ + +#include + +#define _PATH_KLOG "/dev/klog" +#define _PATH_LOGCONF "/etc/syslog.conf" diff --git a/usr.sbin/syslogd/sign.c b/usr.sbin/syslogd/sign.c new file mode 100644 index 000000000..ef6acb1ea --- /dev/null +++ b/usr.sbin/syslogd/sign.c @@ -0,0 +1,938 @@ +/* $NetBSD: sign.c,v 1.5 2012/06/06 00:33:45 christos Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * sign.c + * syslog-sign related code for syslogd + * + * Martin Schütte + */ +/* + * Issues with the current internet draft: + * 1. The draft is a bit unclear on the input format for the signature, + * so this might have to be changed later. Cf. sign_string_sign() + * 2. The draft only defines DSA signatures. I hope it will be extended + * to DSS, thus allowing DSA, RSA (ANSI X9.31) and ECDSA (ANSI X9.62) + * 3. The draft does not define the data format for public keys in CBs. + * This implementation sends public keys in DER encoding. + * 4. This current implementation uses high-level OpenSSL API. + * I am not sure if these completely implement the FIPS/ANSI standards. + * Update after WG discussion in August: + * 1. check; next draft will be clearer and specify the format as implemented. + * 2. check; definitely only DSA in this version. + * 3. remains a problem, so far no statement from authors or WG. + * 4. check; used EVP_dss1 method implements FIPS. + */ +/* + * Limitations of this implementation: + * - cannot use OpenPGP keys, only PKIX or DSA due to OpenSSL capabilities + * - only works for correctly formatted messages, because incorrect messages + * are reformatted (e.g. if it receives a message with two spaces between + * fields it might even be parsed, but the output will have only one space). + */ + +#include +__RCSID("$NetBSD: sign.c,v 1.5 2012/06/06 00:33:45 christos Exp $"); + +#ifndef DISABLE_SIGN +#include "syslogd.h" +#ifndef DISABLE_TLS +#include "tls.h" +#endif /* !DISABLE_TLS */ +#include "sign.h" +#include "extern.h" + +/* + * init all SGs for a given algorithm + */ +bool +sign_global_init(struct filed *Files) +{ + DPRINTF((D_CALL|D_SIGN), "sign_global_init()\n"); + if (!(GlobalSign.sg == 0 || GlobalSign.sg == 1 + || GlobalSign.sg == 2 || GlobalSign.sg == 3)) { + logerror("sign_init(): invalid SG %d", GlobalSign.sg); + return false; + } + + if (!sign_get_keys()) + return false; + + /* signature algorithm */ + /* can probably be merged with the hash algorithm/context but + * I leave the optimization for later until the RFC is ready */ + GlobalSign.sigctx = EVP_MD_CTX_create(); + EVP_MD_CTX_init(GlobalSign.sigctx); + + /* the signature algorithm depends on the type of key */ + if (EVP_PKEY_DSA == EVP_PKEY_type(GlobalSign.pubkey->type)) { + GlobalSign.sig = EVP_dss1(); + GlobalSign.sig_len_b64 = SIGN_B64SIGLEN_DSS; +/* this is the place to add non-DSA key types and algorithms + } else if (EVP_PKEY_RSA == EVP_PKEY_type(GlobalSign.pubkey->type)) { + GlobalSign.sig = EVP_sha1(); + GlobalSign.sig_len_b64 = 28; +*/ + } else { + logerror("key type not supported for syslog-sign"); + return false; + } + + assert(GlobalSign.keytype == 'C' || GlobalSign.keytype == 'K'); + assert(GlobalSign.pubkey_b64 && GlobalSign.privkey && + GlobalSign.pubkey); + assert(GlobalSign.privkey->pkey.dsa->priv_key); + + GlobalSign.gbc = 0; + STAILQ_INIT(&GlobalSign.SigGroups); + + /* hash algorithm */ + OpenSSL_add_all_digests(); + GlobalSign.mdctx = EVP_MD_CTX_create(); + EVP_MD_CTX_init(GlobalSign.mdctx); + + /* values for SHA-1 */ + GlobalSign.md = EVP_dss1(); + GlobalSign.md_len_b64 = 28; + GlobalSign.ver = "0111"; + + if (!sign_sg_init(Files)) + return false; + sign_new_reboot_session(); + + DPRINTF(D_SIGN, "length values: SIGN_MAX_SD_LENGTH %d, " + "SIGN_MAX_FRAG_LENGTH %d, SIGN_MAX_SB_LENGTH %d, " + "SIGN_MAX_HASH_NUM %d\n", SIGN_MAX_SD_LENGTH, + SIGN_MAX_FRAG_LENGTH, SIGN_MAX_SB_LENGTH, SIGN_MAX_HASH_NUM); + + /* set just before return, so it indicates initialization */ + GlobalSign.rsid = now; + return true; +} + +/* + * get keys for syslog-sign + * either from the X.509 certificate used for TLS + * or by generating a new one + * + * sets the global variables + * GlobalSign.keytype, GlobalSign.pubkey_b64, + * GlobalSign.privkey, and GlobalSign.pubkey + */ +bool +sign_get_keys(void) +{ + EVP_PKEY *pubkey = NULL, *privkey = NULL; + unsigned char *der_pubkey = NULL, *ptr_der_pubkey = NULL; + char *pubkey_b64 = NULL; + int der_len; + + /* try PKIX/TLS key first */ +#ifndef DISABLE_TLS + SSL *ssl; + if (tls_opt.global_TLS_CTX + && (ssl = SSL_new(tls_opt.global_TLS_CTX))) { + X509 *cert; + DPRINTF(D_SIGN, "Try to get keys from TLS X.509 cert...\n"); + + if (!(cert = SSL_get_certificate(ssl))) { + logerror("SSL_get_certificate() failed"); + FREE_SSL(ssl); + return false; + } + if (!(privkey = SSL_get_privatekey(ssl))) { + logerror("SSL_get_privatekey() failed"); + FREE_SSL(ssl); + return false; + } + if (!(pubkey = X509_get_pubkey(cert))) { + logerror("X509_get_pubkey() failed"); + FREE_SSL(ssl); + return false; + } + /* note: + * - privkey is just a pointer into SSL_CTX and + * must not be changed nor be free()d + * - but pubkey has to be freed with EVP_PKEY_free() + */ + FREE_SSL(ssl); + + if (EVP_PKEY_DSA != EVP_PKEY_type(pubkey->type)) { + DPRINTF(D_SIGN, "X.509 cert has no DSA key\n"); + EVP_PKEY_free(pubkey); + privkey = NULL; + pubkey = NULL; + } else { + DPRINTF(D_SIGN, "Got public and private key " + "from X.509 --> use type PKIX\n"); + GlobalSign.keytype = 'C'; + GlobalSign.privkey = privkey; + GlobalSign.pubkey = pubkey; + + /* base64 certificate encoding */ + der_len = i2d_X509(cert, NULL); + if (!(ptr_der_pubkey = der_pubkey = malloc(der_len)) + || !(pubkey_b64 = malloc(der_len*2))) { + free(der_pubkey); + logerror("malloc() failed"); + return false; + } + if (i2d_X509(cert, &ptr_der_pubkey) <= 0) { + logerror("i2d_X509() failed"); + return false; + } + b64_ntop(der_pubkey, der_len, pubkey_b64, der_len*2); + free(der_pubkey); + /* try to resize memory object as needed */ + GlobalSign.pubkey_b64 = realloc(pubkey_b64, + strlen(pubkey_b64)+1); + if (!GlobalSign.pubkey_b64) + GlobalSign.pubkey_b64 = pubkey_b64; + } + } +#endif /* !DISABLE_TLS */ + if (!(privkey && pubkey)) { /* PKIX not available --> generate key */ + DSA *dsa; + + DPRINTF(D_SIGN, "Unable to get keys from X.509 " + "--> use DSA with type 'K'\n"); + if (!(privkey = EVP_PKEY_new())) { + logerror("EVP_PKEY_new() failed"); + return false; + } + dsa = DSA_generate_parameters(SIGN_GENCERT_BITS, NULL, 0, + NULL, NULL, NULL, NULL); + if (!DSA_generate_key(dsa)) { + logerror("DSA_generate_key() failed"); + return false; + } + if (!EVP_PKEY_assign_DSA(privkey, dsa)) { + logerror("EVP_PKEY_assign_DSA() failed"); + return false; + } + GlobalSign.keytype = 'K'; /* public/private keys used */ + GlobalSign.privkey = privkey; + GlobalSign.pubkey = privkey; + + /* pubkey base64 encoding */ + der_len = i2d_DSA_PUBKEY(dsa, NULL); + if (!(ptr_der_pubkey = der_pubkey = malloc(der_len)) + || !(pubkey_b64 = malloc(der_len*2))) { + free(der_pubkey); + logerror("malloc() failed"); + return false; + } + if (i2d_DSA_PUBKEY(dsa, &ptr_der_pubkey) <= 0) { + logerror("i2d_DSA_PUBKEY() failed"); + free(der_pubkey); + free(pubkey_b64); + return false; + } + b64_ntop(der_pubkey, der_len, pubkey_b64, der_len*2); + free(der_pubkey); + /* try to resize memory object as needed */ + GlobalSign.pubkey_b64 = realloc(pubkey_b64, + strlen(pubkey_b64) + 1); + if (!GlobalSign.pubkey_b64) + GlobalSign.pubkey_b64 = pubkey_b64; + } + return true; +} + +/* + * init SGs + */ +bool +sign_sg_init(struct filed *Files) +{ + struct signature_group_t *sg, *newsg, *last_sg; + struct filed_queue *fq; + struct string_queue *sqentry, *last_sqentry; + struct filed *f; + unsigned int i; + + /* note on SG 1 and 2: + * it is assumed that redundant signature groups + * and especially signature groups without an associated + * destination are harmless. + * this currently holds true because sign_append_hash() + * is called from fprintlog(), so only actually used + * signature group get hashes and need memory for them + */ + /* possible optimization for SGs 1 and 2: + * use a struct signature_group_t *newsg[IETF_NUM_PRIVALUES] + * for direct group lookup + */ + +#define ALLOC_OR_FALSE(x) do { \ + if(!((x) = calloc(1, sizeof(*(x))))) { \ + logerror("Unable to allocate memory"); \ + return false; \ + } \ +} while (/*CONSTCOND*/0) + +#define ALLOC_SG(x) do { \ + ALLOC_OR_FALSE(x); \ + (x)->last_msg_num = 1; /* cf. section 4.2.5 */ \ + STAILQ_INIT(&(x)->hashes); \ + STAILQ_INIT(&(x)->files); \ +} while (/*CONSTCOND*/0) + +/* alloc(fq) and add to SGs file queue */ +#define ASSIGN_FQ() do { \ + ALLOC_OR_FALSE(fq); \ + fq->f = f; \ + f->f_sg = newsg; \ + DPRINTF(D_SIGN, "SG@%p <--> f@%p\n", newsg, f); \ + STAILQ_INSERT_TAIL(&newsg->files, fq, entries); \ +} while (/*CONSTCOND*/0) + + switch (GlobalSign.sg) { + case 0: + /* one SG, linked to all files */ + ALLOC_SG(newsg); + newsg->spri = 0; + for (f = Files; f; f = f->f_next) + ASSIGN_FQ(); + STAILQ_INSERT_TAIL(&GlobalSign.SigGroups, + newsg, entries); + break; + case 1: + /* every PRI gets one SG */ + for (i = 0; i < IETF_NUM_PRIVALUES; i++) { + int fac, prilev; + fac = LOG_FAC(i); + prilev = LOG_PRI(i); + ALLOC_SG(newsg); + newsg->spri = i; + + /* now find all destinations associated with this SG */ + for (f = Files; f; f = f->f_next) + /* check priorities */ + if (MATCH_PRI(f, fac, prilev)) + ASSIGN_FQ(); + STAILQ_INSERT_TAIL(&GlobalSign.SigGroups, + newsg, entries); + } + break; + case 2: + /* PRI ranges get one SG, boundaries given by the + * SPRI, indicating the largest PRI in the SG + * + * either GlobalSign.sig2_delims has a list of + * user configured delimiters, or we use a default + * and set up one SG per facility + */ + if (STAILQ_EMPTY(&GlobalSign.sig2_delims)) { + DPRINTF(D_SIGN, "sign_sg_init(): set default " + "values for SG 2\n"); + for (i = 0; i < (IETF_NUM_PRIVALUES>>3); i++) { + ALLOC_OR_FALSE(sqentry); + sqentry->data = NULL; + sqentry->key = (i<<3); + STAILQ_INSERT_TAIL(&GlobalSign.sig2_delims, + sqentry, entries); + } + } + assert(!STAILQ_EMPTY(&GlobalSign.sig2_delims)); + + /* add one more group at the end */ + last_sqentry = STAILQ_LAST(&GlobalSign.sig2_delims, + string_queue, entries); + if (last_sqentry->key < IETF_NUM_PRIVALUES) { + ALLOC_OR_FALSE(sqentry); + sqentry->data = NULL; + sqentry->key = IETF_NUM_PRIVALUES-1; + STAILQ_INSERT_TAIL(&GlobalSign.sig2_delims, + sqentry, entries); + } + + STAILQ_FOREACH(sqentry, &GlobalSign.sig2_delims, entries) { + unsigned int min_pri = 0; + ALLOC_SG(newsg); + newsg->spri = sqentry->key; + + /* check _all_ priorities in SG */ + last_sg = STAILQ_LAST(&GlobalSign.SigGroups, + signature_group_t, entries); + if (last_sg) + min_pri = last_sg->spri + 1; + + DPRINTF(D_SIGN, "sign_sg_init(): add SG@%p: SG=\"2\"," + " SPRI=\"%d\" -- for msgs with " + "%d <= pri <= %d\n", + newsg, newsg->spri, min_pri, newsg->spri); + /* now find all destinations associated with this SG */ + for (f = Files; f; f = f->f_next) { + bool match = false; + for (i = min_pri; i <= newsg->spri; i++) { + int fac, prilev; + fac = LOG_FAC(i); + prilev = LOG_PRI(i); + if (MATCH_PRI(f, fac, prilev)) { + match = true; + break; + } + } + if (match) + ASSIGN_FQ(); + } + STAILQ_INSERT_TAIL(&GlobalSign.SigGroups, + newsg, entries); + } + break; + case 3: + /* every file (with flag) gets one SG */ + for (f = Files; f; f = f->f_next) { + if (!(f->f_flags & FFLAG_SIGN)) { + f->f_sg = NULL; + continue; + } + ALLOC_SG(newsg); + newsg->spri = f->f_file; /* not needed but shows SGs */ + ASSIGN_FQ(); + STAILQ_INSERT_TAIL(&GlobalSign.SigGroups, + newsg, entries); + } + break; + } + DPRINTF((D_PARSE|D_SIGN), "sign_sg_init() set up these " + "Signature Groups:\n"); + STAILQ_FOREACH(sg, &GlobalSign.SigGroups, entries) { + DPRINTF((D_PARSE|D_SIGN), "SG@%p with SG=\"%d\", SPRI=\"%d\"," + " associated files:\n", sg, GlobalSign.sg, sg->spri); + STAILQ_FOREACH(fq, &sg->files, entries) { + DPRINTF((D_PARSE|D_SIGN), " f@%p with type %d\n", + fq->f, fq->f->f_type); + } + } + return true; +} + +/* + * free all SGs for a given algorithm + */ +void +sign_global_free(void) +{ + struct signature_group_t *sg, *tmp_sg; + struct filed_queue *fq, *tmp_fq; + + DPRINTF((D_CALL|D_SIGN), "sign_global_free()\n"); + STAILQ_FOREACH_SAFE(sg, &GlobalSign.SigGroups, entries, tmp_sg) { + if (!STAILQ_EMPTY(&sg->hashes)) { + /* send CB and SB twice to get minimal redundancy + * for the last few message hashes */ + sign_send_certificate_block(sg); + sign_send_certificate_block(sg); + sign_send_signature_block(sg, true); + sign_send_signature_block(sg, true); + sign_free_hashes(sg); + } + fq = STAILQ_FIRST(&sg->files); + while (fq != NULL) { + tmp_fq = STAILQ_NEXT(fq, entries); + free(fq); + fq = tmp_fq; + } + STAILQ_REMOVE(&GlobalSign.SigGroups, + sg, signature_group_t, entries); + free(sg); + } + sign_free_string_queue(&GlobalSign.sig2_delims); + + if (GlobalSign.privkey) { + GlobalSign.privkey = NULL; + } + if (GlobalSign.pubkey) { + EVP_PKEY_free(GlobalSign.pubkey); + GlobalSign.pubkey = NULL; + } + if(GlobalSign.mdctx) { + EVP_MD_CTX_destroy(GlobalSign.mdctx); + GlobalSign.mdctx = NULL; + } + if(GlobalSign.sigctx) { + EVP_MD_CTX_destroy(GlobalSign.sigctx); + GlobalSign.sigctx = NULL; + } + FREEPTR(GlobalSign.pubkey_b64); +} + +/* + * create and send certificate block + */ +bool +sign_send_certificate_block(struct signature_group_t *sg) +{ + struct filed_queue *fq; + struct buf_msg *buffer; + char *tstamp; + char payload[SIGN_MAX_PAYLOAD_LENGTH]; + char sd[SIGN_MAX_SD_LENGTH]; + size_t payload_len, sd_len, fragment_len; + size_t payload_index = 0; + + /* do nothing if CBs already sent or if there was no message in SG */ + if (!sg->resendcount + || ((sg->resendcount == SIGN_RESENDCOUNT_CERTBLOCK) + && STAILQ_EMPTY(&sg->hashes))) + return false; + + DPRINTF((D_CALL|D_SIGN), "sign_send_certificate_block(%p)\n", sg); + tstamp = make_timestamp(NULL, true); + + payload_len = snprintf(payload, sizeof(payload), "%s %c %s", tstamp, + GlobalSign.keytype, GlobalSign.pubkey_b64); + if (payload_len >= sizeof(payload)) { + DPRINTF(D_SIGN, "Buffer too small for syslog-sign setup\n"); + return false; + } + + while (payload_index < payload_len) { + if (payload_len - payload_index <= SIGN_MAX_FRAG_LENGTH) + fragment_len = payload_len - payload_index; + else + fragment_len = SIGN_MAX_FRAG_LENGTH; + + /* format SD */ + sd_len = snprintf(sd, sizeof(sd), "[ssign-cert " + "VER=\"%s\" RSID=\"%" PRIuFAST64 "\" SG=\"%d\" " + "SPRI=\"%d\" TBPL=\"%zu\" INDEX=\"%zu\" " + "FLEN=\"%zu\" FRAG=\"%.*s\" " + "SIGN=\"\"]", + GlobalSign.ver, GlobalSign.rsid, GlobalSign.sg, + sg->spri, payload_len, payload_index+1, + fragment_len, (int)fragment_len, + &payload[payload_index]); + assert(sd_len < sizeof(sd)); + assert(sd[sd_len] == '\0'); + assert(sd[sd_len-1] == ']'); + assert(sd[sd_len-2] == '"'); + + if (!sign_msg_sign(&buffer, sd, sizeof(sd))) + return 0; + DPRINTF((D_CALL|D_SIGN), "sign_send_certificate_block(): " + "calling fprintlog()\n"); + + STAILQ_FOREACH(fq, &sg->files, entries) { + /* we have to preserve the f_prevcount */ + int tmpcnt; + tmpcnt = fq->f->f_prevcount; + fprintlog(fq->f, buffer, NULL); + fq->f->f_prevcount = tmpcnt; + } + sign_inc_gbc(); + DELREF(buffer); + payload_index += fragment_len; + } + sg->resendcount--; + return true; +} + +/* + * determine the SG for a message + * returns NULL if -sign not configured or no SG for this priority + */ +struct signature_group_t * +sign_get_sg(int pri, struct filed *f) +{ + struct signature_group_t *sg, *rc = NULL; + + if (GlobalSign.rsid && f) + switch (GlobalSign.sg) { + case 0: + rc = f->f_sg; + break; + case 1: + case 2: + STAILQ_FOREACH(sg, &GlobalSign.SigGroups, entries) { + if (sg->spri >= (unsigned int)pri) { + rc = sg; + break; + } + } + break; + case 3: + if (f->f_flags & FFLAG_SIGN) + rc = f->f_sg; + else + rc = NULL; + break; + } + + DPRINTF((D_CALL|D_SIGN), "sign_get_sg(%d, %p) --> %p\n", pri, f, rc); + return rc; +} + +/* + * create and send signature block + * + * uses a sliding window for redundancy + * if force==true then simply send all available hashes, e.g. on shutdown + * + * sliding window checks implicitly assume that new hashes are appended + * to the SG between two calls. if that is not the case (e.g. with repeated + * messages) the queue size will shrink. + * this has no negative consequences except generating more and shorter SBs + * than expected and confusing the operator because two consecutive SBs will + * have same FMNn + */ +unsigned +sign_send_signature_block(struct signature_group_t *sg, bool force) +{ + char sd[SIGN_MAX_SD_LENGTH]; + size_t sd_len; + size_t sg_num_hashes = 0; /* hashes in SG queue */ + size_t hashes_in_sb = 0; /* number of hashes in current SB */ + size_t hashes_sent = 0; /* count of hashes sent */ + struct string_queue *qentry, *old_qentry; + struct buf_msg *buffer; + struct filed_queue *fq; + size_t i; + + if (!sg) return 0; + DPRINTF((D_CALL|D_SIGN), "sign_send_signature_block(%p, %d)\n", + sg, force); + + STAILQ_FOREACH(qentry, &sg->hashes, entries) + sg_num_hashes++; + + /* only act if a division is full */ + if (!sg_num_hashes + || (!force && (sg_num_hashes % SIGN_HASH_DIVISION_NUM))) + return 0; + + /* if no CB sent so far then do now, just before first SB */ + if (sg->resendcount == SIGN_RESENDCOUNT_CERTBLOCK) + sign_send_certificate_block(sg); + + /* shortly after reboot we have shorter SBs */ + hashes_in_sb = MIN(sg_num_hashes, SIGN_HASH_NUM); + + DPRINTF(D_SIGN, "sign_send_signature_block(): " + "sg_num_hashes = %zu, hashes_in_sb = %zu, SIGN_HASH_NUM = %d\n", + sg_num_hashes, hashes_in_sb, SIGN_HASH_NUM); + if (sg_num_hashes > SIGN_HASH_NUM) { + DPRINTF(D_SIGN, "sign_send_signature_block(): sg_num_hashes" + " > SIGN_HASH_NUM -- This should not happen!\n"); + } + + /* now the SD */ + qentry = STAILQ_FIRST(&sg->hashes); + sd_len = snprintf(sd, sizeof(sd), "[ssign " + "VER=\"%s\" RSID=\"%" PRIuFAST64 "\" SG=\"%d\" " + "SPRI=\"%d\" GBC=\"%" PRIuFAST64 "\" FMN=\"%" PRIuFAST64 "\" " + "CNT=\"%zu\" HB=\"", + GlobalSign.ver, GlobalSign.rsid, GlobalSign.sg, + sg->spri, GlobalSign.gbc, qentry->key, + hashes_in_sb); + while (hashes_sent < hashes_in_sb) { + assert(qentry); + sd_len += snprintf(sd+sd_len, sizeof(sd)-sd_len, "%s ", + qentry->data); + hashes_sent++; + qentry = STAILQ_NEXT(qentry, entries); + } + /* overwrite last space and close SD */ + assert(sd_len < sizeof(sd)); + assert(sd[sd_len] == '\0'); + assert(sd[sd_len-1] == ' '); + sd[sd_len-1] = '\0'; + sd_len = strlcat(sd, "\" SIGN=\"\"]", sizeof(sd)); + + if (sign_msg_sign(&buffer, sd, sizeof(sd))) { + DPRINTF((D_CALL|D_SIGN), "sign_send_signature_block(): calling" + " fprintlog(), sending %zu out of %zu hashes\n", + MIN(SIGN_MAX_HASH_NUM, sg_num_hashes), sg_num_hashes); + + STAILQ_FOREACH(fq, &sg->files, entries) { + int tmpcnt; + tmpcnt = fq->f->f_prevcount; + fprintlog(fq->f, buffer, NULL); + fq->f->f_prevcount = tmpcnt; + } + sign_inc_gbc(); + DELREF(buffer); + } + /* always drop the oldest division of hashes */ + if (sg_num_hashes >= SIGN_HASH_NUM) { + qentry = STAILQ_FIRST(&sg->hashes); + for (i = 0; i < SIGN_HASH_DIVISION_NUM; i++) { + old_qentry = qentry; + qentry = STAILQ_NEXT(old_qentry, entries); + STAILQ_REMOVE(&sg->hashes, old_qentry, + string_queue, entries); + FREEPTR(old_qentry->data); + FREEPTR(old_qentry); + } + } + return hashes_sent; +} + +void +sign_free_hashes(struct signature_group_t *sg) +{ + DPRINTF((D_CALL|D_SIGN), "sign_free_hashes(%p)\n", sg); + sign_free_string_queue(&sg->hashes); +} + +void +sign_free_string_queue(struct string_queue_head *sqhead) +{ + struct string_queue *qentry, *tmp_qentry; + + DPRINTF((D_CALL|D_SIGN), "sign_free_string_queue(%p)\n", sqhead); + STAILQ_FOREACH_SAFE(qentry, sqhead, entries, tmp_qentry) { + STAILQ_REMOVE(sqhead, qentry, string_queue, entries); + FREEPTR(qentry->data); + free(qentry); + } + assert(STAILQ_EMPTY(sqhead)); +} + +/* + * hash one syslog message + */ +bool +sign_msg_hash(char *line, char **hash) +{ + unsigned char md_value[EVP_MAX_MD_SIZE]; + unsigned char md_b64[EVP_MAX_MD_SIZE*2]; + /* TODO: exact expression for b64 length? */ + unsigned md_len = 0; + + DPRINTF((D_CALL|D_SIGN), "sign_msg_hash('%s')\n", line); + + SSL_CHECK_ONE(EVP_DigestInit_ex(GlobalSign.mdctx, GlobalSign.md, NULL)); + SSL_CHECK_ONE(EVP_DigestUpdate(GlobalSign.mdctx, line, strlen(line))); + SSL_CHECK_ONE(EVP_DigestFinal_ex(GlobalSign.mdctx, md_value, &md_len)); + + b64_ntop(md_value, md_len, (char *)md_b64, EVP_MAX_MD_SIZE*2); + *hash = strdup((char *)md_b64); + + DPRINTF((D_CALL|D_SIGN), "sign_msg_hash() --> \"%s\"\n", *hash); + return true; +} + +/* + * append hash to SG queue + */ +bool +sign_append_hash(char *hash, struct signature_group_t *sg) +{ + struct string_queue *qentry; + + /* if one SG is shared by several destinations + * prevent duplicate entries */ + if ((qentry = STAILQ_LAST(&sg->hashes, string_queue, entries)) + && !strcmp(qentry->data, hash)) { + DPRINTF((D_CALL|D_SIGN), "sign_append_hash('%s', %p): " + "hash already in queue\n", hash, sg); + return false; + } + + MALLOC(qentry, sizeof(*qentry)); + qentry->key = sign_assign_msg_num(sg); + qentry->data = hash; + STAILQ_INSERT_TAIL(&sg->hashes, qentry, entries); + DPRINTF((D_CALL|D_SIGN), "sign_append_hash('%s', %p): " + "#%" PRIdFAST64 "\n", hash, sg, qentry->key); + return true; +} + +/* + * sign one syslog-sign message + * + * requires a ssign or ssigt-cert SD element + * ending with ' SIGN=""]' in sd + * linesize is available memory (= sizeof(sd)) + * + * function will calculate signature and return a new buffer + */ +bool +sign_msg_sign(struct buf_msg **bufferptr, char *sd, size_t linesize) +{ + char *signature, *line; + size_t linelen, tlsprefixlen, endptr, newlinelen; + struct buf_msg *buffer; + + DPRINTF((D_CALL|D_SIGN), "sign_msg_sign()\n"); + endptr = strlen(sd); + + assert(endptr < linesize); + assert(sd[endptr] == '\0'); + assert(sd[endptr-1] == ']'); + assert(sd[endptr-2] == '"'); + + /* set up buffer */ + buffer = buf_msg_new(0); + buffer->timestamp = strdup(make_timestamp(NULL, !BSDOutputFormat)); + buffer->prog = appname; + buffer->pid = include_pid; + buffer->recvhost = buffer->host = LocalFQDN; + buffer->pri = 110; + buffer->flags = IGN_CONS|SIGN_MSG; + buffer->sd = sd; + + /* SD ready, now format and sign */ + if (!format_buffer(buffer, &line, &linelen, NULL, + &tlsprefixlen, NULL)) { + DPRINTF((D_CALL|D_SIGN), "sign_send_signature_block():" + " format_buffer() failed\n"); + buffer->sd = NULL; + DELREF(buffer); + return false; + } + if (!sign_string_sign(line+tlsprefixlen, &signature)) { + DPRINTF((D_CALL|D_SIGN), "sign_send_signature_block():" + " sign_string_sign() failed\n"); + buffer->sd = NULL; + DELREF(buffer); + FREEPTR(line); + return false; + } + FREEPTR(line); + sd[endptr-2] = '\0'; + newlinelen = strlcat(sd, signature, linesize); + newlinelen = strlcat(sd, "\"]", linesize); + + if (newlinelen >= linesize) { + DPRINTF(D_SIGN, "sign_send_signature_block(): " + "buffer too small\n"); + buffer->sd = NULL; + DELREF(buffer); + return false; + } + assert(newlinelen < linesize); + assert(sd[newlinelen] == '\0'); + assert(sd[newlinelen-1] == ']'); + assert(sd[newlinelen-2] == '"'); + + buffer->sd = strdup(sd); + *bufferptr = buffer; + return true; +} + +/* + * sign one string + */ +bool +sign_string_sign(char *line, char **signature) +{ + char buf[SIGN_MAX_LENGTH+1]; + unsigned char sig_value[SIGN_B64SIGLEN_DSS]; + unsigned char sig_b64[SIGN_B64SIGLEN_DSS]; + unsigned sig_len = 0; + char *p, *q; + /* + * The signature is calculated over the completely formatted + * syslog-message, including all of the PRI, HEADER, and hashes + * in the hash block, excluding spaces between fields, and also + * excluding the signature field (SD Parameter Name "SIGN", "=", + * and corresponding value). + * + * -- I am not quite sure which spaces are to be removed. + * Only the ones inside the "ssign" element or those between + * header fields as well? + */ + /* removes the string ' SIGN=""' */ + for (p = line, q = buf; + *p && (q - buf <= SIGN_MAX_LENGTH);) { + if (strncmp(p, " SIGN=\"\"", 8) == 0) + p += 8; + *q++ = *p++; + } + *q = '\0'; + + SSL_CHECK_ONE(EVP_SignInit(GlobalSign.sigctx, GlobalSign.sig)); + SSL_CHECK_ONE(EVP_SignUpdate(GlobalSign.sigctx, buf, q-buf)); + assert(GlobalSign.privkey); + SSL_CHECK_ONE(EVP_SignFinal(GlobalSign.sigctx, sig_value, &sig_len, + GlobalSign.privkey)); + + b64_ntop(sig_value, sig_len, (char *)sig_b64, sizeof(sig_b64)); + *signature = strdup((char *)sig_b64); + + DPRINTF((D_CALL|D_SIGN), "sign_string_sign('%s') --> '%s'\n", + buf, *signature); + return *signature != NULL; +} + +void +sign_new_reboot_session(void) +{ + struct signature_group_t *sg; + + DPRINTF((D_CALL|D_SIGN), "sign_new_reboot_session()\n"); + + /* global counters */ + GlobalSign.gbc = 0; + /* might be useful for later analysis: + * rebooted session IDs are sequential, + * normal IDs are almost always not */ + GlobalSign.rsid++; + + assert(GlobalSign.sg <= 3); + /* reset SGs */ + STAILQ_FOREACH(sg, &GlobalSign.SigGroups, entries) { + sg->resendcount = SIGN_RESENDCOUNT_CERTBLOCK; + sg->last_msg_num = 1; + } +} + +/* get msg_num, increment counter, check overflow */ +uint_fast64_t +sign_assign_msg_num(struct signature_group_t *sg) +{ + uint_fast64_t old; + + old = sg->last_msg_num++; + if (sg->last_msg_num > SIGN_MAX_COUNT) + sign_new_reboot_session(); + return old; +} + + +/* increment gbc, check overflow */ +void +sign_inc_gbc(void) +{ + if (++GlobalSign.gbc > SIGN_MAX_COUNT) + sign_new_reboot_session(); +} +#endif /* !DISABLE_SIGN */ diff --git a/usr.sbin/syslogd/sign.h b/usr.sbin/syslogd/sign.h new file mode 100644 index 000000000..af7868253 --- /dev/null +++ b/usr.sbin/syslogd/sign.h @@ -0,0 +1,207 @@ +/* $NetBSD: sign.h,v 1.2 2008/11/07 07:36:38 minskim Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * sign.h + * + */ +#ifndef SIGN_H_ +#define SIGN_H_ + +#include +#include +#include +#include +#include +#include + +/* default Signature Group value, + * defines signature strategy: + * 0 one global SG + * 1 one SG per PRI + * 2 SGs for PRI ranges + * 3 other (SGs not defined by PRI) + * + * We use '3' and assign one SG to every destination (=struct filed) + */ +#define SIGN_SG 3 + +/* maximum value for several counters in -sign */ +#define SIGN_MAX_COUNT 9999999999 + +/* + * many of these options could be made user configurable if desired, + * but I do not see the need for that + */ + +/* redundancy options */ +/* + * note on the implementation of redundancy: + * - certificate blocks: sending the first CB just before first SB. + * after that domark() triggers resends until resend count is reached. + * - signature blocks: to send every hash n times I use a sliding window. + * the hashes in every SB are grouped into n divisions: + * * the 1st hashcount/n hashes are sent for the 1st time + * * the 2nd hashcount/n hashes are sent for the 2nd time + * * ... + * * the n-th hashcount/n hashes are sent for the n-th time + * (and deleted thereafter) + */ +#define SIGN_RESENDCOUNT_CERTBLOCK 2 +#define SIGN_RESENDCOUNT_HASHES 3 + +/* maximum length of syslog-sign messages should be <= 2048 by standard + * and should be >= 1024 to be long enough. + * be careful with small values because there is no check for a lower bound + * thus the following derived values would become negative. + */ +#define SIGN_MAX_LENGTH 2048 +/* the length we can use for the SD and keep the + * message length with header below 2048 octets */ +#define SIGN_MAX_SD_LENGTH (SIGN_MAX_LENGTH - 1 - HEADER_LEN_MAX) +/* length of signature, currently only for DSA */ +#define SIGN_B64SIGLEN_DSS 64+1 +/* the maximum length of one payload fragment: + * max.SD len - text - max. field lengths - sig len */ +#define SIGN_MAX_FRAG_LENGTH (SIGN_MAX_SD_LENGTH - 82 - 38 - SIGN_B64SIGLEN_DSS) +/* the maximum length of one signature block: + * max.SD len - text - max. field lens - sig len */ +#define SIGN_MAX_SB_LENGTH (SIGN_MAX_SD_LENGTH - 72 - 40 - SIGN_B64SIGLEN_DSS) +/* the maximum number of hashes pec signature block */ +#define SIGN_MAX_HASH_NUM (SIGN_MAX_SB_LENGTH / (GlobalSign.md_len_b64+1)) +/* number of hashes in one signature block */ +#define SIGN_HASH_NUM_WANT 100 +/* make sure to consider SIGN_MAX_HASH_NUM and + * to have a SIGN_HASH_NUM that is a multiple of SIGN_HASH_DIVISION_NUM */ +#define SIGN_HASH_DIVISION_NUM (MIN(SIGN_HASH_NUM_WANT, SIGN_MAX_HASH_NUM) \ + / SIGN_RESENDCOUNT_HASHES) +#define SIGN_HASH_NUM (SIGN_HASH_DIVISION_NUM * SIGN_RESENDCOUNT_HASHES) + +/* the length of payload strings + * since the payload is fragmented there is no technical limit + * it just has to be big enough to hold big b64 encoded PKIX certificates + */ +#define SIGN_MAX_PAYLOAD_LENGTH 20480 + +/* length of generated DSA keys for signing */ +#define SIGN_GENCERT_BITS 1024 + +#define SSL_CHECK_ONE(exp) do { \ + if ((exp) != 1) { \ + DPRINTF(D_SIGN, #exp " failed in %d: %s\n", __LINE__, \ + ERR_error_string(ERR_get_error(), NULL)); \ + return 1; \ + } \ +} while (/*CONSTCOND*/0) + +/* structs use uint_fast64_t in different places because the standard + * requires values in interval [0:9999999999 = SIGN_MAX_COUNT] */ + +/* queue of C-Strings (here used for hashes) */ +struct string_queue { + uint_fast64_t key; + char *data; + STAILQ_ENTRY(string_queue) entries; +}; +STAILQ_HEAD(string_queue_head, string_queue); + +/* queue of destinations (used associate SGs and fileds) */ +struct filed_queue { + struct filed *f; + STAILQ_ENTRY(filed_queue) entries; +}; +STAILQ_HEAD(filed_queue_head, filed_queue); + +/* queue of Signature Groups */ +struct signature_group_t { + unsigned spri; + unsigned resendcount; + uint_fast64_t last_msg_num; + struct string_queue_head hashes; + struct filed_queue_head files; + STAILQ_ENTRY(signature_group_t) entries; +}; +STAILQ_HEAD(signature_group_head, signature_group_t); + +/* all global variables for sign */ +/* note that there is one object of this type which might only be + * partially filled. + * The fields .sg and .sig2_delims are set by init() and are always + * valid. A value >0 in field .rsid indicates whether the rest of the + * structure was already set by sign_global_init(). + */ +struct sign_global_t { + /* params for signature block, named as in RFC nnnn */ + const char *ver; + uint_fast64_t rsid; + int sg; + uint_fast64_t gbc; + struct signature_group_head SigGroups; + struct string_queue_head sig2_delims; + + EVP_PKEY *privkey; + EVP_PKEY *pubkey; + char *pubkey_b64; + char keytype; + + EVP_MD_CTX *mdctx; /* hashing context */ + const EVP_MD *md; /* hashing method/algorithm */ + unsigned md_len_b64; /* length of b64 hash value */ + + EVP_MD_CTX *sigctx; /* signature context */ + const EVP_MD *sig; /* signature method/algorithm */ + unsigned sig_len_b64; /* length of b64 signature */ +}; + +bool sign_global_init(struct filed*); +bool sign_sg_init(struct filed*); +bool sign_get_keys(void); +void sign_global_free(void); +struct signature_group_t* sign_get_sg(int, struct filed*); +bool sign_send_certificate_block(struct signature_group_t*); +unsigned sign_send_signature_block(struct signature_group_t*, bool); +void sign_free_hashes(struct signature_group_t*); +void sign_free_string_queue(struct string_queue_head*); +bool sign_msg_hash(char*, char**); +bool sign_append_hash(char*, struct signature_group_t*); +bool sign_msg_sign(struct buf_msg**, char*, size_t); +bool sign_string_sign(char*, char**); +void sign_new_reboot_session(void); +void sign_inc_gbc(void); +uint_fast64_t sign_assign_msg_num(struct signature_group_t*); + +#endif /* SIGN_H_ */ diff --git a/usr.sbin/syslogd/sign.html b/usr.sbin/syslogd/sign.html new file mode 100644 index 000000000..462bbc211 --- /dev/null +++ b/usr.sbin/syslogd/sign.html @@ -0,0 +1,146 @@ + + + +NetBSD & Google's Summer of Code: Martin Schuette - Improve syslogd (syslogd) + + + +

syslog-sign

+

syslog-sign defines digital signatures for logfiles. This provides end-to-end authentication for network transports, enables the detection of lost UDP messages, and also makes it possible to check a log archive for later modifications (assuming the private key was kept safe).

+ +

Signature Groups

+

A basic concept of syslog-sign is the signature group which describes a set of messages that are grouped and signed together. Their purpose becomes clear with an example: assume you split your messages to two logservers serverA and serverB. Now if all messages were singed as one stream, then a) where do the signatures go to? and b) how could serverA, having only hashes and signatures, decide which message are missing and which are on serverB?
+Thus the messages are selected into two signature groups containing all signatures for messages to serverA and serverB respectively. Then every server has its own messages and its own signatures to verify them.

+

There are three predefined and one custom signature groups:

+
    +
  1. one global signature group, useful if all messages go to one central logserver anyway
  2. +
  3. every syslog priority (=combination of facility and severity) gets its own group, i.e. 192 of them, useful if there are lots of different destinations which all receive messages with different priorities
  4. +
  5. take the priorities and split them into intervals, useful to define bigger subsets, e.g. one signature group for the mail facility and two for everything else
  6. +
  7. not defined and reserved for custom strategy. I use this to have one signature group for every configured destination. In this case the selector in syslog.conf will determine which messages go into one group; it is also the only strategy that allows a message to be in multiple groups.
  8. +
+

Every signature group has several attributes and only the combination of several values determines one signature group unambiguously. Currently the key to identify a signature group is the tuple (hostname, reboot session ID, SG value, SPRI value).

+ +

Configuration/Activation

+

syslog-sign is enabled with the option "sign_sg" in syslog.conf. The value selects the signature group strategy, so for example the line "sign_sg=0" enables syslog-sign with one signature group.

+

The SG="2" strategy is the only one that might require additional configuration. When selected (with "sign_sg=2") the default is to use one signature group per facility (kernel, user, mail, ...). To allow custom configuration there is an additional option "sign_sg2_delim" to specify the numerical SPRI values, i.e. the boundaries betwen the signature groups.
+Example: With "sign_sg2_delim = 15 31" syslogd will set up three signature groups: one for all priorities x ≤ 15 (kernel.*,user.*), one for priorities 15 < x ≤ 31 (mail.*), and one for all priorities x > 31.

+ +

Key, Signature, and Hash Types

+

The current internet draft defines two values for the VERsion field for using either SHA-1 or SHA-256 hashes. Both versions mandate DSA keys and signatures.
+There are several alternatives for sending the public key in the initial Certificate Block. If a X.509 certificate is available (for TLS connections) then syslogd will use key type 'C' (PKIX) and send the certificate in DER encoding. Otherwise it generates a new DSA key and uses key type 'K' (public key) to send the public key in DER encoding.

+ +

Redundancy

+

As mentioned above one design target of syslog-sign is the detection of lost messages, e.g. due to UDP datagram loss. So one has to take extra precaution to prevent lost signature messages and send them multiple times.
+This implementation sends the first Certificate block only on demand, just before the first Signature Block. After that it is resent n times with several seconds delay. The Signature Blocks are not repeated but use a sliding window so that every message hash is included in m sequential Signature Blocks.

+ +

Verification

+

Sending signatures is only half of the job, -- they have to be verified as well. I used Perl to write an offline verification tool that reads a complete logfile and prints all messages in their correct order. See the example below for a sample usage and output.

+ +

Example

+

Here is an example of a signed message sequence. I let syslogd generate me a DSA key for a self-signed X.509 certificate and use that for signing. I also changed one message so you can see the resulting verification output below.

+ +
+$ cat test.log
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg0
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg1
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg2
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg3
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg4
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg5
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg6
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg7
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg8
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg9
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg10
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg11
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - modified msg12
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg13
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg14
+<110>1 2008-08-02T01:09:27.773505+02:00 host.example.org syslogd - - [ssign-cert VER="0111" RSID="1217632162" SG="3" SPRI="0" TBPL="1059" INDEX="1" FLEN="1059" FRAG="2008-08-02T01:09:27.773464+02:00 C MIIC+jCCArmgAwIBAwIBATAJBgcqhkjOOAQDMCIxIDAeBgNVBAMTF2NvcmRlbGlhLm1zY2h1ZXR0ZS5uYW1lMB4XDTA4MDczMDIyMDYyMloXDTA5MDczMDIyMDYyMlowIjEgMB4GA1UEAxMXY29yZGVsaWEubXNjaHVldHRlLm5hbWUwggG3MIIBKwYHKoZIzjgEATCCAR4CgYEA92S335Kxy2TTMfdg9Vi/CJvyDCHMHpPYxWwEkEI26xEdKybzLghTfbG/RZw/nnFuhRTH4Xe6GVvlFi2zIzySSClXr+zyXg/D9uHyiVL5TEsu8uQT2IREmGOB8pu70FukL9nQGOr82YxuRFQzZ1p6KltIggivi5ffR4B33+1xoSkCFQDYe5GJKM9Cw6nkLngHkzFGRmcXIQKBgDbHeOLBKYLkRZyRpXd0aTNU2igcKTWyWlUTySJuv/iTAeB09p9WyTIPyAhtqN77CIwX8Ui2jGu6NYT6TWEYJVvL+C/TvddAvAMyefv+w+HPNF2L77IVrjNVRCneERoNKlWc6IzjKH3otl/Lh2D7NAWRid55vxF6Z0oO459+4vpRA4GFAAKBgQCzcJVR343IRntcQs8aENs/QMxoxHN6JVdpSLB9moY5/RC9ooxz32fkakSL0s8zLITLt/y+yzf0F/9JhmTC1XeD8gvPBesE6dc0ZzPCos0hg8WpKUWR0YqXFDOC//uBwIa94DncC8xZ0mCwavno6gtkz57S7ywSwnmrdjhmpdAZuqOBgDB+MBEGCWCGSAGG+EIBAQQEAwIGQDAzBglghkgBhvhCAQ0EJhYkYXV0by1nZW5lcmF0ZWQgYnkgdGhlIE5ldEJTRCBzeXNsb2dkMCYGCWCGSAGG+EIBDAQZFhdjb3JkZWxpYS5tc2NodWV0dGUubmFtZTAMBgNVHRMBAf8EAjAAMAkGByqGSM44BAMDMAAwLQIUZcsHdrbuyx9lR3tyyeiJvClj0B8CFQC+5+NlulgCd/yoSlLPZgsTHYmCYA==" SIGN="MC0CFFEHx8UX391lbmhbisJNS0zLGD/WAhUAuMfCO0BWtARt2vEWHbM2mAe2k+o="]
+<110>1 2008-08-02T01:09:27.778347+02:00 host.example.org syslogd - - [ssign VER="0111" RSID="1217632162" SG="3" SPRI="0" GBC="1" FMN="1" CNT="15" HB="siUJM358eYFHOS2K0MTlveWeH/U= zTxfthW8WqmtFhOG4k/+ZxkirTA= j9dubU1GNVp7qWShwph/w32nD08= XQDLZ/NuwirmLdMORtm84r9kIW4= RNDFNCo7hiCsK/EKumsPBbFHNZA= ANiE3KbY948J6cEB640fAtWXuO4= e2M/OqjHDfxLVUSPt1CsNJHm9wU= Y+racQst7F1gR8eEUh8O7o+M53s= JAMULRxjMPbOO5EhhKbsUkAwbl0= pd+N5kmlnyQ0BoItELd/KWQrcMg= dsMQSzPHIS6S3Vaa23/t7U8JAJ4= i4rE3x7N4qyQGTkmaWHsWDFP9SY= qgTqV4EgfUFd3uZXNPvJ25erzBI= XW0YrME5kQEh+fxhg1fetnWxfIc= 7YPcRHsDwXWnQuGRWaJtFWw9hus=" SIGN="MCwCFF5hS5GTLxLDwsDCUmOnHhzkmWzbAhRJ0io+LBKM6Ux/cM7eqZ6eRAI11Q=="]
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg15
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg16
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg17
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg18
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg19
+<110>1 2008-08-02T01:09:32.399406+02:00 host.example.org syslogd - - [ssign VER="0111" RSID="1217632162" SG="3" SPRI="0" GBC="4" FMN="1" CNT="20" HB="siUJM358eYFHOS2K0MTlveWeH/U= zTxfthW8WqmtFhOG4k/+ZxkirTA= j9dubU1GNVp7qWShwph/w32nD08= XQDLZ/NuwirmLdMORtm84r9kIW4= RNDFNCo7hiCsK/EKumsPBbFHNZA= ANiE3KbY948J6cEB640fAtWXuO4= e2M/OqjHDfxLVUSPt1CsNJHm9wU= Y+racQst7F1gR8eEUh8O7o+M53s= JAMULRxjMPbOO5EhhKbsUkAwbl0= pd+N5kmlnyQ0BoItELd/KWQrcMg= dsMQSzPHIS6S3Vaa23/t7U8JAJ4= i4rE3x7N4qyQGTkmaWHsWDFP9SY= qgTqV4EgfUFd3uZXNPvJ25erzBI= XW0YrME5kQEh+fxhg1fetnWxfIc= 7YPcRHsDwXWnQuGRWaJtFWw9hus= PIvLm0mh+he5+PDihG1p7sQlx8k= lPzUvx0I1VwSGWV7yKF9W//Yb2U= X+PWYcx5AXnsDVSNAHLZUGk5ioY= okXY88MGG4QybrYMf8HJN23WO1Y= HcaPyHfQ2s1SuSciTKw4woYWuMg=" SIGN="MCwCFFr0i6taT1vWowR7yc5bEQxFfY7/AhQBCK+rBNPgzR0vUgxPeARvD24kIQ=="]
+
+

Just in case you wonder about the different timestamps: The messages were send with a normal syslog(3), so the syslogd received them in BSD Syslog format without subsecond resolution.

+
+
+$ perl verify.pl --help
+
+syslog-sign verifier
+reads logfile and verifies message signatures
+
+Notes:
+- By default uses only SHA-1 hashes. Use option "--sha256" to use only
+  SHA-256 and "--sha1 --sha256"to use both types.
+- Some status messages are printed to stderr.
+  Use option "--quiet" to disable them.
+- All verified messages are printed with their identifying signature group.
+  Every line starts with a comma-separated tuple: hostname, reboot session ID,
+  SG value, SPRI value, and message number.
+- If only one hash is used then all messages not signed are printed as well.
+
+Limitations: handles only key types 'C' (PKIX) and 'K' (public key)
+  with DSA keys and signatures
+
+Command Line Options:
+  -i  --in         input file (default: stdin)
+  -o  --out        output file for verified messages (default: stdout)
+  -u  --unsigned   output file for unsigned messages (default: stdout)
+      --sha1       use SHA-1 hashes (default)
+      --sha256     use SHA-256 hashes
+  -v  --verbose    shows some internals (every CB,SB,hash,...)
+  -q  --quiet      no status messages to stderr
+  -h  --help       this help
+
+$ perl verify.pl -i test.log
+reading input...
+processing CBs...
+decoding SGs...
+got PKIX DSA key
+verifying CBs...
+verified CB and got key for SG: (host.example.org,1217632162,0111,3,0), start: 2008-08-02T01:09:27.773464+02:00
+now process SBs
+signed messages:
+host.example.org,1217632162,0111,3,0,1  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg0
+host.example.org,1217632162,0111,3,0,2  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg1
+host.example.org,1217632162,0111,3,0,3  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg2
+host.example.org,1217632162,0111,3,0,4  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg3
+host.example.org,1217632162,0111,3,0,5  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg4
+host.example.org,1217632162,0111,3,0,6  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg5
+host.example.org,1217632162,0111,3,0,7  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg6
+host.example.org,1217632162,0111,3,0,8  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg7
+host.example.org,1217632162,0111,3,0,9  <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg8
+host.example.org,1217632162,0111,3,0,10 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg9
+host.example.org,1217632162,0111,3,0,11 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg10
+host.example.org,1217632162,0111,3,0,12 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg11
+host.example.org,1217632162,0111,3,0,13 **** msg lost
+host.example.org,1217632162,0111,3,0,14 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg13
+host.example.org,1217632162,0111,3,0,15 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg14
+host.example.org,1217632162,0111,3,0,16 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg15
+host.example.org,1217632162,0111,3,0,17 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg16
+host.example.org,1217632162,0111,3,0,18 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg17
+host.example.org,1217632162,0111,3,0,19 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg18
+host.example.org,1217632162,0111,3,0,20 <15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - msg19
+messages without signature:
+<15>1 2008-08-02T02:09:27+02:00 host.example.org test 6255 - - modified msg12
+
+ +
+ + + +
+SourceForge.net Logo + + + + +
Martin Schütte <info@mschuette.name>
$Id: sign.html,v 1.1 2008/10/31 16:12:19 christos Exp $
+
+ + + diff --git a/usr.sbin/syslogd/syslog.conf.5 b/usr.sbin/syslogd/syslog.conf.5 new file mode 100644 index 000000000..bed1b409b --- /dev/null +++ b/usr.sbin/syslogd/syslog.conf.5 @@ -0,0 +1,652 @@ +.\" $NetBSD: syslog.conf.5,v 1.21 2013/11/10 00:13:50 wiz Exp $ +.\" +.\" Copyright (c) 1990, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)syslog.conf.5 8.1 (Berkeley) 6/9/93 +.\" +.Dd November 9, 2013 +.Dt SYSLOG.CONF 5 +.Os +.Sh NAME +.Nm syslog.conf +.Nd +.Xr syslogd 8 +configuration file +.Sh DESCRIPTION +The +.Nm +file is the configuration file for the +.Xr syslogd 8 +program. +It consists of extended options (lines with one key="value" assignment) +and blocks of lines separated by +.Em program +and +.Em hostname +specifications, with each line containing two fields: the +.Em selector +field which specifies the types of messages and priorities to which the +line applies, and an +.Em action +field which specifies the action to be taken if a message +.Xr syslogd 8 +receives matches the selection criteria. +The +.Em selector +field is separated from the +.Em action +field by one or more tab characters. +.Pp +The +.Em Selectors +function +are encoded as a +.Em facility , +a period +.Pq Sq \&. , +an optional set of comparison flags +.Pq Bo ! Bc Bq \*[Lt]=\*[Gt] , +and a +.Em level , +with no intervening white-space. +Both the +.Em facility +and the +.Em level +are case insensitive. +.Pp +The +.Em facility +describes the part of the system generating the message, and is one of +the following keywords: auth, authpriv, cron, ftp, daemon, kern, lpr, +mail, mark, news, syslog, user, uucp and local0 through local7. +These keywords (with the exception of mark) correspond to the +similar +.Dq Dv LOG_ +values specified to the +.Xr openlog 3 +and +.Xr syslog 3 +library routines. +.Pp +The +.Em comparison flags +may be used to specify exactly what levels are logged. +If unspecified, the default comparison is +.Sq \*[Gt]= +.Pq greater than or equal to , +or, if the +.Fl U +option is passed to +.Xr syslogd 8 , +.Sq = +.Pq equal to . +Comparison flags beginning with +.So ! Sc +will have their logical sense inverted. +Thus, +.Sq !=info +means all levels except info and +.Sq !notice +has the same meaning as +.Sq \*[Lt]notice . +.Pp +The +.Em level +describes the severity of the message, and is a keyword from the +following ordered list (higher to lower): emerg, alert, crit, err, +warning, notice, info and debug. +These keywords correspond to the +similar +.Pq Dv LOG_ +values specified to the +.Xr syslog 3 +library routine. +.Pp +Each block of lines is separated from the previous block by a +.Em program +or +.Em hostname +specification. +A block will only log messages corresponding to the most recent +.Em program +and +.Em hostname +specifications given. +Consider the case of a block that selects +.Ql pppd +as the +.Em program , +directly followed by a block that selects messages from the +.Em hostname +.Ql dialhost . +The second block will log only messages from the +.Xr pppd 8 +program from the host +.Sq dialhost . +.Pp +A +.Em program +specification of the form +.Ql #!+prog1,prog2 +or +.Ql !+prog1,prog2 +will cause subsequent blocks to be applied to messages logged by the +specified programs. +A +.Em program +specification of the form +.Ql #!-prog1,prog2 +or +.Ql !-prog1,prog2 +will cause subsequent blocks to be applied to messages logged by programs +other than the ones specified. +A +.Em program +specification of the form +.Ql #!prog1,prog2 +or +.Ql !prog1,prog2 +is equivalent to +.Ql !+prog1,prog2 . +Program selectors may also match kernel-generated messages. +For example, a program specification of +.Ql !+subsys +will match kernel-generated messages of the form +.Ql subsys: here is a message . +The special specification +.Ql !* +will cause subsequent blocks to apply to all programs. +.Pp +A +.Em hostname +specification of the form +.Ql #+host1,host2 +or +.Ql +host1,host2 +will cause subsequent blocks to be applied to messages received from +the specified hosts. +A +.Em hostname +specification of the form +.Ql #-host1,host2 +or +.Ql -host1,host2 +will cause subsequent blocks to be applied to messages from hosts other +than the ones specified. +If the hostname is given as +.Ql @ , +the local hostname will be used. +The special specification +.Ql +* +will cause subsequent blocks to apply to all hosts. +.Pp +See +.Xr syslog 3 +for a further descriptions of both the +.Em facility +and +.Em level +keywords and their significance. +It is preferred that selections be made based on +.Em facility +rather than +.Em program , +since the latter can vary in a networked environment. +However, there are cases where a +.Em facility +may be too broadly defined. +.Pp +If a received message matches the specified +.Em facility , +and the specified +.Em level +comparison is true, +and the first word in the message after the date matches the +.Em program , +the action specified in the +.Em action +field will be taken. +.Pp +Multiple +.Em selectors +may be specified for a single +.Em action +by separating them with semicolon +.Pq Sq \&; +characters. +It is important to note, however, that each +.Em selector +can modify the ones preceding it. +.Pp +Multiple +.Em facilities +may be specified for a single +.Em level +by separating them with comma +.Pq Sq \&, +characters. +.Pp +An asterisk +.Pq Sq \&* +can be used to specify all +.Em facilities +or all +.Em levels . +.Pp +The special +.Em facility +.Dq mark +receives a message at priority +.Dq info +every 20 minutes +(see +.Xr syslogd 8 ) . +This is not enabled by a +.Em facility +field containing an asterisk. +.Pp +The special +.Em level +.Dq none +disables a particular +.Em facility . +.Pp +The +.Em action +field of each line specifies the action to be taken when the +.Em selector +field selects a message. +There are five forms: +.Bl -bullet +.It +A pathname (beginning with a leading slash). +Selected messages are appended to the file, unless +pathname points to an existing FIFO special file. +.Xr syslogd 8 +treats FIFO specially by opening them in non-blocking mode and +discarding messages sent when no reader is listening on the other side. +.Pp +To ensure that kernel messages are written to disk promptly, +.Xr syslogd 8 +calls +.Xr fsync 2 +after writing messages from the kernel. +Other messages are not synced explcitly. +You may disable syncing of files specified to receive kernel messages +by prefixing the pathname with a minus sign +.Ql - . +Note that use of this option may cause the loss of log information in +the event of a system crash immediately following the write attempt. +However, using this option may prove to be useful if your system's +kernel is logging many messages. +.Pp +Normally the priority and version is not written to file. +In order to use syslog-sign you may prefix a pathname with the plus sign +.Ql + . +If both switches are used the order has to be +.Ql +- . +.It +A hostname (preceded by an at +.Pq Sq @ +sign). +Selected messages are forwarded to the +.Xr syslogd 8 +program on the named host with UDP. +.It +A hostname preceded by an at +.Pq Sq @ +sign and enclosed in brackets +.Pq Sq [] +. +Selected messages are forwarded with TLS to the +.Xr syslogd 8 +program on the named host. +After the closing bracket a colon +.Pq Sq \&: +and a port or service name may be appended. +Additional options are configured in parantheses in the form of key="value". +Recognized keywords are +.Ar subject , +.Ar fingerprint , +.Ar cert , +and +.Ar verify . +.It +A comma separated list of users. +Selected messages are written to those users +if they are logged in. +.It +An asterisk. +Selected messages are written to all logged-in users. +.It +A vertical bar +.Pq Sq | +followed by a command to which to pipe the selected messages. +The command string is passed to +.Pa /bin/sh +for evaluation, so the usual shell metacharacters or input/output +redirection can occur. +(Note 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 exits with a +signal.) +The command itself runs with +.Em stdout +and +.Em stderr +redirected to +.Pa /dev/null . +Upon receipt of a +.Dv SIGHUP , +.Xr syslogd 8 +will close the pipe to the process. +If the process does not exit voluntarily, it will be sent a +.Dv SIGTERM +signal after a grace period of up to 60 seconds. +.Pp +The command will only be started once data arrives that should be +piped to it. +If the command exits, it will be restarted as necessary. +.Pp +If it is desired that the subprocess should receive exactly one line of +input, this can be achieved by exiting after reading and processing the +single line. +A wrapper script can be used to achieve this effect, if necessary. +Note that this method can be very resource-intensive if many log messages +are being piped through the filter. +.Pp +Unless the command is a full pipeline, it may be useful to +start the command with +.Em exec +so that the invoking shell process does not wait for the command to +complete. +Note that the command is started with the UID of the +.Xr syslogd 8 +process, normally the superuser. +.Pp +Just like with files a plus sign +.Ql + +will leave the priority and version information intact. +.El +.Pp +Blank lines and lines whose first non-blank character is a hash +.Pq Sq # +character are ignored. +.Sh "TLS OPTIONS" +Additional options are used for TLS configuration: +.Bl -ohang +.It Em tls_server +Enables TLS server mode. +.It Em tls_bindport +Service name or port number to bind to. +Default is +.Sq syslog . +.Em As long as no official port is assigned this option is required +.Em for TLS servers. +.It Em tls_bindhost +Hostname or IP to bind to. +.It Em tls_gen_cert +Automatically generate a private key and certificate. +.It Em tls_key +File with private key. +Default is +.Sq /etc/openssl/default.key +.It Em tls_cert +File with certificate to use. +Default is +.Sq /etc/openssl/default.crt +.It Em tls_ca +File with CA certificate to use. +.It Em tls_cadir +Directory containing CA certificates. +.It Em tls_verify +If set to +.Sq off +then certificate authentication is skipped. +.It Em tls_allow_fingerprints +List of fingerprints of trusted client certificates. +.It Em tls_allow_clientcerts +List of filenames with trusted client certificates. +.El +.Sh "TLS AUTHENTICATION" +One function of TLS is mutual authentication of client and server. +Unless authentication is disabled by setting +.Sq tls_verify=off +the following rules are used: +.Ss "As client:" +A client can be configured not to check a server's certificate by setting the +parameter +.Ar verify +to +.Sq off . +If the server's certificate is signed by a trusted CA then it is checked +if its hostname or IP is given in its certificate (as a CommonName, as a +DNS SubjectAltName, or as an IP SubjectAltName). +If any match is found then the server is authenticated. +If a +.Ar subject +parameter is given then it is can satisfy this test as well. +This allows DNS-independent configurations using the server's IP address in the +destination and adding its hostname as +.Ar subject +to authenticate the TLS connection without having to add the IP to the X.509 +certificate. +.Pp +If no CA is used or no trust path between CA and server certificate exists, then +hash value of the server's certificate is compared with the hash given in +.Ar fingerprint +and the hash of the certificate in +.Ar cert . +If the hashes are equal then the server is authenticated. +.Ss "As server:" +If using a CA and the client's certificate is signed by it then the client is +authenticated. +Otherwise the hash of the client's certificate is compared with the hashes given +in +.Ar tls_allow_fingerprints +and the hashes of the certificates given in +.Ar tls_allow_clientcerts . +On any match the client is authenticated. +.Sh BUFFERING +.Xr syslogd 8 +is able to buffer temporary not writeable messages in memory. +To limit the memory consumed for this buffering the following optons may be +given: +.Bl -ohang +.It Em file_queue_length +.It Em pipe_queue_length +.It Em tls_queue_length +The maximum number of messages buffered for one destination of type tls, file, +or pipe respectively. +Defaults are +.Sq 1024 , +.Sq 1024 , +and +.Sq -1 +(no limit). +.It Em file_queue_size +.It Em pipe_queue_size +.It Em tls_queue_size +The maximum memory usage in bytes of messages buffered for one destination. +Defaults are +.Sq 1M , +.Sq 1M , +and +.Sq 16M . +.El +.Sh SIGNING +.Xr syslogd 8 +is able to digitally sign all processed messages. +The used protocol is defined by RFC 5848 (syslog-sign): +at the start of a session the signing sender sends so called certificate +blocks containing its public key; after that it periodically sends a signed +message containing hashes of previous messages. +.Pp +To detect later manipulation one has to keep a copy of the key used for +signing (otherwise an attacker could alter the logs and sign them with his +his own key). +If TLS is used with a DSA key then the same key will be used for signing. +This is the recommended setup because it makes it easy to have copies of +the certificate (with the public key) in backups. +Otherwise new keys are generated on every restart and for certain verification +it is necessary to have copies of all used keys. +So logging only to a local file is not secure; at least the used keys should +be logged to another host. +.Bl -ohang +.It Em sign_sg +Enables signing. +Set this option to enable syslog-sign and select how to assign +messages to signature groups (subsets of messages that are signed together). +To enable later signature verification and detection of lost messages the +assignment should be chosen such that all messages of one signature group +are written to the same file. +Four possible values for this option are: +.Bl -hang -offset indent +.It Em 0 +Use one global signature group for all messages. +.It Em 1 +Use one signature group per priority. +.It Em 2 +Use signature groups for ranges of priorities. +.It Em 3 +Use one signature group per destination. +This is a custom strategy not defined by the standard. +With this setting one signature group is set up for +every file and network action. +.El +.It Em sign_delim_sg2 +This option is only evaluated with +.Sq sign_sg=2 +and allows to configure the priority ranges for signature groups. +The parameters are numerical values used as the maximum priority for one group. +The default is to use one signature groups per facility, which is equal to +setting +.Sq sign_delim_sg2=7 15 23 31 39 ... . +.El +.Sh FILES +.Bl -tag -width /etc/syslog.conf -compact +.It Pa /etc/syslog.conf +The +.Xr syslogd 8 +configuration file. +.It Pa /usr/share/examples/syslogd/verify.pl +Example script to verify message signatures. +(Requires Perl and modules not part of NetBSD.) +.El +.Sh EXAMPLES +A configuration file might appear as follows: +.Bd -literal +# Log all kernel messages, authentication messages of +# level notice or higher and anything of level err or +# higher to the console. +# Don't log private authentication messages! +*.err;kern.*;auth.notice;authpriv.none /dev/console + +# Log anything (except mail) of level info or higher. +# Don't log private authentication messages! +*.info;mail.none;authpriv.none /var/log/messages + +# Log daemon messages at debug level only +daemon.=debug /var/log/daemon.debug + +# The authpriv file has restricted access. +# Write logs with priority for later verification with syslog-sign. +authpriv.* +/var/log/secure + +# Log all the mail messages in one place. +mail.* /var/log/maillog + +# Everybody gets emergency messages, plus log them on another +# machine. +*.emerg * +*.emerg @arpa.berkeley.edu + +# Log all messages of level info or higher to another +# machine using TLS with an alternative portname and a +# fingerprint for athentication +*.info @[logserver]:1234(fingerprint="SHA1:01:02:...") + +# Root and Eric get alert and higher messages. +*.alert root,eric + +# Save mail and news errors of level err and higher in a +# special file. +mail,news.err /var/log/spoolerr + +# Pipe all authentication messages to a filter. +auth.* |exec /usr/local/sbin/authfilter + +# Log kernel messages to a separate file without syncing each message. +kern.* -/var/log/kernlog + +# Save ftpd transactions along with mail and news. +!ftpd +*.* /var/log/spoolerr + +# Send all error messages from a RAID array through a filter. +!raid0 +kern.err |exec /usr/local/sbin/raidfilter + +# Save pppd messages from dialhost to a separate file. +!pppd ++dialhost +*.* /var/log/dialhost-pppd + +# Save non-local log messages from all programs to a separate file. +!* +-@ +*.* /var/log/foreign + +# Generate digital signatures for all messages +# to each file or network destination. +sign_sg=3 +.Ed +.Sh SEE ALSO +.Xr syslog 3 , +.Xr syslogd 8 +.Sh HISTORY +The +.Nm +file appeared in +.Bx 4.3 , +along with +.Xr syslogd 8 . +.Sh BUGS +The effects of multiple selectors are sometimes not intuitive. +For example +.Dq mail.crit;*.err +will select +.Dq mail +facility messages at +the level of +.Dq err +or higher, not at the level of +.Dq crit +or higher. diff --git a/usr.sbin/syslogd/syslogd.8 b/usr.sbin/syslogd/syslogd.8 new file mode 100644 index 000000000..d4bdd09e5 --- /dev/null +++ b/usr.sbin/syslogd/syslogd.8 @@ -0,0 +1,272 @@ +.\" $NetBSD: syslogd.8,v 1.54 2013/01/14 03:05:41 dholland Exp $ +.\" +.\" Copyright (c) 1983, 1986, 1991, 1993 +.\" The Regents of the University of California. All rights reserved. +.\" +.\" Redistribution and use in source and binary forms, with or without +.\" modification, are permitted provided that the following conditions +.\" are met: +.\" 1. Redistributions of source code must retain the above copyright +.\" notice, this list of conditions and the following disclaimer. +.\" 2. Redistributions in binary form must reproduce the above copyright +.\" notice, this list of conditions and the following disclaimer in the +.\" documentation and/or other materials provided with the distribution. +.\" 3. Neither the name of the University nor the names of its contributors +.\" may be used to endorse or promote products derived from this software +.\" without specific prior written permission. +.\" +.\" THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND +.\" ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +.\" IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +.\" ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE +.\" FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +.\" DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS +.\" OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +.\" HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +.\" LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +.\" OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF +.\" SUCH DAMAGE. +.\" +.\" from: @(#)syslogd.8 8.1 (Berkeley) 6/6/93 +.\" +.Dd March 28, 2012 +.Dt SYSLOGD 8 +.Os +.Sh NAME +.Nm syslogd +.Nd log systems messages +.Sh SYNOPSIS +.Nm +.Op Fl dnrSsTUv +.Op Fl b Ar bind_address +.Op Fl f Ar config_file +.Op Fl g Ar group +.Op Fl m Ar mark_interval +.Op Fl o Ar output_format +.Op Fl P Ar file_list +.Oo +.Fl p Ar log_socket +.Op Fl p Ar log_socket2 ... +.Oc +.Op Fl t Ar chroot_dir +.Op Fl u Ar user +.Sh DESCRIPTION +.Nm +reads and logs messages to the system console, log files, other +machines and/or users as specified by its configuration file. +The options are as follows: +.Bl -tag -width 15n +.It Fl b Ar bind_address +Specify one specific IP address or hostname to bind to. +If a hostname is specified, the IPv4 or IPv6 address +which corresponds to it is used. +.It Fl d +Enable debugging to the standard output, +and do not disassociate from the controlling terminal. +.It Fl f Ar config_file +Specify the pathname of an alternative configuration file; +the default is +.Pa /etc/syslog.conf . +.It Fl g Ar group +Set GID to +.Ar group +after the sockets and log files have been opened. +.It Fl m Ar mark_interval +Select the number of minutes between ``mark'' messages; +the default is 20 minutes. +.It Fl n +Do not perform hostname lookups; report only numeric addresses. +.It Fl o Ar output_format +Select output message format. +.Bl -hang +.It Em bsd , rfc3164 +traditional BSD Syslog format (default) +.It Em syslog , rfc5424 +new syslog-protocol format +.El +.It Fl P +Specify the pathname of a file containing a list of sockets to be +created. +The format of the file is simply one socket per line. +.It Fl p Ar log_socket +Specify the pathname of a log socket. +Multiple +.Fl p +options create multiple log sockets. +If no +.Fl p +arguments are given, the default socket of +.Pa /var/run/log +is used. +.It Fl r +Disable the compression of repeated instances of the same line +into a single line of the form +.Dq last message repeated N times . +.It Fl S +Sync kernel messages to disk immediately. +.It Fl s +Select +.Dq secure +mode, in which +.Nm +does not listen on a UDP socket but only communicates over a +.Ux +domain socket. +This is valuable when the machine on +which +.Nm +runs is subject to attack over the network and it is desired +that the machine be protected from attempts to remotely fill logs +and similar attacks. +.It Fl t Ar chroot_dir +.Xr chroot 2 +to +.Ar chroot_dir +after the sockets and log files have been opened. +.It Fl T +Always use the local time and date for messages received from the +network, instead of the timestamp field supplied in the message +by the remote host. +This is useful if some of the originating hosts can't keep time +properly or are unable to generate a correct timestamp. +.It Fl u Ar user +Set UID to +.Ar user +after the sockets and log files have been opened. +.It Fl U +Unique priority logging. +Only log messages at the priority specified by the selector in the +configuration file. +Without this option, messages at the specified priority or higher are +logged. +This option changes the default priority comparison from +.Sq \*[Gt]= +to +.Sq = . +.It Fl v +Verbose logging. +If specified once, the numeric facility and priority are logged with +each locally-written message. +If specified more than once, the names of the facility and priority are +logged with each locally-written message. +.El +.Pp +.Nm +reads its configuration file when it starts up and whenever it +receives a hangup signal. +For information on the format of the configuration file, +see +.Xr syslog.conf 5 . +.Pp +.Nm +reads messages from the +.Ux +domain socket +.Pa /var/run/log , +from an Internet domain socket specified in +.Pa /etc/services , +and from the special device +.Pa /dev/klog +(to read kernel messages). +.Pp +.Nm +creates the file +.Pa /var/run/syslogd.pid , +and stores its process +id there. +This can be used to kill or reconfigure +.Nm . +.Pp +By using multiple +.Fl p +options, one can set up many chroot environments by passing the pathname +to the log socket +.Pa ( /var/run/log ) +in each chroot area to +.Nm . +For example: +.Dl syslogd -p /var/run/log -p /web/var/run/log -p /ftp/var/run/log +.Pp +Note: the normal log socket must now also be passed to +.Nm . +.Pp +The logged message includes the date, time, and hostname (or pathname of +the log socket). +Commonly, the program name and the process id is included. +.Pp +The date and time are taken from the received message. +If the format of the timestamp field is incorrect, time obtained from +the local host is used instead. +This can be overridden by the +.Fl T +flag. +.Pp +Accesses from UDP socket can be filtered by libwrap configuration files, like +.Pa /etc/hosts.deny . +Specify +.Dq Li syslogd +in +.Ar daemon_list +portion of the configuration files. +Refer to +.Xr hosts_access 5 +for details. +.Ss SYSLOG PROTOCOL NOTES +.Nm +accepts messages in traditional BSD Syslog or in newer Syslog Protocol +format. +See RFC 3164 (BSD Syslog) and RFC 5424 (Syslog Protocol) for detailed +description of the message format. +Messages from the local kernel that are not tagged with a priority code +receive the default facility +.Dv LOG_KERN +and priority +.Dv LOG_NOTICE . +All other untagged messages receive the default facility +.Dv LOG_USER +and priority +.Dv LOG_NOTICE . +.Sh FILES +.Bl -tag -width /var/run/syslogd.pid -compact +.It Pa /etc/syslog.conf +The configuration file. +.It Pa /var/run/syslogd.pid +The process id of current +.Nm . +.It Pa /var/run/log +Name of the +.Ux +domain datagram log socket. +.It Pa /dev/klog +The kernel log device. +.El +.Sh SEE ALSO +.Xr logger 1 , +.Xr syslog 3 , +.Xr services 5 , +.Xr syslog.conf 5 , +.Xr newsyslog 8 +.Rs +.%R RFC +.%N 3164 +.%D August 2001 +.%T The BSD syslog Protocol +.Re +.Rs +.%R RFC +.%N 5424 +.%D March 2009 +.%T The Syslog Protocol +.Re +.Sh HISTORY +The +.Nm +command appeared in +.Bx 4.3 . +Support for multiple log sockets appeared in +.Nx 1.4 . +libwrap support appeared in +.Nx 1.6 . +Support for RFC 5424, TLS encryption and authentication, signed messages +appeared in +.Nx 6.0 . diff --git a/usr.sbin/syslogd/syslogd.c b/usr.sbin/syslogd/syslogd.c new file mode 100644 index 000000000..435c9b9e0 --- /dev/null +++ b/usr.sbin/syslogd/syslogd.c @@ -0,0 +1,4853 @@ +/* $NetBSD: syslogd.c,v 1.119 2013/11/27 20:48:28 christos Exp $ */ + +/* + * Copyright (c) 1983, 1988, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#include +#ifndef lint +__COPYRIGHT("@(#) Copyright (c) 1983, 1988, 1993, 1994\ + The Regents of the University of California. All rights reserved."); +#endif /* not lint */ + +#ifndef lint +#if 0 +static char sccsid[] = "@(#)syslogd.c 8.3 (Berkeley) 4/4/94"; +#else +__RCSID("$NetBSD: syslogd.c,v 1.119 2013/11/27 20:48:28 christos Exp $"); +#endif +#endif /* not lint */ + +/* + * syslogd -- log system messages + * + * This program implements a system log. It takes a series of lines. + * Each line may have a priority, signified as "" as + * the first characters of the line. If this is + * not present, a default priority is used. + * + * To kill syslogd, send a signal 15 (terminate). A signal 1 (hup) will + * cause it to reread its configuration file. + * + * Defined Constants: + * + * MAXLINE -- the maximimum line length that can be handled. + * DEFUPRI -- the default priority for user messages + * DEFSPRI -- the default priority for kernel messages + * + * 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. + * -U and -v by Harlan Stenn. + * Priority comparison code by Harlan Stenn. + * TLS, syslog-protocol, and syslog-sign code by Martin Schuette. + */ +#define SYSLOG_NAMES +#include +#include +#include "syslogd.h" +#include "extern.h" + +#ifndef DISABLE_SIGN +#include "sign.h" +struct sign_global_t GlobalSign = { + .rsid = 0, + .sig2_delims = STAILQ_HEAD_INITIALIZER(GlobalSign.sig2_delims) +}; +#endif /* !DISABLE_SIGN */ + +#ifndef DISABLE_TLS +#include "tls.h" +#endif /* !DISABLE_TLS */ + +#ifdef LIBWRAP +int allow_severity = LOG_AUTH|LOG_INFO; +int deny_severity = LOG_AUTH|LOG_WARNING; +#endif + +const char *ConfFile = _PATH_LOGCONF; +char ctty[] = _PATH_CONSOLE; + +/* + * 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. + */ +int repeatinterval[] = { 30, 120, 600 }; /* # of secs before flush */ +#define MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1) +#define REPEATTIME(f) ((f)->f_time + repeatinterval[(f)->f_repeatcount]) +#define BACKOFF(f) { if ((size_t)(++(f)->f_repeatcount) > MAXREPEAT) \ + (f)->f_repeatcount = MAXREPEAT; \ + } + +/* values for f_type */ +#define F_UNUSED 0 /* unused entry */ +#define F_FILE 1 /* regular file */ +#define F_TTY 2 /* terminal */ +#define F_CONSOLE 3 /* console terminal */ +#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 */ +#define F_FIFO 8 /* mkfifo(2) file */ +#define F_TLS 9 + +struct TypeInfo { + const char *name; + char *queue_length_string; + const char *default_length_string; + char *queue_size_string; + const char *default_size_string; + int64_t queue_length; + int64_t queue_size; + int max_msg_length; +} TypeInfo[] = { + /* numeric values are set in init() + * -1 in length/size or max_msg_length means infinite */ + {"UNUSED", NULL, "0", NULL, "0", 0, 0, 0}, + {"FILE", NULL, "1024", NULL, "1M", 0, 0, 16384}, + {"TTY", NULL, "0", NULL, "0", 0, 0, 1024}, + {"CONSOLE", NULL, "0", NULL, "0", 0, 0, 1024}, + {"FORW", NULL, "0", NULL, "1M", 0, 0, 16384}, + {"USERS", NULL, "0", NULL, "0", 0, 0, 1024}, + {"WALL", NULL, "0", NULL, "0", 0, 0, 1024}, + {"PIPE", NULL, "1024", NULL, "1M", 0, 0, 16384}, + {"FIFO", NULL, "1024", NULL, "1M", 0, 0, 16384}, +#ifndef DISABLE_TLS + {"TLS", NULL, "-1", NULL, "16M", 0, 0, 16384} +#endif /* !DISABLE_TLS */ +}; + +struct filed *Files = NULL; +struct filed consfile; + +time_t now; +int Debug = D_NONE; /* debug flag */ +int daemonized = 0; /* we are not daemonized yet */ +char *LocalFQDN = NULL; /* our FQDN */ +char *oldLocalFQDN = NULL; /* our previous FQDN */ +char LocalHostName[MAXHOSTNAMELEN]; /* our hostname */ +struct socketEvent *finet; /* Internet datagram sockets and events */ +int *funix; /* Unix domain datagram sockets */ +#ifndef DISABLE_TLS +struct socketEvent *TLS_Listen_Set; /* TLS/TCP sockets and events */ +#endif /* !DISABLE_TLS */ +int Initialized = 0; /* 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 */ +int UseNameService = 1; /* make domain name queries */ +int NumForwards = 0; /* number of forwarding actions in conf file */ +char **LogPaths; /* array of pathnames to read messages from */ +int NoRepeat = 0; /* disable "repeated"; log always */ +int RemoteAddDate = 0; /* always add date to messages from network */ +int SyncKernel = 0; /* write kernel messages synchronously */ +int UniquePriority = 0; /* only log specified priority */ +int LogFacPri = 0; /* put facility and priority in log messages: */ + /* 0=no, 1=numeric, 2=names */ +bool BSDOutputFormat = true; /* if true emit traditional BSD Syslog lines, + * otherwise new syslog-protocol lines + * + * Open Issue: having a global flag is the + * easiest solution. If we get a more detailed + * config file this could/should be changed + * into a destination-specific flag. + * Most output code should be ready to handle + * this, it will only break some syslog-sign + * configurations (e.g. with SG="0"). + */ +char appname[] = "syslogd";/* the APPNAME for own messages */ +char *include_pid = NULL; /* include PID in own messages */ + + +/* init and setup */ +void usage(void) __attribute__((__noreturn__)); +void logpath_add(char ***, int *, int *, const char *); +void logpath_fileadd(char ***, int *, int *, const char *); +void init(int fd, short event, void *ev); /* SIGHUP kevent dispatch routine */ +struct socketEvent* + socksetup(int, const char *); +int getmsgbufsize(void); +char *getLocalFQDN(void); +void trim_anydomain(char *); +/* pipe & subprocess handling */ +int p_open(char *, pid_t *); +void deadq_enter(pid_t, const char *); +int deadq_remove(pid_t); +void log_deadchild(pid_t, int, const char *); +void reapchild(int fd, short event, void *ev); /* SIGCHLD kevent dispatch routine */ +/* input message parsing & formatting */ +const char *cvthname(struct sockaddr_storage *); +void printsys(char *); +struct buf_msg *printline_syslogprotocol(const char*, char*, int, int); +struct buf_msg *printline_bsdsyslog(const char*, char*, int, int); +struct buf_msg *printline_kernelprintf(const char*, char*, int, int); +size_t check_timestamp(unsigned char *, char **, bool, bool); +char *copy_utf8_ascii(char*, size_t); +uint_fast32_t get_utf8_value(const char*); +unsigned valid_utf8(const char *); +static unsigned check_sd(char*); +static unsigned check_msgid(char *); +/* event handling */ +static void dispatch_read_klog(int fd, short event, void *ev); +static void dispatch_read_finet(int fd, short event, void *ev); +static void dispatch_read_funix(int fd, short event, void *ev); +static void domark(int fd, short event, void *ev); /* timer kevent dispatch routine */ +/* log messages */ +void logmsg_async(int, const char *, const char *, int); +void logmsg(struct buf_msg *); +int matches_spec(const char *, const char *, + char *(*)(const char *, const char *)); +void udp_send(struct filed *, char *, size_t); +void wallmsg(struct filed *, struct iovec *, size_t); +/* buffer & queue functions */ +size_t message_queue_purge(struct filed *f, size_t, int); +size_t message_allqueues_check(void); +static struct buf_queue * + find_qentry_to_delete(const struct buf_queue_head *, int, bool); +struct buf_queue * + message_queue_add(struct filed *, struct buf_msg *); +size_t buf_queue_obj_size(struct buf_queue*); +/* configuration & parsing */ +void cfline(size_t, const char *, struct filed *, const char *, + const char *); +void read_config_file(FILE*, struct filed**); +void store_sign_delim_sg2(char*); +int decode(const char *, CODE *); +bool copy_config_value(const char *, char **, const char **, + const char *, int); +bool copy_config_value_word(char **, const char **); + +/* config parsing */ +#ifndef DISABLE_TLS +void free_cred_SLIST(struct peer_cred_head *); +static inline void + free_incoming_tls_sockets(void); +#endif /* !DISABLE_TLS */ +static int writev1(int, struct iovec *, size_t); + +/* for make_timestamp() */ +#define TIMESTAMPBUFSIZE 35 +char timestamp[TIMESTAMPBUFSIZE]; + +/* + * Global line buffer. Since we only process one event at a time, + * a global one will do. But for klog, we use own buffer so that + * partial line at the end of buffer can be deferred. + */ +char *linebuf, *klog_linebuf; +size_t linebufsize, klog_linebufoff; + +static const char *bindhostname = NULL; + +#ifndef DISABLE_TLS +struct TLS_Incoming TLS_Incoming_Head = \ + SLIST_HEAD_INITIALIZER(TLS_Incoming_Head); +extern char *SSL_ERRCODE[]; +struct tls_global_options_t tls_opt; +#endif /* !DISABLE_TLS */ + +int +main(int argc, char *argv[]) +{ + int ch, j, fklog; + int funixsize = 0, funixmaxsize = 0; + struct sockaddr_un sunx; + char **pp; + struct event *ev; + uid_t uid = 0; + gid_t gid = 0; + char *user = NULL; + char *group = NULL; + const char *root = "/"; + char *endp; + struct group *gr; + struct passwd *pw; + unsigned long l; + + /* should we set LC_TIME="C" to ensure correct timestamps&parsing? */ + (void)setlocale(LC_ALL, ""); + + while ((ch = getopt(argc, argv, "b:dnsSf:m:o:p:P:ru:g:t:TUv")) != -1) + switch(ch) { + case 'b': + bindhostname = optarg; + break; + case 'd': /* debug */ + Debug = D_DEFAULT; + /* is there a way to read the integer value + * for Debug as an optional argument? */ + break; + case 'f': /* configuration file */ + ConfFile = optarg; + break; + case 'g': + group = optarg; + if (*group == '\0') + usage(); + break; + case 'm': /* mark interval */ + MarkInterval = atoi(optarg) * 60; + break; + case 'n': /* turn off DNS queries */ + UseNameService = 0; + break; + case 'o': /* message format */ +#define EQ(a) (strncmp(optarg, # a, sizeof(# a) - 1) == 0) + if (EQ(bsd) || EQ(rfc3264)) + BSDOutputFormat = true; + else if (EQ(syslog) || EQ(rfc5424)) + BSDOutputFormat = false; + else + usage(); + /* TODO: implement additional output option "osyslog" + * for old syslogd behaviour as introduced after + * FreeBSD PR#bin/7055. + */ + break; + case 'p': /* path */ + logpath_add(&LogPaths, &funixsize, + &funixmaxsize, optarg); + break; + case 'P': /* file of paths */ + logpath_fileadd(&LogPaths, &funixsize, + &funixmaxsize, optarg); + break; + case 'r': /* disable "repeated" compression */ + NoRepeat++; + break; + case 's': /* no network listen mode */ + SecureMode++; + break; + case 'S': + SyncKernel = 1; + break; + case 't': + root = optarg; + if (*root == '\0') + usage(); + break; + case 'T': + RemoteAddDate = 1; + break; + case 'u': + user = optarg; + if (*user == '\0') + usage(); + break; + case 'U': /* only log specified priority */ + UniquePriority = 1; + break; + case 'v': /* log facility and priority */ + if (LogFacPri < 2) + LogFacPri++; + break; + default: + usage(); + } + if ((argc -= optind) != 0) + usage(); + + setlinebuf(stdout); + tzset(); /* init TZ information for localtime. */ + + if (user != NULL) { + if (isdigit((unsigned char)*user)) { + errno = 0; + endp = NULL; + l = strtoul(user, &endp, 0); + if (errno || *endp != '\0') + goto getuser; + uid = (uid_t)l; + if (uid != l) {/* TODO: never executed */ + errno = 0; + logerror("UID out of range"); + die(0, 0, NULL); + } + } else { +getuser: + if ((pw = getpwnam(user)) != NULL) { + uid = pw->pw_uid; + } else { + errno = 0; + logerror("Cannot find user `%s'", user); + die(0, 0, NULL); + } + } + } + + if (group != NULL) { + if (isdigit((unsigned char)*group)) { + errno = 0; + endp = NULL; + l = strtoul(group, &endp, 0); + if (errno || *endp != '\0') + goto getgroup; + gid = (gid_t)l; + if (gid != l) {/* TODO: never executed */ + errno = 0; + logerror("GID out of range"); + die(0, 0, NULL); + } + } else { +getgroup: + if ((gr = getgrnam(group)) != NULL) { + gid = gr->gr_gid; + } else { + errno = 0; + logerror("Cannot find group `%s'", group); + die(0, 0, NULL); + } + } + } + + if (access(root, F_OK | R_OK)) { + logerror("Cannot access `%s'", root); + die(0, 0, NULL); + } + + consfile.f_type = F_CONSOLE; + (void)strlcpy(consfile.f_un.f_fname, ctty, + sizeof(consfile.f_un.f_fname)); + linebufsize = getmsgbufsize(); + if (linebufsize < MAXLINE) + linebufsize = MAXLINE; + linebufsize++; + + if (!(linebuf = malloc(linebufsize))) { + logerror("Couldn't allocate buffer"); + die(0, 0, NULL); + } + if (!(klog_linebuf = malloc(linebufsize))) { + logerror("Couldn't allocate buffer for klog"); + die(0, 0, NULL); + } + + +#ifndef SUN_LEN +#define SUN_LEN(unp) (strlen((unp)->sun_path) + 2) +#endif + if (funixsize == 0) + logpath_add(&LogPaths, &funixsize, + &funixmaxsize, _PATH_LOG); + funix = malloc(sizeof(*funix) * funixsize); + if (funix == NULL) { + logerror("Couldn't allocate funix descriptors"); + die(0, 0, NULL); + } + for (j = 0, pp = LogPaths; *pp; pp++, j++) { + DPRINTF(D_NET, "Making unix dgram socket `%s'\n", *pp); + unlink(*pp); + memset(&sunx, 0, sizeof(sunx)); + sunx.sun_family = AF_LOCAL; + (void)strncpy(sunx.sun_path, *pp, sizeof(sunx.sun_path)); + funix[j] = socket(AF_LOCAL, SOCK_DGRAM, 0); + if (funix[j] < 0 || bind(funix[j], + (struct sockaddr *)&sunx, SUN_LEN(&sunx)) < 0 || + chmod(*pp, 0666) < 0) { + logerror("Cannot create `%s'", *pp); + die(0, 0, NULL); + } + DPRINTF(D_NET, "Listening on unix dgram socket `%s'\n", *pp); + } + + if ((fklog = open(_PATH_KLOG, O_RDONLY, 0)) < 0) { + DPRINTF(D_FILE, "Can't open `%s' (%d)\n", _PATH_KLOG, errno); + } else { + DPRINTF(D_FILE, "Listening on kernel log `%s' with fd %d\n", + _PATH_KLOG, fklog); + } + +#if (!defined(DISABLE_TLS) && !defined(DISABLE_SIGN)) + /* basic OpenSSL init */ + SSL_load_error_strings(); + (void) SSL_library_init(); + OpenSSL_add_all_digests(); + /* OpenSSL PRNG needs /dev/urandom, thus initialize before chroot() */ + if (!RAND_status()) { + errno = 0; + logerror("Unable to initialize OpenSSL PRNG"); + } else { + DPRINTF(D_TLS, "Initializing PRNG\n"); + } +#endif /* (!defined(DISABLE_TLS) && !defined(DISABLE_SIGN)) */ +#ifndef DISABLE_SIGN + /* initialize rsid -- we will use that later to determine + * whether sign_global_init() was already called */ + GlobalSign.rsid = 0; +#endif /* !DISABLE_SIGN */ +#if (IETF_NUM_PRIVALUES != (LOG_NFACILITIES<<3)) + logerror("Warning: system defines %d priority values, but " + "syslog-protocol/syslog-sign specify %d values", + LOG_NFACILITIES, SIGN_NUM_PRIVALS); +#endif + + /* + * All files are open, we can drop privileges and chroot + */ + DPRINTF(D_MISC, "Attempt to chroot to `%s'\n", root); + if (chroot(root) == -1) { + logerror("Failed to chroot to `%s'", root); + die(0, 0, NULL); + } + DPRINTF(D_MISC, "Attempt to set GID/EGID to `%d'\n", gid); + if (setgid(gid) || setegid(gid)) { + logerror("Failed to set gid to `%d'", gid); + die(0, 0, NULL); + } + DPRINTF(D_MISC, "Attempt to set UID/EUID to `%d'\n", uid); + if (setuid(uid) || seteuid(uid)) { + logerror("Failed to set uid to `%d'", uid); + die(0, 0, NULL); + } + /* + * We cannot detach from the terminal before we are sure we won't + * have a fatal error, because error message would not go to the + * terminal and would not be logged because syslogd dies. + * All die() calls are behind us, we can call daemon() + */ + if (!Debug) { + (void)daemon(0, 0); + daemonized = 1; + /* tuck my process id away, if i'm not in debug mode */ +#ifdef __NetBSD_Version__ + pidfile(NULL); +#endif /* __NetBSD_Version__ */ + } + +#define MAX_PID_LEN 5 + include_pid = malloc(MAX_PID_LEN+1); + snprintf(include_pid, MAX_PID_LEN+1, "%d", getpid()); + + /* + * Create the global kernel event descriptor. + * + * NOTE: We MUST do this after daemon(), bacause the kqueue() + * API dictates that kqueue descriptors are not inherited + * across forks (lame!). + */ + (void)event_init(); + + /* + * We must read the configuration file for the first time + * after the kqueue descriptor is created, because we install + * events during this process. + */ + init(0, 0, NULL); + + /* + * Always exit on SIGTERM. Also exit on SIGINT and SIGQUIT + * if we're debugging. + */ + (void)signal(SIGTERM, SIG_IGN); + (void)signal(SIGINT, SIG_IGN); + (void)signal(SIGQUIT, SIG_IGN); + + ev = allocev(); + signal_set(ev, SIGTERM, die, ev); + EVENT_ADD(ev); + + if (Debug) { + ev = allocev(); + signal_set(ev, SIGINT, die, ev); + EVENT_ADD(ev); + ev = allocev(); + signal_set(ev, SIGQUIT, die, ev); + EVENT_ADD(ev); + } + + ev = allocev(); + signal_set(ev, SIGCHLD, reapchild, ev); + EVENT_ADD(ev); + + ev = allocev(); + schedule_event(&ev, + &((struct timeval){TIMERINTVL, 0}), + domark, ev); + + (void)signal(SIGPIPE, SIG_IGN); /* We'll catch EPIPE instead. */ + + /* Re-read configuration on SIGHUP. */ + (void) signal(SIGHUP, SIG_IGN); + ev = allocev(); + signal_set(ev, SIGHUP, init, ev); + EVENT_ADD(ev); + +#ifndef DISABLE_TLS + ev = allocev(); + signal_set(ev, SIGUSR1, dispatch_force_tls_reconnect, ev); + EVENT_ADD(ev); +#endif /* !DISABLE_TLS */ + + if (fklog >= 0) { + ev = allocev(); + DPRINTF(D_EVENT, + "register klog for fd %d with ev@%p\n", fklog, ev); + event_set(ev, fklog, EV_READ | EV_PERSIST, + dispatch_read_klog, ev); + EVENT_ADD(ev); + } + for (j = 0, pp = LogPaths; *pp; pp++, j++) { + ev = allocev(); + event_set(ev, funix[j], EV_READ | EV_PERSIST, + dispatch_read_funix, ev); + EVENT_ADD(ev); + } + + DPRINTF(D_MISC, "Off & running....\n"); + + j = event_dispatch(); + /* normal termination via die(), reaching this is an error */ + DPRINTF(D_MISC, "event_dispatch() returned %d\n", j); + die(0, 0, NULL); + /*NOTREACHED*/ + return 0; +} + +void +usage(void) +{ + + (void)fprintf(stderr, + "usage: %s [-dnrSsTUv] [-b bind_address] [-f config_file] [-g group]\n" + "\t[-m mark_interval] [-P file_list] [-p log_socket\n" + "\t[-p log_socket2 ...]] [-t chroot_dir] [-u user]\n", + getprogname()); + exit(1); +} + +/* + * Dispatch routine for reading /dev/klog + * + * Note: slightly different semantic in dispatch_read functions: + * - read_klog() might give multiple messages in linebuf and + * leaves the task of splitting them to printsys() + * - all other read functions receive one message and + * then call printline() with one buffer. + */ +static void +dispatch_read_klog(int fd, short event, void *ev) +{ + ssize_t rv; + size_t resid = linebufsize - klog_linebufoff; + + DPRINTF((D_CALL|D_EVENT), "Kernel log active (%d, %d, %p)" + " with linebuf@%p, length %zu)\n", fd, event, ev, + klog_linebuf, linebufsize); + + rv = read(fd, &klog_linebuf[klog_linebufoff], resid - 1); + if (rv > 0) { + klog_linebuf[klog_linebufoff + rv] = '\0'; + printsys(klog_linebuf); + } else if (rv < 0 && errno != EINTR) { + /* + * /dev/klog has croaked. Disable the event + * so it won't bother us again. + */ + logerror("klog failed"); + event_del(ev); + } +} + +/* + * Dispatch routine for reading Unix domain sockets. + */ +static void +dispatch_read_funix(int fd, short event, void *ev) +{ + struct sockaddr_un myname, fromunix; + ssize_t rv; + socklen_t sunlen; + + sunlen = sizeof(myname); + if (getsockname(fd, (struct sockaddr *)&myname, &sunlen) != 0) { + /* + * This should never happen, so ensure that it doesn't + * happen again. + */ + logerror("getsockname() unix failed"); + event_del(ev); + return; + } + + DPRINTF((D_CALL|D_EVENT|D_NET), "Unix socket (%.*s) active (%d, %d %p)" + " with linebuf@%p, size %zu)\n", (int)(myname.sun_len + - sizeof(myname.sun_len) - sizeof(myname.sun_family)), + myname.sun_path, fd, event, ev, linebuf, linebufsize-1); + + sunlen = sizeof(fromunix); + rv = recvfrom(fd, linebuf, linebufsize-1, 0, + (struct sockaddr *)&fromunix, &sunlen); + if (rv > 0) { + linebuf[rv] = '\0'; + printline(LocalFQDN, linebuf, 0); + } else if (rv < 0 && errno != EINTR) { + logerror("recvfrom() unix `%.*s'", + myname.sun_len, myname.sun_path); + } +} + +/* + * Dispatch routine for reading Internet sockets. + */ +static void +dispatch_read_finet(int fd, short event, void *ev) +{ +#ifdef LIBWRAP + struct request_info req; +#endif + struct sockaddr_storage frominet; + ssize_t rv; + socklen_t len; + int reject = 0; + + DPRINTF((D_CALL|D_EVENT|D_NET), "inet socket active (%d, %d %p) " + " with linebuf@%p, size %zu)\n", + fd, event, ev, linebuf, linebufsize-1); + +#ifdef LIBWRAP + request_init(&req, RQ_DAEMON, appname, RQ_FILE, fd, NULL); + fromhost(&req); + reject = !hosts_access(&req); + if (reject) + DPRINTF(D_NET, "access denied\n"); +#endif + + len = sizeof(frominet); + rv = recvfrom(fd, linebuf, linebufsize-1, 0, + (struct sockaddr *)&frominet, &len); + if (rv == 0 || (rv < 0 && errno == EINTR)) + return; + else if (rv < 0) { + logerror("recvfrom inet"); + return; + } + + linebuf[rv] = '\0'; + if (!reject) + printline(cvthname(&frominet), linebuf, + RemoteAddDate ? ADDDATE : 0); +} + +/* + * given a pointer to an array of char *'s, a pointer to its current + * size and current allocated max size, and a new char * to add, add + * it, update everything as necessary, possibly allocating a new array + */ +void +logpath_add(char ***lp, int *szp, int *maxszp, const char *new) +{ + char **nlp; + int newmaxsz; + + DPRINTF(D_FILE, "Adding `%s' to the %p logpath list\n", new, *lp); + if (*szp == *maxszp) { + if (*maxszp == 0) { + newmaxsz = 4; /* start of with enough for now */ + *lp = NULL; + } else + newmaxsz = *maxszp * 2; + nlp = realloc(*lp, sizeof(char *) * (newmaxsz + 1)); + if (nlp == NULL) { + logerror("Couldn't allocate line buffer"); + die(0, 0, NULL); + } + *lp = nlp; + *maxszp = newmaxsz; + } + if (((*lp)[(*szp)++] = strdup(new)) == NULL) { + logerror("Couldn't allocate logpath"); + die(0, 0, NULL); + } + (*lp)[(*szp)] = NULL; /* always keep it NULL terminated */ +} + +/* do a file of log sockets */ +void +logpath_fileadd(char ***lp, int *szp, int *maxszp, const char *file) +{ + FILE *fp; + char *line; + size_t len; + + fp = fopen(file, "r"); + if (fp == NULL) { + logerror("Could not open socket file list `%s'", file); + die(0, 0, NULL); + } + + while ((line = fgetln(fp, &len)) != NULL) { + line[len - 1] = 0; + logpath_add(lp, szp, maxszp, line); + } + fclose(fp); +} + +/* + * checks UTF-8 codepoint + * returns either its length in bytes or 0 if *input is invalid +*/ +unsigned +valid_utf8(const char *c) { + unsigned rc, nb; + + /* first byte gives sequence length */ + if ((*c & 0x80) == 0x00) return 1; /* 0bbbbbbb -- ASCII */ + else if ((*c & 0xc0) == 0x80) return 0; /* 10bbbbbb -- trailing byte */ + else if ((*c & 0xe0) == 0xc0) nb = 2; /* 110bbbbb */ + else if ((*c & 0xf0) == 0xe0) nb = 3; /* 1110bbbb */ + else if ((*c & 0xf8) == 0xf0) nb = 4; /* 11110bbb */ + else return 0; /* UTF-8 allows only up to 4 bytes */ + + /* catch overlong encodings */ + if ((*c & 0xfe) == 0xc0) + return 0; /* 1100000b ... */ + else if (((*c & 0xff) == 0xe0) && ((*(c+1) & 0xe0) == 0x80)) + return 0; /* 11100000 100bbbbb ... */ + else if (((*c & 0xff) == 0xf0) && ((*(c+1) & 0xf0) == 0x80)) + return 0; /* 11110000 1000bbbb ... ... */ + + /* and also filter UTF-16 surrogates (=invalid in UTF-8) */ + if (((*c & 0xff) == 0xed) && ((*(c+1) & 0xe0) == 0xa0)) + return 0; /* 11101101 101bbbbb ... */ + + rc = nb; + /* check trailing bytes */ + switch (nb) { + default: return 0; + case 4: if ((*(c+3) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ + case 3: if ((*(c+2) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ + case 2: if ((*(c+1) & 0xc0) != 0x80) return 0; /*FALLTHROUGH*/ + } + return rc; +} +#define UTF8CHARMAX 4 + +/* + * read UTF-8 value + * returns a the codepoint number + */ +uint_fast32_t +get_utf8_value(const char *c) { + uint_fast32_t sum; + unsigned nb, i; + + /* first byte gives sequence length */ + if ((*c & 0x80) == 0x00) return *c;/* 0bbbbbbb -- ASCII */ + else if ((*c & 0xc0) == 0x80) return 0; /* 10bbbbbb -- trailing byte */ + else if ((*c & 0xe0) == 0xc0) { /* 110bbbbb */ + nb = 2; + sum = (*c & ~0xe0) & 0xff; + } else if ((*c & 0xf0) == 0xe0) { /* 1110bbbb */ + nb = 3; + sum = (*c & ~0xf0) & 0xff; + } else if ((*c & 0xf8) == 0xf0) { /* 11110bbb */ + nb = 4; + sum = (*c & ~0xf8) & 0xff; + } else return 0; /* UTF-8 allows only up to 4 bytes */ + + /* check trailing bytes -- 10bbbbbb */ + i = 1; + while (i < nb) { + sum <<= 6; + sum |= ((*(c+i) & ~0xc0) & 0xff); + i++; + } + return sum; +} + +/* note previous versions transscribe + * control characters, e.g. \007 --> "^G" + * did anyone rely on that? + * + * this new version works on only one buffer and + * replaces control characters with a space + */ +#define NEXTFIELD(ptr) if (*(p) == ' ') (p)++; /* SP */ \ + else { \ + DPRINTF(D_DATA, "format error\n"); \ + if (*(p) == '\0') start = (p); \ + goto all_syslog_msg; \ + } +#define FORCE2ASCII(c) ((iscntrl((unsigned char)(c)) && (c) != '\t') \ + ? ((c) == '\n' ? ' ' : '?') \ + : (c) & 0177) + +/* following syslog-protocol */ +#define printusascii(ch) (ch >= 33 && ch <= 126) +#define sdname(ch) (ch != '=' && ch != ' ' \ + && ch != ']' && ch != '"' \ + && printusascii(ch)) + +/* checks whether the first word of string p can be interpreted as + * a syslog-protocol MSGID and if so returns its length. + * + * otherwise returns 0 + */ +static unsigned +check_msgid(char *p) +{ + char *q = p; + + /* consider the NILVALUE to be valid */ + if (*q == '-' && *(q+1) == ' ') + return 1; + + for (;;) { + if (*q == ' ') + return q - p; + else if (*q == '\0' || !printusascii(*q) || q - p >= MSGID_MAX) + return 0; + else + q++; + } +} + +/* + * returns number of chars found in SD at beginning of string p + * thus returns 0 if no valid SD is found + * + * if ascii == true then substitute all non-ASCII chars + * otherwise use syslog-protocol rules to allow UTF-8 in values + * note: one pass for filtering and scanning, so a found SD + * is always filtered, but an invalid one could be partially + * filtered up to the format error. + */ +static unsigned +check_sd(char* p) +{ + char *q = p; + bool esc = false; + + /* consider the NILVALUE to be valid */ + if (*q == '-' && (*(q+1) == ' ' || *(q+1) == '\0')) + return 1; + + for(;;) { /* SD-ELEMENT */ + if (*q++ != '[') return 0; + /* SD-ID */ + if (!sdname(*q)) return 0; + while (sdname(*q)) { + *q = FORCE2ASCII(*q); + q++; + } + for(;;) { /* SD-PARAM */ + if (*q == ']') { + q++; + if (*q == ' ' || *q == '\0') return q - p; + else if (*q == '[') break; + } else if (*q++ != ' ') return 0; + + /* PARAM-NAME */ + if (!sdname(*q)) return 0; + while (sdname(*q)) { + *q = FORCE2ASCII(*q); + q++; + } + + if (*q++ != '=') return 0; + if (*q++ != '"') return 0; + + for(;;) { /* PARAM-VALUE */ + if (esc) { + esc = false; + if (*q == '\\' || *q == '"' || + *q == ']') { + q++; + continue; + } + /* no else because invalid + * escape sequences are accepted */ + } + else if (*q == '"') break; + else if (*q == '\0' || *q == ']') return 0; + else if (*q == '\\') esc = true; + else { + int i; + i = valid_utf8(q); + if (i == 0) + *q = '?'; + else if (i == 1) + *q = FORCE2ASCII(*q); + else /* multi byte char */ + q += (i-1); + } + q++; + } + q++; + } + } +} + +struct buf_msg * +printline_syslogprotocol(const char *hname, char *msg, + int flags, int pri) +{ + struct buf_msg *buffer; + char *p, *start; + unsigned sdlen = 0, i = 0; + bool utf8allowed = false; /* for some fields */ + + DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_syslogprotocol(" + "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); + + buffer = buf_msg_new(0); + p = msg; + p += check_timestamp((unsigned char*) p, + &buffer->timestamp, true, !BSDOutputFormat); + DPRINTF(D_DATA, "Got timestamp \"%s\"\n", buffer->timestamp); + + if (flags & ADDDATE) { + FREEPTR(buffer->timestamp); + buffer->timestamp = strdup(make_timestamp(NULL, + !BSDOutputFormat)); + } + + start = p; + NEXTFIELD(p); + /* extract host */ + for (start = p;; p++) { + if ((*p == ' ' || *p == '\0') + && start == p-1 && *(p-1) == '-') { + /* NILVALUE */ + break; + } else if ((*p == ' ' || *p == '\0') + && (start != p-1 || *(p-1) != '-')) { + buffer->host = strndup(start, p - start); + break; + } else { + *p = FORCE2ASCII(*p); + } + } + /* p @ SP after host */ + DPRINTF(D_DATA, "Got host \"%s\"\n", buffer->host); + + /* extract app-name */ + NEXTFIELD(p); + for (start = p;; p++) { + if ((*p == ' ' || *p == '\0') + && start == p-1 && *(p-1) == '-') { + /* NILVALUE */ + break; + } else if ((*p == ' ' || *p == '\0') + && (start != p-1 || *(p-1) != '-')) { + buffer->prog = strndup(start, p - start); + break; + } else { + *p = FORCE2ASCII(*p); + } + } + DPRINTF(D_DATA, "Got prog \"%s\"\n", buffer->prog); + + /* extract procid */ + NEXTFIELD(p); + for (start = p;; p++) { + if ((*p == ' ' || *p == '\0') + && start == p-1 && *(p-1) == '-') { + /* NILVALUE */ + break; + } else if ((*p == ' ' || *p == '\0') + && (start != p-1 || *(p-1) != '-')) { + buffer->pid = strndup(start, p - start); + start = p; + break; + } else { + *p = FORCE2ASCII(*p); + } + } + DPRINTF(D_DATA, "Got pid \"%s\"\n", buffer->pid); + + /* extract msgid */ + NEXTFIELD(p); + for (start = p;; p++) { + if ((*p == ' ' || *p == '\0') + && start == p-1 && *(p-1) == '-') { + /* NILVALUE */ + start = p+1; + break; + } else if ((*p == ' ' || *p == '\0') + && (start != p-1 || *(p-1) != '-')) { + buffer->msgid = strndup(start, p - start); + start = p+1; + break; + } else { + *p = FORCE2ASCII(*p); + } + } + DPRINTF(D_DATA, "Got msgid \"%s\"\n", buffer->msgid); + + /* extract SD */ + NEXTFIELD(p); + start = p; + sdlen = check_sd(p); + DPRINTF(D_DATA, "check_sd(\"%s\") returned %d\n", p, sdlen); + + if (sdlen == 1 && *p == '-') { + /* NILVALUE */ + p++; + } else if (sdlen > 1) { + buffer->sd = strndup(p, sdlen); + p += sdlen; + } else { + DPRINTF(D_DATA, "format error\n"); + } + if (*p == '\0') start = p; + else if (*p == ' ') start = ++p; /* SP */ + DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); + + /* and now the message itself + * note: move back to last start to check for BOM + */ +all_syslog_msg: + p = start; + + /* check for UTF-8-BOM */ + if (IS_BOM(p)) { + DPRINTF(D_DATA, "UTF-8 BOM\n"); + utf8allowed = true; + p += 3; + } + + if (*p != '\0' && !utf8allowed) { + size_t msglen; + + msglen = strlen(p); + assert(!buffer->msg); + buffer->msg = copy_utf8_ascii(p, msglen); + buffer->msgorig = buffer->msg; + buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; + } else if (*p != '\0' && utf8allowed) { + while (*p != '\0') { + i = valid_utf8(p); + if (i == 0) + *p++ = '?'; + else if (i == 1) + *p = FORCE2ASCII(*p); + p += i; + } + assert(p != start); + assert(!buffer->msg); + buffer->msg = strndup(start, p - start); + buffer->msgorig = buffer->msg; + buffer->msglen = buffer->msgsize = 1 + p - start; + } + DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); + + buffer->recvhost = strdup(hname); + buffer->pri = pri; + buffer->flags = flags; + + return buffer; +} + +/* copies an input into a new ASCII buffer + * ASCII controls are converted to format "^X" + * multi-byte UTF-8 chars are converted to format "" + */ +#define INIT_BUFSIZE 512 +char * +copy_utf8_ascii(char *p, size_t p_len) +{ + size_t idst = 0, isrc = 0, dstsize = INIT_BUFSIZE, i; + char *dst, *tmp_dst; + + MALLOC(dst, dstsize); + while (isrc < p_len) { + if (dstsize < idst + 10) { + /* check for enough space for \0 and a UTF-8 + * conversion; longest possible is */ + tmp_dst = realloc(dst, dstsize + INIT_BUFSIZE); + if (!tmp_dst) + break; + dst = tmp_dst; + dstsize += INIT_BUFSIZE; + } + + i = valid_utf8(&p[isrc]); + if (i == 0) { /* invalid encoding */ + dst[idst++] = '?'; + isrc++; + } else if (i == 1) { /* check printable */ + if (iscntrl((unsigned char)p[isrc]) + && p[isrc] != '\t') { + if (p[isrc] == '\n') { + dst[idst++] = ' '; + isrc++; + } else { + dst[idst++] = '^'; + dst[idst++] = p[isrc++] ^ 0100; + } + } else + dst[idst++] = p[isrc++]; + } else { /* convert UTF-8 to ASCII */ + dst[idst++] = '<'; + idst += snprintf(&dst[idst], dstsize - idst, "U+%x", + get_utf8_value(&p[isrc])); + isrc += i; + dst[idst++] = '>'; + } + } + dst[idst] = '\0'; + + /* shrink buffer to right size */ + tmp_dst = realloc(dst, idst+1); + if (tmp_dst) + return tmp_dst; + else + return dst; +} + +struct buf_msg * +printline_bsdsyslog(const char *hname, char *msg, + int flags, int pri) +{ + struct buf_msg *buffer; + char *p, *start; + unsigned msgidlen = 0, sdlen = 0; + + DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_bsdsyslog(" + "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); + + buffer = buf_msg_new(0); + p = msg; + p += check_timestamp((unsigned char*) p, + &buffer->timestamp, false, !BSDOutputFormat); + DPRINTF(D_DATA, "Got timestamp \"%s\"\n", buffer->timestamp); + + if (flags & ADDDATE || !buffer->timestamp) { + FREEPTR(buffer->timestamp); + buffer->timestamp = strdup(make_timestamp(NULL, + !BSDOutputFormat)); + } + + if (*p == ' ') p++; /* SP */ + else goto all_bsd_msg; + /* in any error case we skip header parsing and + * treat all following data as message content */ + + /* extract host */ + for (start = p;; p++) { + if (*p == ' ' || *p == '\0') { + buffer->host = strndup(start, p - start); + break; + } else if (*p == '[' || (*p == ':' + && (*(p+1) == ' ' || *(p+1) == '\0'))) { + /* no host in message */ + buffer->host = LocalFQDN; + buffer->prog = strndup(start, p - start); + break; + } else { + *p = FORCE2ASCII(*p); + } + } + DPRINTF(D_DATA, "Got host \"%s\"\n", buffer->host); + /* p @ SP after host, or @ :/[ after prog */ + + /* extract program */ + if (!buffer->prog) { + if (*p == ' ') p++; /* SP */ + else goto all_bsd_msg; + + for (start = p;; p++) { + if (*p == ' ' || *p == '\0') { /* error */ + goto all_bsd_msg; + } else if (*p == '[' || (*p == ':' + && (*(p+1) == ' ' || *(p+1) == '\0'))) { + buffer->prog = strndup(start, p - start); + break; + } else { + *p = FORCE2ASCII(*p); + } + } + } + DPRINTF(D_DATA, "Got prog \"%s\"\n", buffer->prog); + start = p; + + /* p @ :/[ after prog */ + if (*p == '[') { + p++; + if (*p == ' ') p++; /* SP */ + for (start = p;; p++) { + if (*p == ' ' || *p == '\0') { /* error */ + goto all_bsd_msg; + } else if (*p == ']') { + buffer->pid = strndup(start, p - start); + break; + } else { + *p = FORCE2ASCII(*p); + } + } + } + DPRINTF(D_DATA, "Got pid \"%s\"\n", buffer->pid); + + if (*p == ']') p++; + if (*p == ':') p++; + if (*p == ' ') p++; + + /* p @ msgid, @ opening [ of SD or @ first byte of message + * accept either case and try to detect MSGID and SD fields + * + * only limitation: we do not accept UTF-8 data in + * BSD Syslog messages -- so all SD values are ASCII-filtered + * + * I have found one scenario with 'unexpected' behaviour: + * if there is only a SD intended, but a) it is short enough + * to be a MSGID and b) the first word of the message can also + * be parsed as an SD. + * example: + * "<35>Jul 6 12:39:08 tag[123]: [exampleSDID@0] - hello" + * --> parsed as + * MSGID = "[exampleSDID@0]" + * SD = "-" + * MSG = "hello" + */ + start = p; + msgidlen = check_msgid(p); + if (msgidlen) /* check for SD in 2nd field */ + sdlen = check_sd(p+msgidlen+1); + + if (msgidlen && sdlen) { + /* MSGID in 1st and SD in 2nd field + * now check for NILVALUEs and copy */ + if (msgidlen == 1 && *p == '-') { + p++; /* - */ + p++; /* SP */ + DPRINTF(D_DATA, "Got MSGID \"-\"\n"); + } else { + /* only has ASCII chars after check_msgid() */ + buffer->msgid = strndup(p, msgidlen); + p += msgidlen; + p++; /* SP */ + DPRINTF(D_DATA, "Got MSGID \"%s\"\n", + buffer->msgid); + } + } else { + /* either no msgid or no SD in 2nd field + * --> check 1st field for SD */ + DPRINTF(D_DATA, "No MSGID\n"); + sdlen = check_sd(p); + } + + if (sdlen == 0) { + DPRINTF(D_DATA, "No SD\n"); + } else if (sdlen > 1) { + buffer->sd = copy_utf8_ascii(p, sdlen); + DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); + } else if (sdlen == 1 && *p == '-') { + p++; + DPRINTF(D_DATA, "Got SD \"-\"\n"); + } else { + DPRINTF(D_DATA, "Error\n"); + } + + if (*p == ' ') p++; + start = p; + /* and now the message itself + * note: do not reset start, because we might come here + * by goto and want to have the incomplete field as part + * of the msg + */ +all_bsd_msg: + if (*p != '\0') { + size_t msglen = strlen(p); + buffer->msg = copy_utf8_ascii(p, msglen); + buffer->msgorig = buffer->msg; + buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; + } + DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); + + buffer->recvhost = strdup(hname); + buffer->pri = pri; + buffer->flags = flags | BSDSYSLOG; + + return buffer; +} + +struct buf_msg * +printline_kernelprintf(const char *hname, char *msg, + int flags, int pri) +{ + struct buf_msg *buffer; + char *p; + unsigned sdlen = 0; + + DPRINTF((D_CALL|D_BUFFER|D_DATA), "printline_kernelprintf(" + "\"%s\", \"%s\", %d, %d)\n", hname, msg, flags, pri); + + buffer = buf_msg_new(0); + buffer->timestamp = strdup(make_timestamp(NULL, !BSDOutputFormat)); + buffer->pri = pri; + buffer->flags = flags; + + /* assume there is no MSGID but there might be SD */ + p = msg; + sdlen = check_sd(p); + + if (sdlen == 0) { + DPRINTF(D_DATA, "No SD\n"); + } else if (sdlen > 1) { + buffer->sd = copy_utf8_ascii(p, sdlen); + DPRINTF(D_DATA, "Got SD \"%s\"\n", buffer->sd); + } else if (sdlen == 1 && *p == '-') { + p++; + DPRINTF(D_DATA, "Got SD \"-\"\n"); + } else { + DPRINTF(D_DATA, "Error\n"); + } + + if (*p == ' ') p++; + if (*p != '\0') { + size_t msglen = strlen(p); + buffer->msg = copy_utf8_ascii(p, msglen); + buffer->msgorig = buffer->msg; + buffer->msglen = buffer->msgsize = strlen(buffer->msg)+1; + } + DPRINTF(D_DATA, "Got msg \"%s\"\n", buffer->msg); + + return buffer; +} + +/* + * Take a raw input line, read priority and version, call the + * right message parsing function, then call logmsg(). + */ +void +printline(const char *hname, char *msg, int flags) +{ + struct buf_msg *buffer; + int pri; + char *p, *q; + long n; + bool bsdsyslog = true; + + DPRINTF((D_CALL|D_BUFFER|D_DATA), + "printline(\"%s\", \"%s\", %d)\n", hname, msg, flags); + + /* test for special codes */ + pri = DEFUPRI; + p = msg; + if (*p == '<') { + errno = 0; + n = strtol(p + 1, &q, 10); + if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) { + p = q + 1; + pri = (int)n; + /* check for syslog-protocol version */ + if (*p == '1' && p[1] == ' ') { + p += 2; /* skip version and space */ + bsdsyslog = false; + } else { + bsdsyslog = true; + } + } + } + if (pri & ~(LOG_FACMASK|LOG_PRIMASK)) + pri = DEFUPRI; + + /* + * Don't allow users to log kernel messages. + * NOTE: Since LOG_KERN == 0, this will also match + * messages with no facility specified. + */ + if ((pri & LOG_FACMASK) == LOG_KERN) + pri = LOG_MAKEPRI(LOG_USER, LOG_PRI(pri)); + + if (bsdsyslog) { + buffer = printline_bsdsyslog(hname, p, flags, pri); + } else { + buffer = printline_syslogprotocol(hname, p, flags, pri); + } + logmsg(buffer); + DELREF(buffer); +} + +/* + * Take a raw input line from /dev/klog, split and format similar to syslog(). + */ +void +printsys(char *msg) +{ + int n, is_printf, pri, flags; + char *p, *q; + struct buf_msg *buffer; + + klog_linebufoff = 0; + for (p = msg; *p != '\0'; ) { + bool bsdsyslog = true; + + is_printf = 1; + flags = ISKERNEL | ADDDATE | BSDSYSLOG; + if (SyncKernel) + flags |= SYNC_FILE; + if (is_printf) /* kernel printf's come out on console */ + flags |= IGN_CONS; + pri = DEFSPRI; + + if (*p == '<') { + errno = 0; + n = (int)strtol(p + 1, &q, 10); + if (*q == '>' && n >= 0 && n < INT_MAX && errno == 0) { + p = q + 1; + is_printf = 0; + pri = n; + if (*p == '1') { /* syslog-protocol version */ + p += 2; /* skip version and space */ + bsdsyslog = false; + } else { + bsdsyslog = true; + } + } + } + for (q = p; *q != '\0' && *q != '\n'; q++) + /* look for end of line; no further checks. + * trust the kernel to send ASCII only */; + if (*q != '\0') + *q++ = '\0'; + else { + memcpy(linebuf, p, klog_linebufoff = q - p); + break; + } + + if (pri &~ (LOG_FACMASK|LOG_PRIMASK)) + pri = DEFSPRI; + + /* allow all kinds of input from kernel */ + if (is_printf) + buffer = printline_kernelprintf( + LocalFQDN, p, flags, pri); + else { + if (bsdsyslog) + buffer = printline_bsdsyslog( + LocalFQDN, p, flags, pri); + else + buffer = printline_syslogprotocol( + LocalFQDN, p, flags, pri); + } + + /* set fields left open */ + if (!buffer->prog) + buffer->prog = strdup(_PATH_UNIX); + if (!buffer->host) + buffer->host = LocalFQDN; + if (!buffer->recvhost) + buffer->recvhost = LocalFQDN; + + logmsg(buffer); + DELREF(buffer); + p = q; + } +} + +/* + * 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; + const char *cursor; + char prev, next; + size_t len; + + if (name[0] == '\0') + return 0; + + if (strchr(name, ',')) /* sanity */ + return 0; + + len = strlen(name); + cursor = spec; + while ((s = (*check)(cursor, name)) != NULL) { + prev = s == spec ? ',' : *(s - 1); + cursor = s + len; + next = *cursor; + + if (prev == ',' && (next == '\0' || next == ',')) + return 1; + } + + return 0; +} + +/* + * wrapper with old function signature, + * keeps calling code shorter and hides buffer allocation + */ +void +logmsg_async(int pri, const char *sd, const char *msg, int flags) +{ + struct buf_msg *buffer; + size_t msglen; + + DPRINTF((D_CALL|D_DATA), "logmsg_async(%d, \"%s\", \"%s\", %d)\n", + pri, sd, msg, flags); + + if (msg) { + msglen = strlen(msg); + msglen++; /* adds \0 */ + buffer = buf_msg_new(msglen); + buffer->msglen = strlcpy(buffer->msg, msg, msglen) + 1; + } else { + buffer = buf_msg_new(0); + } + if (sd) buffer->sd = strdup(sd); + buffer->timestamp = strdup(make_timestamp(NULL, !BSDOutputFormat)); + buffer->prog = appname; + buffer->pid = include_pid; + buffer->recvhost = buffer->host = LocalFQDN; + buffer->pri = pri; + buffer->flags = flags; + + logmsg(buffer); + DELREF(buffer); +} + +/* read timestamp in from_buf, convert into a timestamp in to_buf + * + * returns length of timestamp found in from_buf (= number of bytes consumed) + */ +size_t +check_timestamp(unsigned char *from_buf, char **to_buf, + bool from_iso, bool to_iso) +{ + unsigned char *q; + int p; + bool found_ts = false; + + DPRINTF((D_CALL|D_DATA), "check_timestamp(%p = \"%s\", from_iso=%d, " + "to_iso=%d)\n", from_buf, from_buf, from_iso, to_iso); + + if (!from_buf) return 0; + /* + * Check to see if msg looks non-standard. + * looks at every char because we do not have a msg length yet + */ + /* detailed checking adapted from Albert Mietus' sl_timestamp.c */ + if (from_iso) { + if (from_buf[4] == '-' && from_buf[7] == '-' + && from_buf[10] == 'T' && from_buf[13] == ':' + && from_buf[16] == ':' + && isdigit(from_buf[0]) && isdigit(from_buf[1]) + && isdigit(from_buf[2]) && isdigit(from_buf[3]) /* YYYY */ + && isdigit(from_buf[5]) && isdigit(from_buf[6]) + && isdigit(from_buf[8]) && isdigit(from_buf[9]) /* mm dd */ + && isdigit(from_buf[11]) && isdigit(from_buf[12]) /* HH */ + && isdigit(from_buf[14]) && isdigit(from_buf[15]) /* MM */ + && isdigit(from_buf[17]) && isdigit(from_buf[18]) /* SS */ + ) { + /* time-secfrac */ + if (from_buf[19] == '.') + for (p=20; isdigit(from_buf[p]); p++) /* NOP*/; + else + p = 19; + /* time-offset */ + if (from_buf[p] == 'Z' + || ((from_buf[p] == '+' || from_buf[p] == '-') + && from_buf[p+3] == ':' + && isdigit(from_buf[p+1]) && isdigit(from_buf[p+2]) + && isdigit(from_buf[p+4]) && isdigit(from_buf[p+5]) + )) + found_ts = true; + } + } else { + if (from_buf[3] == ' ' && from_buf[6] == ' ' + && from_buf[9] == ':' && from_buf[12] == ':' + && (from_buf[4] == ' ' || isdigit(from_buf[4])) + && isdigit(from_buf[5]) /* dd */ + && isdigit(from_buf[7]) && isdigit(from_buf[8]) /* HH */ + && isdigit(from_buf[10]) && isdigit(from_buf[11]) /* MM */ + && isdigit(from_buf[13]) && isdigit(from_buf[14]) /* SS */ + && isupper(from_buf[0]) && islower(from_buf[1]) /* month */ + && islower(from_buf[2])) + found_ts = true; + } + if (!found_ts) { + if (from_buf[0] == '-' && from_buf[1] == ' ') { + /* NILVALUE */ + if (to_iso) { + /* with ISO = syslog-protocol output leave + * it as is, because it is better to have + * no timestamp than a wrong one. + */ + *to_buf = strdup("-"); + } else { + /* with BSD Syslog the field is reqired + * so replace it with current time + */ + *to_buf = strdup(make_timestamp(NULL, false)); + } + return 2; + } + return 0; + } + + if (!from_iso && !to_iso) { + /* copy BSD timestamp */ + DPRINTF(D_CALL, "check_timestamp(): copy BSD timestamp\n"); + *to_buf = strndup((char *)from_buf, BSD_TIMESTAMPLEN); + return BSD_TIMESTAMPLEN; + } else if (from_iso && to_iso) { + /* copy ISO timestamp */ + DPRINTF(D_CALL, "check_timestamp(): copy ISO timestamp\n"); + if (!(q = (unsigned char *) strchr((char *)from_buf, ' '))) + q = from_buf + strlen((char *)from_buf); + *to_buf = strndup((char *)from_buf, q - from_buf); + return q - from_buf; + } else if (from_iso && !to_iso) { + /* convert ISO->BSD */ + struct tm parsed; + time_t timeval; + char tsbuf[MAX_TIMESTAMPLEN]; + int i = 0; + + DPRINTF(D_CALL, "check_timestamp(): convert ISO->BSD\n"); + for(i = 0; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' + && from_buf[i] != '.' && from_buf[i] != ' '; i++) + tsbuf[i] = from_buf[i]; /* copy date & time */ + for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' + && from_buf[i] != '+' && from_buf[i] != '-' + && from_buf[i] != 'Z' && from_buf[i] != ' '; i++) + ; /* skip fraction digits */ + for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' + && from_buf[i] != ':' && from_buf[i] != ' ' ; i++) + tsbuf[i] = from_buf[i]; /* copy TZ */ + if (from_buf[i] == ':') i++; /* skip colon */ + for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0' + && from_buf[i] != ' ' ; i++) + tsbuf[i] = from_buf[i]; /* copy TZ */ + + (void)memset(&parsed, 0, sizeof(parsed)); + parsed.tm_isdst = -1; + (void)strptime(tsbuf, "%FT%T%z", &parsed); + timeval = mktime(&parsed); + + *to_buf = strndup(make_timestamp(&timeval, false), + BSD_TIMESTAMPLEN); + return i; + } else if (!from_iso && to_iso) { + /* convert BSD->ISO */ + struct tm parsed; + struct tm *current; + time_t timeval; + char *rc; + + (void)memset(&parsed, 0, sizeof(parsed)); + parsed.tm_isdst = -1; + DPRINTF(D_CALL, "check_timestamp(): convert BSD->ISO\n"); + rc = strptime((char *)from_buf, "%b %d %T", &parsed); + current = gmtime(&now); + + /* use current year and timezone */ + parsed.tm_isdst = current->tm_isdst; + parsed.tm_gmtoff = current->tm_gmtoff; + parsed.tm_year = current->tm_year; + if (current->tm_mon == 0 && parsed.tm_mon == 11) + parsed.tm_year--; + + timeval = mktime(&parsed); + rc = make_timestamp(&timeval, true); + *to_buf = strndup(rc, MAX_TIMESTAMPLEN-1); + + return BSD_TIMESTAMPLEN; + } else { + DPRINTF(D_MISC, + "Executing unreachable code in check_timestamp()\n"); + return 0; + } +} + +/* + * Log a message to the appropriate log files, users, etc. based on + * the priority. + */ +void +logmsg(struct buf_msg *buffer) +{ + struct filed *f; +#ifndef __minix + int fac, omask, prilev; +#else /* __minix */ + int fac, prilev; + sigset_t omask; +#endif /* __minix */ + + DPRINTF((D_CALL|D_BUFFER), "logmsg: buffer@%p, pri 0%o/%d, flags 0x%x," + " timestamp \"%s\", from \"%s\", sd \"%s\", msg \"%s\"\n", + buffer, buffer->pri, buffer->pri, buffer->flags, + buffer->timestamp, buffer->recvhost, buffer->sd, buffer->msg); + +#ifndef __minix + omask = sigblock(sigmask(SIGHUP)|sigmask(SIGALRM)); +#else /* __minix */ + sigemptyset(&omask); + sigaddset(&omask, SIGHUP); + sigaddset(&omask, SIGALRM); +#define sigsetmask(s) (sigprocmask(SIG_SETMASK, &s, NULL)) +#endif /* __minix */ + + /* sanity check */ + assert(buffer->refcount == 1); + assert(buffer->msglen <= buffer->msgsize); + assert(buffer->msgorig <= buffer->msg); + assert((buffer->msg && buffer->msglen == strlen(buffer->msg)+1) + || (!buffer->msg && !buffer->msglen)); + if (!buffer->msg && !buffer->sd && !buffer->msgid) + DPRINTF(D_BUFFER, "Empty message?\n"); + + /* extract facility and priority level */ + if (buffer->flags & MARK) + fac = LOG_NFACILITIES; + else + fac = LOG_FAC(buffer->pri); + prilev = LOG_PRI(buffer->pri); + + /* log the message to the particular outputs */ + if (!Initialized) { + f = &consfile; + f->f_file = open(ctty, O_WRONLY | O_NDELAY, 0); + + if (f->f_file >= 0) { + DELREF(f->f_prevmsg); + f->f_prevmsg = NEWREF(buffer); + fprintlog(f, NEWREF(buffer), NULL); + DELREF(buffer); + (void)close(f->f_file); + } + (void)sigsetmask(omask); + return; + } + + for (f = Files; f; f = f->f_next) { + /* skip messages that are incorrect priority */ + if (!MATCH_PRI(f, fac, prilev) + || f->f_pmask[fac] == INTERNAL_NOPRI) + continue; + + /* skip messages with the incorrect host name */ + /* do we compare with host (IMHO correct) or recvhost */ + /* (compatible)? */ + if (f->f_host != NULL && buffer->host != NULL) { + char shost[MAXHOSTNAMELEN + 1], *h; + if (!BSDOutputFormat) { + h = buffer->host; + } else { + (void)strlcpy(shost, buffer->host, + sizeof(shost)); + trim_anydomain(shost); + h = shost; + } + switch (f->f_host[0]) { + case '+': + if (! matches_spec(h, f->f_host + 1, + strcasestr)) + continue; + break; + case '-': + if (matches_spec(h, f->f_host + 1, + strcasestr)) + continue; + break; + } + } + + /* skip messages with the incorrect program name */ + if (f->f_program != NULL && buffer->prog != NULL) { + switch (f->f_program[0]) { + case '+': + if (!matches_spec(buffer->prog, + f->f_program + 1, strstr)) + continue; + break; + case '-': + if (matches_spec(buffer->prog, + f->f_program + 1, strstr)) + continue; + break; + default: + if (!matches_spec(buffer->prog, + f->f_program, strstr)) + continue; + break; + } + } + + if (f->f_type == F_CONSOLE && (buffer->flags & IGN_CONS)) + continue; + + /* don't output marks to recently written files */ + if ((buffer->flags & MARK) + && (now - f->f_time) < MarkInterval / 2) + continue; + + /* + * suppress duplicate lines to this file unless NoRepeat + */ +#define MSG_FIELD_EQ(x) ((!buffer->x && !f->f_prevmsg->x) || \ + (buffer->x && f->f_prevmsg->x && !strcmp(buffer->x, f->f_prevmsg->x))) + + if ((buffer->flags & MARK) == 0 && + f->f_prevmsg && + buffer->msglen == f->f_prevmsg->msglen && + !NoRepeat && + MSG_FIELD_EQ(host) && + MSG_FIELD_EQ(sd) && + MSG_FIELD_EQ(msg) + ) { + f->f_prevcount++; + DPRINTF(D_DATA, "Msg repeated %d times, %ld sec of %d\n", + f->f_prevcount, (long)(now - f->f_time), + repeatinterval[f->f_repeatcount]); + /* + * If domark would have logged this by now, + * flush it now (so we don't hold isolated messages), + * but back off so we'll flush less often + * in the future. + */ + if (now > REPEATTIME(f)) { + fprintlog(f, NEWREF(buffer), NULL); + DELREF(buffer); + BACKOFF(f); + } + } else { + /* new line, save it */ + if (f->f_prevcount) + fprintlog(f, NULL, NULL); + f->f_repeatcount = 0; + DELREF(f->f_prevmsg); + f->f_prevmsg = NEWREF(buffer); + fprintlog(f, NEWREF(buffer), NULL); + DELREF(buffer); + } + } + (void)sigsetmask(omask); +} + +/* + * format one buffer into output format given by flag BSDOutputFormat + * line is allocated and has to be free()d by caller + * size_t pointers are optional, if not NULL then they will return + * different lenghts used for formatting and output + */ +#define OUT(x) ((x)?(x):"-") +bool +format_buffer(struct buf_msg *buffer, char **line, size_t *ptr_linelen, + size_t *ptr_msglen, size_t *ptr_tlsprefixlen, size_t *ptr_prilen) +{ +#define FPBUFSIZE 30 + static char ascii_empty[] = ""; + char fp_buf[FPBUFSIZE] = "\0"; + char *hostname, *shorthostname = NULL; + char *ascii_sd = ascii_empty; + char *ascii_msg = ascii_empty; + size_t linelen, msglen, tlsprefixlen, prilen, j; + + DPRINTF(D_CALL, "format_buffer(%p)\n", buffer); + if (!buffer) return false; + + /* All buffer fields are set with strdup(). To avoid problems + * on memory exhaustion we allow them to be empty and replace + * the essential fields with already allocated generic values. + */ + if (!buffer->timestamp) + buffer->timestamp = timestamp; + if (!buffer->host && !buffer->recvhost) + buffer->host = LocalFQDN; + + if (LogFacPri) { + const char *f_s = NULL, *p_s = NULL; + int fac = buffer->pri & LOG_FACMASK; + int pri = LOG_PRI(buffer->pri); + char f_n[5], p_n[5]; + + if (LogFacPri > 1) { + CODE *c; + + for (c = facilitynames; c->c_name != NULL; c++) { + if (c->c_val == fac) { + f_s = c->c_name; + break; + } + } + for (c = prioritynames; c->c_name != NULL; c++) { + if (c->c_val == pri) { + p_s = c->c_name; + break; + } + } + } + if (f_s == NULL) { + snprintf(f_n, sizeof(f_n), "%d", LOG_FAC(fac)); + f_s = f_n; + } + if (p_s == NULL) { + snprintf(p_n, sizeof(p_n), "%d", pri); + p_s = p_n; + } + snprintf(fp_buf, sizeof(fp_buf), "<%s.%s>", f_s, p_s); + } + + /* hostname or FQDN */ + hostname = (buffer->host ? buffer->host : buffer->recvhost); + if (BSDOutputFormat + && (shorthostname = strdup(hostname))) { + /* if the previous BSD output format with "host [recvhost]:" + * gets implemented, this is the right place to distinguish + * between buffer->host and buffer->recvhost + */ + trim_anydomain(shorthostname); + hostname = shorthostname; + } + + /* new message formatting: + * instead of using iov always assemble one complete TLS-ready line + * with length and priority (depending on BSDOutputFormat either in + * BSD Syslog or syslog-protocol format) + * + * additionally save the length of the prefixes, + * so UDP destinations can skip the length prefix and + * file/pipe/wall destinations can omit length and priority + */ + /* first determine required space */ + if (BSDOutputFormat) { + /* only output ASCII chars */ + if (buffer->sd) + ascii_sd = copy_utf8_ascii(buffer->sd, + strlen(buffer->sd)); + if (buffer->msg) { + if (IS_BOM(buffer->msg)) + ascii_msg = copy_utf8_ascii(buffer->msg, + buffer->msglen - 1); + else /* assume already converted at input */ + ascii_msg = buffer->msg; + } + msglen = snprintf(NULL, 0, "<%d>%s%.15s %s %s%s%s%s: %s%s%s", + buffer->pri, fp_buf, buffer->timestamp, + hostname, OUT(buffer->prog), + buffer->pid ? "[" : "", + buffer->pid ? buffer->pid : "", + buffer->pid ? "]" : "", ascii_sd, + (buffer->sd && buffer->msg ? " ": ""), ascii_msg); + } else + msglen = snprintf(NULL, 0, "<%d>1 %s%s %s %s %s %s %s%s%s", + buffer->pri, fp_buf, buffer->timestamp, + hostname, OUT(buffer->prog), OUT(buffer->pid), + OUT(buffer->msgid), OUT(buffer->sd), + (buffer->msg ? " ": ""), + (buffer->msg ? buffer->msg: "")); + /* add space for length prefix */ + tlsprefixlen = 0; + for (j = msglen; j; j /= 10) + tlsprefixlen++; + /* one more for the space */ + tlsprefixlen++; + + prilen = snprintf(NULL, 0, "<%d>", buffer->pri); + if (!BSDOutputFormat) + prilen += 2; /* version char and space */ + MALLOC(*line, msglen + tlsprefixlen + 1); + if (BSDOutputFormat) + linelen = snprintf(*line, + msglen + tlsprefixlen + 1, + "%zu <%d>%s%.15s %s %s%s%s%s: %s%s%s", + msglen, buffer->pri, fp_buf, buffer->timestamp, + hostname, OUT(buffer->prog), + (buffer->pid ? "[" : ""), + (buffer->pid ? buffer->pid : ""), + (buffer->pid ? "]" : ""), ascii_sd, + (buffer->sd && buffer->msg ? " ": ""), ascii_msg); + else + linelen = snprintf(*line, + msglen + tlsprefixlen + 1, + "%zu <%d>1 %s%s %s %s %s %s %s%s%s", + msglen, buffer->pri, fp_buf, buffer->timestamp, + hostname, OUT(buffer->prog), OUT(buffer->pid), + OUT(buffer->msgid), OUT(buffer->sd), + (buffer->msg ? " ": ""), + (buffer->msg ? buffer->msg: "")); + DPRINTF(D_DATA, "formatted %zu octets to: '%.*s' (linelen %zu, " + "msglen %zu, tlsprefixlen %zu, prilen %zu)\n", linelen, + (int)linelen, *line, linelen, msglen, tlsprefixlen, prilen); + + FREEPTR(shorthostname); + if (ascii_sd != ascii_empty) + FREEPTR(ascii_sd); + if (ascii_msg != ascii_empty && ascii_msg != buffer->msg) + FREEPTR(ascii_msg); + + if (ptr_linelen) *ptr_linelen = linelen; + if (ptr_msglen) *ptr_msglen = msglen; + if (ptr_tlsprefixlen) *ptr_tlsprefixlen = tlsprefixlen; + if (ptr_prilen) *ptr_prilen = prilen; + return true; +} + +/* + * if qentry == NULL: new message, if temporarily undeliverable it will be enqueued + * if qentry != NULL: a temporarily undeliverable message will not be enqueued, + * but after delivery be removed from the queue + */ +void +fprintlog(struct filed *f, struct buf_msg *passedbuffer, struct buf_queue *qentry) +{ + static char crnl[] = "\r\n"; + struct buf_msg *buffer = passedbuffer; + struct iovec iov[4]; + struct iovec *v = iov; + bool error = false; + int e = 0, len = 0; + size_t msglen, linelen, tlsprefixlen, prilen; + char *p, *line = NULL, *lineptr = NULL; +#ifndef DISABLE_SIGN + bool newhash = false; +#endif +#define REPBUFSIZE 80 + char greetings[200]; +#define ADDEV() do { v++; assert((size_t)(v - iov) < A_CNT(iov)); } while(/*CONSTCOND*/0) + + DPRINTF(D_CALL, "fprintlog(%p, %p, %p)\n", f, buffer, qentry); + + f->f_time = now; + + /* increase refcount here and lower again at return. + * this enables the buffer in the else branch to be freed + * --> every branch needs one NEWREF() or buf_msg_new()! */ + if (buffer) { + (void)NEWREF(buffer); + } else { + if (f->f_prevcount > 1) { + /* possible syslog-sign incompatibility: + * assume destinations f1 and f2 share one SG and + * get the same message sequence. + * + * now both f1 and f2 generate "repeated" messages + * "repeated" messages are different due to different + * timestamps + * the SG will get hashes for the two "repeated" messages + * + * now both f1 and f2 are just fine, but a verification + * will report that each 'lost' a message, i.e. the + * other's "repeated" message + * + * conditions for 'safe configurations': + * - use NoRepeat option, + * - use SG 3, or + * - have exactly one destination for every PRI + */ + buffer = buf_msg_new(REPBUFSIZE); + buffer->msglen = snprintf(buffer->msg, REPBUFSIZE, + "last message repeated %d times", f->f_prevcount); + buffer->timestamp = + strdup(make_timestamp(NULL, !BSDOutputFormat)); + buffer->pri = f->f_prevmsg->pri; + buffer->host = LocalFQDN; + buffer->prog = appname; + buffer->pid = include_pid; + + } else { + buffer = NEWREF(f->f_prevmsg); + } + } + + /* no syslog-sign messages to tty/console/... */ + if ((buffer->flags & SIGN_MSG) + && ((f->f_type == F_UNUSED) + || (f->f_type == F_TTY) + || (f->f_type == F_CONSOLE) + || (f->f_type == F_USERS) + || (f->f_type == F_WALL) + || (f->f_type == F_FIFO))) { + DELREF(buffer); + return; + } + + /* buffering works only for few types */ + if (qentry + && (f->f_type != F_TLS) + && (f->f_type != F_PIPE) + && (f->f_type != F_FILE) + && (f->f_type != F_FIFO)) { + errno = 0; + logerror("Warning: unexpected message type %d in buffer", + f->f_type); + DELREF(buffer); + return; + } + + if (!format_buffer(buffer, &line, + &linelen, &msglen, &tlsprefixlen, &prilen)) { + DPRINTF(D_CALL, "format_buffer() failed, skip message\n"); + DELREF(buffer); + return; + } + /* assert maximum message length */ + if (TypeInfo[f->f_type].max_msg_length != -1 + && (size_t)TypeInfo[f->f_type].max_msg_length + < linelen - tlsprefixlen - prilen) { + linelen = TypeInfo[f->f_type].max_msg_length + + tlsprefixlen + prilen; + DPRINTF(D_DATA, "truncating oversized message to %zu octets\n", + linelen); + } + +#ifndef DISABLE_SIGN + /* keep state between appending the hash (before buffer is sent) + * and possibly sending a SB (after buffer is sent): */ + /* get hash */ + if (!(buffer->flags & SIGN_MSG) && !qentry) { + char *hash = NULL; + struct signature_group_t *sg; + + if ((sg = sign_get_sg(buffer->pri, f)) != NULL) { + if (sign_msg_hash(line + tlsprefixlen, &hash)) + newhash = sign_append_hash(hash, sg); + else + DPRINTF(D_SIGN, + "Unable to hash line \"%s\"\n", line); + } + } +#endif /* !DISABLE_SIGN */ + + /* set start and length of buffer and/or fill iovec */ + switch (f->f_type) { + case F_UNUSED: + /* nothing */ + break; + case F_TLS: + /* nothing, as TLS uses whole buffer to send */ + lineptr = line; + len = linelen; + break; + case F_FORW: + lineptr = line + tlsprefixlen; + len = linelen - tlsprefixlen; + break; + case F_PIPE: + case F_FIFO: + case F_FILE: /* fallthrough */ + if (f->f_flags & FFLAG_FULL) { + v->iov_base = line + tlsprefixlen; + v->iov_len = linelen - tlsprefixlen; + } else { + v->iov_base = line + tlsprefixlen + prilen; + v->iov_len = linelen - tlsprefixlen - prilen; + } + ADDEV(); + v->iov_base = &crnl[1]; + v->iov_len = 1; + ADDEV(); + break; + case F_CONSOLE: + case F_TTY: + /* filter non-ASCII */ + p = line; + while (*p) { + *p = FORCE2ASCII(*p); + p++; + } + v->iov_base = line + tlsprefixlen + prilen; + v->iov_len = linelen - tlsprefixlen - prilen; + ADDEV(); + v->iov_base = crnl; + v->iov_len = 2; + ADDEV(); + break; + case F_WALL: + v->iov_base = greetings; + v->iov_len = snprintf(greetings, sizeof(greetings), + "\r\n\7Message from syslogd@%s at %s ...\r\n", + (buffer->host ? buffer->host : buffer->recvhost), + buffer->timestamp); + ADDEV(); + case F_USERS: /* fallthrough */ + /* filter non-ASCII */ + p = line; + while (*p) { + *p = FORCE2ASCII(*p); + p++; + } + v->iov_base = line + tlsprefixlen + prilen; + v->iov_len = linelen - tlsprefixlen - prilen; + ADDEV(); + v->iov_base = &crnl[1]; + v->iov_len = 1; + ADDEV(); + break; + } + + /* send */ + switch (f->f_type) { + case F_UNUSED: + DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name); + break; + + case F_FORW: + DPRINTF(D_MISC, "Logging to %s %s\n", + TypeInfo[f->f_type].name, f->f_un.f_forw.f_hname); + udp_send(f, lineptr, len); + break; + +#ifndef DISABLE_TLS + case F_TLS: + DPRINTF(D_MISC, "Logging to %s %s\n", + TypeInfo[f->f_type].name, + f->f_un.f_tls.tls_conn->hostname); + /* make sure every message gets queued once + * it will be removed when sendmsg is sent and free()d */ + if (!qentry) + qentry = message_queue_add(f, NEWREF(buffer)); + (void)tls_send(f, lineptr, len, qentry); + break; +#endif /* !DISABLE_TLS */ + + case F_PIPE: + DPRINTF(D_MISC, "Logging to %s %s\n", + TypeInfo[f->f_type].name, f->f_un.f_pipe.f_pname); + if (f->f_un.f_pipe.f_pid == 0) { + /* (re-)open */ + 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("%s", f->f_un.f_pipe.f_pname); + message_queue_freeall(f); + break; + } else if (!qentry) /* prevent recursion */ + SEND_QUEUE(f); + } + if (writev(f->f_file, iov, v - iov) < 0) { + e = errno; + if (f->f_un.f_pipe.f_pid > 0) { + (void) close(f->f_file); + deadq_enter(f->f_un.f_pipe.f_pid, + f->f_un.f_pipe.f_pname); + } + f->f_un.f_pipe.f_pid = 0; + /* + * If the error was EPIPE, then what is likely + * has happened is we have a command that is + * designed to take a single message line and + * then exit, but we tried to feed it another + * one before we reaped the child and thus + * reset our state. + * + * Well, now we've reset our state, so try opening + * the pipe and sending the message again if EPIPE + * was the error. + */ + if (e == EPIPE) { + 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("%s", f->f_un.f_pipe.f_pname); + message_queue_freeall(f); + break; + } + if (writev(f->f_file, iov, v - iov) < 0) { + e = errno; + if (f->f_un.f_pipe.f_pid > 0) { + (void) close(f->f_file); + deadq_enter(f->f_un.f_pipe.f_pid, + f->f_un.f_pipe.f_pname); + } + f->f_un.f_pipe.f_pid = 0; + error = true; /* enqueue on return */ + } else + e = 0; + } + if (e != 0 && !error) { + errno = e; + logerror("%s", f->f_un.f_pipe.f_pname); + } + } + if (e == 0 && qentry) { /* sent buffered msg */ + message_queue_remove(f, qentry); + } + break; + + case F_CONSOLE: + if (buffer->flags & IGN_CONS) { + DPRINTF(D_MISC, "Logging to %s (ignored)\n", + TypeInfo[f->f_type].name); + break; + } + /* FALLTHROUGH */ + + case F_TTY: + case F_FILE: + DPRINTF(D_MISC, "Logging to %s %s\n", + TypeInfo[f->f_type].name, f->f_un.f_fname); + again: + if ((f->f_type == F_FILE ? writev(f->f_file, iov, v - iov) : + writev1(f->f_file, iov, v - iov)) < 0) { + e = errno; + if (f->f_type == F_FILE && e == ENOSPC) { + int lasterror = f->f_lasterror; + f->f_lasterror = e; + if (lasterror != e) + logerror("%s", f->f_un.f_fname); + error = true; /* enqueue on return */ + } + (void)close(f->f_file); + /* + * Check for errors on TTY's due to loss of tty + */ + if ((e == EIO || e == EBADF) && f->f_type != F_FILE) { + f->f_file = open(f->f_un.f_fname, + O_WRONLY|O_APPEND|O_NONBLOCK, 0); + if (f->f_file < 0) { + f->f_type = F_UNUSED; + logerror("%s", f->f_un.f_fname); + message_queue_freeall(f); + } else + goto again; + } else { + f->f_type = F_UNUSED; + errno = e; + f->f_lasterror = e; + logerror("%s", f->f_un.f_fname); + message_queue_freeall(f); + } + } else { + f->f_lasterror = 0; + if ((buffer->flags & SYNC_FILE) + && (f->f_flags & FFLAG_SYNC)) + (void)fsync(f->f_file); + /* Problem with files: We cannot check beforehand if + * they would be writeable and call send_queue() first. + * So we call send_queue() after a successful write, + * which means the first message will be out of order. + */ + if (!qentry) /* prevent recursion */ + SEND_QUEUE(f); + else if (qentry) /* sent buffered msg */ + message_queue_remove(f, qentry); + } + break; + + case F_FIFO: + DPRINTF(D_MISC, "Logging to %s %s\n", + TypeInfo[f->f_type].name, f->f_un.f_fname); + if (f->f_file < 0) { + f->f_file = + open(f->f_un.f_fname, O_WRONLY|O_NONBLOCK, 0); + e = errno; + if (f->f_file < 0 && e == ENXIO) { + /* Drop messages with no reader */ + if (qentry) + message_queue_remove(f, qentry); + break; + } + } + + if (f->f_file >= 0 && writev(f->f_file, iov, v - iov) < 0) { + e = errno; + + /* Enqueue if the fifo buffer is full */ + if (e == EAGAIN) { + if (f->f_lasterror != e) + logerror("%s", f->f_un.f_fname); + f->f_lasterror = e; + error = true; /* enqueue on return */ + break; + } + + close(f->f_file); + f->f_file = -1; + + /* Drop messages with no reader */ + if (e == EPIPE) { + if (qentry) + message_queue_remove(f, qentry); + break; + } + } + + if (f->f_file < 0) { + f->f_type = F_UNUSED; + errno = e; + f->f_lasterror = e; + logerror("%s", f->f_un.f_fname); + message_queue_freeall(f); + break; + } + + f->f_lasterror = 0; + if (!qentry) /* prevent recursion (see comment for F_FILE) */ + SEND_QUEUE(f); + if (qentry) /* sent buffered msg */ + message_queue_remove(f, qentry); + break; + + case F_USERS: + case F_WALL: + DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name); + wallmsg(f, iov, v - iov); + break; + } + f->f_prevcount = 0; + + if (error && !qentry) + message_queue_add(f, NEWREF(buffer)); +#ifndef DISABLE_SIGN + if (newhash) { + struct signature_group_t *sg; + sg = sign_get_sg(buffer->pri, f); + (void)sign_send_signature_block(sg, false); + } +#endif /* !DISABLE_SIGN */ + /* this belongs to the ad-hoc buffer at the first if(buffer) */ + DELREF(buffer); + /* TLS frees on its own */ + if (f->f_type != F_TLS) + FREEPTR(line); +} + +/* send one line by UDP */ +void +udp_send(struct filed *f, char *line, size_t len) +{ + int lsent, fail, retry, j; + struct addrinfo *r; + + DPRINTF((D_NET|D_CALL), "udp_send(f=%p, line=\"%s\", " + "len=%zu) to dest.\n", f, line, len); + + if (!finet) + return; + + lsent = -1; + fail = 0; + assert(f->f_type == F_FORW); + for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) { + retry = 0; + for (j = 0; j < finet->fd; j++) { + if (finet[j+1].af != r->ai_family) + continue; +sendagain: + lsent = sendto(finet[j+1].fd, line, len, 0, + r->ai_addr, r->ai_addrlen); + if (lsent == -1) { + switch (errno) { + case ENOBUFS: + /* wait/retry/drop */ + if (++retry < 5) { + usleep(1000); + goto sendagain; + } + break; + case EHOSTDOWN: + case EHOSTUNREACH: + case ENETDOWN: + /* drop */ + break; + default: + /* busted */ + fail++; + break; + } + } else if ((size_t)lsent == len) + break; + } + if ((size_t)lsent != len && fail) { + f->f_type = F_UNUSED; + logerror("sendto() failed"); + } + } +} + +/* + * WALLMSG -- Write a message to the world at large + * + * Write the specified message to either the entire + * world, or a list of approved users. + */ +void +wallmsg(struct filed *f, struct iovec *iov, size_t iovcnt) +{ +#ifdef __NetBSD_Version__ + static int reenter; /* avoid calling ourselves */ + int i; + char *p; + struct utmpentry *ep; + + if (reenter++) + return; + + (void)getutentries(NULL, &ep); + /* NOSTRICT */ + for (; ep; ep = ep->next) { + if (f->f_type == F_WALL) { + if ((p = ttymsg(iov, iovcnt, ep->line, TTYMSGTIME)) + != NULL) { + errno = 0; /* already in msg */ + logerror("%s", p); + } + continue; + } + /* should we send the message to this user? */ + for (i = 0; i < MAXUNAMES; i++) { + if (!f->f_un.f_uname[i][0]) + break; + if (strcmp(f->f_un.f_uname[i], ep->name) == 0) { + struct stat st; + char tty[MAXPATHLEN]; + snprintf(tty, sizeof(tty), "%s/%s", _PATH_DEV, + ep->line); + if (stat(tty, &st) != -1 && + (st.st_mode & S_IWGRP) == 0) + break; + + if ((p = ttymsg(iov, iovcnt, ep->line, + TTYMSGTIME)) != NULL) { + errno = 0; /* already in msg */ + logerror("%s", p); + } + break; + } + } + } + reenter = 0; +#endif /* __NetBSD_Version__ */ +} + +void +/*ARGSUSED*/ +reapchild(int fd, short event, void *ev) +{ + int status; + pid_t pid; + struct filed *f; + +#ifdef __minix +#define wait3(s,o,r) (assert(r == NULL), waitpid(-1,s,o)) +#endif /* __minix */ + while ((pid = wait3(&status, WNOHANG, NULL)) > 0) { + if (!Initialized || ShuttingDown) { + /* + * Be silent while we are initializing or + * shutting down. + */ + continue; + } + + if (deadq_remove(pid)) + continue; + + /* 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; + } + } + } +} + +/* + * Return a printable representation of a host address (FQDN if available) + */ +const char * +cvthname(struct sockaddr_storage *f) +{ + int error; + int niflag = NI_DGRAM; + static char host[NI_MAXHOST], ip[NI_MAXHOST]; + + error = getnameinfo((struct sockaddr*)f, ((struct sockaddr*)f)->sa_len, + ip, sizeof ip, NULL, 0, NI_NUMERICHOST|niflag); + + DPRINTF(D_CALL, "cvthname(%s)\n", ip); + + if (error) { + DPRINTF(D_NET, "Malformed from address %s\n", + gai_strerror(error)); + return "???"; + } + + if (!UseNameService) + return ip; + + error = getnameinfo((struct sockaddr*)f, ((struct sockaddr*)f)->sa_len, + host, sizeof host, NULL, 0, niflag); + if (error) { + DPRINTF(D_NET, "Host name for your address (%s) unknown\n", ip); + return ip; + } + + return host; +} + +void +trim_anydomain(char *host) +{ + bool onlydigits = true; + int i; + + if (!BSDOutputFormat) + return; + + /* if non-digits found, then assume hostname and cut at first dot (this + * case also covers IPv6 addresses which should not contain dots), + * if only digits then assume IPv4 address and do not cut at all */ + for (i = 0; host[i]; i++) { + if (host[i] == '.' && !onlydigits) + host[i] = '\0'; + else if (!isdigit((unsigned char)host[i]) && host[i] != '.') + onlydigits = false; + } +} + +static void +/*ARGSUSED*/ +domark(int fd, short event, void *ev) +{ + struct event *ev_pass = (struct event *)ev; + struct filed *f; + dq_t q, nextq; + sigset_t newmask, omask; + + schedule_event(&ev_pass, + &((struct timeval){TIMERINTVL, 0}), + domark, ev_pass); + DPRINTF((D_CALL|D_EVENT), "domark()\n"); + + BLOCK_SIGNALS(omask, newmask); + now = time(NULL); + MarkSeq += TIMERINTVL; + if (MarkSeq >= MarkInterval) { + logmsg_async(LOG_INFO, NULL, "-- MARK --", ADDDATE|MARK); + MarkSeq = 0; + } + + for (f = Files; f; f = f->f_next) { + if (f->f_prevcount && now >= REPEATTIME(f)) { + DPRINTF(D_DATA, "Flush %s: repeated %d times, %d sec.\n", + TypeInfo[f->f_type].name, f->f_prevcount, + repeatinterval[f->f_repeatcount]); + fprintlog(f, NULL, NULL); + BACKOFF(f); + } + } + message_allqueues_check(); + RESTORE_SIGNALS(omask); + + /* 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--; + } + } +#ifndef DISABLE_SIGN + if (GlobalSign.rsid) { /* check if initialized */ + struct signature_group_t *sg; + STAILQ_FOREACH(sg, &GlobalSign.SigGroups, entries) { + sign_send_certificate_block(sg); + } + } +#endif /* !DISABLE_SIGN */ +} + +/* + * Print syslogd errors some place. + */ +void +logerror(const char *fmt, ...) +{ + static int logerror_running; + va_list ap; + char tmpbuf[BUFSIZ]; + char buf[BUFSIZ]; + char *outbuf; + + /* If there's an error while trying to log an error, give up. */ + if (logerror_running) + return; + logerror_running = 1; + + va_start(ap, fmt); + (void)vsnprintf(tmpbuf, sizeof(tmpbuf), fmt, ap); + va_end(ap); + + if (errno) { + (void)snprintf(buf, sizeof(buf), "%s: %s", + tmpbuf, strerror(errno)); + outbuf = buf; + } else { + (void)snprintf(buf, sizeof(buf), "%s", tmpbuf); + outbuf = tmpbuf; + } + + if (daemonized) + logmsg_async(LOG_SYSLOG|LOG_ERR, NULL, outbuf, ADDDATE); + if (!daemonized && Debug) + DPRINTF(D_MISC, "%s\n", outbuf); + if (!daemonized && !Debug) + printf("%s\n", outbuf); + + logerror_running = 0; +} + +/* + * Print syslogd info some place. + */ +void +loginfo(const char *fmt, ...) +{ + va_list ap; + char buf[BUFSIZ]; + + va_start(ap, fmt); + (void)vsnprintf(buf, sizeof(buf), fmt, ap); + va_end(ap); + + DPRINTF(D_MISC, "%s\n", buf); + logmsg_async(LOG_SYSLOG|LOG_INFO, NULL, buf, ADDDATE); +} + +#ifndef DISABLE_TLS +static inline void +free_incoming_tls_sockets(void) +{ + struct TLS_Incoming_Conn *tls_in; + int i; + + /* + * close all listening and connected TLS sockets + */ + if (TLS_Listen_Set) + for (i = 0; i < TLS_Listen_Set->fd; i++) { + if (close(TLS_Listen_Set[i+1].fd) == -1) + logerror("close() failed"); + DEL_EVENT(TLS_Listen_Set[i+1].ev); + FREEPTR(TLS_Listen_Set[i+1].ev); + } + FREEPTR(TLS_Listen_Set); + /* close/free incoming TLS connections */ + while (!SLIST_EMPTY(&TLS_Incoming_Head)) { + tls_in = SLIST_FIRST(&TLS_Incoming_Head); + SLIST_REMOVE_HEAD(&TLS_Incoming_Head, entries); + FREEPTR(tls_in->inbuf); + free_tls_conn(tls_in->tls_conn); + free(tls_in); + } +} +#endif /* !DISABLE_TLS */ + +void +/*ARGSUSED*/ +die(int fd, short event, void *ev) +{ + struct filed *f, *next; + char **p; + sigset_t newmask, omask; + int i; + size_t j; + + ShuttingDown = 1; /* Don't log SIGCHLDs. */ + /* prevent recursive signals */ + BLOCK_SIGNALS(omask, newmask); + + errno = 0; + if (ev != NULL) + logerror("Exiting on signal %d", fd); + else + logerror("Fatal error, exiting"); + + /* + * flush any pending output + */ + for (f = Files; f != NULL; f = f->f_next) { + /* flush any pending output */ + if (f->f_prevcount) + fprintlog(f, NULL, NULL); + SEND_QUEUE(f); + } + +#ifndef DISABLE_TLS + free_incoming_tls_sockets(); +#endif /* !DISABLE_TLS */ +#ifndef DISABLE_SIGN + sign_global_free(); +#endif /* !DISABLE_SIGN */ + + /* + * Close all open log files. + */ + for (f = Files; f != NULL; f = next) { + message_queue_freeall(f); + + switch (f->f_type) { + case F_FILE: + case F_TTY: + case F_CONSOLE: + case F_FIFO: + if (f->f_file >= 0) + (void)close(f->f_file); + break; + case F_PIPE: + if (f->f_un.f_pipe.f_pid > 0) { + (void)close(f->f_file); + } + 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; +#ifndef DISABLE_TLS + case F_TLS: + free_tls_conn(f->f_un.f_tls.tls_conn); + break; +#endif /* !DISABLE_TLS */ + } + next = f->f_next; + DELREF(f->f_prevmsg); + FREEPTR(f->f_program); + FREEPTR(f->f_host); + DEL_EVENT(f->f_sq_event); + free((char *)f); + } + + /* + * Close all open UDP sockets + */ + if (finet) { + for (i = 0; i < finet->fd; i++) { + if (close(finet[i+1].fd) < 0) { + logerror("close() failed"); + die(0, 0, NULL); + } + DEL_EVENT(finet[i+1].ev); + FREEPTR(finet[i+1].ev); + } + FREEPTR(finet); + } + + /* free config options */ + for (j = 0; j < A_CNT(TypeInfo); j++) { + FREEPTR(TypeInfo[j].queue_length_string); + FREEPTR(TypeInfo[j].queue_size_string); + } + +#ifndef DISABLE_TLS + FREEPTR(tls_opt.CAdir); + FREEPTR(tls_opt.CAfile); + FREEPTR(tls_opt.keyfile); + FREEPTR(tls_opt.certfile); + FREEPTR(tls_opt.x509verify); + FREEPTR(tls_opt.bindhost); + FREEPTR(tls_opt.bindport); + FREEPTR(tls_opt.server); + FREEPTR(tls_opt.gen_cert); + free_cred_SLIST(&tls_opt.cert_head); + free_cred_SLIST(&tls_opt.fprint_head); + FREE_SSL_CTX(tls_opt.global_TLS_CTX); +#endif /* !DISABLE_TLS */ + + FREEPTR(funix); + for (p = LogPaths; p && *p; p++) + unlink(*p); + exit(0); +} + +#ifndef DISABLE_SIGN +/* + * get one "sign_delim_sg2" item, convert and store in ordered queue + */ +void +store_sign_delim_sg2(char *tmp_buf) +{ + struct string_queue *sqentry, *sqe1, *sqe2; + + if(!(sqentry = malloc(sizeof(*sqentry)))) { + logerror("Unable to allocate memory"); + return; + } + /*LINTED constcond/null effect */ + assert(sizeof(int64_t) == sizeof(uint_fast64_t)); + if (dehumanize_number(tmp_buf, (int64_t*) &(sqentry->key)) == -1 + || sqentry->key > (LOG_NFACILITIES<<3)) { + DPRINTF(D_PARSE, "invalid sign_delim_sg2: %s\n", tmp_buf); + free(sqentry); + FREEPTR(tmp_buf); + return; + } + sqentry->data = tmp_buf; + + if (STAILQ_EMPTY(&GlobalSign.sig2_delims)) { + STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims, + sqentry, entries); + return; + } + + /* keep delimiters sorted */ + sqe1 = sqe2 = STAILQ_FIRST(&GlobalSign.sig2_delims); + if (sqe1->key > sqentry->key) { + STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims, + sqentry, entries); + return; + } + + while ((sqe1 = sqe2) + && (sqe2 = STAILQ_NEXT(sqe1, entries))) { + if (sqe2->key > sqentry->key) { + break; + } else if (sqe2->key == sqentry->key) { + DPRINTF(D_PARSE, "duplicate sign_delim_sg2: %s\n", + tmp_buf); + FREEPTR(sqentry); + FREEPTR(tmp_buf); + return; + } + } + STAILQ_INSERT_AFTER(&GlobalSign.sig2_delims, sqe1, sqentry, entries); +} +#endif /* !DISABLE_SIGN */ + +/* + * read syslog.conf + */ +void +read_config_file(FILE *cf, struct filed **f_ptr) +{ + size_t linenum = 0; + size_t i; + struct filed *f, **nextp; + char cline[LINE_MAX]; + char prog[NAME_MAX + 1]; + char host[MAXHOSTNAMELEN]; + const char *p; + char *q; + bool found_keyword; +#ifndef DISABLE_TLS + struct peer_cred *cred = NULL; + struct peer_cred_head *credhead = NULL; +#endif /* !DISABLE_TLS */ +#ifndef DISABLE_SIGN + char *sign_sg_str = NULL; +#endif /* !DISABLE_SIGN */ +#if (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN)) + char *tmp_buf = NULL; +#endif /* (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN)) */ + /* central list of recognized configuration keywords + * and an address for their values as strings */ + const struct config_keywords { + const char *keyword; + char **variable; + } config_keywords[] = { +#ifndef DISABLE_TLS + /* TLS settings */ + {"tls_ca", &tls_opt.CAfile}, + {"tls_cadir", &tls_opt.CAdir}, + {"tls_cert", &tls_opt.certfile}, + {"tls_key", &tls_opt.keyfile}, + {"tls_verify", &tls_opt.x509verify}, + {"tls_bindport", &tls_opt.bindport}, + {"tls_bindhost", &tls_opt.bindhost}, + {"tls_server", &tls_opt.server}, + {"tls_gen_cert", &tls_opt.gen_cert}, + /* special cases in parsing */ + {"tls_allow_fingerprints",&tmp_buf}, + {"tls_allow_clientcerts", &tmp_buf}, + /* buffer settings */ + {"tls_queue_length", &TypeInfo[F_TLS].queue_length_string}, + {"tls_queue_size", &TypeInfo[F_TLS].queue_size_string}, +#endif /* !DISABLE_TLS */ + {"file_queue_length", &TypeInfo[F_FILE].queue_length_string}, + {"pipe_queue_length", &TypeInfo[F_PIPE].queue_length_string}, + {"fifo_queue_length", &TypeInfo[F_FIFO].queue_length_string}, + {"file_queue_size", &TypeInfo[F_FILE].queue_size_string}, + {"pipe_queue_size", &TypeInfo[F_PIPE].queue_size_string}, + {"fifo_queue_size", &TypeInfo[F_FIFO].queue_size_string}, +#ifndef DISABLE_SIGN + /* syslog-sign setting */ + {"sign_sg", &sign_sg_str}, + /* also special case in parsing */ + {"sign_delim_sg2", &tmp_buf}, +#endif /* !DISABLE_SIGN */ + }; + + DPRINTF(D_CALL, "read_config_file()\n"); + + /* free all previous config options */ + for (i = 0; i < A_CNT(TypeInfo); i++) { + if (TypeInfo[i].queue_length_string + && TypeInfo[i].queue_length_string + != TypeInfo[i].default_length_string) { + FREEPTR(TypeInfo[i].queue_length_string); + TypeInfo[i].queue_length_string = + strdup(TypeInfo[i].default_length_string); + } + if (TypeInfo[i].queue_size_string + && TypeInfo[i].queue_size_string + != TypeInfo[i].default_size_string) { + FREEPTR(TypeInfo[i].queue_size_string); + TypeInfo[i].queue_size_string = + strdup(TypeInfo[i].default_size_string); + } + } + for (i = 0; i < A_CNT(config_keywords); i++) + FREEPTR(*config_keywords[i].variable); + /* + * global settings + */ + while (fgets(cline, sizeof(cline), cf) != NULL) { + linenum++; + for (p = cline; isspace((unsigned char)*p); ++p) + continue; + if ((*p == '\0') || (*p == '#')) + continue; + + for (i = 0; i < A_CNT(config_keywords); i++) { + if (copy_config_value(config_keywords[i].keyword, + config_keywords[i].variable, &p, ConfFile, + linenum)) { + DPRINTF((D_PARSE|D_MEM), + "found option %s, saved @%p\n", + config_keywords[i].keyword, + *config_keywords[i].variable); +#ifndef DISABLE_SIGN + if (!strcmp("sign_delim_sg2", + config_keywords[i].keyword)) + do { + store_sign_delim_sg2(tmp_buf); + } while (copy_config_value_word( + &tmp_buf, &p)); + +#endif /* !DISABLE_SIGN */ + +#ifndef DISABLE_TLS + /* special cases with multiple parameters */ + if (!strcmp("tls_allow_fingerprints", + config_keywords[i].keyword)) + credhead = &tls_opt.fprint_head; + else if (!strcmp("tls_allow_clientcerts", + config_keywords[i].keyword)) + credhead = &tls_opt.cert_head; + + if (credhead) do { + if(!(cred = malloc(sizeof(*cred)))) { + logerror("Unable to " + "allocate memory"); + break; + } + cred->data = tmp_buf; + tmp_buf = NULL; + SLIST_INSERT_HEAD(credhead, + cred, entries); + } while /* additional values? */ + (copy_config_value_word(&tmp_buf, &p)); + credhead = NULL; + break; +#endif /* !DISABLE_TLS */ + } + } + } + /* convert strings to integer values */ + for (i = 0; i < A_CNT(TypeInfo); i++) { + if (!TypeInfo[i].queue_length_string + || dehumanize_number(TypeInfo[i].queue_length_string, + &TypeInfo[i].queue_length) == -1) + if (dehumanize_number(TypeInfo[i].default_length_string, + &TypeInfo[i].queue_length) == -1) + abort(); + if (!TypeInfo[i].queue_size_string + || dehumanize_number(TypeInfo[i].queue_size_string, + &TypeInfo[i].queue_size) == -1) + if (dehumanize_number(TypeInfo[i].default_size_string, + &TypeInfo[i].queue_size) == -1) + abort(); + } + +#ifndef DISABLE_SIGN + if (sign_sg_str) { + if (sign_sg_str[1] == '\0' + && (sign_sg_str[0] == '0' || sign_sg_str[0] == '1' + || sign_sg_str[0] == '2' || sign_sg_str[0] == '3')) + GlobalSign.sg = sign_sg_str[0] - '0'; + else { + GlobalSign.sg = SIGN_SG; + DPRINTF(D_MISC, "Invalid sign_sg value `%s', " + "use default value `%d'\n", + sign_sg_str, GlobalSign.sg); + } + } else /* disable syslog-sign */ + GlobalSign.sg = -1; +#endif /* !DISABLE_SIGN */ + + rewind(cf); + linenum = 0; + /* + * Foreach line in the conf table, open that file. + */ + f = NULL; + nextp = &f; + + strcpy(prog, "*"); + strcpy(host, "*"); + while (fgets(cline, sizeof(cline), cf) != NULL) { + linenum++; + found_keyword = false; + /* + * check for end-of-section, comments, strip off trailing + * 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') + continue; + if (*p == '#') { + p++; + if (*p != '!' && *p != '+' && *p != '-') + continue; + } + + for (i = 0; i < A_CNT(config_keywords); i++) { + if (!strncasecmp(p, config_keywords[i].keyword, + strlen(config_keywords[i].keyword))) { + DPRINTF(D_PARSE, + "skip cline %zu with keyword %s\n", + linenum, config_keywords[i].keyword); + found_keyword = true; + } + } + if (found_keyword) + continue; + + if (*p == '+' || *p == '-') { + host[0] = *p++; + while (isspace((unsigned char)*p)) + p++; + if (*p == '\0' || *p == '*') { + strcpy(host, "*"); + continue; + } + /* the +hostname expression will continue + * to use the LocalHostName, not the FQDN */ + for (i = 1; i < MAXHOSTNAMELEN - 1; i++) { + if (*p == '@') { + (void)strncpy(&host[i], LocalHostName, + sizeof(host) - 1 - i); + host[sizeof(host) - 1] = '\0'; + i = strlen(host) - 1; + p++; + continue; + } + 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 (q = strchr(cline, '\0'); isspace((unsigned char)*--q);) + continue; + *++q = '\0'; + if ((f = calloc(1, sizeof(*f))) == NULL) { + logerror("alloc failed"); + die(0, 0, NULL); + } + if (!*f_ptr) *f_ptr = f; /* return first node */ + *nextp = f; + nextp = &f->f_next; + cfline(linenum, cline, f, prog, host); + } +} + +/* + * INIT -- Initialize syslogd from configuration table + */ +void +/*ARGSUSED*/ +init(int fd, short event, void *ev) +{ + FILE *cf; + int i; + struct filed *f, *newf, **nextp, *f2; + char *p; + sigset_t newmask, omask; +#ifndef DISABLE_TLS + char *tls_status_msg = NULL; + struct peer_cred *cred = NULL; +#endif /* !DISABLE_TLS */ + + /* prevent recursive signals */ + BLOCK_SIGNALS(omask, newmask); + + DPRINTF((D_EVENT|D_CALL), "init\n"); + + /* + * be careful about dependencies and order of actions: + * 1. flush buffer queues + * 2. flush -sign SBs + * 3. flush/delete buffer queue again, in case an SB got there + * 4. close files/connections + */ + + /* + * flush any pending output + */ + for (f = Files; f != NULL; f = f->f_next) { + /* flush any pending output */ + if (f->f_prevcount) + fprintlog(f, NULL, NULL); + SEND_QUEUE(f); + } + /* some actions only on SIGHUP and not on first start */ + if (Initialized) { +#ifndef DISABLE_SIGN + sign_global_free(); +#endif /* !DISABLE_SIGN */ +#ifndef DISABLE_TLS + free_incoming_tls_sockets(); +#endif /* !DISABLE_TLS */ + Initialized = 0; + } + /* + * Close all open log files. + */ + for (f = Files; f != NULL; f = f->f_next) { + switch (f->f_type) { + case F_FILE: + case F_TTY: + case F_CONSOLE: + (void)close(f->f_file); + break; + case F_PIPE: + if (f->f_un.f_pipe.f_pid > 0) { + (void)close(f->f_file); + 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; +#ifndef DISABLE_TLS + case F_TLS: + free_tls_sslptr(f->f_un.f_tls.tls_conn); + break; +#endif /* !DISABLE_TLS */ + } + } + + /* + * Close all open UDP sockets + */ + if (finet) { + for (i = 0; i < finet->fd; i++) { + if (close(finet[i+1].fd) < 0) { + logerror("close() failed"); + die(0, 0, NULL); + } + DEL_EVENT(finet[i+1].ev); + FREEPTR(finet[i+1].ev); + } + FREEPTR(finet); + } + + /* get FQDN and hostname/domain */ + FREEPTR(oldLocalFQDN); + oldLocalFQDN = LocalFQDN; + LocalFQDN = getLocalFQDN(); + if ((p = strchr(LocalFQDN, '.')) != NULL) + (void)strlcpy(LocalHostName, LocalFQDN, 1+p-LocalFQDN); + else + (void)strlcpy(LocalHostName, LocalFQDN, sizeof(LocalHostName)); + + /* + * Reset counter of forwarding actions + */ + + NumForwards=0; + + /* new destination list to replace Files */ + newf = NULL; + nextp = &newf; + + /* open the configuration file */ + if ((cf = fopen(ConfFile, "r")) == NULL) { + DPRINTF(D_FILE, "Cannot open `%s'\n", ConfFile); + *nextp = (struct filed *)calloc(1, sizeof(*f)); + cfline(0, "*.ERR\t/dev/console", *nextp, "*", "*"); + (*nextp)->f_next = (struct filed *)calloc(1, sizeof(*f)); + cfline(0, "*.PANIC\t*", (*nextp)->f_next, "*", "*"); + Initialized = 1; + RESTORE_SIGNALS(omask); + return; + } + +#ifndef DISABLE_TLS + /* init with new TLS_CTX + * as far as I see one cannot change the cert/key of an existing CTX + */ + FREE_SSL_CTX(tls_opt.global_TLS_CTX); + + free_cred_SLIST(&tls_opt.cert_head); + free_cred_SLIST(&tls_opt.fprint_head); +#endif /* !DISABLE_TLS */ + + /* read and close configuration file */ + read_config_file(cf, &newf); + newf = *nextp; + (void)fclose(cf); + DPRINTF(D_MISC, "read_config_file() returned newf=%p\n", newf); + +#define MOVE_QUEUE(dst, src) do { \ + struct buf_queue *buf; \ + STAILQ_CONCAT(&dst->f_qhead, &src->f_qhead); \ + STAILQ_FOREACH(buf, &dst->f_qhead, entries) { \ + dst->f_qelements++; \ + dst->f_qsize += buf_queue_obj_size(buf); \ + } \ + src->f_qsize = 0; \ + src->f_qelements = 0; \ +} while (/*CONSTCOND*/0) + + /* + * Free old log files. + */ + for (f = Files; f != NULL;) { + struct filed *ftmp; + + /* check if a new logfile is equal, if so pass the queue */ + for (f2 = newf; f2 != NULL; f2 = f2->f_next) { + if (f->f_type == f2->f_type + && ((f->f_type == F_PIPE + && !strcmp(f->f_un.f_pipe.f_pname, + f2->f_un.f_pipe.f_pname)) +#ifndef DISABLE_TLS + || (f->f_type == F_TLS + && !strcmp(f->f_un.f_tls.tls_conn->hostname, + f2->f_un.f_tls.tls_conn->hostname) + && !strcmp(f->f_un.f_tls.tls_conn->port, + f2->f_un.f_tls.tls_conn->port)) +#endif /* !DISABLE_TLS */ + || (f->f_type == F_FORW + && !strcmp(f->f_un.f_forw.f_hname, + f2->f_un.f_forw.f_hname)))) { + DPRINTF(D_BUFFER, "move queue from f@%p " + "to f2@%p\n", f, f2); + MOVE_QUEUE(f2, f); + } + } + message_queue_freeall(f); + DELREF(f->f_prevmsg); +#ifndef DISABLE_TLS + if (f->f_type == F_TLS) + free_tls_conn(f->f_un.f_tls.tls_conn); +#endif /* !DISABLE_TLS */ + FREEPTR(f->f_program); + FREEPTR(f->f_host); + DEL_EVENT(f->f_sq_event); + + ftmp = f->f_next; + free((char *)f); + f = ftmp; + } + Files = newf; + Initialized = 1; + + if (Debug) { + for (f = Files; f; f = f->f_next) { + for (i = 0; i <= LOG_NFACILITIES; i++) + if (f->f_pmask[i] == INTERNAL_NOPRI) + printf("X "); + else + printf("%d ", f->f_pmask[i]); + printf("%s: ", TypeInfo[f->f_type].name); + switch (f->f_type) { + case F_FILE: + case F_TTY: + case F_CONSOLE: + case F_FIFO: + printf("%s", f->f_un.f_fname); + break; + + case F_FORW: + printf("%s", f->f_un.f_forw.f_hname); + break; +#ifndef DISABLE_TLS + case F_TLS: + printf("[%s]", f->f_un.f_tls.tls_conn->hostname); + break; +#endif /* !DISABLE_TLS */ + 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"); + } + } + + finet = socksetup(PF_UNSPEC, bindhostname); + if (finet) { + if (SecureMode) { + for (i = 0; i < finet->fd; i++) { + if (shutdown(finet[i+1].fd, SHUT_RD) < 0) { + logerror("shutdown() failed"); + die(0, 0, NULL); + } + } + } else + DPRINTF(D_NET, "Listening on inet and/or inet6 socket\n"); + DPRINTF(D_NET, "Sending on inet and/or inet6 socket\n"); + } + +#ifndef DISABLE_TLS + /* TLS setup -- after all local destinations opened */ + DPRINTF(D_PARSE, "Parsed options: tls_ca: %s, tls_cadir: %s, " + "tls_cert: %s, tls_key: %s, tls_verify: %s, " + "bind: %s:%s, max. queue_lengths: %" + PRId64 ", %" PRId64 ", %" PRId64 ", " + "max. queue_sizes: %" + PRId64 ", %" PRId64 ", %" PRId64 "\n", + tls_opt.CAfile, tls_opt.CAdir, + tls_opt.certfile, tls_opt.keyfile, tls_opt.x509verify, + tls_opt.bindhost, tls_opt.bindport, + TypeInfo[F_TLS].queue_length, TypeInfo[F_FILE].queue_length, + TypeInfo[F_PIPE].queue_length, + TypeInfo[F_TLS].queue_size, TypeInfo[F_FILE].queue_size, + TypeInfo[F_PIPE].queue_size); + SLIST_FOREACH(cred, &tls_opt.cert_head, entries) { + DPRINTF(D_PARSE, "Accepting peer certificate " + "from file: \"%s\"\n", cred->data); + } + SLIST_FOREACH(cred, &tls_opt.fprint_head, entries) { + DPRINTF(D_PARSE, "Accepting peer certificate with " + "fingerprint: \"%s\"\n", cred->data); + } + + /* Note: The order of initialization is important because syslog-sign + * should use the TLS cert for signing. -- So we check first if TLS + * will be used and initialize it before starting -sign. + * + * This means that if we are a client without TLS destinations TLS + * will not be initialized and syslog-sign will generate a new key. + * -- Even if the user has set a usable tls_cert. + * Is this the expected behaviour? The alternative would be to always + * initialize the TLS structures, even if they will not be needed + * (or only needed to read the DSA key for -sign). + */ + + /* Initialize TLS only if used */ + if (tls_opt.server) + tls_status_msg = init_global_TLS_CTX(); + else + for (f = Files; f; f = f->f_next) { + if (f->f_type != F_TLS) + continue; + tls_status_msg = init_global_TLS_CTX(); + break; + } + +#endif /* !DISABLE_TLS */ + +#ifndef DISABLE_SIGN + /* only initialize -sign if actually used */ + if (GlobalSign.sg == 0 || GlobalSign.sg == 1 || GlobalSign.sg == 2) + (void)sign_global_init(Files); + else if (GlobalSign.sg == 3) + for (f = Files; f; f = f->f_next) + if (f->f_flags & FFLAG_SIGN) { + (void)sign_global_init(Files); + break; + } +#endif /* !DISABLE_SIGN */ + +#ifndef DISABLE_TLS + if (tls_status_msg) { + loginfo("%s", tls_status_msg); + free(tls_status_msg); + } + DPRINTF((D_NET|D_TLS), "Preparing sockets for TLS\n"); + TLS_Listen_Set = + socksetup_tls(PF_UNSPEC, tls_opt.bindhost, tls_opt.bindport); + + for (f = Files; f; f = f->f_next) { + if (f->f_type != F_TLS) + continue; + if (!tls_connect(f->f_un.f_tls.tls_conn)) { + logerror("Unable to connect to TLS server %s", + f->f_un.f_tls.tls_conn->hostname); + /* Reconnect after x seconds */ + schedule_event(&f->f_un.f_tls.tls_conn->event, + &((struct timeval){TLS_RECONNECT_SEC, 0}), + tls_reconnect, f->f_un.f_tls.tls_conn); + } + } +#endif /* !DISABLE_TLS */ + + loginfo("restart"); + /* + * Log a change in hostname, but only on a restart (we detect this + * by checking to see if we're passed a kevent). + */ + if (oldLocalFQDN && strcmp(oldLocalFQDN, LocalFQDN) != 0) + loginfo("host name changed, \"%s\" to \"%s\"", + oldLocalFQDN, LocalFQDN); + + RESTORE_SIGNALS(omask); +} + +/* + * Crack a configuration file line + */ +void +cfline(size_t linenum, const char *line, struct filed *f, const char *prog, + const char *host) +{ + struct addrinfo hints, *res; + int error, i, pri, syncfile; + const char *p, *q; + char *bp; + char buf[MAXLINE]; + struct stat sb; + + DPRINTF((D_CALL|D_PARSE), + "cfline(%zu, \"%s\", f, \"%s\", \"%s\")\n", + linenum, line, prog, host); + + errno = 0; /* keep strerror() stuff out of logerror messages */ + + /* clear out file entry */ + memset(f, 0, sizeof(*f)); + for (i = 0; i <= LOG_NFACILITIES; i++) + f->f_pmask[i] = INTERNAL_NOPRI; + STAILQ_INIT(&f->f_qhead); + + /* + * There should not be any space before the log facility. + * Check this is okay, complain and fix if it is not. + */ + q = line; + if (isblank((unsigned char)*line)) { + errno = 0; + logerror("Warning: `%s' space or tab before the log facility", + line); + /* Fix: strip all spaces/tabs before the log facility */ + while (*q++ && isblank((unsigned char)*q)) + /* skip blanks */; + line = q; + } + + /* + * q is now at the first char of the log facility + * There should be at least one tab after the log facility + * Check this is okay, and complain and fix if it is not. + */ + q = line + strlen(line); + while (!isblank((unsigned char)*q) && (q != line)) + q--; + if ((q == line) && strlen(line)) { + /* No tabs or space in a non empty line: complain */ + errno = 0; + logerror( + "Error: `%s' log facility or log target missing", + line); + return; + } + + /* save host name, if any */ + if (*host == '*') + f->f_host = NULL; + else { + f->f_host = strdup(host); + trim_anydomain(f->f_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 && !isblank((unsigned char)*p);) { + int pri_done, pri_cmp, pri_invert; + + /* find the end of this facility name list */ + for (q = p; *q && !isblank((unsigned char)*q) && *q++ != '.'; ) + continue; + + /* get the priority comparison */ + pri_cmp = 0; + pri_done = 0; + pri_invert = 0; + if (*q == '!') { + pri_invert = 1; + q++; + } + while (! pri_done) { + switch (*q) { + case '<': + pri_cmp = PRI_LT; + q++; + break; + case '=': + pri_cmp = PRI_EQ; + q++; + break; + case '>': + pri_cmp = PRI_GT; + q++; + break; + default: + pri_done = 1; + break; + } + } + + /* collect priority name */ + for (bp = buf; *q && !strchr("\t ,;", *q); ) + *bp++ = *q++; + *bp = '\0'; + + /* skip cruft */ + while (strchr(",;", *q)) + q++; + + /* decode priority name */ + if (*buf == '*') { + pri = LOG_PRIMASK + 1; + pri_cmp = PRI_LT | PRI_EQ | PRI_GT; + } else { + pri = decode(buf, prioritynames); + if (pri < 0) { + errno = 0; + logerror("Unknown priority name `%s'", buf); + return; + } + } + if (pri_cmp == 0) + pri_cmp = UniquePriority ? PRI_EQ + : PRI_EQ | PRI_GT; + if (pri_invert) + pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT; + + /* scan facilities */ + while (*p && !strchr("\t .;", *p)) { + for (bp = buf; *p && !strchr("\t ,;.", *p); ) + *bp++ = *p++; + *bp = '\0'; + if (*buf == '*') + for (i = 0; i < LOG_NFACILITIES; i++) { + f->f_pmask[i] = pri; + f->f_pcmp[i] = pri_cmp; + } + else { + i = decode(buf, facilitynames); + if (i < 0) { + errno = 0; + logerror("Unknown facility name `%s'", + buf); + return; + } + f->f_pmask[i >> 3] = pri; + f->f_pcmp[i >> 3] = pri_cmp; + } + while (*p == ',' || *p == ' ') + p++; + } + + p = q; + } + + /* skip to action part */ + while (isblank((unsigned char)*p)) + p++; + + /* + * should this be "#ifndef DISABLE_SIGN" or is it a general option? + * '+' before file destination: write with PRI field for later + * verification + */ + if (*p == '+') { + f->f_flags |= FFLAG_FULL; + p++; + } + if (*p == '-') { + syncfile = 0; + p++; + } else + syncfile = 1; + + switch (*p) { + case '@': +#ifndef DISABLE_SIGN + if (GlobalSign.sg == 3) + f->f_flags |= FFLAG_SIGN; +#endif /* !DISABLE_SIGN */ +#ifndef DISABLE_TLS + if (*(p+1) == '[') { + /* TLS destination */ + if (!parse_tls_destination(p, f, linenum)) { + logerror("Unable to parse action %s", p); + break; + } + f->f_type = F_TLS; + break; + } +#endif /* !DISABLE_TLS */ + (void)strlcpy(f->f_un.f_forw.f_hname, ++p, + sizeof(f->f_un.f_forw.f_hname)); + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_DGRAM; + hints.ai_protocol = 0; + error = getaddrinfo(f->f_un.f_forw.f_hname, "syslog", &hints, + &res); + if (error) { + errno = 0; + logerror("%s", gai_strerror(error)); + break; + } + f->f_un.f_forw.f_addr = res; + f->f_type = F_FORW; + NumForwards++; + break; + + case '/': +#ifndef DISABLE_SIGN + if (GlobalSign.sg == 3) + f->f_flags |= FFLAG_SIGN; +#endif /* !DISABLE_SIGN */ + (void)strlcpy(f->f_un.f_fname, p, sizeof(f->f_un.f_fname)); + if ((f->f_file = open(p, O_WRONLY|O_APPEND|O_NONBLOCK, 0)) < 0) + { + f->f_type = F_UNUSED; + logerror("%s", p); + break; + } + if (!fstat(f->f_file, &sb) && S_ISFIFO(sb.st_mode)) { + f->f_file = -1; + f->f_type = F_FIFO; + break; + } + + if (isatty(f->f_file)) { + f->f_type = F_TTY; + if (strcmp(p, ctty) == 0) + f->f_type = F_CONSOLE; + } else + f->f_type = F_FILE; + + if (syncfile) + f->f_flags |= FFLAG_SYNC; + break; + + case '|': +#ifndef DISABLE_SIGN + if (GlobalSign.sg == 3) + f->f_flags |= FFLAG_SIGN; +#endif + f->f_un.f_pipe.f_pid = 0; + (void) strlcpy(f->f_un.f_pipe.f_pname, p + 1, + sizeof(f->f_un.f_pipe.f_pname)); + f->f_type = F_PIPE; + break; + + case '*': + f->f_type = F_WALL; + break; + + default: + for (i = 0; i < MAXUNAMES && *p; i++) { + for (q = p; *q && *q != ','; ) + q++; + (void)strncpy(f->f_un.f_uname[i], p, UT_NAMESIZE); + if ((q - p) > UT_NAMESIZE) + f->f_un.f_uname[i][UT_NAMESIZE] = '\0'; + else + f->f_un.f_uname[i][q - p] = '\0'; + while (*q == ',' || *q == ' ') + q++; + p = q; + } + f->f_type = F_USERS; + break; + } +} + + +/* + * Decode a symbolic name to a numeric value + */ +int +decode(const char *name, CODE *codetab) +{ + CODE *c; + char *p, buf[40]; + + if (isdigit((unsigned char)*name)) + return atoi(name); + + for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) { + if (isupper((unsigned char)*name)) + *p = tolower((unsigned char)*name); + else + *p = *name; + } + *p = '\0'; + for (c = codetab; c->c_name; c++) + if (!strcmp(buf, c->c_name)) + return c->c_val; + + return -1; +} + +/* + * Retrieve the size of the kernel message buffer, via sysctl. + */ +int +getmsgbufsize(void) +{ +#ifdef __NetBSD_Version__ + int msgbufsize, mib[2]; + size_t size; + + mib[0] = CTL_KERN; + mib[1] = KERN_MSGBUFSIZE; + size = sizeof msgbufsize; + if (sysctl(mib, 2, &msgbufsize, &size, NULL, 0) == -1) { + DPRINTF(D_MISC, "Couldn't get kern.msgbufsize\n"); + return 0; + } + return msgbufsize; +#else + return MAXLINE; +#endif /* __NetBSD_Version__ */ +} + +/* + * Retrieve the hostname, via sysctl. + */ +char * +getLocalFQDN(void) +{ + int mib[2]; + char *hostname; + size_t len; + +#ifdef __minix + len = MAXHOSTNAMELEN + 1; +#endif /* __minix */ + + mib[0] = CTL_KERN; + mib[1] = KERN_HOSTNAME; + sysctl(mib, 2, NULL, &len, NULL, 0); + + if (!(hostname = malloc(len))) { + logerror("Unable to allocate memory"); + die(0,0,NULL); + } else if (sysctl(mib, 2, hostname, &len, NULL, 0) == -1) { + DPRINTF(D_MISC, "Couldn't get kern.hostname\n"); + (void)gethostname(hostname, len); + } + return hostname; +} + +struct socketEvent * +socksetup(int af, const char *hostname) +{ + struct addrinfo hints, *res, *r; + int error, maxs; +#ifdef IPV6_V6ONLY + int on = 1; +#endif /* IPV6_V6ONLY */ + struct socketEvent *s, *socks; + + if(SecureMode && !NumForwards) + return NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = af; + hints.ai_socktype = SOCK_DGRAM; + error = getaddrinfo(hostname, "syslog", &hints, &res); + if (error) { + errno = 0; + logerror("%s", gai_strerror(error)); + die(0, 0, NULL); + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r; r = r->ai_next, maxs++) + continue; + socks = calloc(maxs+1, sizeof(*socks)); + if (!socks) { + logerror("Couldn't allocate memory for sockets"); + die(0, 0, NULL); + } + + socks->fd = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r; r = r->ai_next) { + s->fd = socket(r->ai_family, r->ai_socktype, r->ai_protocol); + if (s->fd < 0) { + logerror("socket() failed"); + continue; + } + s->af = r->ai_family; +#ifdef IPV6_V6ONLY + if (r->ai_family == AF_INET6 && setsockopt(s->fd, IPPROTO_IPV6, + IPV6_V6ONLY, &on, sizeof(on)) < 0) { + logerror("setsockopt(IPV6_V6ONLY) failed"); + close(s->fd); + continue; + } +#endif /* IPV6_V6ONLY */ + + if (!SecureMode) { + if (bind(s->fd, r->ai_addr, r->ai_addrlen) < 0) { + logerror("bind() failed"); + close(s->fd); + continue; + } + s->ev = allocev(); + event_set(s->ev, s->fd, EV_READ | EV_PERSIST, + dispatch_read_finet, s->ev); + if (event_add(s->ev, NULL) == -1) { + DPRINTF((D_EVENT|D_NET), + "Failure in event_add()\n"); + } else { + DPRINTF((D_EVENT|D_NET), + "Listen on UDP port " + "(event@%p)\n", s->ev); + } + } + + socks->fd++; /* num counter */ + s++; + } + + if (res) + freeaddrinfo(res); + if (socks->fd == 0) { + free (socks); + if(Debug) + return NULL; + else + die(0, 0, NULL); + } + return socks; +} + +/* + * Fairly similar to popen(3), but returns an open descriptor, as opposed + * to a FILE *. + */ +int +p_open(char *prog, pid_t *rpid) +{ + static char sh[] = "sh", mc[] = "-c"; + int pfd[2], nulldesc, i; + pid_t pid; + char *argv[4]; /* sh -c cmd NULL */ + + if (pipe(pfd) == -1) + return -1; + if ((nulldesc = open(_PATH_DEVNULL, O_RDWR)) == -1) { + /* We are royally screwed anyway. */ + return -1; + } + + switch ((pid = fork())) { + case -1: + (void) close(nulldesc); + return -1; + + case 0: + argv[0] = sh; + argv[1] = mc; + argv[2] = prog; + argv[3] = NULL; + + (void) setsid(); /* avoid catching SIGHUPs. */ + + /* + * Reset ignored signals to their default behavior. + */ + (void)signal(SIGTERM, SIG_DFL); + (void)signal(SIGINT, SIG_DFL); + (void)signal(SIGQUIT, SIG_DFL); + (void)signal(SIGPIPE, SIG_DFL); + (void)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); + } + + (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. */ + logerror("Warning: cannot change pipe to pid %d to " + "non-blocking.", (int) pid); + } + *rpid = pid; + 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) { + 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("%s", buf); +} + +struct event * +allocev(void) +{ + struct event *ev; + + if (!(ev = calloc(1, sizeof(*ev)))) + logerror("Unable to allocate memory"); + return ev; +} + +/* *ev is allocated if necessary */ +void +schedule_event(struct event **ev, struct timeval *tv, + void (*cb)(int, short, void *), void *arg) +{ + if (!*ev && !(*ev = allocev())) { + return; + } + event_set(*ev, 0, 0, cb, arg); + DPRINTF(D_EVENT, "event_add(%s@%p)\n", "schedule_ev", *ev); \ + if (event_add(*ev, tv) == -1) { + DPRINTF(D_EVENT, "Failure in event_add()\n"); + } +} + +#ifndef DISABLE_TLS +/* abbreviation for freeing credential lists */ +void +free_cred_SLIST(struct peer_cred_head *head) +{ + struct peer_cred *cred; + + while (!SLIST_EMPTY(head)) { + cred = SLIST_FIRST(head); + SLIST_REMOVE_HEAD(head, entries); + FREEPTR(cred->data); + free(cred); + } +} +#endif /* !DISABLE_TLS */ + +/* + * send message queue after reconnect + */ +/*ARGSUSED*/ +void +send_queue(int fd, short event, void *arg) +{ + struct filed *f = (struct filed *) arg; + struct buf_queue *qentry; +#define SQ_CHUNK_SIZE 250 + size_t cnt = 0; + +#ifndef DISABLE_TLS + if (f->f_type == F_TLS) { + /* use a flag to prevent recursive calls to send_queue() */ + if (f->f_un.f_tls.tls_conn->send_queue) + return; + else + f->f_un.f_tls.tls_conn->send_queue = true; + } + DPRINTF((D_DATA|D_CALL), "send_queue(f@%p with %zu msgs, " + "cnt@%p = %zu)\n", f, f->f_qelements, &cnt, cnt); +#endif /* !DISABLE_TLS */ + + while ((qentry = STAILQ_FIRST(&f->f_qhead))) { +#ifndef DISABLE_TLS + /* send_queue() might be called with an unconnected destination + * from init() or die() or one message might take longer, + * leaving the connection in state ST_WAITING and thus not + * ready for the next message. + * this check is a shortcut to skip these unnecessary calls */ + if (f->f_type == F_TLS + && f->f_un.f_tls.tls_conn->state != ST_TLS_EST) { + DPRINTF(D_TLS, "abort send_queue(cnt@%p = %zu) " + "on TLS connection in state %d\n", + &cnt, cnt, f->f_un.f_tls.tls_conn->state); + return; + } +#endif /* !DISABLE_TLS */ + fprintlog(f, qentry->msg, qentry); + + /* Sending a long queue can take some time during which + * SIGHUP and SIGALRM are blocked and no events are handled. + * To avoid that we only send SQ_CHUNK_SIZE messages at once + * and then reschedule ourselves to continue. Thus the control + * will return first from all signal-protected functions so a + * possible SIGHUP/SIGALRM is handled and then back to the + * main loop which can handle possible input. + */ + if (++cnt >= SQ_CHUNK_SIZE) { + if (!f->f_sq_event) { /* alloc on demand */ + f->f_sq_event = allocev(); + event_set(f->f_sq_event, 0, 0, send_queue, f); + } + if (event_add(f->f_sq_event, &((struct timeval){0, 1})) == -1) { + DPRINTF(D_EVENT, "Failure in event_add()\n"); + } + break; + } + } +#ifndef DISABLE_TLS + if (f->f_type == F_TLS) + f->f_un.f_tls.tls_conn->send_queue = false; +#endif + +} + +/* + * finds the next queue element to delete + * + * has stateful behaviour, before using it call once with reset = true + * after that every call will return one next queue elemen to delete, + * depending on strategy either the oldest or the one with the lowest priority + */ +static struct buf_queue * +find_qentry_to_delete(const struct buf_queue_head *head, int strategy, + bool reset) +{ + static int pri; + static struct buf_queue *qentry_static; + + struct buf_queue *qentry_tmp; + + if (reset || STAILQ_EMPTY(head)) { + pri = LOG_DEBUG; + qentry_static = STAILQ_FIRST(head); + return NULL; + } + + /* find elements to delete */ + if (strategy == PURGE_BY_PRIORITY) { + qentry_tmp = qentry_static; + if (!qentry_tmp) return NULL; + while ((qentry_tmp = STAILQ_NEXT(qentry_tmp, entries)) != NULL) + { + if (LOG_PRI(qentry_tmp->msg->pri) == pri) { + /* save the successor, because qentry_tmp + * is probably deleted by the caller */ + qentry_static = STAILQ_NEXT(qentry_tmp, entries); + return qentry_tmp; + } + } + /* nothing found in while loop --> next pri */ + if (--pri) + return find_qentry_to_delete(head, strategy, false); + else + return NULL; + } else /* strategy == PURGE_OLDEST or other value */ { + qentry_tmp = qentry_static; + qentry_static = STAILQ_NEXT(qentry_tmp, entries); + return qentry_tmp; /* is NULL on empty queue */ + } +} + +/* note on TAILQ: newest message added at TAIL, + * oldest to be removed is FIRST + */ +/* + * checks length of a destination's message queue + * if del_entries == 0 then assert queue length is + * less or equal to configured number of queue elements + * otherwise del_entries tells how many entries to delete + * + * returns the number of removed queue elements + * (which not necessarily means free'd messages) + * + * strategy PURGE_OLDEST to delete oldest entry, e.g. after it was resent + * strategy PURGE_BY_PRIORITY to delete messages with lowest priority first, + * this is much slower but might be desirable when unsent messages have + * to be deleted, e.g. in call from domark() + */ +size_t +message_queue_purge(struct filed *f, size_t del_entries, int strategy) +{ + size_t removed = 0; + struct buf_queue *qentry = NULL; + + DPRINTF((D_CALL|D_BUFFER), "purge_message_queue(%p, %zu, %d) with " + "f_qelements=%zu and f_qsize=%zu\n", + f, del_entries, strategy, + f->f_qelements, f->f_qsize); + + /* reset state */ + (void)find_qentry_to_delete(&f->f_qhead, strategy, true); + + while (removed < del_entries + || (TypeInfo[f->f_type].queue_length != -1 + && (size_t)TypeInfo[f->f_type].queue_length <= f->f_qelements) + || (TypeInfo[f->f_type].queue_size != -1 + && (size_t)TypeInfo[f->f_type].queue_size <= f->f_qsize)) { + qentry = find_qentry_to_delete(&f->f_qhead, strategy, 0); + if (message_queue_remove(f, qentry)) + removed++; + else + break; + } + return removed; +} + +/* run message_queue_purge() for all destinations to free memory */ +size_t +message_allqueues_purge(void) +{ + size_t sum = 0; + struct filed *f; + + for (f = Files; f; f = f->f_next) + sum += message_queue_purge(f, + f->f_qelements/10, PURGE_BY_PRIORITY); + + DPRINTF(D_BUFFER, + "message_allqueues_purge(): removed %zu buffer entries\n", sum); + return sum; +} + +/* run message_queue_purge() for all destinations to check limits */ +size_t +message_allqueues_check(void) +{ + size_t sum = 0; + struct filed *f; + + for (f = Files; f; f = f->f_next) + sum += message_queue_purge(f, 0, PURGE_BY_PRIORITY); + DPRINTF(D_BUFFER, + "message_allqueues_check(): removed %zu buffer entries\n", sum); + return sum; +} + +struct buf_msg * +buf_msg_new(const size_t len) +{ + struct buf_msg *newbuf; + + CALLOC(newbuf, sizeof(*newbuf)); + + if (len) { /* len = 0 is valid */ + MALLOC(newbuf->msg, len); + newbuf->msgorig = newbuf->msg; + newbuf->msgsize = len; + } + return NEWREF(newbuf); +} + +void +buf_msg_free(struct buf_msg *buf) +{ + if (!buf) + return; + + buf->refcount--; + if (buf->refcount == 0) { + FREEPTR(buf->timestamp); + /* small optimizations: the host/recvhost may point to the + * global HostName/FQDN. of course this must not be free()d + * same goes for appname and include_pid + */ + if (buf->recvhost != buf->host + && buf->recvhost != LocalHostName + && buf->recvhost != LocalFQDN + && buf->recvhost != oldLocalFQDN) + FREEPTR(buf->recvhost); + if (buf->host != LocalHostName + && buf->host != LocalFQDN + && buf->host != oldLocalFQDN) + FREEPTR(buf->host); + if (buf->prog != appname) + FREEPTR(buf->prog); + if (buf->pid != include_pid) + FREEPTR(buf->pid); + FREEPTR(buf->msgid); + FREEPTR(buf->sd); + FREEPTR(buf->msgorig); /* instead of msg */ + FREEPTR(buf); + } +} + +size_t +buf_queue_obj_size(struct buf_queue *qentry) +{ + size_t sum = 0; + + if (!qentry) + return 0; + sum += sizeof(*qentry) + + sizeof(*qentry->msg) + + qentry->msg->msgsize + + SAFEstrlen(qentry->msg->timestamp)+1 + + SAFEstrlen(qentry->msg->msgid)+1; + if (qentry->msg->prog + && qentry->msg->prog != include_pid) + sum += strlen(qentry->msg->prog)+1; + if (qentry->msg->pid + && qentry->msg->pid != appname) + sum += strlen(qentry->msg->pid)+1; + if (qentry->msg->recvhost + && qentry->msg->recvhost != LocalHostName + && qentry->msg->recvhost != LocalFQDN + && qentry->msg->recvhost != oldLocalFQDN) + sum += strlen(qentry->msg->recvhost)+1; + if (qentry->msg->host + && qentry->msg->host != LocalHostName + && qentry->msg->host != LocalFQDN + && qentry->msg->host != oldLocalFQDN) + sum += strlen(qentry->msg->host)+1; + + return sum; +} + +bool +message_queue_remove(struct filed *f, struct buf_queue *qentry) +{ + if (!f || !qentry || !qentry->msg) + return false; + + assert(!STAILQ_EMPTY(&f->f_qhead)); + STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries); + f->f_qelements--; + f->f_qsize -= buf_queue_obj_size(qentry); + + DPRINTF(D_BUFFER, "msg @%p removed from queue @%p, new qlen = %zu\n", + qentry->msg, f, f->f_qelements); + DELREF(qentry->msg); + FREEPTR(qentry); + return true; +} + +/* + * returns *qentry on success and NULL on error + */ +struct buf_queue * +message_queue_add(struct filed *f, struct buf_msg *buffer) +{ + struct buf_queue *qentry; + + /* check on every call or only every n-th time? */ + message_queue_purge(f, 0, PURGE_BY_PRIORITY); + + while (!(qentry = malloc(sizeof(*qentry))) + && message_queue_purge(f, 1, PURGE_OLDEST)) + continue; + if (!qentry) { + logerror("Unable to allocate memory"); + DPRINTF(D_BUFFER, "queue empty, no memory, msg dropped\n"); + return NULL; + } else { + qentry->msg = buffer; + f->f_qelements++; + f->f_qsize += buf_queue_obj_size(qentry); + STAILQ_INSERT_TAIL(&f->f_qhead, qentry, entries); + + DPRINTF(D_BUFFER, "msg @%p queued @%p, qlen = %zu\n", + buffer, f, f->f_qelements); + return qentry; + } +} + +void +message_queue_freeall(struct filed *f) +{ + struct buf_queue *qentry; + + if (!f) return; + DPRINTF(D_MEM, "message_queue_freeall(f@%p) with f_qhead@%p\n", f, + &f->f_qhead); + + while (!STAILQ_EMPTY(&f->f_qhead)) { + qentry = STAILQ_FIRST(&f->f_qhead); + STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries); + DELREF(qentry->msg); + FREEPTR(qentry); + } + + f->f_qelements = 0; + f->f_qsize = 0; +} + +#ifndef DISABLE_TLS +/* utility function for tls_reconnect() */ +struct filed * +get_f_by_conninfo(struct tls_conn_settings *conn_info) +{ + struct filed *f; + + for (f = Files; f; f = f->f_next) { + if ((f->f_type == F_TLS) && f->f_un.f_tls.tls_conn == conn_info) + return f; + } + DPRINTF(D_TLS, "get_f_by_conninfo() called on invalid conn_info\n"); + return NULL; +} + +/* + * Called on signal. + * Lets the admin reconnect without waiting for the reconnect timer expires. + */ +/*ARGSUSED*/ +void +dispatch_force_tls_reconnect(int fd, short event, void *ev) +{ + struct filed *f; + DPRINTF((D_TLS|D_CALL|D_EVENT), "dispatch_force_tls_reconnect()\n"); + for (f = Files; f; f = f->f_next) { + if (f->f_type == F_TLS && + f->f_un.f_tls.tls_conn->state == ST_NONE) + tls_reconnect(fd, event, f->f_un.f_tls.tls_conn); + } +} +#endif /* !DISABLE_TLS */ + +/* + * return a timestamp in a static buffer, + * either format the timestamp given by parameter in_now + * or use the current time if in_now is NULL. + */ +char * +make_timestamp(time_t *in_now, bool iso) +{ + int frac_digits = 6; + struct timeval tv; + time_t mytime; + struct tm ltime; + int len = 0; + int tzlen = 0; + /* uses global var: time_t now; */ + + if (in_now) { + mytime = *in_now; + } else { + gettimeofday(&tv, NULL); + mytime = now = (time_t) tv.tv_sec; + } + + if (!iso) { + strlcpy(timestamp, ctime(&mytime) + 4, TIMESTAMPBUFSIZE); + timestamp[BSD_TIMESTAMPLEN] = '\0'; + return timestamp; + } + + localtime_r(&mytime, <ime); + len += strftime(timestamp, TIMESTAMPBUFSIZE, "%FT%T", <ime); + snprintf(&(timestamp[len]), frac_digits+2, ".%.*ld", + frac_digits, (long)tv.tv_usec); + len += frac_digits+1; + tzlen = strftime(&(timestamp[len]), TIMESTAMPBUFSIZE-len, "%z", <ime); + len += tzlen; + + if (tzlen == 5) { + /* strftime gives "+0200", but we need "+02:00" */ + timestamp[len+1] = timestamp[len]; + timestamp[len] = timestamp[len-1]; + timestamp[len-1] = timestamp[len-2]; + timestamp[len-2] = ':'; + } + return timestamp; +} + +/* auxillary code to allocate memory and copy a string */ +bool +copy_string(char **mem, const char *p, const char *q) +{ + const size_t len = 1 + q - p; + if (!(*mem = malloc(len))) { + logerror("Unable to allocate memory for config"); + return false; + } + strlcpy(*mem, p, len); + return true; +} + +/* keyword has to end with ", everything until next " is copied */ +bool +copy_config_value_quoted(const char *keyword, char **mem, const char **p) +{ + const char *q; + if (strncasecmp(*p, keyword, strlen(keyword))) + return false; + q = *p += strlen(keyword); + if (!(q = strchr(*p, '"'))) { + errno = 0; + logerror("unterminated \"\n"); + return false; + } + if (!(copy_string(mem, *p, q))) + return false; + *p = ++q; + return true; +} + +/* for config file: + * following = required but whitespace allowed, quotes optional + * if numeric, then conversion to integer and no memory allocation + */ +bool +copy_config_value(const char *keyword, char **mem, + const char **p, const char *file, int line) +{ + if (strncasecmp(*p, keyword, strlen(keyword))) + return false; + *p += strlen(keyword); + + while (isspace((unsigned char)**p)) + *p += 1; + if (**p != '=') { + errno = 0; + logerror("expected \"=\" in file %s, line %d", file, line); + return false; + } + *p += 1; + + return copy_config_value_word(mem, p); +} + +/* copy next parameter from a config line */ +bool +copy_config_value_word(char **mem, const char **p) +{ + const char *q; + while (isspace((unsigned char)**p)) + *p += 1; + if (**p == '"') + return copy_config_value_quoted("\"", mem, p); + + /* without quotes: find next whitespace or end of line */ + (void)((q = strchr(*p, ' ')) || (q = strchr(*p, '\t')) + || (q = strchr(*p, '\n')) || (q = strchr(*p, '\0'))); + + if (q-*p == 0 || !(copy_string(mem, *p, q))) + return false; + + *p = ++q; + return true; +} + +static int +writev1(int fd, struct iovec *iov, size_t count) +{ + ssize_t nw = 0, tot = 0; + size_t ntries = 5; + + if (count == 0) + return 0; + while (ntries--) { + switch ((nw = writev(fd, iov, count))) { + case -1: + if (errno == EAGAIN || errno == EWOULDBLOCK) { + struct pollfd pfd; + pfd.fd = fd; + pfd.events = POLLOUT; + pfd.revents = 0; + (void)poll(&pfd, 1, 500); + continue; + } + return -1; + case 0: + return 0; + default: + tot += nw; + while (nw > 0) { + if (iov->iov_len > (size_t)nw) { + iov->iov_len -= nw; + iov->iov_base = + (char *)iov->iov_base + nw; + break; + } else { + if (--count == 0) + return tot; + nw -= iov->iov_len; + iov++; + } + } + } + } + return tot == 0 ? nw : tot; +} diff --git a/usr.sbin/syslogd/syslogd.h b/usr.sbin/syslogd/syslogd.h new file mode 100644 index 000000000..3327ce893 --- /dev/null +++ b/usr.sbin/syslogd/syslogd.h @@ -0,0 +1,438 @@ +/* $NetBSD: syslogd.h,v 1.4 2013/05/27 23:15:51 christos Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +#ifndef SYSLOGD_H_ +#define SYSLOGD_H_ +/* + * hold common data structures and prototypes + * for syslogd.c and tls.c + * + */ + +#include +#define MAXLINE 1024 /* maximum line length */ +#define MAXSVLINE 120 /* maximum saved line length */ +#define DEFUPRI (LOG_USER|LOG_NOTICE) +#define DEFSPRI (LOG_KERN|LOG_NOTICE) +#define TIMERINTVL 30 /* interval for checking flush, mark */ +#define TTYMSGTIME 1 /* timeout passed to ttymsg */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef __NetBSD_Version__ +#include +#include "utmpentry.h" +#endif /* __NetBSD_Version__ */ +#ifdef __FreeBSD_version +#include +#include +#include +#include +#endif /* __FreeBSD_version */ + +#ifndef DISABLE_TLS +#include +#include +#endif /* !DISABLE_TLS */ + +#include +#include + +#include "pathnames.h" +#include + +/* some differences between the BSDs */ +#ifdef __FreeBSD_version +#undef _PATH_UNIX +#define _PATH_UNIX "kernel" +#define HAVE_STRNDUP 0 +#endif /* __FreeBSD_version */ + +#ifdef __NetBSD_Version__ +#define HAVE_STRNDUP 1 +#define HAVE_DEHUMANIZE_NUMBER 1 +#endif /* __NetBSD_Version__ */ + +#ifdef __minix +#undef _PATH_UNIX +#define _PATH_UNIX "kernel" +#endif /* __minix */ + +#ifndef HAVE_DEHUMANIZE_NUMBER /* not in my 4.0-STABLE yet */ +extern int dehumanize_number(const char *str, int64_t *size); +#endif /* !HAVE_DEHUMANIZE_NUMBER */ + +#if !HAVE_STRNDUP +char *strndup(const char *str, size_t n); +#endif /* !HAVE_STRNDUP */ + +#ifndef __minix +#ifdef LIBWRAP +#include +#endif +#endif /* !__minix */ + +#define FDMASK(fd) (1 << (fd)) + +#define A_CNT(x) (sizeof((x)) / sizeof((x)[0])) + +/* debug messages with categories */ +#define D_NONE 0 +#define D_CALL 1 /* function calls */ +#define D_DATA 2 /* syslog message reading/formatting */ +#define D_NET 4 /* sockets/network */ +#define D_FILE 8 /* local files */ +#define D_TLS 16 /* TLS */ +#define D_PARSE 32 /* configuration/parsing */ +#define D_EVENT 64 /* libevent */ +#define D_BUFFER 128 /* message queues */ +#define D_MEM 256 /* malloc/free */ +#define D_MEM2 1024 /* every single malloc/free */ +#define D_SIGN 2048 /* -sign */ +#define D_MISC 4096 /* everything else */ +#define D_ALL (D_CALL | D_DATA | D_NET | D_FILE | D_TLS | D_PARSE | \ + D_EVENT | D_BUFFER | D_MEM | D_MEM2 | D_SIGN | D_MISC) +#define D_DEFAULT (D_CALL | D_NET | D_FILE | D_TLS | D_MISC) + + +/* build with -DNDEBUG to remove all assert()s and DPRINTF()s */ +#ifdef NDEBUG +#define DPRINTF(x, ...) (void)0 +#else +#define DPRINTF(x, ...) /*LINTED null effect */(void)(Debug & (x) \ + ? (printf("%s:%s:%s:%.4d\t", make_timestamp(NULL, true), \ + __FILE__, __func__, __LINE__), printf(__VA_ARGS__)) : 0) +#endif + +/* shortcuts for libevent */ +#define EVENT_ADD(x) do { \ + DPRINTF(D_EVENT, "event_add(%s@%p)\n", #x, x); \ + if (event_add(x, NULL) == -1) { \ + DPRINTF(D_EVENT, "Failure in event_add()\n"); \ + } \ +} while (/*CONSTCOND*/0) +#define RETRYEVENT_ADD(x) do { \ + struct timeval _tv; \ + _tv.tv_sec = 0; \ + _tv.tv_usec = TLS_RETRY_EVENT_USEC; \ + DPRINTF(D_EVENT, "retryevent_add(%s@%p)\n", #x, x); \ + if (event_add(x, &_tv) == -1) { \ + DPRINTF(D_EVENT, "Failure in event_add()\n"); \ + } \ +} while (/*CONSTCOND*/0) +#define DEL_EVENT(x) do { \ + DPRINTF(D_MEM2, "DEL_EVENT(%s@%p)\n", #x, x); \ + if ((x) && (event_del(x) == -1)) { \ + DPRINTF(D_EVENT, "Failure in event_del()\n"); \ + } \ +} while (/*CONSTCOND*/0) + +/* safe calls to free() */ +#define FREEPTR(x) if (x) { \ + DPRINTF(D_MEM2, "free(%s@%p)\n", #x, x); \ + free(x); x = NULL; } +#define FREE_SSL(x) if (x) { \ + DPRINTF(D_MEM2, "SSL_free(%s@%p)\n", #x, x); \ + SSL_free(x); x = NULL; } +#define FREE_SSL_CTX(x) if (x) { \ + DPRINTF(D_MEM2, "SSL_CTX_free(%s@%p)\n", #x, x); \ + SSL_CTX_free(x); x = NULL; } + +/* reference counting macros for buffers */ +#define NEWREF(x) ((x) ? (DPRINTF(D_BUFFER, "inc refcount of " #x \ + " @ %p: %zu --> %zu\n", (x), (x)->refcount, \ + (x)->refcount + 1), (x)->refcount++, (x))\ + : (DPRINTF(D_BUFFER, "inc refcount of NULL!\n"), NULL)) +#define DELREF(x) /*LINTED null effect*/(void)((x) ? (DPRINTF(D_BUFFER, "dec refcount of " #x \ + " @ %p: %zu --> %zu\n", (x), (x)->refcount, \ + (x)->refcount - 1), buf_msg_free(x), NULL) \ + : (DPRINTF(D_BUFFER, "dec refcount of NULL!\n"), NULL)) + +/* assumption: + * - malloc()/calloc() only fails if not enough memory available + * - once init() has set up all global variables etc. + * the bulk of available memory is used for buffers + * and can be freed if necessary + */ +#define MALLOC(ptr, size) do { \ + while(!(ptr = malloc(size))) { \ + DPRINTF(D_MEM, "Unable to allocate memory"); \ + message_allqueues_purge(); \ + } \ + DPRINTF(D_MEM2, "MALLOC(%s@%p, %zu)\n", #ptr, ptr, size); \ +} while (/*CONSTCOND*/0) + +#define CALLOC(ptr, size) do { \ + while(!(ptr = calloc(1, size))) { \ + DPRINTF(D_MEM, "Unable to allocate memory"); \ + message_allqueues_purge(); \ + } \ + DPRINTF(D_MEM2, "CALLOC(%s@%p, %zu)\n", #ptr, ptr, size); \ +} while (/*CONSTCOND*/0) + +/* define strlen(NULL) to be 0 */ +#define SAFEstrlen(x) ((x) ? strlen(x) : 0) + +/* shorthand to block/restore signals for the duration of one function */ +#define BLOCK_SIGNALS(omask, newmask) do { \ + sigemptyset(&newmask); \ + sigaddset(&newmask, SIGHUP); \ + sigaddset(&newmask, SIGALRM); \ + sigprocmask(SIG_BLOCK, &newmask, &omask); \ +} while (/*CONSTCOND*/0) + +#define RESTORE_SIGNALS(omask) sigprocmask(SIG_SETMASK, &omask, NULL) + +/* small optimization to call send_queue() only if queue has elements */ +#define SEND_QUEUE(f) do { \ + if ((f)->f_qelements) \ + send_queue(0, 0, f); \ +} while (/*CONSTCOND*/0) + +#define MAXUNAMES 20 /* maximum number of user names */ +#define BSD_TIMESTAMPLEN 14+1 +#define MAX_TIMESTAMPLEN 31+1 + +/* maximum field lengths in syslog-protocol */ +#define PRI_MAX 5 +#define HOST_MAX 255 +#define APPNAME_MAX 48 +#define PROCID_MAX 128 +#define MSGID_MAX 32 +/* longest possible header length */ +#define HEADER_LEN_MAX (PRI_MAX + 1 + 1 + MAX_TIMESTAMPLEN + 1 + HOST_MAX \ + + 1 + APPNAME_MAX + 1 + PROCID_MAX + 1 + MSGID_MAX) + +/* allowed number of priorities by IETF standards */ +#define IETF_NUM_PRIVALUES 192 + +/* check if message with fac/sev belogs to a destination f */ +#define MATCH_PRI(f, fac, sev) \ + ( (((f)->f_pcmp[fac] & PRI_EQ) && ((f)->f_pmask[fac] == (sev))) \ + ||(((f)->f_pcmp[fac] & PRI_LT) && ((f)->f_pmask[fac] < (sev))) \ + ||(((f)->f_pcmp[fac] & PRI_GT) && ((f)->f_pmask[fac] > (sev))) \ + ) + +/* shorthand to test Byte Order Mark which indicates UTF-8 content */ +#define IS_BOM(p) ( \ + (p)[0] != '\0' && (unsigned char)(p)[0] == (unsigned char)0xEF && \ + (p)[1] != '\0' && (unsigned char)(p)[1] == (unsigned char)0xBB && \ + (p)[2] != '\0' && (unsigned char)(p)[2] == (unsigned char)0xBF) + +/* message buffer container used for processing, formatting, and queueing */ +struct buf_msg { + size_t refcount; + int pri; + int flags; + char *timestamp; + char *recvhost; + char *host; + char *prog; + char *pid; + char *msgid; + char *sd; /* structured data */ + char *msg; /* message content */ + char *msgorig; /* in case we advance *msg beyond header fields + we still want to free() the original ptr */ + size_t msglen; /* strlen(msg) */ + size_t msgsize; /* allocated memory size */ + size_t tlsprefixlen; /* bytes for the TLS length prefix */ + size_t prilen; /* bytes for priority and version */ +}; + +/* queue of messages */ +struct buf_queue { + struct buf_msg* msg; + STAILQ_ENTRY(buf_queue) entries; +}; +STAILQ_HEAD(buf_queue_head, buf_queue); + +/* a pair of a socket and an associated event object */ +struct socketEvent { + int fd; + int af; + struct event *ev; +}; + +/* + * Flags to logmsg(). + */ +#define IGN_CONS 0x001 /* don't print on console */ +#define SYNC_FILE 0x002 /* do fsync on file after printing */ +#define ADDDATE 0x004 /* add a date to the message */ +#define MARK 0x008 /* this message is a mark */ +#define ISKERNEL 0x010 /* kernel generated message */ +#define BSDSYSLOG 0x020 /* line in traditional BSD Syslog format */ +#define SIGN_MSG 0x040 /* syslog-sign data, not signed again */ + +/* strategies for message_queue_purge() */ +#define PURGE_OLDEST 1 +#define PURGE_BY_PRIORITY 2 + +/* + * This structure represents the files that will have log + * copies printed. + * We require f_file to be valid if f_type is F_FILE, F_CONSOLE, F_TTY, + * or if f_type is F_PIPE and f_pid > 0. + */ + +struct filed { + struct filed *f_next; /* next in linked list */ + 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 */ + u_char f_pcmp[LOG_NFACILITIES+1]; /* compare priority */ +#define PRI_LT 0x1 +#define PRI_EQ 0x2 +#define PRI_GT 0x4 + char *f_program; /* program this applies to */ + union { + char f_uname[MAXUNAMES][UT_NAMESIZE+1]; + struct { + char f_hname[MAXHOSTNAMELEN]; + struct addrinfo *f_addr; + } f_forw; /* UDP forwarding address */ +#ifndef DISABLE_TLS + struct { + SSL *ssl; /* SSL object */ + struct tls_conn_settings *tls_conn; /* certificate info */ + } f_tls; /* TLS forwarding address */ +#endif /* !DISABLE_TLS */ + char f_fname[MAXPATHLEN]; + struct { + char f_pname[MAXPATHLEN]; + pid_t f_pid; + } f_pipe; + } f_un; +#ifndef DISABLE_SIGN + struct signature_group_t *f_sg; /* one signature group */ +#endif /* !DISABLE_SIGN */ + struct buf_queue_head f_qhead; /* undelivered msgs queue */ + size_t f_qelements; /* elements in queue */ + size_t f_qsize; /* size of queue in bytes */ + struct buf_msg *f_prevmsg; /* last message logged */ + struct event *f_sq_event; /* timer for send_queue() */ + int f_prevcount; /* repetition cnt of prevmsg */ + int f_repeatcount; /* number of "repeated" msgs */ + int f_lasterror; /* last error on writev() */ + int f_flags; /* file-specific flags */ +#define FFLAG_SYNC 0x01 /* for F_FILE: fsync after every msg */ +#define FFLAG_FULL 0x02 /* for F_FILE | F_PIPE: write PRI header */ +#define FFLAG_SIGN 0x04 /* for syslog-sign with SG="3": + * sign the messages to this destination */ +}; + +#ifndef DISABLE_TLS + +/* linked list for allowed TLS peer credentials + * (one for fingerprint, one for cert-files) + */ +SLIST_HEAD(peer_cred_head, peer_cred); +struct peer_cred { + SLIST_ENTRY(peer_cred) entries; + char *data; +}; + +/* config options for TLS server-side */ +struct tls_global_options_t { + SSL_CTX *global_TLS_CTX; + struct peer_cred_head fprint_head; /* trusted client fingerprints */ + struct peer_cred_head cert_head; /* trusted client cert files */ + char *keyfile; /* file with private key */ + char *certfile; /* file with own certificate */ + char *CAfile; /* file with CA certificate */ + char *CAdir; /* alternative: path to directory with CA certs */ + char *x509verify; /* level of peer verification */ + char *bindhost; /* hostname/IP to bind to */ + char *bindport; /* port/service to bind to */ + char *server; /* if !NULL: do not listen to incoming TLS */ + char *gen_cert; /* if !NULL: generate self-signed certificate */ +}; + +/* TLS needs three sets of sockets: + * - listening sockets: a fixed size array TLS_Listen_Set, just like finet for UDP. + * - outgoing connections: managed as part of struct filed. + * - incoming connections: variable sized, thus a linked list TLS_Incoming. + */ +/* every connection has its own input buffer with status + * variables for message reading */ +SLIST_HEAD(TLS_Incoming, TLS_Incoming_Conn); + +struct TLS_Incoming_Conn { + SLIST_ENTRY(TLS_Incoming_Conn) entries; + struct tls_conn_settings *tls_conn; + int socket; + char *inbuf; /* input buffer */ + size_t inbuflen; + size_t cur_msg_len; /* length of current msg */ + size_t cur_msg_start; /* beginning of current msg */ + size_t read_pos; /* ring buffer position to write to */ + size_t errorcount; /* to close faulty connections */ + bool closenow; /* close connection as soon as buffer processed */ + bool dontsave; /* for receiving oversized messages w/o saving them */ +}; + +#endif /* !DISABLE_TLS */ + +#endif /*SYSLOGD_H_*/ diff --git a/usr.sbin/syslogd/tls.c b/usr.sbin/syslogd/tls.c new file mode 100644 index 000000000..243ef2581 --- /dev/null +++ b/usr.sbin/syslogd/tls.c @@ -0,0 +1,2188 @@ +/* $NetBSD: tls.c,v 1.11 2013/05/27 23:15:51 christos Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * tls.c TLS related code for syslogd + * + * implements the TLS init and handshake callbacks with all required + * checks from http://tools.ietf.org/html/draft-ietf-syslog-transport-tls-13 + * + * Martin Schütte + */ + +#include +__RCSID("$NetBSD: tls.c,v 1.11 2013/05/27 23:15:51 christos Exp $"); + +#ifndef DISABLE_TLS +#include "syslogd.h" +#include "tls.h" +#include +#include +#include "extern.h" + +static unsigned getVerifySetting(const char *x509verifystring); + +/* to output SSL error codes */ +static const char *SSL_ERRCODE[] = { + "SSL_ERROR_NONE", + "SSL_ERROR_SSL", + "SSL_ERROR_WANT_READ", + "SSL_ERROR_WANT_WRITE", + "SSL_ERROR_WANT_X509_LOOKUP", + "SSL_ERROR_SYSCALL", + "SSL_ERROR_ZERO_RETURN", + "SSL_ERROR_WANT_CONNECT", + "SSL_ERROR_WANT_ACCEPT"}; +/* TLS connection states -- keep in sync with symbols in .h */ +static const char *TLS_CONN_STATES[] = { + "ST_NONE", + "ST_TLS_EST", + "ST_TCP_EST", + "ST_CONNECTING", + "ST_ACCEPTING", + "ST_READING", + "ST_WRITING", + "ST_EOF", + "ST_CLOSING0", + "ST_CLOSING1", + "ST_CLOSING2"}; + +DH *get_dh1024(void); +/* DH parameter precomputed with "openssl dhparam -C -2 1024" */ +#ifndef HEADER_DH_H +#include +#endif +DH * +get_dh1024(void) +{ + static const unsigned char dh1024_p[]={ + 0x94,0xBC,0xC4,0x71,0xD4,0xD3,0x2B,0x17,0x69,0xEA,0x82,0x1B, + 0x0F,0x86,0x45,0x57,0xF8,0x86,0x2C,0xC8,0xF5,0x37,0x1F,0x1F, + 0x12,0xDA,0x2C,0x62,0x4C,0xF6,0x95,0xF0,0xE4,0x6A,0x63,0x00, + 0x32,0x54,0x5F,0xA9,0xAA,0x2E,0xD2,0xD3,0xA5,0x7A,0x4E,0xCF, + 0xE8,0x2A,0xF6,0xAB,0xAF,0xD3,0x71,0x3E,0x75,0x9E,0x6B,0xF3, + 0x2E,0x6D,0x97,0x42,0xC2,0x45,0xC0,0x03,0xE1,0x17,0xA4,0x39, + 0xF6,0x36,0xA7,0x11,0xBD,0x30,0xF6,0x6F,0x21,0xBF,0x28,0xE4, + 0xF9,0xE1,0x1E,0x48,0x72,0x58,0xA9,0xC8,0x61,0x65,0xDB,0x66, + 0x36,0xA3,0x77,0x0A,0x81,0x79,0x2C,0x45,0x1E,0x97,0xA6,0xB1, + 0xD9,0x25,0x9C,0x28,0x96,0x91,0x40,0xF8,0xF6,0x86,0x11,0x9C, + 0x88,0xEC,0xA6,0xBA,0x9F,0x4F,0x85,0x43 }; + static const unsigned char dh1024_g[]={ 0x02 }; + DH *dh; + + if ((dh=DH_new()) == NULL) + return NULL; + dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL); + dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL); + if ((dh->p == NULL) || (dh->g == NULL)) { + DH_free(dh); + return NULL; + } + return dh; +} + +#define ST_CHANGE(x, y) do { \ + if ((x) != (y)) { \ + DPRINTF(D_TLS, "Change state: %s --> %s\n", \ + TLS_CONN_STATES[x], TLS_CONN_STATES[y]); \ + (x) = (y); \ + } \ +} while (/*CONSTCOND*/0) + +static unsigned +getVerifySetting(const char *x509verifystring) +{ + if (!x509verifystring) + return X509VERIFY_ALWAYS; + + if (!strcasecmp(x509verifystring, "off")) + return X509VERIFY_NONE; + else if (!strcasecmp(x509verifystring, "opt")) + return X509VERIFY_IFPRESENT; + else + return X509VERIFY_ALWAYS; +} +/* + * init OpenSSL lib and one context. + * returns NULL if global context already exists. + * returns a status message on successfull init (to be free()d by caller). + * calls die() on serious error. + */ +char* +init_global_TLS_CTX(void) +{ + const char *keyfilename = tls_opt.keyfile; + const char *certfilename = tls_opt.certfile; + const char *CAfile = tls_opt.CAfile; + const char *CApath = tls_opt.CAdir; + + SSL_CTX *ctx; + unsigned x509verify = X509VERIFY_ALWAYS; + EVP_PKEY *pkey = NULL; + X509 *cert = NULL; + FILE *certfile = NULL; + FILE *keyfile = NULL; + unsigned long err; + char *fp = NULL, *cn = NULL; + + char statusmsg[1024]; + + if (tls_opt.global_TLS_CTX) /* already initialized */ + return NULL; + + x509verify = getVerifySetting(tls_opt.x509verify); + if (x509verify != X509VERIFY_ALWAYS) + loginfo("insecure configuration, peer authentication disabled"); + + if (!(ctx = SSL_CTX_new(SSLv23_method()))) { + logerror("Unable to initialize OpenSSL: %s", + ERR_error_string(ERR_get_error(), NULL)); + die(0,0,NULL); + } + + if (!keyfilename) + keyfilename = DEFAULT_X509_KEYFILE; + if (!certfilename) + certfilename = DEFAULT_X509_CERTFILE; + + /* TODO: would it be better to use stat() for access checking? */ + if (!(keyfile = fopen(keyfilename, "r")) + && !(certfile = fopen(certfilename, "r"))) { + errno = 0; + if (!tls_opt.gen_cert) { + logerror("TLS certificate files \"%s\" and \"%s\"" + "not readable. Please configure them with " + "\"tls_cert\" and \"tls_key\" or set " + "\"tls_gen_cert=1\" to generate a new " + "certificate", keyfilename, certfilename); + die(0,0,NULL); + } + + loginfo("Generating a self-signed certificate and writing " + "files \"%s\" and \"%s\"", keyfilename, certfilename); + if (!mk_x509_cert(&cert, &pkey, TLS_GENCERT_BITS, + TLS_GENCERT_SERIAL, TLS_GENCERT_DAYS)) { + logerror("Unable to generate new certificate."); + die(0,0,NULL); + } + if (!write_x509files(pkey, cert, + keyfilename, certfilename)) { + logerror("Unable to write certificate to files \"%s\"" + " and \"%s\"", keyfilename, certfilename); + /* not fatal */ + } + } + if (keyfile) + (void)fclose(keyfile); + if (certfile) + (void)fclose(certfile); + errno = 0; + + /* if generated, then use directly */ + if (cert && pkey) { + if (!SSL_CTX_use_PrivateKey(ctx, pkey) + || !SSL_CTX_use_certificate(ctx, cert)) { + logerror("Unable to use generated private " + "key and certificate: %s", + ERR_error_string(ERR_get_error(), NULL)); + die(0,0,NULL); /* any better reaction? */ + } + } else { + /* load keys and certs from files */ + if (!SSL_CTX_use_PrivateKey_file(ctx, keyfilename, + SSL_FILETYPE_PEM) + || !SSL_CTX_use_certificate_chain_file(ctx, certfilename)) { + logerror("Unable to load private key and " + "certificate from files \"%s\" and \"%s\": %s", + keyfilename, certfilename, + ERR_error_string(ERR_get_error(), NULL)); + die(0,0,NULL); /* any better reaction? */ + } + } + if (!SSL_CTX_check_private_key(ctx)) { + logerror("Private key \"%s\" does not match " + "certificate \"%s\": %s", + keyfilename, certfilename, + ERR_error_string(ERR_get_error(), NULL)); + die(0,0,NULL); + } + + if (CAfile || CApath) { + if (SSL_CTX_load_verify_locations(ctx, CAfile, CApath) != 1) { + if (CAfile && CApath) + logerror("unable to load trust anchors from " + "\"%s\" and \"%s\": %s\n", + CAfile, CApath, ERR_error_string( + ERR_get_error(), NULL)); + else + logerror("unable to load trust anchors from " + "\"%s\": %s\n", (CAfile?CAfile:CApath), + ERR_error_string( + ERR_get_error(), NULL)); + } else { + DPRINTF(D_TLS, "loaded trust anchors\n"); + } + } + + /* options */ + (void)SSL_CTX_set_options(ctx, + SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3 | SSL_OP_SINGLE_DH_USE); + (void)SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY); + + /* peer verification */ + if ((x509verify == X509VERIFY_NONE) + || (x509verify == X509VERIFY_IFPRESENT)) + /* ask for cert, but a client does not have to send one */ + SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, check_peer_cert); + else + /* default: ask for cert and check it */ + SSL_CTX_set_verify(ctx, + SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, + check_peer_cert); + + if (SSL_CTX_set_tmp_dh(ctx, get_dh1024()) != 1) + logerror("SSL_CTX_set_tmp_dh() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + + /* make sure the OpenSSL error queue is empty */ + while ((err = ERR_get_error()) != 0) + logerror("Unexpected OpenSSL error: %s", + ERR_error_string(err, NULL)); + + + /* On successful init the status message is not logged immediately + * but passed to the caller. The reason is that init() can continue + * to initialize syslog-sign. When the status message is logged + * after that it will get a valid signature and not cause errors + * with signature verification. + */ + if (cert || read_certfile(&cert, certfilename)) { + get_fingerprint(cert, &fp, NULL); + get_commonname(cert, &cn); + } + DPRINTF(D_TLS, "loaded and checked own certificate\n"); + snprintf(statusmsg, sizeof(statusmsg), + "Initialized TLS settings using library \"%s\". " + "Use certificate from file \"%s\" with CN \"%s\" " + "and fingerprint \"%s\"", SSLeay_version(SSLEAY_VERSION), + certfilename, cn, fp); + free(cn); + free(fp); + + tls_opt.global_TLS_CTX = ctx; + return strdup(statusmsg); +} + + +/* + * get fingerprint of cert + * returnstring will be allocated and should be free()d by the caller + * alg_name selects an algorithm, if it is NULL then DEFAULT_FINGERPRINT_ALG + * (should be "sha-1") will be used + * return value and non-NULL *returnstring indicate success + */ +bool +get_fingerprint(const X509 *cert, char **returnstring, const char *alg_name) +{ +#define MAX_ALG_NAME_LENGTH 8 + unsigned char md[EVP_MAX_MD_SIZE]; + char fp_val[4]; + size_t memsize, i; + unsigned len; + const EVP_MD *digest; + const char *openssl_algname; + /* RFC nnnn uses hash function names from + * http://www.iana.org/assignments/hash-function-text-names/ + * in certificate fingerprints. + * We have to map them to the hash function names used by OpenSSL. + * Actually we use the union of both namespaces to be RFC compliant + * and to let the user use "openssl -fingerprint ..." + * + * Intended behaviour is to prefer the IANA names, + * but allow the user to use OpenSSL names as well + * (e.g. for "RIPEMD160" wich has no IANA name) + */ + static const struct hash_alg_namemap { + const char *iana; + const char *openssl; + } hash_alg_namemap[] = { + {"md2", "MD2" }, + {"md5", "MD5" }, + {"sha-1", "SHA1" }, + {"sha-224", "SHA224"}, + {"sha-256", "SHA256"}, + {"sha-384", "SHA384"}, + {"sha-512", "SHA512"} + }; + + DPRINTF(D_TLS, "get_fingerprint(cert@%p, return@%p, alg \"%s\")\n", + cert, returnstring, alg_name); + *returnstring = NULL; + + if (!alg_name) + alg_name = DEFAULT_FINGERPRINT_ALG; + openssl_algname = alg_name; + for (i = 0; i < A_CNT(hash_alg_namemap); i++) + if (!strcasecmp(alg_name, hash_alg_namemap[i].iana)) + openssl_algname = hash_alg_namemap[i].openssl; + + if (!(digest = (const EVP_MD *) EVP_get_digestbyname( + __UNCONST(openssl_algname)))) { + DPRINTF(D_TLS, "unknown digest algorithm %s\n", + openssl_algname); + return false; + } + if (!X509_digest(cert, digest, md, &len)) { + DPRINTF(D_TLS, "cannot get %s digest\n", openssl_algname); + return false; + } + + /* 'normalise' and translate back to IANA name */ + alg_name = openssl_algname = OBJ_nid2sn(EVP_MD_type(digest)); + for (i = 0; i < A_CNT(hash_alg_namemap); i++) + if (!strcasecmp(openssl_algname, hash_alg_namemap[i].openssl)) + alg_name = hash_alg_namemap[i].iana; + + /* needed memory: 3 string bytes for every binary byte with delimiter + * + max_iana_strlen with delimiter */ + memsize = (len * 3) + strlen(alg_name) + 1; + MALLOC(*returnstring, memsize); + (void)strlcpy(*returnstring, alg_name, memsize); + (void)strlcat(*returnstring, ":", memsize); + /* append the fingeprint data */ + for (i = 0; i < len; i++) { + (void)snprintf(fp_val, sizeof(fp_val), + "%02X:", (unsigned) md[i]); + (void)strlcat(*returnstring, fp_val, memsize); + } + return true; +} + +/* + * gets first CN from cert in returnstring (has to be freed by caller) + * on failure it returns false and *returnstring is NULL + */ +bool +get_commonname(X509 *cert, char **returnstring) +{ + X509_NAME *x509name; + X509_NAME_ENTRY *entry; + unsigned char *ubuf; + int len, i; + + x509name = X509_get_subject_name(cert); + i = X509_NAME_get_index_by_NID(x509name, NID_commonName, -1); + if (i != -1) { + entry = X509_NAME_get_entry(x509name, i); + len = ASN1_STRING_to_UTF8(&ubuf, + X509_NAME_ENTRY_get_data(entry)); + if (len > 0) { + MALLOC(*returnstring, (size_t)len+1); + strlcpy(*returnstring, (const char*)ubuf, len+1); + OPENSSL_free(ubuf); + return true; + } + OPENSSL_free(ubuf); + } + *returnstring = NULL; + return false; +} +/* + * test if cert matches as configured hostname or IP + * checks a 'really used' hostname and optionally a second expected subject + * against iPAddresses, dnsNames and commonNames + * + * TODO: wildcard matching for dnsNames is not implemented. + * in transport-tls that is a MAY, and I do not trust them anyway. + * but there might be demand for, so it's a todo item. + */ +bool +match_hostnames(X509 *cert, const char *hostname, const char *subject) +{ + int i, len, num; + char *buf; + unsigned char *ubuf; + GENERAL_NAMES *gennames; + GENERAL_NAME *gn; + X509_NAME *x509name; + X509_NAME_ENTRY *entry; + ASN1_OCTET_STRING *asn1_ip, *asn1_cn_ip; + int crit, idx; + + DPRINTF((D_TLS|D_CALL), "match_hostnames(%p, \"%s\", \"%s\")\n", + cert, hostname, subject); + + /* see if hostname is an IP */ + if ((subject && (asn1_ip = a2i_IPADDRESS(subject ))) + || (hostname && (asn1_ip = a2i_IPADDRESS(hostname)))) + /* nothing */; + else + asn1_ip = NULL; + + if (!(gennames = X509_get_ext_d2i(cert, NID_subject_alt_name, + &crit, &idx))) { + DPRINTF(D_TLS, "X509_get_ext_d2i() returned (%p,%d,%d) " + "--> no subjectAltName\n", gennames, crit, idx); + } else { + num = sk_GENERAL_NAME_num(gennames); + if (asn1_ip) { + /* first loop: check IPs */ + for (i = 0; i < num; ++i) { + gn = sk_GENERAL_NAME_value(gennames, i); + if (gn->type == GEN_IPADD + && !ASN1_OCTET_STRING_cmp(asn1_ip, + gn->d.iPAddress)) + return true; + } + } + /* second loop: check DNS names */ + for (i = 0; i < num; ++i) { + gn = sk_GENERAL_NAME_value(gennames, i); + if (gn->type == GEN_DNS) { + buf = (char *)ASN1_STRING_data(gn->d.ia5); + len = ASN1_STRING_length(gn->d.ia5); + if (!strncasecmp(subject, buf, len) + || !strncasecmp(hostname, buf, len)) + return true; + } + } + } + + /* check commonName; not sure if more than one CNs possible, but we + * will look at all of them */ + x509name = X509_get_subject_name(cert); + i = X509_NAME_get_index_by_NID(x509name, NID_commonName, -1); + while (i != -1) { + entry = X509_NAME_get_entry(x509name, i); + len = ASN1_STRING_to_UTF8(&ubuf, + X509_NAME_ENTRY_get_data(entry)); + if (len > 0) { + DPRINTF(D_TLS, "found CN: %.*s\n", len, ubuf); + /* hostname */ + if ((subject && !strncasecmp(subject, + (const char*)ubuf, len)) + || (hostname && !strncasecmp(hostname, + (const char*)ubuf, len))) { + OPENSSL_free(ubuf); + return true; + } + OPENSSL_free(ubuf); + /* IP -- convert to ASN1_OCTET_STRING and compare then + * so that "10.1.2.3" and "10.01.02.03" are equal */ + if ((asn1_ip) + && subject + && (asn1_cn_ip = a2i_IPADDRESS(subject)) + && !ASN1_OCTET_STRING_cmp(asn1_ip, asn1_cn_ip)) { + return true; + } + } + i = X509_NAME_get_index_by_NID(x509name, NID_commonName, i); + } + return false; +} + +/* + * check if certificate matches given fingerprint + */ +bool +match_fingerprint(const X509 *cert, const char *fingerprint) +{ +#define MAX_ALG_NAME_LENGTH 8 + char alg[MAX_ALG_NAME_LENGTH]; + char *certfingerprint; + char *p; + const char *q; + + DPRINTF((D_TLS|D_CALL), "match_fingerprint(cert@%p, fp \"%s\")\n", + cert, fingerprint); + if (!fingerprint) + return false; + + /* get algorithm */ + p = alg; + q = fingerprint; + while (*q != ':' && *q != '\0' && p < alg + MAX_ALG_NAME_LENGTH) + *p++ = *q++; + *p = '\0'; + + if (!get_fingerprint(cert, &certfingerprint, alg)) { + DPRINTF(D_TLS, "cannot get %s digest\n", alg); + return false; + } + if (strncmp(certfingerprint, fingerprint, strlen(certfingerprint))) { + DPRINTF(D_TLS, "fail: fingerprints do not match\n"); + free(certfingerprint); + return false; + } + DPRINTF(D_TLS, "accepted: fingerprints match\n"); + free(certfingerprint); + return true; +} + +/* + * check if certificate matches given certificate file + */ +bool +match_certfile(const X509 *cert1, const char *certfilename) +{ + X509 *cert2; + char *fp1, *fp2; + bool rc = false; + errno = 0; + + if (read_certfile(&cert2, certfilename) + && get_fingerprint(cert1, &fp1, NULL) + && get_fingerprint(cert2, &fp2, NULL)) { + if (!strcmp(fp1, fp2)) + rc = true; + FREEPTR(fp1); + FREEPTR(fp2); + } + DPRINTF((D_TLS|D_CALL), "match_certfile(cert@%p, file \"%s\") " + "returns %d\n", cert1, certfilename, rc); + return rc; +} + +/* + * reads X.509 certificate from file + * caller has to free it later with 'OPENSSL_free(cert);' + */ +bool +read_certfile(X509 **cert, const char *certfilename) +{ + FILE *certfile; + errno = 0; + + DPRINTF((D_TLS|D_CALL), "read_certfile(%p, \"%s\")\n", + cert, certfilename); + if (!cert || !certfilename) + return false; + + if (!(certfile = fopen(certfilename, "rb"))) { + logerror("Unable to open certificate file: %s", certfilename); + return false; + } + + /* either PEM or DER */ + if (!(*cert = PEM_read_X509(certfile, NULL, NULL, NULL)) + && !(*cert = d2i_X509_fp(certfile, NULL))) { + DPRINTF((D_TLS), "Unable to read certificate from %s\n", + certfilename); + (void)fclose(certfile); + return false; + } + else { + DPRINTF((D_TLS), "Read certificate from %s\n", certfilename); + (void)fclose(certfile); + return true; + } +} + +/* used for incoming connections in check_peer_cert() */ +int +accept_cert(const char* reason, struct tls_conn_settings *conn_info, + char *cur_fingerprint, char *cur_subjectline) +{ + /* When using DSA keys the callback gets called twice. + * This flag avoids multiple log messages for the same connection. + */ + if (!conn_info->accepted) + loginfo("Established connection and accepted %s certificate " + "from %s due to %s. Subject is \"%s\", fingerprint is" + " \"%s\"", conn_info->incoming ? "server" : "client", + conn_info->hostname, reason, cur_subjectline, + cur_fingerprint); + + if (cur_fingerprint && !conn_info->fingerprint) + conn_info->fingerprint = cur_fingerprint; + else + FREEPTR(cur_fingerprint); + + if (cur_subjectline && !conn_info->subject) + conn_info->subject = cur_subjectline; + else + FREEPTR(cur_subjectline); + + conn_info->accepted = true; + return 1; +} +int +deny_cert(struct tls_conn_settings *conn_info, + char *cur_fingerprint, char *cur_subjectline) +{ + if (!conn_info->accepted) + loginfo("Deny %s certificate from %s. " + "Subject is \"%s\", fingerprint is \"%s\"", + conn_info->incoming ? "client" : "server", + conn_info->hostname, + cur_subjectline, cur_fingerprint); + else + logerror("Error with TLS %s certificate authentication, " + "already approved certificate became invalid. " + "Subject is \"%s\", fingerprint is \"%s\"", + conn_info->incoming ? "client" : "server", + cur_subjectline, cur_fingerprint); + FREEPTR(cur_fingerprint); + FREEPTR(cur_subjectline); + return 0; +} + +/* + * Callback after OpenSSL has verified a peer certificate, + * gets called for every certificate in a chain (starting with root CA). + * preverify_ok indicates a valid trust path (necessary), + * then we check whether the hostname or configured subject matches the cert. + */ +int +check_peer_cert(int preverify_ok, X509_STORE_CTX *ctx) +{ + char *cur_subjectline = NULL; + char *cur_fingerprint = NULL; + char cur_issuerline[256]; + SSL *ssl; + X509 *cur_cert; + int cur_err, cur_depth; + struct tls_conn_settings *conn_info; + struct peer_cred *cred, *tmp_cred; + + /* read context info */ + cur_cert = X509_STORE_CTX_get_current_cert(ctx); + cur_err = X509_STORE_CTX_get_error(ctx); + cur_depth = X509_STORE_CTX_get_error_depth(ctx); + ssl = X509_STORE_CTX_get_ex_data(ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + conn_info = SSL_get_app_data(ssl); + + /* some info */ + (void)get_commonname(cur_cert, &cur_subjectline); + (void)get_fingerprint(cur_cert, &cur_fingerprint, NULL); + DPRINTF((D_TLS|D_CALL), "check cert for connection with %s. " + "depth is %d, preverify is %d, subject is %s, fingerprint " + "is %s, conn_info@%p%s\n", conn_info->hostname, cur_depth, + preverify_ok, cur_subjectline, cur_fingerprint, conn_info, + (conn_info->accepted ? ", cb was already called" : "")); + + if (Debug && !preverify_ok) { + DPRINTF(D_TLS, "openssl verify error:" + "num=%d:%s:depth=%d:%s\t\n", cur_err, + X509_verify_cert_error_string(cur_err), + cur_depth, cur_subjectline); + if (cur_err == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT) { + X509_NAME_oneline( + X509_get_issuer_name(ctx->current_cert), + cur_issuerline, sizeof(cur_issuerline)); + DPRINTF(D_TLS, "openssl verify error:missing " + "cert for issuer=%s\n", cur_issuerline); + } + } + + /* + * quite a lot of variables here, + * the big if/elseif covers all possible combinations. + * + * here is a list, ordered like the conditions below: + * - conn_info->x509verify + * X509VERIFY_NONE: do not verify certificates, + * only log its subject and fingerprint + * X509VERIFY_IFPRESENT: if we got her, then a cert is present, + * so check it normally + * X509VERIFY_ALWAYS: normal certificate check + * - cur_depth: + * > 0: peer provided CA cert. remember if its valid, + * but always accept, because most checks work on depth 0 + * == 0: the peer's own cert. check this for final decision + * - preverify_ok: + * true: valid certificate chain from a trust anchor to this cert + * false: no valid and trusted certificate chain + * - conn_info->incoming: + * true: we are the server, means we authenticate against all + * allowed attributes in tls_opt + * false: otherwise we are client and conn_info has all attributes + * to check + * - conn_info->fingerprint (only if !conn_info->incoming) + * NULL: no fingerprint configured, only check certificate chain + * !NULL: a peer cert with this fingerprint is trusted + * + */ + /* shortcut */ + if (cur_depth != 0) { + FREEPTR(cur_fingerprint); + FREEPTR(cur_subjectline); + return 1; + } + + if (conn_info->x509verify == X509VERIFY_NONE) + return accept_cert("disabled verification", conn_info, + cur_fingerprint, cur_subjectline); + + /* implicit: (cur_depth == 0) + * && (conn_info->x509verify != X509VERIFY_NONE) */ + if (conn_info->incoming) { + if (preverify_ok) + return accept_cert("valid certificate chain", + conn_info, cur_fingerprint, cur_subjectline); + + /* else: now check allowed client fingerprints/certs */ + SLIST_FOREACH(cred, &tls_opt.fprint_head, entries) { + if (match_fingerprint(cur_cert, cred->data)) { + return accept_cert("matching fingerprint", + conn_info, cur_fingerprint, + cur_subjectline); + } + } + SLIST_FOREACH_SAFE(cred, &tls_opt.cert_head, + entries, tmp_cred) { + if (match_certfile(cur_cert, cred->data)) + return accept_cert("matching certfile", + conn_info, cur_fingerprint, + cur_subjectline); + } + return deny_cert(conn_info, cur_fingerprint, cur_subjectline); + } + + /* implicit: (cur_depth == 0) + * && (conn_info->x509verify != X509VERIFY_NONE) + * && !conn_info->incoming */ + if (!conn_info->incoming && preverify_ok) { + /* certificate chain OK. check subject/hostname */ + if (match_hostnames(cur_cert, conn_info->hostname, + conn_info->subject)) + return accept_cert("matching hostname/subject", + conn_info, cur_fingerprint, cur_subjectline); + else + return deny_cert(conn_info, cur_fingerprint, + cur_subjectline); + } else if (!conn_info->incoming && !preverify_ok) { + /* chain not OK. check fingerprint/subject/hostname */ + if (match_fingerprint(cur_cert, conn_info->fingerprint)) + return accept_cert("matching fingerprint", conn_info, + cur_fingerprint, cur_subjectline); + else if (match_certfile(cur_cert, conn_info->certfile)) + return accept_cert("matching certfile", conn_info, + cur_fingerprint, cur_subjectline); + else + return deny_cert(conn_info, cur_fingerprint, + cur_subjectline); + } + + FREEPTR(cur_fingerprint); + FREEPTR(cur_subjectline); + return 0; +} + +/* + * Create TCP sockets for incoming TLS connections. + * To be used like socksetup(), hostname and port are optional, + * returns bound stream sockets. + */ +struct socketEvent * +socksetup_tls(const int af, const char *bindhostname, const char *port) +{ + struct addrinfo hints, *res, *r; + int error, maxs; + const int on = 1; + struct socketEvent *s, *socks; + + if(!tls_opt.server + || !tls_opt.global_TLS_CTX) + return NULL; + + memset(&hints, 0, sizeof(hints)); + hints.ai_flags = AI_PASSIVE; + hints.ai_family = af; + hints.ai_socktype = SOCK_STREAM; + + error = getaddrinfo(bindhostname, (port ? port : "syslog-tls"), + &hints, &res); + if (error) { + logerror("%s", gai_strerror(error)); + errno = 0; + die(0, 0, NULL); + } + + /* Count max number of sockets we may open */ + for (maxs = 0, r = res; r; r = r->ai_next, maxs++) + continue; + socks = malloc((maxs+1) * sizeof(*socks)); + if (!socks) { + logerror("Unable to allocate memory for sockets"); + die(0, 0, NULL); + } + + socks->fd = 0; /* num of sockets counter at start of array */ + s = socks + 1; + for (r = res; r; r = r->ai_next) { + if ((s->fd = socket(r->ai_family, r->ai_socktype, + r->ai_protocol)) == -1) { + logerror("socket() failed: %s", strerror(errno)); + continue; + } + s->af = r->ai_family; + if (r->ai_family == AF_INET6 + && setsockopt(s->fd, IPPROTO_IPV6, IPV6_V6ONLY, + &on, sizeof(on)) == -1) { + logerror("setsockopt(IPV6_V6ONLY) failed: %s", + strerror(errno)); + close(s->fd); + continue; + } + if (setsockopt(s->fd, SOL_SOCKET, SO_REUSEADDR, + &on, sizeof(on)) == -1) { + DPRINTF(D_NET, "Unable to setsockopt(): %s\n", + strerror(errno)); + } + if ((error = bind(s->fd, r->ai_addr, r->ai_addrlen)) == -1) { + logerror("bind() failed: %s", strerror(errno)); + /* is there a better way to handle a EADDRINUSE? */ + close(s->fd); + continue; + } + if (listen(s->fd, TLSBACKLOG) == -1) { + logerror("listen() failed: %s", strerror(errno)); + close(s->fd); + continue; + } + s->ev = allocev(); + event_set(s->ev, s->fd, EV_READ | EV_PERSIST, + dispatch_socket_accept, s->ev); + EVENT_ADD(s->ev); + + socks->fd = socks->fd + 1; /* num counter */ + s++; + } + + if (socks->fd == 0) { + free (socks); + if(Debug) + return NULL; + else + die(0, 0, NULL); + } + if (res) + freeaddrinfo(res); + + return socks; +} + +/* + * Dispatch routine for non-blocking SSL_connect() + * Has to be idempotent in case of TLS_RETRY (~ EAGAIN), + * so we can continue a slow handshake. + */ +/*ARGSUSED*/ +void +dispatch_SSL_connect(int fd, short event, void *arg) +{ + struct tls_conn_settings *conn_info = (struct tls_conn_settings *) arg; + SSL *ssl = conn_info->sslptr; + int rc, error; + sigset_t newmask, omask; + struct timeval tv; + + BLOCK_SIGNALS(omask, newmask); + DPRINTF((D_TLS|D_CALL), "dispatch_SSL_connect(conn_info@%p, fd %d)\n", + conn_info, fd); + assert(conn_info->state == ST_TCP_EST + || conn_info->state == ST_CONNECTING); + + ST_CHANGE(conn_info->state, ST_CONNECTING); + rc = SSL_connect(ssl); + if (0 >= rc) { + error = tls_examine_error("SSL_connect()", + conn_info->sslptr, NULL, rc); + switch (error) { + case TLS_RETRY_READ: + event_set(conn_info->retryevent, fd, EV_READ, + dispatch_SSL_connect, conn_info); + EVENT_ADD(conn_info->retryevent); + break; + case TLS_RETRY_WRITE: + event_set(conn_info->retryevent, fd, EV_WRITE, + dispatch_SSL_connect, conn_info); + EVENT_ADD(conn_info->retryevent); + break; + default: /* should not happen, + * ... but does if the cert is not accepted */ + logerror("Cannot establish TLS connection " + "to \"%s\" -- TLS handshake aborted " + "before certificate authentication.", + conn_info->hostname); + ST_CHANGE(conn_info->state, ST_NONE); + conn_info->reconnect = 5 * TLS_RECONNECT_SEC; + tv.tv_sec = conn_info->reconnect; + tv.tv_usec = 0; + schedule_event(&conn_info->event, &tv, + tls_reconnect, conn_info); + break; + } + RESTORE_SIGNALS(omask); + return; + } + /* else */ + conn_info->reconnect = TLS_RECONNECT_SEC; + event_set(conn_info->event, fd, EV_READ, dispatch_tls_eof, conn_info); + EVENT_ADD(conn_info->event); + + DPRINTF(D_TLS, "TLS connection established.\n"); + ST_CHANGE(conn_info->state, ST_TLS_EST); + + send_queue(0, 0, get_f_by_conninfo(conn_info)); + RESTORE_SIGNALS(omask); +} + +/* + * establish TLS connection + */ +bool +tls_connect(struct tls_conn_settings *conn_info) +{ + struct addrinfo hints, *res, *res1; + int error, rc, sock; + const int one = 1; + char buf[MAXLINE]; + SSL *ssl = NULL; + + DPRINTF((D_TLS|D_CALL), "tls_connect(conn_info@%p)\n", conn_info); + assert(conn_info->state == ST_NONE); + + if(!tls_opt.global_TLS_CTX) + return false; + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_protocol = 0; + hints.ai_flags = AI_CANONNAME; + error = getaddrinfo(conn_info->hostname, + (conn_info->port ? conn_info->port : "syslog-tls"), &hints, &res); + if (error) { + logerror("%s", gai_strerror(error)); + return false; + } + + sock = -1; + for (res1 = res; res1; res1 = res1->ai_next) { + if ((sock = socket(res1->ai_family, res1->ai_socktype, + res1->ai_protocol)) == -1) { + DPRINTF(D_NET, "Unable to open socket.\n"); + continue; + } + if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, + &one, sizeof(one)) == -1) { + DPRINTF(D_NET, "Unable to setsockopt(): %s\n", + strerror(errno)); + } + if (connect(sock, res1->ai_addr, res1->ai_addrlen) == -1) { + DPRINTF(D_NET, "Unable to connect() to %s: %s\n", + res1->ai_canonname, strerror(errno)); + close(sock); + sock = -1; + continue; + } + ST_CHANGE(conn_info->state, ST_TCP_EST); + + if (!(ssl = SSL_new(tls_opt.global_TLS_CTX))) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + DPRINTF(D_TLS, "Unable to establish TLS: %s\n", buf); + close(sock); + sock = -1; + ST_CHANGE(conn_info->state, ST_NONE); + continue; + } + if (!SSL_set_fd(ssl, sock)) { + ERR_error_string_n(ERR_get_error(), buf, sizeof(buf)); + DPRINTF(D_TLS, "Unable to connect TLS to socket: %s\n", + buf); + FREE_SSL(ssl); + close(sock); + sock = -1; + ST_CHANGE(conn_info->state, ST_NONE); + continue; + } + + SSL_set_app_data(ssl, conn_info); + SSL_set_connect_state(ssl); + while ((rc = ERR_get_error()) != 0) { + ERR_error_string_n(rc, buf, sizeof(buf)); + DPRINTF(D_TLS, "Found SSL error in queue: %s\n", buf); + } + errno = 0; /* reset to be sure we get the right one later on */ + + if ((fcntl(sock, F_SETFL, O_NONBLOCK)) == -1) { + DPRINTF(D_NET, "Unable to fcntl(sock, O_NONBLOCK): " + "%s\n", strerror(errno)); + } + + /* now we have a TCP connection, so assume we can + * use that and do not have to try another res */ + conn_info->sslptr = ssl; + + assert(conn_info->state == ST_TCP_EST); + assert(conn_info->event); + assert(conn_info->retryevent); + + freeaddrinfo(res); + dispatch_SSL_connect(sock, 0, conn_info); + return true; + } + /* still no connection after for loop */ + DPRINTF((D_TLS|D_NET), "Unable to establish a TCP connection to %s\n", + conn_info->hostname); + freeaddrinfo(res); + + assert(conn_info->state == ST_NONE); + if (sock != -1) + close(sock); + if (ssl) { + SSL_shutdown(ssl); + SSL_free(ssl); + } + return false; +} + +int +tls_examine_error(const char *functionname, const SSL *ssl, + struct tls_conn_settings *tls_conn, const int rc) +{ + int ssl_error, err_error; + + ssl_error = SSL_get_error(ssl, rc); + DPRINTF(D_TLS, "%s returned rc %d and error %s: %s\n", functionname, + rc, SSL_ERRCODE[ssl_error], ERR_error_string(ssl_error, NULL)); + switch (ssl_error) { + case SSL_ERROR_WANT_READ: + return TLS_RETRY_READ; + case SSL_ERROR_WANT_WRITE: + return TLS_RETRY_WRITE; + case SSL_ERROR_SYSCALL: + DPRINTF(D_TLS, "SSL_ERROR_SYSCALL: "); + err_error = ERR_get_error(); + if ((rc == -1) && (err_error == 0)) { + DPRINTF(D_TLS, "socket I/O error: %s\n", + strerror(errno)); + } else if ((rc == 0) && (err_error == 0)) { + DPRINTF(D_TLS, "unexpected EOF from %s\n", + tls_conn ? tls_conn->hostname : NULL); + } else { + DPRINTF(D_TLS, "no further info\n"); + } + return TLS_PERM_ERROR; + case SSL_ERROR_ZERO_RETURN: + logerror("TLS connection closed by %s", + tls_conn ? tls_conn->hostname : NULL); + return TLS_PERM_ERROR; + case SSL_ERROR_SSL: + logerror("internal SSL error, error queue gives %s", + ERR_error_string(ERR_get_error(), NULL)); + return TLS_PERM_ERROR; + default: + break; + } + if (tls_conn) + tls_conn->errorcount++; + /* TODO: is this ever reached? */ + return TLS_TEMP_ERROR; +} + + +bool +parse_tls_destination(const char *p, struct filed *f, size_t linenum) +{ + const char *q; + + if ((*p++ != '@') || *p++ != '[') { + logerror("parse_tls_destination() on non-TLS action " + "in config line %zu", linenum); + return false; + } + + if (!(q = strchr(p, ']'))) { + logerror("Unterminated [ " + "in config line %zu", linenum); + return false; + } + + if (!(f->f_un.f_tls.tls_conn = + calloc(1, sizeof(*f->f_un.f_tls.tls_conn))) + || !(f->f_un.f_tls.tls_conn->event = allocev()) + || !(f->f_un.f_tls.tls_conn->retryevent = allocev())) { + if (f->f_un.f_tls.tls_conn) + free(f->f_un.f_tls.tls_conn->event); + free(f->f_un.f_tls.tls_conn); + logerror("Couldn't allocate memory for TLS config"); + return false; + } + /* default values */ + f->f_un.f_tls.tls_conn->x509verify = X509VERIFY_ALWAYS; + f->f_un.f_tls.tls_conn->reconnect = TLS_RECONNECT_SEC; + + if (!(copy_string(&(f->f_un.f_tls.tls_conn->hostname), p, q))) { + logerror("Unable to read TLS server name" + "in config line %zu", linenum); + free_tls_conn(f->f_un.f_tls.tls_conn); + return false; + } + p = ++q; + + if (*p == ':') { + p++; q++; + while (isalnum((unsigned char)*q)) + q++; + if (!(copy_string(&(f->f_un.f_tls.tls_conn->port), p, q))) { + logerror("Unable to read TLS port or service name" + " after ':' in config line %zu", linenum); + free_tls_conn(f->f_un.f_tls.tls_conn); + return false; + } + p = q; + } + /* allow whitespace for readability? */ + while (isblank((unsigned char)*p)) + p++; + if (*p == '(') { + p++; + while (*p != ')') { + if (copy_config_value_quoted("subject=\"", + &(f->f_un.f_tls.tls_conn->subject), &p) + || copy_config_value_quoted("fingerprint=\"", + &(f->f_un.f_tls.tls_conn->fingerprint), &p) + || copy_config_value_quoted("cert=\"", + &(f->f_un.f_tls.tls_conn->certfile), &p)) { + /* nothing */ + } else if (!strcmp(p, "verify=")) { + q = p += sizeof("verify=")-1; + /* "" are optional */ + if (*p == '\"') { p++; q++; } + while (isalpha((unsigned char)*q)) q++; + f->f_un.f_tls.tls_conn->x509verify = + getVerifySetting(p); + if (*q == '\"') q++; /* "" are optional */ + p = q; + } else { + logerror("unknown keyword %s " + "in config line %zu", p, linenum); + } + while (*p == ',' || isblank((unsigned char)*p)) + p++; + if (*p == '\0') { + logerror("unterminated (" + "in config line %zu", linenum); + } + } + } + + DPRINTF((D_TLS|D_PARSE), + "got TLS config: host %s, port %s, " + "subject: %s, certfile: %s, fingerprint: %s\n", + f->f_un.f_tls.tls_conn->hostname, + f->f_un.f_tls.tls_conn->port, + f->f_un.f_tls.tls_conn->subject, + f->f_un.f_tls.tls_conn->certfile, + f->f_un.f_tls.tls_conn->fingerprint); + return true; +} + +/* + * Dispatch routine (triggered by timer) to reconnect to a lost TLS server + */ +/*ARGSUSED*/ +void +tls_reconnect(int fd, short event, void *arg) +{ + struct tls_conn_settings *conn_info = (struct tls_conn_settings *) arg; + + DPRINTF((D_TLS|D_CALL|D_EVENT), "tls_reconnect(conn_info@%p, " + "server %s)\n", conn_info, conn_info->hostname); + if (conn_info->sslptr) { + conn_info->shutdown = true; + free_tls_sslptr(conn_info); + } + assert(conn_info->state == ST_NONE); + + if (!tls_connect(conn_info)) { + if (conn_info->reconnect > TLS_RECONNECT_GIVEUP) { + logerror("Unable to connect to TLS server %s, " + "giving up now", conn_info->hostname); + message_queue_freeall(get_f_by_conninfo(conn_info)); + /* free the message queue; but do not free the + * tls_conn_settings nor change the f_type to F_UNUSED. + * that way one can still trigger a reconnect + * with a SIGUSR1 + */ + } else { + struct timeval tv; + logerror("Unable to connect to TLS server %s, " + "try again in %d sec", conn_info->hostname, + conn_info->reconnect); + tv.tv_sec = conn_info->reconnect; + tv.tv_usec = 0; + schedule_event(&conn_info->event, &tv, + tls_reconnect, conn_info); + TLS_RECONNECT_BACKOFF(conn_info->reconnect); + } + } else { + assert(conn_info->state == ST_TLS_EST + || conn_info->state == ST_CONNECTING + || conn_info->state == ST_NONE); + } +} +/* + * Dispatch routine for accepting TLS connections. + * Has to be idempotent in case of TLS_RETRY (~ EAGAIN), + * so we can continue a slow handshake. + */ +/*ARGSUSED*/ +void +dispatch_tls_accept(int fd, short event, void *arg) +{ + struct tls_conn_settings *conn_info = (struct tls_conn_settings *) arg; + int rc, error; + struct TLS_Incoming_Conn *tls_in; + sigset_t newmask, omask; + + DPRINTF((D_TLS|D_CALL), + "dispatch_tls_accept(conn_info@%p, fd %d)\n", conn_info, fd); + assert(conn_info->event); + assert(conn_info->retryevent); + BLOCK_SIGNALS(omask, newmask); + + ST_CHANGE(conn_info->state, ST_ACCEPTING); + rc = SSL_accept(conn_info->sslptr); + if (0 >= rc) { + error = tls_examine_error("SSL_accept()", + conn_info->sslptr, NULL, rc); + switch (error) { + case TLS_RETRY_READ: + event_set(conn_info->retryevent, fd, EV_READ, + dispatch_tls_accept, conn_info); + EVENT_ADD(conn_info->retryevent); + break; + case TLS_RETRY_WRITE: + event_set(conn_info->retryevent, fd, EV_WRITE, + dispatch_tls_accept, conn_info); + EVENT_ADD(conn_info->retryevent); + break; + default: /* should not happen */ + free_tls_conn(conn_info); + break; + } + RESTORE_SIGNALS(omask); + return; + } + /* else */ + CALLOC(tls_in, sizeof(*tls_in)); + CALLOC(tls_in->inbuf, (size_t)TLS_MIN_LINELENGTH); + + tls_in->tls_conn = conn_info; + tls_in->socket = SSL_get_fd(conn_info->sslptr); + tls_in->inbuf[0] = '\0'; + tls_in->inbuflen = TLS_MIN_LINELENGTH; + SLIST_INSERT_HEAD(&TLS_Incoming_Head, tls_in, entries); + + event_set(conn_info->event, tls_in->socket, EV_READ | EV_PERSIST, + dispatch_tls_read, tls_in); + EVENT_ADD(conn_info->event); + ST_CHANGE(conn_info->state, ST_TLS_EST); + + loginfo("established TLS connection from %s with certificate " + "%s (%s)", conn_info->hostname, conn_info->subject, + conn_info->fingerprint); + RESTORE_SIGNALS(omask); + /* + * We could also listen to EOF kevents -- but I do not think + * that would be useful, because we still had to read() the buffer + * before closing the socket. + */ +} + +/* + * Dispatch routine for accepting TCP connections and preparing + * the tls_conn_settings object for a following SSL_accept(). + */ +/*ARGSUSED*/ +void +dispatch_socket_accept(int fd, short event, void *ev) +{ +#ifdef LIBWRAP + struct request_info req; +#endif + struct sockaddr_storage frominet; + socklen_t addrlen; + int newsock, rc; + sigset_t newmask, omask; + SSL *ssl; + struct tls_conn_settings *conn_info; + char hbuf[NI_MAXHOST]; + char *peername; + + DPRINTF((D_TLS|D_NET), "incoming TCP connection\n"); + if (!tls_opt.global_TLS_CTX) { + logerror("global_TLS_CTX not initialized!"); + return; + } + + BLOCK_SIGNALS(omask, newmask); + addrlen = sizeof(frominet); + if ((newsock = accept(fd, (struct sockaddr *)&frominet, + &addrlen)) == -1) { + logerror("Error in accept(): %s", strerror(errno)); + RESTORE_SIGNALS(omask); + return; + } + /* TODO: do we want an IP or a hostname? maybe even both? */ + if ((rc = getnameinfo((struct sockaddr *)&frominet, addrlen, + hbuf, sizeof(hbuf), NULL, 0, NI_NUMERICHOST|NI_NUMERICSERV)) != 0) { + DPRINTF(D_NET, "could not get peername: %s", gai_strerror(rc)); + peername = NULL; + } + else { + size_t len = strlen(hbuf) + 1; + MALLOC(peername, len); + (void)memcpy(peername, hbuf, len); + } + +#ifdef LIBWRAP + request_init(&req, RQ_DAEMON, appname, RQ_FILE, newsock, NULL); + fromhost(&req); + if (!hosts_access(&req)) { + logerror("access from %s denied by hosts_access", peername); + shutdown(newsock, SHUT_RDWR); + close(newsock); + RESTORE_SIGNALS(omask); + return; + } +#endif + + if ((fcntl(newsock, F_SETFL, O_NONBLOCK)) == -1) { + DPRINTF(D_NET, "Unable to fcntl(sock, O_NONBLOCK): %s\n", + strerror(errno)); + } + + if (!(ssl = SSL_new(tls_opt.global_TLS_CTX))) { + DPRINTF(D_TLS, "Unable to establish TLS: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + close(newsock); + RESTORE_SIGNALS(omask); + return; + } + if (!SSL_set_fd(ssl, newsock)) { + DPRINTF(D_TLS, "Unable to connect TLS to socket %d: %s\n", + newsock, ERR_error_string(ERR_get_error(), NULL)); + SSL_free(ssl); + close(newsock); + RESTORE_SIGNALS(omask); + return; + } + + if (!(conn_info = calloc(1, sizeof(*conn_info))) + || !(conn_info->event = allocev()) + || !(conn_info->retryevent = allocev())) { + if (conn_info) + free(conn_info->event); + free(conn_info); + SSL_free(ssl); + close(newsock); + logerror("Unable to allocate memory to accept incoming " + "TLS connection from %s", peername); + RESTORE_SIGNALS(omask); + return; + } + ST_CHANGE(conn_info->state, ST_NONE); + /* store connection details inside ssl object, used to verify + * cert and immediately match against hostname */ + conn_info->hostname = peername; + conn_info->sslptr = ssl; + conn_info->x509verify = getVerifySetting(tls_opt.x509verify); + conn_info->incoming = true; + SSL_set_app_data(ssl, conn_info); + SSL_set_accept_state(ssl); + + assert(conn_info->event); + assert(conn_info->retryevent); + + ST_CHANGE(conn_info->state, ST_TCP_EST); + DPRINTF(D_TLS, "socket connection from %s accept()ed with fd %d, " + "calling SSL_accept()...\n", peername, newsock); + dispatch_tls_accept(newsock, 0, conn_info); + RESTORE_SIGNALS(omask); +} + +/* + * Dispatch routine to read from outgoing TCP/TLS sockets. + * + * I do not know if libevent can tell us the difference + * between available data and an EOF. But it does not matter + * because there should not be any incoming data. + * So we close the connection either because the peer closed its + * side or because the peer broke the protocol by sending us stuff ;-) + */ +void +dispatch_tls_eof(int fd, short event, void *arg) +{ + struct tls_conn_settings *conn_info = (struct tls_conn_settings *) arg; + sigset_t newmask, omask; + struct timeval tv; + + BLOCK_SIGNALS(omask, newmask); + DPRINTF((D_TLS|D_EVENT|D_CALL), "dispatch_eof_tls(%d, %d, %p)\n", + fd, event, arg); + assert(conn_info->state == ST_TLS_EST); + ST_CHANGE(conn_info->state, ST_EOF); + DEL_EVENT(conn_info->event); + + free_tls_sslptr(conn_info); + + /* this overwrites the EV_READ event */ + tv.tv_sec = conn_info->reconnect; + tv.tv_usec = 0; + schedule_event(&conn_info->event, &tv, tls_reconnect, conn_info); + TLS_RECONNECT_BACKOFF(conn_info->reconnect); + RESTORE_SIGNALS(omask); +} + +/* + * Dispatch routine to read from TCP/TLS sockets. + * NB: This gets called when the TCP socket has data available, thus + * we can call SSL_read() on it. But that does not mean the SSL buffer + * holds a complete record and SSL_read() lets us read any data now. + */ +/*ARGSUSED*/ +void +dispatch_tls_read(int fd_lib, short event, void *arg) +{ + struct TLS_Incoming_Conn *c = (struct TLS_Incoming_Conn *) arg; + int fd = c->socket; + int error; + int rc; + sigset_t newmask, omask; + bool retrying; + + BLOCK_SIGNALS(omask, newmask); + DPRINTF((D_TLS|D_EVENT|D_CALL), "active TLS socket %d\n", fd); + DPRINTF(D_TLS, "calling SSL_read(%p, %p, %zu)\n", c->tls_conn->sslptr, + &(c->inbuf[c->read_pos]), c->inbuflen - c->read_pos); + retrying = (c->tls_conn->state == ST_READING); + ST_CHANGE(c->tls_conn->state, ST_READING); + rc = SSL_read(c->tls_conn->sslptr, &(c->inbuf[c->read_pos]), + c->inbuflen - c->read_pos); + if (rc <= 0) { + error = tls_examine_error("SSL_read()", c->tls_conn->sslptr, + c->tls_conn, rc); + switch (error) { + case TLS_RETRY_READ: + /* normal event loop will call us again */ + break; + case TLS_RETRY_WRITE: + if (!retrying) + event_del(c->tls_conn->event); + event_set(c->tls_conn->retryevent, fd, + EV_WRITE, dispatch_tls_read, c); + EVENT_ADD(c->tls_conn->retryevent); + RESTORE_SIGNALS(omask); + return; + case TLS_TEMP_ERROR: + if (c->tls_conn->errorcount < TLS_MAXERRORCOUNT) + break; + /* FALLTHROUGH */ + case TLS_PERM_ERROR: + /* there might be data in the inbuf, so only + * mark for closing after message retrieval */ + c->closenow = true; + break; + default: + break; + } + } else { + DPRINTF(D_TLS, "SSL_read() returned %d\n", rc); + c->errorcount = 0; + c->read_pos += rc; + } + if (retrying) + EVENT_ADD(c->tls_conn->event); + tls_split_messages(c); + if (c->closenow) { + free_tls_conn(c->tls_conn); + FREEPTR(c->inbuf); + SLIST_REMOVE(&TLS_Incoming_Head, c, TLS_Incoming_Conn, entries); + free(c); + } else + ST_CHANGE(c->tls_conn->state, ST_TLS_EST); + RESTORE_SIGNALS(omask); +} + +/* moved message splitting out of dispatching function. + * now we can call it recursively. + * + * TODO: the code for oversized messages still needs testing, + * especially for the skipping case. + */ +void +tls_split_messages(struct TLS_Incoming_Conn *c) +{ +/* define only to make it better readable */ +#define MSG_END_OFFSET (c->cur_msg_start + c->cur_msg_len) + size_t offset = 0; + size_t msglen = 0; + char *newbuf; + char buf_char; + + DPRINTF((D_TLS|D_CALL|D_DATA), "tls_split_messages() -- " + "incoming status is msg_start %zu, msg_len %zu, pos %zu\n", + c->cur_msg_start, c->cur_msg_len, c->read_pos); + + if (!c->read_pos) + return; + + if (c->dontsave && c->read_pos < MSG_END_OFFSET) { + c->cur_msg_len -= c->read_pos; + c->read_pos = 0; + } else if (c->dontsave && c->read_pos == MSG_END_OFFSET) { + c->cur_msg_start = c->cur_msg_len = c->read_pos = 0; + c->dontsave = false; + } else if (c->dontsave && c->read_pos > MSG_END_OFFSET) { + /* move remaining input to start of buffer */ + DPRINTF(D_DATA, "move inbuf of length %zu by %zu chars\n", + c->read_pos - (MSG_END_OFFSET), + MSG_END_OFFSET); + memmove(&c->inbuf[0], + &c->inbuf[MSG_END_OFFSET], + c->read_pos - (MSG_END_OFFSET)); + c->read_pos -= (MSG_END_OFFSET); + c->cur_msg_start = c->cur_msg_len = 0; + c->dontsave = false; + } + if (c->read_pos < MSG_END_OFFSET) { + return; + } + + /* read length prefix, always at start of buffer */ + while (isdigit((unsigned char)c->inbuf[offset]) + && offset < c->read_pos) { + msglen *= 10; + msglen += c->inbuf[offset] - '0'; + offset++; + } + if (offset == c->read_pos) { + /* next invocation will have more data */ + return; + } + if (c->inbuf[offset] == ' ') { + c->cur_msg_len = msglen; + c->cur_msg_start = offset + 1; + if (MSG_END_OFFSET+1 > c->inbuflen) { /* +1 for the '\0' */ + newbuf = realloc(c->inbuf, MSG_END_OFFSET+1); + if (newbuf) { + DPRINTF(D_DATA, "Reallocated inbuf\n"); + c->inbuflen = MSG_END_OFFSET+1; + c->inbuf = newbuf; + } else { + logerror("Couldn't reallocate buffer, " + "will skip this message"); + c->dontsave = true; + c->cur_msg_len -= c->read_pos; + c->cur_msg_start = 0; + c->read_pos = 0; + } + } + } else { + /* found non-digit in prefix */ + /* Question: would it be useful to skip this message and + * try to find next message by looking for its beginning? + * IMHO not. + */ + logerror("Unable to handle TLS length prefix. " + "Protocol error? Closing connection now."); + /* only set flag -- caller has to close then */ + c->closenow = true; + return; + } + /* read one syslog message */ + if (c->read_pos >= MSG_END_OFFSET) { + /* process complete msg */ + assert(MSG_END_OFFSET+1 <= c->inbuflen); + /* message in c->inbuf is not NULL-terminated, + * so this avoids a complete copy */ + buf_char = c->inbuf[MSG_END_OFFSET]; + c->inbuf[MSG_END_OFFSET] = '\0'; + printline(c->tls_conn->hostname, &c->inbuf[c->cur_msg_start], + RemoteAddDate ? ADDDATE : 0); + c->inbuf[MSG_END_OFFSET] = buf_char; + + if (MSG_END_OFFSET == c->read_pos) { + /* no unprocessed data in buffer --> reset to empty */ + c->cur_msg_start = c->cur_msg_len = c->read_pos = 0; + } else { + /* move remaining input to start of buffer */ + DPRINTF(D_DATA, "move inbuf of length %zu by %zu " + "chars\n", c->read_pos - (MSG_END_OFFSET), + MSG_END_OFFSET); + memmove(&c->inbuf[0], &c->inbuf[MSG_END_OFFSET], + c->read_pos - (MSG_END_OFFSET)); + c->read_pos -= (MSG_END_OFFSET); + c->cur_msg_start = c->cur_msg_len = 0; + } + } + + /* shrink inbuf if too large */ + if ((c->inbuflen > TLS_PERSIST_LINELENGTH) + && (c->read_pos < TLS_LARGE_LINELENGTH)) { + newbuf = realloc(c->inbuf, TLS_LARGE_LINELENGTH); + if (newbuf) { + DPRINTF(D_DATA, "Shrink inbuf\n"); + c->inbuflen = TLS_LARGE_LINELENGTH; + c->inbuf = newbuf; + } else { + logerror("Couldn't shrink inbuf"); + /* no change necessary */ + } + } + DPRINTF(D_DATA, "return with status: msg_start %zu, msg_len %zu, " + "pos %zu\n", c->cur_msg_start, c->cur_msg_len, c->read_pos); + + /* try to read another message */ + if (c->read_pos > 10) + tls_split_messages(c); + return; +} + +/* + * wrapper for dispatch_tls_send() + * + * send one line with tls + * f has to be of typ TLS + * + * returns false if message cannot be sent right now, + * caller is responsible to enqueue it + * returns true if message passed to dispatch_tls_send() + * delivery is not garantueed, but likely + */ +#define DEBUG_LINELENGTH 40 +bool +tls_send(struct filed *f, char *line, size_t len, struct buf_queue *qentry) +{ + struct tls_send_msg *smsg; + + DPRINTF((D_TLS|D_CALL), "tls_send(f=%p, line=\"%.*s%s\", " + "len=%zu) to %sconnected dest.\n", f, + (int)(len > DEBUG_LINELENGTH ? DEBUG_LINELENGTH : len), + line, (len > DEBUG_LINELENGTH ? "..." : ""), + len, f->f_un.f_tls.tls_conn->sslptr ? "" : "un"); + + if(f->f_un.f_tls.tls_conn->state == ST_TLS_EST) { + /* send now */ + if (!(smsg = calloc(1, sizeof(*smsg)))) { + logerror("Unable to allocate memory, drop message"); + return false; + } + smsg->f = f; + smsg->line = line; + smsg->linelen = len; + (void)NEWREF(qentry->msg); + smsg->qentry = qentry; + DPRINTF(D_DATA, "now sending line: \"%.*s\"\n", + (int)smsg->linelen, smsg->line); + dispatch_tls_send(0, 0, smsg); + return true; + } else { + /* other socket operation active, send later */ + DPRINTF(D_DATA, "connection not ready to send: \"%.*s\"\n", + (int)len, line); + return false; + } +} + +/*ARGSUSED*/ +void +dispatch_tls_send(int fd, short event, void *arg) +{ + struct tls_send_msg *smsg = (struct tls_send_msg *) arg; + struct tls_conn_settings *conn_info = smsg->f->f_un.f_tls.tls_conn; + struct filed *f = smsg->f; + int rc, error; + sigset_t newmask, omask; + bool retrying; + struct timeval tv; + + BLOCK_SIGNALS(omask, newmask); + DPRINTF((D_TLS|D_CALL), "dispatch_tls_send(f=%p, buffer=%p, " + "line@%p, len=%zu, offset=%zu) to %sconnected dest.\n", + smsg->f, smsg->qentry->msg, smsg->line, + smsg->linelen, smsg->offset, + conn_info->sslptr ? "" : "un"); + assert(conn_info->state == ST_TLS_EST + || conn_info->state == ST_WRITING); + + retrying = (conn_info->state == ST_WRITING); + ST_CHANGE(conn_info->state, ST_WRITING); + rc = SSL_write(conn_info->sslptr, + (smsg->line + smsg->offset), + (smsg->linelen - smsg->offset)); + if (0 >= rc) { + error = tls_examine_error("SSL_write()", + conn_info->sslptr, + conn_info, rc); + switch (error) { + case TLS_RETRY_READ: + /* collides with eof event */ + if (!retrying) + event_del(conn_info->event); + event_set(conn_info->retryevent, fd, EV_READ, + dispatch_tls_send, smsg); + RETRYEVENT_ADD(conn_info->retryevent); + break; + case TLS_RETRY_WRITE: + event_set(conn_info->retryevent, fd, EV_WRITE, + dispatch_tls_send, smsg); + RETRYEVENT_ADD(conn_info->retryevent); + break; + case TLS_PERM_ERROR: + /* no need to check active events */ + free_tls_send_msg(smsg); + free_tls_sslptr(conn_info); + tv.tv_sec = conn_info->reconnect; + tv.tv_usec = 0; + schedule_event(&conn_info->event, &tv, + tls_reconnect, conn_info); + TLS_RECONNECT_BACKOFF(conn_info->reconnect); + break; + default: + break; + } + RESTORE_SIGNALS(omask); + return; + } else if ((size_t)rc < smsg->linelen) { + DPRINTF((D_TLS|D_DATA), "TLS: SSL_write() wrote %d out of %zu " + "bytes\n", rc, (smsg->linelen - smsg->offset)); + smsg->offset += rc; + /* try again */ + if (retrying) + EVENT_ADD(conn_info->event); + dispatch_tls_send(0, 0, smsg); + return; + } else if ((size_t)rc == (smsg->linelen - smsg->offset)) { + DPRINTF((D_TLS|D_DATA), "TLS: SSL_write() complete\n"); + ST_CHANGE(conn_info->state, ST_TLS_EST); + free_tls_send_msg(smsg); + send_queue(0, 0, f); + + } else { + /* should not be reached */ + /*LINTED constcond */ + assert(0); + DPRINTF((D_TLS|D_DATA), "unreachable code after SSL_write()\n"); + ST_CHANGE(conn_info->state, ST_TLS_EST); + free_tls_send_msg(smsg); + send_queue(0, 0, f); + } + if (retrying && conn_info->event->ev_events) + EVENT_ADD(conn_info->event); + RESTORE_SIGNALS(omask); +} + +/* + * Close a SSL connection and its queue and its tls_conn. + */ +void +free_tls_conn(struct tls_conn_settings *conn_info) +{ + DPRINTF(D_MEM, "free_tls_conn(conn_info@%p) with sslptr@%p\n", + conn_info, conn_info->sslptr); + + if (conn_info->sslptr) { + conn_info->shutdown = true; + free_tls_sslptr(conn_info); + } + assert(conn_info->state == ST_NONE); + + FREEPTR(conn_info->port); + FREEPTR(conn_info->subject); + FREEPTR(conn_info->hostname); + FREEPTR(conn_info->certfile); + FREEPTR(conn_info->fingerprint); + DEL_EVENT(conn_info->event); + DEL_EVENT(conn_info->retryevent); + FREEPTR(conn_info->event); + FREEPTR(conn_info->retryevent); + FREEPTR(conn_info); + DPRINTF(D_MEM2, "free_tls_conn(conn_info@%p) returns\n", conn_info); +} + +/* + * Dispatch routine for non-blocking TLS shutdown + */ +/*ARGSUSED*/ +void +dispatch_SSL_shutdown(int fd, short event, void *arg) +{ + struct tls_conn_settings *conn_info = (struct tls_conn_settings *) arg; + int rc, error; + sigset_t newmask, omask; + bool retrying; + + BLOCK_SIGNALS(omask, newmask); + DPRINTF((D_TLS|D_CALL), + "dispatch_SSL_shutdown(conn_info@%p, fd %d)\n", conn_info, fd); + retrying = ((conn_info->state == ST_CLOSING0) + || (conn_info->state == ST_CLOSING1) + || (conn_info->state == ST_CLOSING2)); + if (!retrying) + ST_CHANGE(conn_info->state, ST_CLOSING0); + + rc = SSL_shutdown(conn_info->sslptr); + if (rc == 1) { /* shutdown complete */ + DPRINTF((D_TLS|D_NET), "Closed TLS connection to %s\n", + conn_info->hostname); + ST_CHANGE(conn_info->state, ST_TCP_EST); /* check this */ + conn_info->accepted = false; + /* closing TCP comes below */ + } else if (rc == 0) { /* unidirectional, now call a 2nd time */ + /* problem: when connecting as a client to rsyslogd this + * loops and I keep getting rc == 0 + * maybe I hit this bug? + * http://www.mail-archive.com/openssl-dev@openssl.org/msg24105.html + * + * anyway, now I use three closing states to make sure I abort + * after two rc = 0. + */ + if (conn_info->state == ST_CLOSING0) { + ST_CHANGE(conn_info->state, ST_CLOSING1); + dispatch_SSL_shutdown(fd, 0, conn_info); + } else if (conn_info->state == ST_CLOSING1) { + ST_CHANGE(conn_info->state, ST_CLOSING2); + dispatch_SSL_shutdown(fd, 0, conn_info); + } else if (conn_info->state == ST_CLOSING2) { + /* abort shutdown, jump to close TCP below */ + } else + DPRINTF(D_TLS, "Unexpected connection state %d\n", + conn_info->state); + /* and abort here too*/ + } else if (rc == -1 && conn_info->shutdown ) { + (void)tls_examine_error("SSL_shutdown()", + conn_info->sslptr, NULL, rc); + DPRINTF((D_TLS|D_NET), "Ignore error in SSL_shutdown()" + " and force connection shutdown."); + ST_CHANGE(conn_info->state, ST_TCP_EST); + conn_info->accepted = false; + } else if (rc == -1 && !conn_info->shutdown ) { + error = tls_examine_error("SSL_shutdown()", + conn_info->sslptr, NULL, rc); + switch (error) { + case TLS_RETRY_READ: + if (!retrying) + event_del(conn_info->event); + event_set(conn_info->retryevent, fd, EV_READ, + dispatch_SSL_shutdown, conn_info); + EVENT_ADD(conn_info->retryevent); + RESTORE_SIGNALS(omask); + return; + case TLS_RETRY_WRITE: + if (!retrying) + event_del(conn_info->event); + event_set(conn_info->retryevent, fd, EV_WRITE, + dispatch_SSL_shutdown, conn_info); + EVENT_ADD(conn_info->retryevent); + RESTORE_SIGNALS(omask); + return; + default: + /* force close() on the TCP connection */ + ST_CHANGE(conn_info->state, ST_TCP_EST); + conn_info->accepted = false; + break; + } + } + if ((conn_info->state != ST_TLS_EST) + && (conn_info->state != ST_NONE) + && (conn_info->state != ST_CLOSING0) + && (conn_info->state != ST_CLOSING1)) { + int sock = SSL_get_fd(conn_info->sslptr); + + if (shutdown(sock, SHUT_RDWR) == -1) + logerror("Cannot shutdown socket"); + DEL_EVENT(conn_info->retryevent); + DEL_EVENT(conn_info->event); + + if (close(sock) == -1) + logerror("Cannot close socket"); + DPRINTF((D_TLS|D_NET), "Closed TCP connection to %s\n", + conn_info->hostname); + ST_CHANGE(conn_info->state, ST_NONE); + FREE_SSL(conn_info->sslptr); + } + RESTORE_SIGNALS(omask); +} + +/* + * Close a SSL object + */ +void +free_tls_sslptr(struct tls_conn_settings *conn_info) +{ + int sock; + DPRINTF(D_MEM, "free_tls_sslptr(conn_info@%p)\n", conn_info); + + if (!conn_info->sslptr) { + assert(conn_info->incoming == 1 + || conn_info->state == ST_NONE); + return; + } else { + sock = SSL_get_fd(conn_info->sslptr); + dispatch_SSL_shutdown(sock, 0, conn_info); + } +} + +/* write self-generated certificates */ +bool +write_x509files(EVP_PKEY *pkey, X509 *cert, + const char *keyfilename, const char *certfilename) +{ + FILE *certfile, *keyfile; + + if (!(umask(0177),(keyfile = fopen(keyfilename, "a")))) { + logerror("Unable to write to file \"%s\"", keyfilename); + return false; + } + if (!(umask(0122),(certfile = fopen(certfilename, "a")))) { + logerror("Unable to write to file \"%s\"", certfilename); + (void)fclose(keyfile); + return false; + } + if (!PEM_write_PrivateKey(keyfile, pkey, NULL, NULL, 0, NULL, NULL)) + logerror("Unable to write key to \"%s\"", keyfilename); + if (!X509_print_fp(certfile, cert) + || !PEM_write_X509(certfile, cert)) + logerror("Unable to write certificate to \"%s\"", + certfilename); + + (void)fclose(keyfile); + (void)fclose(certfile); + return true; +} + + +/* adds all local IP addresses as subjectAltNames to cert x. + * getifaddrs() should be quite portable among BSDs and Linux + * but if not available the whole function can simply be removed. + */ +bool +x509_cert_add_subjectAltName(X509 *cert, X509V3_CTX *ctx) +{ + struct ifaddrs *ifa = NULL, *ifp = NULL; + char ip[100]; + char subjectAltName[2048]; + int idx = 0; + socklen_t salen; + X509_EXTENSION *ext; +#ifdef notdef + STACK_OF(X509_EXTENSION) *extlist; + extlist = sk_X509_EXTENSION_new_null(); +#endif + + if (getifaddrs (&ifp) == -1) { + logerror("Unable to get list of local interfaces"); + return false; + } + + idx = snprintf(subjectAltName, sizeof(subjectAltName), + "DNS:%s", LocalFQDN); + + for (ifa = ifp; ifa; ifa = ifa->ifa_next) { + if(!ifa->ifa_addr) + continue; + + /* only IP4 and IP6 addresses, but filter loopbacks */ + if (ifa->ifa_addr->sa_family == AF_INET) { + struct sockaddr_in *addr = + (struct sockaddr_in *)ifa->ifa_addr; + if (addr->sin_addr.s_addr == htonl(INADDR_LOOPBACK)) + continue; + salen = sizeof(struct sockaddr_in); + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + struct in6_addr *addr6 = + &((struct sockaddr_in6 *)ifa->ifa_addr)->sin6_addr; + if (IN6_IS_ADDR_LOOPBACK(addr6)) + continue; + salen = sizeof(struct sockaddr_in6); + } else + continue; + + if (getnameinfo(ifa->ifa_addr, salen, ip, sizeof(ip), + NULL, 0, NI_NUMERICHOST)) { + continue; + } + + /* add IP to list */ + idx += snprintf(&subjectAltName[idx], + sizeof(subjectAltName)-idx, ", IP:%s", ip); + } + freeifaddrs (ifp); + + ext = X509V3_EXT_conf_nid(NULL, ctx, + NID_subject_alt_name, subjectAltName); + X509_add_ext(cert, ext, -1); + X509_EXTENSION_free(ext); + + return true; +} + +/* + * generates a private key and a X.509 certificate + */ +bool +mk_x509_cert(X509 **x509p, EVP_PKEY **pkeyp, int bits, int serial, int days) +{ + X509 *cert; + EVP_PKEY *pk; + DSA *dsa; + X509_NAME *name = NULL; + X509_EXTENSION *ex = NULL; + X509V3_CTX ctx; + + DPRINTF((D_CALL|D_TLS), "mk_x509_cert(%p, %p, %d, %d, %d)\n", + x509p, pkeyp, bits, serial, days); + + if (pkeyp && *pkeyp) + pk = *pkeyp; + else if ((pk = EVP_PKEY_new()) == NULL) { + DPRINTF(D_TLS, "EVP_PKEY_new() failed\n"); + return false; + } + + if (x509p && *x509p) + cert = *x509p; + else if ((cert = X509_new()) == NULL) { + DPRINTF(D_TLS, "X509_new() failed\n"); + return false; + } + + dsa = DSA_generate_parameters(bits, NULL, 0, + NULL, NULL, NULL, NULL); + if (!DSA_generate_key(dsa)) { + DPRINTF(D_TLS, "DSA_generate_key() failed\n"); + return false; + } + if (!EVP_PKEY_assign_DSA(pk, dsa)) { + DPRINTF(D_TLS, "EVP_PKEY_assign_DSA() failed\n"); + return false; + } + + X509_set_version(cert, 3); + ASN1_INTEGER_set(X509_get_serialNumber(cert), serial); + X509_gmtime_adj(X509_get_notBefore(cert), 0); + X509_gmtime_adj(X509_get_notAfter(cert), (long)60 * 60 * 24 * days); + + if (!X509_set_pubkey(cert, pk)) { + DPRINTF(D_TLS, "X509_set_pubkey() failed\n"); + return false; + } + + /* + * This function creates and adds the entry, working out the correct + * string type and performing checks on its length. Normally we'd check + * the return value for errors... + */ + name = X509_get_subject_name(cert); + /* + X509_NAME_add_entry_by_txt(name, "O", MBSTRING_ASC, + (unsigned char *)"The NetBSD Project", -1, -1, 0); + X509_NAME_add_entry_by_txt(name, "OU", MBSTRING_ASC, + (unsigned char *)"syslogd", -1, -1, 0); + */ + X509_NAME_add_entry_by_txt(name, "CN", MBSTRING_ASC, + (unsigned char *) LocalFQDN, -1, -1, 0); + X509_set_issuer_name(cert, name); + + /* + * Add extension using V3 code: we can set the config file as NULL + * because we wont reference any other sections. + */ + X509V3_set_ctx(&ctx, cert, cert, NULL, NULL, 0); + + ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_netscape_comment, + __UNCONST("auto-generated by the NetBSD syslogd")); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_netscape_ssl_server_name, + LocalFQDN); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_netscape_cert_type, + __UNCONST("server, client")); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_key_usage, + __UNCONST("keyAgreement, keyEncipherment, " + "nonRepudiation, digitalSignature")); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + ex = X509V3_EXT_conf_nid(NULL, &ctx, NID_basic_constraints, + __UNCONST("critical,CA:FALSE")); + X509_add_ext(cert, ex, -1); + X509_EXTENSION_free(ex); + + (void)x509_cert_add_subjectAltName(cert, &ctx); + + if (!X509_sign(cert, pk, EVP_dss1())) { + DPRINTF(D_TLS, "X509_sign() failed\n"); + return false; + } + if (X509_verify(cert, pk) != 1) { + DPRINTF(D_TLS, "X509_verify() failed\n"); + return false; + } + + *x509p = cert; + *pkeyp = pk; + return true; +} + +void +free_tls_send_msg(struct tls_send_msg *msg) +{ + if (!msg) { + DPRINTF((D_DATA), "invalid tls_send_msg_free(NULL)\n"); + return; + } + DELREF(msg->qentry->msg); + (void)message_queue_remove(msg->f, msg->qentry); + FREEPTR(msg->line); + FREEPTR(msg); +} +#endif /* !DISABLE_TLS */ diff --git a/usr.sbin/syslogd/tls.h b/usr.sbin/syslogd/tls.h new file mode 100644 index 000000000..9672f1ce2 --- /dev/null +++ b/usr.sbin/syslogd/tls.h @@ -0,0 +1,196 @@ +/* $NetBSD: tls.h,v 1.2 2008/11/07 07:36:38 minskim Exp $ */ + +/*- + * Copyright (c) 2008 The NetBSD Foundation, Inc. + * All rights reserved. + * + * This code is derived from software contributed to The NetBSD Foundation + * by Martin Schütte. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the NetBSD + * Foundation, Inc. and its contributors. + * 4. Neither the name of The NetBSD Foundation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +/* + * tls.h + * + */ +#ifndef _TLS_H +#define _TLS_H +#include +#include +#include +#include + +/* initial size for TLS inbuf, minimum prefix + linelength + * guaranteed to be accepted */ +#define TLS_MIN_LINELENGTH (2048 + 5) +/* usually the inbuf is enlarged as needed and then kept. + * if bigger than TLS_PERSIST_LINELENGTH, then shrink + * to TLS_LARGE_LINELENGTH immediately */ +#define TLS_LARGE_LINELENGTH 8192 +#define TLS_PERSIST_LINELENGTH 32768 + +/* timeout to call non-blocking TLS operations again */ +#define TLS_RETRY_EVENT_USEC 20000 + +/* reconnect to lost server after n sec (initial value) */ +#define TLS_RECONNECT_SEC 10 +/* backoff connection attempts */ +#define TLS_RECONNECT_BACKOFF_FACTOR 15/10 +#define TLS_RECONNECT_BACKOFF(x) (x) = (x) * TLS_RECONNECT_BACKOFF_FACTOR +/* abandon connection attempts after n sec + * This has to be <= 5h (with 10sec initial interval), + * otherwise a daily SIGHUP from newsylog will reset + * all timers and the giveup time will never be reached + * + * set here: 2h, reached after ca. 7h of reconnecting + */ +#define TLS_RECONNECT_GIVEUP 60*60*2 + +/* default algorithm for certificate fingerprints */ +#define DEFAULT_FINGERPRINT_ALG "sha-1" + +/* default X.509 files */ +#define DEFAULT_X509_CERTFILE "/etc/openssl/default.crt" +#define DEFAULT_X509_KEYFILE "/etc/openssl/default.key" + +/* options for peer certificate verification */ +#define X509VERIFY_ALWAYS 0 +#define X509VERIFY_IFPRESENT 1 +#define X509VERIFY_NONE 2 + +/* attributes for self-generated keys/certificates */ +#define TLS_GENCERT_BITS 1024 +#define TLS_GENCERT_SERIAL 1 +#define TLS_GENCERT_DAYS 5*365 + +/* TLS connection states */ +#define ST_NONE 0 +#define ST_TLS_EST 1 +#define ST_TCP_EST 2 +#define ST_CONNECTING 3 +#define ST_ACCEPTING 4 +#define ST_READING 5 +#define ST_WRITING 6 +#define ST_EOF 7 +#define ST_CLOSING0 8 +#define ST_CLOSING1 9 +#define ST_CLOSING2 10 + +/* backlog for listen */ +#define TLSBACKLOG 4 +/* close TLS connection after multiple 'soft' errors */ +#define TLS_MAXERRORCOUNT 4 + +/* + * holds TLS related settings for one connection to be + * included in the SSL object and available in callbacks + * + * Many fields have a slightly different semantic for + * incoming and outgoing connections: + * - for outgoing connections it contains the values from syslog.conf and + * the server's cert is checked against these values by check_peer_cert() + * - for incoming connections it is not used for checking, instead + * dispatch_tls_accept() fills in the connected hostname/port and + * check_peer_cert() fills in subject and fingerprint from the peer cert + */ +struct tls_conn_settings { + unsigned send_queue:1, /* currently sending buffer */ + errorcount:4, /* counter [0;TLS_MAXERRORCOUNT] */ + accepted:1, /* workaround cf. check_peer_cert*/ + shutdown:1, /* fast connection close on exit */ + x509verify:2, /* kind of validation needed */ + incoming:1, /* set if we are server */ + state:4; /* outgoing connection state */ + struct event *event; /* event for read/write activity */ + struct event *retryevent; /* event for retries */ + SSL *sslptr; /* active SSL object */ + char *hostname; /* hostname or IP we connect to */ + char *port; /* service name or port number */ + char *subject; /* configured hostname in cert */ + char *fingerprint; /* fingerprint of peer cert */ + char *certfile; /* filename of peer cert */ + unsigned reconnect; /* seconds between reconnects */ +}; + +/* argument struct only used for tls_send() */ +struct tls_send_msg { + struct filed *f; + struct buf_queue *qentry; + char *line; /* formatted message */ + size_t linelen; + size_t offset; /* in case of partial writes */ +}; + +/* return values for TLS_examine_error() */ +#define TLS_OK 0 /* no real problem, just ignore */ +#define TLS_RETRY_READ 1 /* just retry, non-blocking operation not finished yet */ +#define TLS_RETRY_WRITE 2 /* just retry, non-blocking operation not finished yet */ +#define TLS_TEMP_ERROR 4 /* recoverable error condition, but try again */ +#define TLS_PERM_ERROR 8 /* non-recoverable error condition, closed TLS and socket */ + +/* global TLS setup and utility */ +char *init_global_TLS_CTX(void); +struct socketEvent *socksetup_tls(const int, const char *, const char *); +int check_peer_cert(int, X509_STORE_CTX *); +int accept_cert(const char* , struct tls_conn_settings *, char *, char *); +int deny_cert(struct tls_conn_settings *, char *, char *); +bool read_certfile(X509 **, const char *); +bool write_x509files(EVP_PKEY *, X509 *, const char *, const char *); +bool mk_x509_cert(X509 **, EVP_PKEY **, int, int, int); +bool x509_cert_add_subjectAltName(X509 *, X509V3_CTX *); +int tls_examine_error(const char *, const SSL *, struct tls_conn_settings *, const int); + +bool get_fingerprint(const X509 *, char **, const char *); +bool get_commonname(X509 *, char **); +bool match_hostnames(X509 *, const char *, const char *); +bool match_fingerprint(const X509 *, const char *); +bool match_certfile(const X509 *, const char *); + +/* configuration & parsing */ +bool parse_tls_destination(const char *, struct filed *, size_t); +/* event callbacks */ +void dispatch_socket_accept(int, short, void *); +void dispatch_tls_accept(int, short, void *); +void dispatch_tls_read(int, short, void *); +void dispatch_tls_send(int, short, void *); +void dispatch_tls_eof(int, short, void *); +void dispatch_SSL_connect(int, short, void *); +void dispatch_SSL_shutdown(int, short, void *); +void dispatch_force_tls_reconnect(int, short, void *); + +bool tls_connect(struct tls_conn_settings *); +void tls_reconnect(int, short, void *); +bool tls_send(struct filed *, char *, size_t, struct buf_queue*); +void tls_split_messages(struct TLS_Incoming_Conn *); + +void free_tls_conn(struct tls_conn_settings *); +void free_tls_sslptr(struct tls_conn_settings *); +void free_tls_send_msg(struct tls_send_msg *); + +#endif /* !_TLS_H */