NetBSD/usr.sbin/syslogd/syslogd.c
christos c68a8e00e8 PR/47861: Fredrik Pettai: keep track of the address family for each socket
opened and don't send the a message to the wrong family type.
2013-05-27 23:15:51 +00:00

4757 lines
120 KiB
C

/* $NetBSD: syslogd.c,v 1.115 2013/05/27 23:15:51 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 <sys/cdefs.h>
#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.115 2013/05/27 23:15:51 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 "<n>" 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 <poll.h>
#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_TLS 8
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},
#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 "<ab><cd>"
*/
#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 <U+123456> */
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;
int fac, omask, prilev;
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);
omask = sigblock(sigmask(SIGHUP)|sigmask(SIGALRM));
/* 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))) {
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)) {
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_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_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;
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:
(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},
{"file_queue_size", &TypeInfo[F_FILE].queue_size_string},
{"pipe_queue_size", &TypeInfo[F_PIPE].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)
TypeInfo[i].queue_length = strtol(
TypeInfo[i].default_length_string, NULL, 10);
if (!TypeInfo[i].queue_size_string
|| dehumanize_number(TypeInfo[i].queue_size_string,
&TypeInfo[i].queue_size) == -1)
TypeInfo[i].queue_size = strtol(
TypeInfo[i].default_size_string, NULL, 10);
}
#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:
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];
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, 0)) < 0) {
f->f_type = F_UNUSED;
logerror("%s", p);
break;
}
if (isatty(f->f_file)) {
f->f_type = F_TTY;
if (strcmp(p, ctty) == 0)
f->f_type = F_CONSOLE;
if (fcntl(f->f_file, F_SETFL, O_NONBLOCK) == -1)
logerror("Warning: cannot change tty fd for"
" `%s' to non-blocking.", p);
} 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;
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, sizeof(len));
}
return hostname;
}
struct socketEvent *
socksetup(int af, const char *hostname)
{
struct addrinfo hints, *res, *r;
int error, maxs;
int on = 1;
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;
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;
}
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;
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, &ltime);
len += strftime(timestamp, TIMESTAMPBUFSIZE, "%FT%T", &ltime);
snprintf(&(timestamp[len]), frac_digits+2, ".%.*ld",
frac_digits, (long)tv.tv_usec);
len += frac_digits+1;
tzlen = strftime(&(timestamp[len]), TIMESTAMPBUFSIZE-len, "%z", &ltime);
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;
}