/*      $NetBSD: syslogd.c,v 1.147 2024/11/09 16:31:31 jschauma 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.147 2024/11/09 16:31:31 jschauma 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 <sys/stat.h>
#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

#define RCVBUFLEN       16384
int     buflen = RCVBUFLEN;
/*
* Intervals at which we flush out "message repeated" messages,
* in seconds after previous message is logged.  After each flush,
* we move to the next interval until we reach the largest.
*/
int     repeatinterval[] = { 30, 120, 600 };    /* # of secs before flush */
#define MAXREPEAT ((sizeof(repeatinterval) / sizeof(repeatinterval[0])) - 1)
#define REPEATTIME(f)   ((f)->f_time + repeatinterval[(f)->f_repeatcount])
#define BACKOFF(f)      { if ((size_t)(++(f)->f_repeatcount) > MAXREPEAT) \
                                (f)->f_repeatcount = MAXREPEAT; \
                       }

/* values for f_type */
#define F_UNUSED        0               /* unused entry */
#define F_FILE          1               /* regular file */
#define F_TTY           2               /* terminal */
#define F_CONSOLE       3               /* console terminal */
#define F_FORW          4               /* remote machine */
#define F_USERS         5               /* list of users */
#define F_WALL          6               /* everyone logged on */
#define F_PIPE          7               /* pipe to program */
#define F_FIFO          8               /* mkfifo(2) file */
#define F_TLS           9

struct TypeInfo {
       const char *name;
       char       *queue_length_string;
       const char *default_length_string;
       char       *queue_size_string;
       const char *default_size_string;
       int64_t     queue_length;
       int64_t     queue_size;
       int   max_msg_length;
} TypeInfo[] = {
       /* numeric values are set in init()
        * -1 in length/size or max_msg_length means infinite */
       {"UNUSED",  NULL,    "0", NULL,   "0", 0, 0,     0},
       {"FILE",    NULL, "1024", NULL,  "1M", 0, 0, 16384},
       {"TTY",     NULL,    "0", NULL,   "0", 0, 0,  1024},
       {"CONSOLE", NULL,    "0", NULL,   "0", 0, 0,  1024},
       {"FORW",    NULL,    "0", NULL,  "1M", 0, 0, 16384},
       {"USERS",   NULL,    "0", NULL,   "0", 0, 0,  1024},
       {"WALL",    NULL,    "0", NULL,   "0", 0, 0,  1024},
       {"PIPE",    NULL, "1024", NULL,  "1M", 0, 0, 16384},
       {"FIFO",    NULL, "1024", NULL,  "1M", 0, 0, 16384},
#ifndef DISABLE_TLS
       {"TLS",     NULL,   "-1", NULL, "16M", 0, 0, 16384}
#endif /* !DISABLE_TLS */
};

struct  filed *Files = NULL;
struct  filed consfile;

time_t  now;
int     Debug = D_NONE;         /* debug flag */
int     daemonized = 0;         /* we are not daemonized yet */
char    *LocalFQDN = NULL;             /* our FQDN */
char    *oldLocalFQDN = NULL;          /* our previous FQDN */
char    LocalHostName[MAXHOSTNAMELEN]; /* our hostname */
struct socketEvent *finet;      /* Internet datagram sockets and events */
int   *funix;                   /* Unix domain datagram sockets */
#ifndef DISABLE_TLS
struct socketEvent *TLS_Listen_Set; /* TLS/TCP sockets and events */
#endif /* !DISABLE_TLS */
int     Initialized = 0;        /* set when we have initialized ourselves */
int     ShuttingDown;           /* set when we die() */
int     MarkInterval = 20 * 60; /* interval between marks in seconds */
int     MarkSeq = 0;            /* mark sequence number */
int     SecureMode = 0;         /* listen only on unix domain socks */
int     UseNameService = 1;     /* make domain name queries */
int     NumForwards = 0;        /* number of forwarding actions in conf file */
char    **LogPaths;             /* array of pathnames to read messages from */
int     NoRepeat = 0;           /* disable "repeated"; log always */
int     RemoteAddDate = 0;      /* always add date to messages from network */
int     SyncKernel = 0;         /* write kernel messages synchronously */
int     UniquePriority = 0;     /* only log specified priority */
int     LogFacPri = 0;          /* put facility and priority in log messages: */
                               /* 0=no, 1=numeric, 2=names */
int     LogOverflow = 1;        /* 0=no, any other value = yes */
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").
                                */
bool    KernXlat = true;        /* translate kern.* -> user.* */
char    appname[]   = "syslogd";/* the APPNAME for own messages */
char   *include_pid;            /* include PID in own messages */
char    include_pid_buf[11];


/* init and setup */
void            usage(void) __attribute__((__noreturn__));
void            set_debug(const char *);
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);

static void setsockbuf(int, const char *);

/* for make_timestamp() */
char    timestamp[MAX_TIMESTAMPLEN + 1];
/*
* 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;
       char pfpath[PATH_MAX];

       /* should we set LC_TIME="C" to ensure correct timestamps&parsing? */
       (void)setlocale(LC_ALL, "");

       while ((ch = getopt(argc, argv, "b:B:d::knsSf:m:o:p:P:ru:g:t:TUvX")) != -1)
               switch(ch) {
               case 'b':
                       bindhostname = optarg;
                       break;
               case 'B':
                       buflen = atoi(optarg);
                       if (buflen < RCVBUFLEN)
                               buflen = RCVBUFLEN;
                       break;
               case 'd':               /* debug */
                       if (optarg != NULL) {
                               /*
                                * getopt passes as optarg everything
                                * after 'd' in -darg, manually accept
                                * -d=arg too.
                                */
                               if (optarg[0] == '=')
                                       ++optarg;
                       } else if (optind < argc) {
                               /*
                                * :: treats "-d ..." as missing
                                * optarg, so look ahead manually and
                                * pick up the next arg if it looks
                                * like one.
                                */
                               if (argv[optind][0] != '-') {
                                       optarg = argv[optind];
                                       ++optind;
                               }
                       }
                       set_debug(optarg);
                       break;
               case 'f':               /* configuration file */
                       ConfFile = optarg;
                       break;
               case 'g':
                       group = optarg;
                       if (*group == '\0')
                               usage();
                       break;
               case 'k':               /* pass-through (remote) kern.* */
                       KernXlat = false;
                       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;
               case 'X':
                       LogOverflow = 0;
                       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);
               }
               setsockbuf(funix[j], *pp);
               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, IETF_NUM_PRIVALUES>>3);
#endif

#ifdef __NetBSD_Version__
       if ((uid != 0) || (gid != 0)) {
               /* Create the pidfile here so we can chown it to the target
                * user/group and possibly report any error before daemonizing.
                * We then call pidfile(3) again to write the actual
                * daemon pid below.
                *
                * Note: this will likely leave the truncated pidfile in
                * place upon exit, since the effective user is unlikely
                * to have write permissions to _PATH_VARRUN. */
               if (pidfile(NULL)) {
                       logerror("Failed to create pidfile");
                       die(0, 0, NULL);
               }
               j = sizeof(pfpath);
               if (snprintf(pfpath, j, "%s%s.pid",
                                       _PATH_VARRUN, getprogname()) >= j) {
                       logerror("Pidfile path `%s' too long.", pfpath);
                       die(0, 0, NULL);
               }
               if (chown(pfpath, uid, gid) < 0) {
                       logerror("Failed to chown pidfile `%s` to `%d:%d`", pfpath, uid, gid);
                       die(0, 0, NULL);
               }
       }
#endif /* __NetBSD_Version__ */

       /*
        * 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__ */
       }

       include_pid = include_pid_buf;
       snprintf(include_pid_buf, sizeof(include_pid_buf), "%d", getpid());

       /*
        * Create the global kernel event descriptor.
        *
        * NOTE: We MUST do this after daemon(), because 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 [-dknrSsTUvX] [-B buffer_length] [-b bind_address]\n"
           "\t[-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);
}

static void
setsockbuf(int fd, const char *name)
{
       int curbuflen;
       socklen_t socklen = sizeof(buflen);

       if (getsockopt(fd, SOL_SOCKET, SO_RCVBUF, &curbuflen, &socklen) == -1) {
               logerror("getsockopt: SO_RCVBUF: `%s'", name);
               return;
       }
       if (curbuflen >= buflen)
               return;
       if (setsockopt(fd, SOL_SOCKET, SO_RCVBUF, &buflen, socklen) == -1) {
               logerror("setsockopt: SO_RCVBUF: `%s'", name);
               return;
       }
}

/*
* 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 &&
           (errno != ENOBUFS || LogOverflow))
       {
               /*
                * /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;
       }

#define SUN_PATHLEN(su) \
       ((su)->sun_len - (sizeof(*(su)) - sizeof((su)->sun_path)))

       DPRINTF((D_CALL|D_EVENT|D_NET), "Unix socket (%.*s) active (%d, %d %p)"
               " with linebuf@%p, size %zu)\n", (int)SUN_PATHLEN(&myname),
               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 &&
           (errno != ENOBUFS || LogOverflow))
       {
               logerror("recvfrom() unix `%.*s'",
                       (int)SUN_PATHLEN(&myname), 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 ||
                       (errno == ENOBUFS && LogOverflow == 0))))
               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 = make_timestamp(NULL, !BSDOutputFormat, 0);
       }

       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 = make_timestamp(NULL, !BSDOutputFormat, 0);
       }

       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 = strdup(hname);
                       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 = make_timestamp(NULL, !BSDOutputFormat, 0);
       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 (usually) 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 && KernXlat)
               pri = 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 = make_timestamp(NULL, !BSDOutputFormat, 0);
       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 required
                                * so replace it with current time
                                */
                               *to_buf = make_timestamp(NULL, false, 0);
                       }
                       return 2;
               }
               *to_buf = make_timestamp(NULL, false, 0);
               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, j;

               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 */
               j = i;
               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++, j++)
                       tsbuf[j] = from_buf[i]; /* copy TZ */
               if (from_buf[i] == ':') i++;    /* skip colon */
               for(; i < MAX_TIMESTAMPLEN && from_buf[i] != '\0'
                   && from_buf[i] != ' ' ; i++, j++)
                       tsbuf[j] = from_buf[i]; /* copy TZ */

               (void)memset(&parsed, 0, sizeof(parsed));
               (void)strptime(tsbuf, "%FT%T%z", &parsed);
               parsed.tm_isdst = -1;
               timeval = mktime(&parsed);

               *to_buf = 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;

               (void)memset(&parsed, 0, sizeof(parsed));
               parsed.tm_isdst = -1;
               DPRINTF(D_CALL, "check_timestamp(): convert BSD->ISO\n");
               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);
               *to_buf = make_timestamp(&timeval, true, 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) {
               char *h;        /* host to use for comparing */

               /* 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 */
               /* compare with host (which is supposedly more correct), */
               /* but fallback to recvhost if host is NULL */
               h = (buffer->host != NULL) ? buffer->host : buffer->recvhost;
               if (f->f_host != NULL && h != NULL) {
                       char shost[MAXHOSTNAMELEN + 1];

                       if (BSDOutputFormat) {
                               (void)strlcpy(shost, h, 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 lengths 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 = make_timestamp(NULL,
                           !BSDOutputFormat, 0);
                       buffer->pri = f->f_prevmsg->pri;
                       buffer->host = LocalFQDN;
                       buffer->prog = appname;
                       buffer->pid = include_pid;

               } else {
                       buffer = NEWREF(f->f_prevmsg);
               }
       }

       /* no syslog-sign messages to tty/console/... */
       if ((buffer->flags & SIGN_MSG)
           && ((f->f_type == F_UNUSED)
           || (f->f_type == F_TTY)
           || (f->f_type == F_CONSOLE)
           || (f->f_type == F_USERS)
           || (f->f_type == F_WALL)
           || (f->f_type == F_FIFO))) {
               DELREF(buffer);
               return;
       }

       /* buffering works only for few types */
       if (qentry
           && (f->f_type != F_TLS)
           && (f->f_type != F_PIPE)
           && (f->f_type != F_FILE)
           && (f->f_type != F_FIFO)) {
               errno = 0;
               logerror("Warning: unexpected message type %d in buffer",
                   f->f_type);
               DELREF(buffer);
               return;
       }

       if (!format_buffer(buffer, &line,
           &linelen, &msglen, &tlsprefixlen, &prilen)) {
               DPRINTF(D_CALL, "format_buffer() failed, skip message\n");
               DELREF(buffer);
               return;
       }
       /* assert maximum message length */
       if (TypeInfo[f->f_type].max_msg_length != -1
           && (size_t)TypeInfo[f->f_type].max_msg_length
           < linelen - tlsprefixlen - prilen) {
               linelen = TypeInfo[f->f_type].max_msg_length
                   + tlsprefixlen + prilen;
               DPRINTF(D_DATA, "truncating oversized message to %zu octets\n",
                   linelen);
       }

#ifndef DISABLE_SIGN
       /* keep state between appending the hash (before buffer is sent)
        * and possibly sending a SB (after buffer is sent): */
       /* get hash */
       if (!(buffer->flags & SIGN_MSG) && !qentry) {
               char *hash = NULL;
               struct signature_group_t *sg;

               if ((sg = sign_get_sg(buffer->pri, f)) != NULL) {
                       if (sign_msg_hash(line + tlsprefixlen, &hash))
                               newhash = sign_append_hash(hash, sg);
                       else
                               DPRINTF(D_SIGN,
                                       "Unable to hash line \"%s\"\n", line);
               }
       }
#endif /* !DISABLE_SIGN */

       /* set start and length of buffer and/or fill iovec */
       switch (f->f_type) {
       case F_UNUSED:
               /* nothing */
               break;
       case F_TLS:
               /* nothing, as TLS uses whole buffer to send */
               lineptr = line;
               len = linelen;
               break;
       case F_FORW:
               lineptr = line + tlsprefixlen;
               len = linelen - tlsprefixlen;
               break;
       case F_PIPE:
       case F_FIFO:
       case F_FILE:  /* fallthrough */
               if (f->f_flags & FFLAG_FULL) {
                       v->iov_base = line + tlsprefixlen;
                       v->iov_len = linelen - tlsprefixlen;
               } else {
                       v->iov_base = line + tlsprefixlen + prilen;
                       v->iov_len = linelen - tlsprefixlen - prilen;
               }
               ADDEV();
               v->iov_base = &crnl[1];
               v->iov_len = 1;
               ADDEV();
               break;
       case F_CONSOLE:
       case F_TTY:
               /* filter non-ASCII */
               p = line;
               while (*p) {
                       *p = FORCE2ASCII(*p);
                       p++;
               }
               v->iov_base = line + tlsprefixlen + prilen;
               v->iov_len = linelen - tlsprefixlen - prilen;
               ADDEV();
               v->iov_base = crnl;
               v->iov_len = 2;
               ADDEV();
               break;
       case F_WALL:
               v->iov_base = greetings;
               v->iov_len = snprintf(greetings, sizeof(greetings),
                   "\r\n\7Message from syslogd@%s at %s ...\r\n",
                   (buffer->host ? buffer->host : buffer->recvhost),
                   buffer->timestamp);
               ADDEV();
               /* FALLTHROUGH */
       case F_USERS: /* fallthrough */
               /* filter non-ASCII */
               p = line;
               while (*p) {
                       *p = FORCE2ASCII(*p);
                       p++;
               }
               v->iov_base = line + tlsprefixlen + prilen;
               v->iov_len = linelen - tlsprefixlen - prilen;
               ADDEV();
               v->iov_base = &crnl[1];
               v->iov_len = 1;
               ADDEV();
               break;
       }

       /* send */
       switch (f->f_type) {
       case F_UNUSED:
               DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name);
               break;

       case F_FORW:
               DPRINTF(D_MISC, "Logging to %s %s\n",
                   TypeInfo[f->f_type].name, f->f_un.f_forw.f_hname);
               udp_send(f, lineptr, len);
               break;

#ifndef DISABLE_TLS
       case F_TLS:
               DPRINTF(D_MISC, "Logging to %s %s\n",
                   TypeInfo[f->f_type].name,
                   f->f_un.f_tls.tls_conn->hostname);
               /* make sure every message gets queued once
                * it will be removed when sendmsg is sent and free()d */
               if (!qentry)
                       qentry = message_queue_add(f, NEWREF(buffer));
               (void)tls_send(f, lineptr, len, qentry);
               break;
#endif /* !DISABLE_TLS */

       case F_PIPE:
               DPRINTF(D_MISC, "Logging to %s %s\n",
                   TypeInfo[f->f_type].name, f->f_un.f_pipe.f_pname);
               if (f->f_un.f_pipe.f_pid == 0) {
                       /* (re-)open */
                       if ((f->f_file = p_open(f->f_un.f_pipe.f_pname,
                           &f->f_un.f_pipe.f_pid)) < 0) {
                               f->f_type = F_UNUSED;
                               logerror("%s", f->f_un.f_pipe.f_pname);
                               message_queue_freeall(f);
                               break;
                       } else if (!qentry) /* prevent recursion */
                               SEND_QUEUE(f);
               }
               if (writev(f->f_file, iov, v - iov) < 0) {
                       e = errno;
                       if (f->f_un.f_pipe.f_pid > 0) {
                               (void) close(f->f_file);
                               deadq_enter(f->f_un.f_pipe.f_pid,
                                   f->f_un.f_pipe.f_pname);
                       }
                       f->f_un.f_pipe.f_pid = 0;
                       /*
                        * If the error was EPIPE, then what is likely
                        * has happened is we have a command that is
                        * designed to take a single message line and
                        * then exit, but we tried to feed it another
                        * one before we reaped the child and thus
                        * reset our state.
                        *
                        * Well, now we've reset our state, so try opening
                        * the pipe and sending the message again if EPIPE
                        * was the error.
                        */
                       if (e == EPIPE) {
                               if ((f->f_file = p_open(f->f_un.f_pipe.f_pname,
                                    &f->f_un.f_pipe.f_pid)) < 0) {
                                       f->f_type = F_UNUSED;
                                       logerror("%s", f->f_un.f_pipe.f_pname);
                                       message_queue_freeall(f);
                                       break;
                               }
                               if (writev(f->f_file, iov, v - iov) < 0) {
                                       e = errno;
                                       if (f->f_un.f_pipe.f_pid > 0) {
                                           (void) close(f->f_file);
                                           deadq_enter(f->f_un.f_pipe.f_pid,
                                               f->f_un.f_pipe.f_pname);
                                       }
                                       f->f_un.f_pipe.f_pid = 0;
                                       error = true;   /* enqueue on return */
                               } else
                                       e = 0;
                       }
                       if (e != 0 && !error) {
                               errno = e;
                               logerror("%s", f->f_un.f_pipe.f_pname);
                       }
               }
               if (e == 0 && qentry) { /* sent buffered msg */
                       message_queue_remove(f, qentry);
               }
               break;

       case F_CONSOLE:
               if (buffer->flags & IGN_CONS) {
                       DPRINTF(D_MISC, "Logging to %s (ignored)\n",
                               TypeInfo[f->f_type].name);
                       break;
               }
               /* FALLTHROUGH */

       case F_TTY:
       case F_FILE:
               DPRINTF(D_MISC, "Logging to %s %s\n",
                       TypeInfo[f->f_type].name, f->f_un.f_fname);
       again:
               if ((f->f_type == F_FILE ? writev(f->f_file, iov, v - iov) :
                   writev1(f->f_file, iov, v - iov)) < 0) {
                       e = errno;
                       if (f->f_type == F_FILE && e == ENOSPC) {
                               int lasterror = f->f_lasterror;
                               f->f_lasterror = e;
                               if (lasterror != e)
                                       logerror("%s", f->f_un.f_fname);
                               error = true;   /* enqueue on return */
                       }
                       (void)close(f->f_file);
                       /*
                        * Check for errors on TTY's due to loss of tty
                        */
                       if ((e == EIO || e == EBADF) && f->f_type != F_FILE) {
                               f->f_file = open(f->f_un.f_fname,
                                   O_WRONLY|O_APPEND|O_NONBLOCK, 0);
                               if (f->f_file < 0) {
                                       f->f_type = F_UNUSED;
                                       logerror("%s", f->f_un.f_fname);
                                       message_queue_freeall(f);
                               } else
                                       goto again;
                       } else {
                               f->f_type = F_UNUSED;
                               errno = e;
                               f->f_lasterror = e;
                               logerror("%s", f->f_un.f_fname);
                               message_queue_freeall(f);
                       }
               } else {
                       f->f_lasterror = 0;
                       if ((buffer->flags & SYNC_FILE)
                        && (f->f_flags & FFLAG_SYNC))
                               (void)fsync(f->f_file);
                       /* Problem with files: We cannot check beforehand if
                        * they would be writeable and call send_queue() first.
                        * So we call send_queue() after a successful write,
                        * which means the first message will be out of order.
                        */
                       if (!qentry) /* prevent recursion */
                               SEND_QUEUE(f);
                       else if (qentry) /* sent buffered msg */
                               message_queue_remove(f, qentry);
               }
               break;

       case F_FIFO:
               DPRINTF(D_MISC, "Logging to %s %s\n",
                       TypeInfo[f->f_type].name, f->f_un.f_fname);
               if (f->f_file < 0) {
                       f->f_file =
                         open(f->f_un.f_fname, O_WRONLY|O_NONBLOCK, 0);
                       e = errno;
                       if (f->f_file < 0 && e == ENXIO) {
                               /* Drop messages with no reader */
                               if (qentry)
                                       message_queue_remove(f, qentry);
                               break;
                       }
               }

               if (f->f_file >= 0 && writev(f->f_file, iov, v - iov) < 0) {
                       e = errno;

                       /* Enqueue if the fifo buffer is full */
                       if (e == EAGAIN) {
                               if (f->f_lasterror != e)
                                       logerror("%s", f->f_un.f_fname);
                               f->f_lasterror = e;
                               error = true;   /* enqueue on return */
                               break;
                       }

                       close(f->f_file);
                       f->f_file = -1;

                       /* Drop messages with no reader */
                       if (e == EPIPE) {
                               if (qentry)
                                       message_queue_remove(f, qentry);
                               break;
                       }
               }

               if (f->f_file < 0) {
                       f->f_type = F_UNUSED;
                       errno = e;
                       f->f_lasterror = e;
                       logerror("%s", f->f_un.f_fname);
                       message_queue_freeall(f);
                       break;
               }

               f->f_lasterror = 0;
               if (!qentry) /* prevent recursion (see comment for F_FILE) */
                       SEND_QUEUE(f);
               if (qentry) /* sent buffered msg */
                       message_queue_remove(f, qentry);
               break;

       case F_USERS:
       case F_WALL:
               DPRINTF(D_MISC, "Logging to %s\n", TypeInfo[f->f_type].name);
               wallmsg(f, iov, v - iov);
               break;
       }
       f->f_prevcount = 0;

       if (error && !qentry)
               message_queue_add(f, NEWREF(buffer));
#ifndef DISABLE_SIGN
       if (newhash) {
               struct signature_group_t *sg;
               sg = sign_get_sg(buffer->pri, f);
               (void)sign_send_signature_block(sg, false);
       }
#endif /* !DISABLE_SIGN */
       /* this belongs to the ad-hoc buffer at the first if(buffer) */
       DELREF(buffer);
       /* TLS frees on its own */
       if (f->f_type != F_TLS)
               FREEPTR(line);
}

/* send one line by UDP */
void
udp_send(struct filed *f, char *line, size_t len)
{
       int lsent, fail, retry, j;
       struct addrinfo *r;

       DPRINTF((D_NET|D_CALL), "udp_send(f=%p, line=\"%s\", "
           "len=%zu) to dest.\n", f, line, len);

       if (!finet)
               return;

       lsent = -1;
       fail = 0;
       assert(f->f_type == F_FORW);
       for (r = f->f_un.f_forw.f_addr; r; r = r->ai_next) {
               retry = 0;
               for (j = 0; j < finet->fd; j++) {
                       if (finet[j+1].af != r->ai_family)
                               continue;
sendagain:
                       lsent = sendto(finet[j+1].fd, line, len, 0,
                           r->ai_addr, r->ai_addrlen);
                       if (lsent == -1) {
                               switch (errno) {
                               case ENOBUFS:
                                       /* wait/retry/drop */
                                       if (++retry < 5) {
                                               usleep(1000);
                                               goto sendagain;
                                       }
                                       break;
                               case EHOSTDOWN:
                               case EHOSTUNREACH:
                               case ENETDOWN:
                                       /* drop */
                                       break;
                               default:
                                       /* busted */
                                       fail++;
                                       break;
                               }
                       } else if ((size_t)lsent == len)
                               break;
               }
               if ((size_t)lsent != len && fail) {
                       f->f_type = F_UNUSED;
                       logerror("sendto() failed");
               }
       }
}

/*
*  WALLMSG -- Write a message to the world at large
*
*      Write the specified message to either the entire
*      world, or a list of approved users.
*/
void
wallmsg(struct filed *f, struct iovec *iov, size_t iovcnt)
{
#ifdef __NetBSD_Version__
       static int reenter;                     /* avoid calling ourselves */
       int i;
       char *p;
       struct utmpentry *ep;

       if (reenter++)
               return;

       (void)getutentries(NULL, &ep);
       /* NOSTRICT */
       for (; ep; ep = ep->next) {
               if (f->f_type == F_WALL) {
                       if ((p = ttymsg(iov, iovcnt, ep->line, TTYMSGTIME))
                           != NULL) {
                               errno = 0;      /* already in msg */
                               logerror("%s", p);
                       }
                       continue;
               }
               /* should we send the message to this user? */
               for (i = 0; i < MAXUNAMES; i++) {
                       if (!f->f_un.f_uname[i][0])
                               break;
                       if (strcmp(f->f_un.f_uname[i], ep->name) == 0) {
                               struct stat st;
                               char tty[MAXPATHLEN];
                               snprintf(tty, sizeof(tty), "%s/%s", _PATH_DEV,
                                   ep->line);
                               if (stat(tty, &st) != -1 &&
                                   (st.st_mode & S_IWGRP) == 0)
                                       break;

                               if ((p = ttymsg(iov, iovcnt, ep->line,
                                   TTYMSGTIME)) != NULL) {
                                       errno = 0;      /* already in msg */
                                       logerror("%s", p);
                               }
                               break;
                       }
               }
       }
       reenter = 0;
#endif /* __NetBSD_Version__ */
}

void
/*ARGSUSED*/
reapchild(int fd, short event, void *ev)
{
       int status;
       pid_t pid;
       struct filed *f;

       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: %s\n", getprogname(), outbuf);

       logerror_running = 0;
}

/*
* Print syslogd info some place.
*/
void
loginfo(const char *fmt, ...)
{
       va_list ap;
       char buf[BUFSIZ];

       va_start(ap, fmt);
       (void)vsnprintf(buf, sizeof(buf), fmt, ap);
       va_end(ap);

       DPRINTF(D_MISC, "%s\n", buf);
       logmsg_async(LOG_SYSLOG|LOG_INFO, NULL, buf, ADDDATE);
}

#ifndef DISABLE_TLS
static inline void
free_incoming_tls_sockets(void)
{
       struct TLS_Incoming_Conn *tls_in;
       int i;

       /*
        * close all listening and connected TLS sockets
        */
       if (TLS_Listen_Set)
               for (i = 0; i < TLS_Listen_Set->fd; i++) {
                       if (close(TLS_Listen_Set[i+1].fd) == -1)
                               logerror("close() failed");
                       DEL_EVENT(TLS_Listen_Set[i+1].ev);
                       FREEPTR(TLS_Listen_Set[i+1].ev);
               }
       FREEPTR(TLS_Listen_Set);
       /* close/free incoming TLS connections */
       while (!SLIST_EMPTY(&TLS_Incoming_Head)) {
               tls_in = SLIST_FIRST(&TLS_Incoming_Head);
               SLIST_REMOVE_HEAD(&TLS_Incoming_Head, entries);
               FREEPTR(tls_in->inbuf);
               free_tls_conn(tls_in->tls_conn);
               free(tls_in);
       }
}
#endif /* !DISABLE_TLS */

void
/*ARGSUSED*/
die(int fd, short event, void *ev)
{
       struct filed *f, *next;
       char **p;
       sigset_t newmask, omask;
       int i;
       size_t j;

       ShuttingDown = 1;       /* Don't log SIGCHLDs. */
       /* prevent recursive signals */
       BLOCK_SIGNALS(omask, newmask);

       errno = 0;
       if (ev != NULL)
               logerror("Exiting on signal %d", fd);
       else
               logerror("Fatal error, exiting");

       /*
        *  flush any pending output
        */
       for (f = Files; f != NULL; f = f->f_next) {
               /* flush any pending output */
               if (f->f_prevcount)
                       fprintlog(f, NULL, NULL);
               SEND_QUEUE(f);
       }

#ifndef DISABLE_TLS
       free_incoming_tls_sockets();
#endif /* !DISABLE_TLS */
#ifndef DISABLE_SIGN
       sign_global_free();
#endif /* !DISABLE_SIGN */

       /*
        *  Close all open log files.
        */
       for (f = Files; f != NULL; f = next) {
               message_queue_freeall(f);

               switch (f->f_type) {
               case F_FILE:
               case F_TTY:
               case F_CONSOLE:
               case F_FIFO:
                       if (f->f_file >= 0)
                               (void)close(f->f_file);
                       break;
               case F_PIPE:
                       if (f->f_un.f_pipe.f_pid > 0) {
                               (void)close(f->f_file);
                       }
                       f->f_un.f_pipe.f_pid = 0;
                       break;
               case F_FORW:
                       if (f->f_un.f_forw.f_addr)
                               freeaddrinfo(f->f_un.f_forw.f_addr);
                       break;
#ifndef DISABLE_TLS
               case F_TLS:
                       free_tls_conn(f->f_un.f_tls.tls_conn);
                       break;
#endif /* !DISABLE_TLS */
               }
               next = f->f_next;
               DELREF(f->f_prevmsg);
               FREEPTR(f->f_program);
               FREEPTR(f->f_host);
               DEL_EVENT(f->f_sq_event);
               free((char *)f);
       }

       /*
        *  Close all open UDP sockets
        */
       if (finet) {
               for (i = 0; i < finet->fd; i++) {
                       (void)close(finet[i+1].fd);
                       DEL_EVENT(finet[i+1].ev);
                       FREEPTR(finet[i+1].ev);
               }
               FREEPTR(finet);
       }

       /* free config options */
       for (j = 0; j < A_CNT(TypeInfo); j++) {
               FREEPTR(TypeInfo[j].queue_length_string);
               FREEPTR(TypeInfo[j].queue_size_string);
       }

#ifndef DISABLE_TLS
       FREEPTR(tls_opt.CAdir);
       FREEPTR(tls_opt.CAfile);
       FREEPTR(tls_opt.keyfile);
       FREEPTR(tls_opt.certfile);
       FREEPTR(tls_opt.x509verify);
       FREEPTR(tls_opt.bindhost);
       FREEPTR(tls_opt.bindport);
       FREEPTR(tls_opt.server);
       FREEPTR(tls_opt.gen_cert);
       free_cred_SLIST(&tls_opt.cert_head);
       free_cred_SLIST(&tls_opt.fprint_head);
       FREE_SSL_CTX(tls_opt.global_TLS_CTX);
#endif /* !DISABLE_TLS */

       FREEPTR(funix);
       for (p = LogPaths; p && *p; p++)
               unlink(*p);
       exit(0);
}

#ifndef DISABLE_SIGN
/*
* get one "sign_delim_sg2" item, convert and store in ordered queue
*/
void
store_sign_delim_sg2(char *tmp_buf)
{
       struct string_queue *sqentry, *sqe1, *sqe2;

       if(!(sqentry = malloc(sizeof(*sqentry)))) {
               logerror("Unable to allocate memory");
               return;
       }
       /*LINTED constcond/null effect */
       assert(sizeof(int64_t) == sizeof(uint_fast64_t));
       if (dehumanize_number(tmp_buf, (int64_t*) &(sqentry->key)) == -1
           || sqentry->key > (LOG_NFACILITIES<<3)) {
               DPRINTF(D_PARSE, "invalid sign_delim_sg2: %s\n", tmp_buf);
               free(sqentry);
               FREEPTR(tmp_buf);
               return;
       }
       sqentry->data = tmp_buf;

       if (STAILQ_EMPTY(&GlobalSign.sig2_delims)) {
               STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims,
                   sqentry, entries);
               return;
       }

       /* keep delimiters sorted */
       sqe1 = sqe2 = STAILQ_FIRST(&GlobalSign.sig2_delims);
       if (sqe1->key > sqentry->key) {
               STAILQ_INSERT_HEAD(&GlobalSign.sig2_delims,
                   sqentry, entries);
               return;
       }

       while ((sqe1 = sqe2)
          && (sqe2 = STAILQ_NEXT(sqe1, entries))) {
               if (sqe2->key > sqentry->key) {
                       break;
               } else if (sqe2->key == sqentry->key) {
                       DPRINTF(D_PARSE, "duplicate sign_delim_sg2: %s\n",
                           tmp_buf);
                       FREEPTR(sqentry);
                       FREEPTR(tmp_buf);
                       return;
               }
       }
       STAILQ_INSERT_AFTER(&GlobalSign.sig2_delims, sqe1, sqentry, entries);
}
#endif /* !DISABLE_SIGN */

/*
* read syslog.conf
*/
void
read_config_file(FILE *cf, struct filed **f_ptr)
{
       size_t linenum = 0;
       size_t i;
       struct filed *f, **nextp;
       char cline[LINE_MAX];
       char prog[NAME_MAX + 1];
       char host[MAXHOSTNAMELEN];
       const char *p;
       char *q;
       bool found_keyword;
#ifndef DISABLE_TLS
       struct peer_cred *cred = NULL;
       struct peer_cred_head *credhead = NULL;
#endif /* !DISABLE_TLS */
#ifndef DISABLE_SIGN
       char *sign_sg_str = NULL;
#endif /* !DISABLE_SIGN */
#if (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN))
       char *tmp_buf = NULL;
#endif /* (!defined(DISABLE_TLS) || !defined(DISABLE_SIGN)) */
       /* central list of recognized configuration keywords
        * and an address for their values as strings */
       const struct config_keywords {
               const char *keyword;
               char **variable;
       } config_keywords[] = {
#ifndef DISABLE_TLS
               /* TLS settings */
               {"tls_ca",                &tls_opt.CAfile},
               {"tls_cadir",             &tls_opt.CAdir},
               {"tls_cert",              &tls_opt.certfile},
               {"tls_key",               &tls_opt.keyfile},
               {"tls_verify",            &tls_opt.x509verify},
               {"tls_bindport",          &tls_opt.bindport},
               {"tls_bindhost",          &tls_opt.bindhost},
               {"tls_server",            &tls_opt.server},
               {"tls_gen_cert",          &tls_opt.gen_cert},
               /* special cases in parsing */
               {"tls_allow_fingerprints",&tmp_buf},
               {"tls_allow_clientcerts", &tmp_buf},
               /* buffer settings */
               {"tls_queue_length",      &TypeInfo[F_TLS].queue_length_string},
               {"tls_queue_size",        &TypeInfo[F_TLS].queue_size_string},
#endif /* !DISABLE_TLS */
               {"file_queue_length",     &TypeInfo[F_FILE].queue_length_string},
               {"pipe_queue_length",     &TypeInfo[F_PIPE].queue_length_string},
               {"fifo_queue_length",     &TypeInfo[F_FIFO].queue_length_string},
               {"file_queue_size",       &TypeInfo[F_FILE].queue_size_string},
               {"pipe_queue_size",       &TypeInfo[F_PIPE].queue_size_string},
               {"fifo_queue_size",       &TypeInfo[F_FIFO].queue_size_string},
#ifndef DISABLE_SIGN
               /* syslog-sign setting */
               {"sign_sg",               &sign_sg_str},
               /* also special case in parsing */
               {"sign_delim_sg2",        &tmp_buf},
#endif /* !DISABLE_SIGN */
       };

       DPRINTF(D_CALL, "read_config_file()\n");

       /* free all previous config options */
       for (i = 0; i < A_CNT(TypeInfo); i++) {
               if (TypeInfo[i].queue_length_string
                   && TypeInfo[i].queue_length_string
                   != TypeInfo[i].default_length_string) {
                       FREEPTR(TypeInfo[i].queue_length_string);
                       TypeInfo[i].queue_length_string =
                               strdup(TypeInfo[i].default_length_string);
                }
               if (TypeInfo[i].queue_size_string
                   && TypeInfo[i].queue_size_string
                   != TypeInfo[i].default_size_string) {
                       FREEPTR(TypeInfo[i].queue_size_string);
                       TypeInfo[i].queue_size_string =
                               strdup(TypeInfo[i].default_size_string);
                }
       }
       for (i = 0; i < A_CNT(config_keywords); i++)
               FREEPTR(*config_keywords[i].variable);
       /*
        * global settings
        */
       while (fgets(cline, sizeof(cline), cf) != NULL) {
               linenum++;
               for (p = cline; isspace((unsigned char)*p); ++p)
                       continue;
               if ((*p == '\0') || (*p == '#'))
                       continue;

               for (i = 0; i < A_CNT(config_keywords); i++) {
                       if (copy_config_value(config_keywords[i].keyword,
                           config_keywords[i].variable, &p, ConfFile,
                           linenum)) {
                               DPRINTF((D_PARSE|D_MEM),
                                   "found option %s, saved @%p\n",
                                   config_keywords[i].keyword,
                                   *config_keywords[i].variable);
#ifndef DISABLE_SIGN
                               if (!strcmp("sign_delim_sg2",
                                   config_keywords[i].keyword))
                                       do {
                                               store_sign_delim_sg2(tmp_buf);
                                       } while (copy_config_value_word(
                                           &tmp_buf, &p));

#endif /* !DISABLE_SIGN */

#ifndef DISABLE_TLS
                               /* special cases with multiple parameters */
                               if (!strcmp("tls_allow_fingerprints",
                                   config_keywords[i].keyword))
                                       credhead = &tls_opt.fprint_head;
                               else if (!strcmp("tls_allow_clientcerts",
                                   config_keywords[i].keyword))
                                       credhead = &tls_opt.cert_head;

                               if (credhead) do {
                                       if(!(cred = malloc(sizeof(*cred)))) {
                                               logerror("Unable to "
                                                       "allocate memory");
                                               break;
                                       }
                                       cred->data = tmp_buf;
                                       tmp_buf = NULL;
                                       SLIST_INSERT_HEAD(credhead,
                                               cred, entries);
                               } while /* additional values? */
                                       (copy_config_value_word(&tmp_buf, &p));
                               credhead = NULL;
                               break;
#endif /* !DISABLE_TLS */
                       }
               }
       }
       /* convert strings to integer values */
       for (i = 0; i < A_CNT(TypeInfo); i++) {
               if (!TypeInfo[i].queue_length_string
                   || dehumanize_number(TypeInfo[i].queue_length_string,
                   &TypeInfo[i].queue_length) == -1)
                       if (dehumanize_number(TypeInfo[i].default_length_string,
                           &TypeInfo[i].queue_length) == -1)
                               abort();
               if (!TypeInfo[i].queue_size_string
                   || dehumanize_number(TypeInfo[i].queue_size_string,
                   &TypeInfo[i].queue_size) == -1)
                       if (dehumanize_number(TypeInfo[i].default_size_string,
                           &TypeInfo[i].queue_size) == -1)
                               abort();
       }

#ifndef DISABLE_SIGN
       if (sign_sg_str) {
               if (sign_sg_str[1] == '\0'
                   && (sign_sg_str[0] == '0' || sign_sg_str[0] == '1'
                   || sign_sg_str[0] == '2' || sign_sg_str[0] == '3'))
                       GlobalSign.sg = sign_sg_str[0] - '0';
               else {
                       GlobalSign.sg = SIGN_SG;
                       DPRINTF(D_MISC, "Invalid sign_sg value `%s', "
                           "use default value `%d'\n",
                           sign_sg_str, GlobalSign.sg);
               }
       } else  /* disable syslog-sign */
               GlobalSign.sg = -1;
#endif /* !DISABLE_SIGN */

       rewind(cf);
       linenum = 0;
       /*
        *  Foreach line in the conf table, open that file.
        */
       f = NULL;
       nextp = &f;

       strcpy(prog, "*");
       strcpy(host, "*");
       while (fgets(cline, sizeof(cline), cf) != NULL) {
               linenum++;
               found_keyword = false;
               /*
                * check for end-of-section, comments, strip off trailing
                * spaces and newline character.  #!prog is treated specially:
                * following lines apply only to that program.
                */
               for (p = cline; isspace((unsigned char)*p); ++p)
                       continue;
               if (*p == '\0')
                       continue;
               if (*p == '#') {
                       p++;
                       if (*p != '!' && *p != '+' && *p != '-')
                               continue;
               }

               for (i = 0; i < A_CNT(config_keywords); i++) {
                       if (!strncasecmp(p, config_keywords[i].keyword,
                               strlen(config_keywords[i].keyword))) {
                               DPRINTF(D_PARSE,
                                   "skip cline %zu with keyword %s\n",
                                   linenum, config_keywords[i].keyword);
                               found_keyword = true;
                       }
               }
               if (found_keyword)
                       continue;

               if (*p == '+' || *p == '-') {
                       host[0] = *p++;
                       while (isspace((unsigned char)*p))
                               p++;
                       if (*p == '\0' || *p == '*') {
                               strcpy(host, "*");
                               continue;
                       }
                       /* the +hostname expression will continue
                        * to use the LocalHostName, not the FQDN */
                       for (i = 1; i < MAXHOSTNAMELEN - 1; i++) {
                               if (*p == '@') {
                                       (void)strncpy(&host[i], LocalHostName,
                                           sizeof(host) - 1 - i);
                                       host[sizeof(host) - 1] = '\0';
                                       i = strlen(host) - 1;
                                       p++;
                                       continue;
                               }
                               if (!isalnum((unsigned char)*p) &&
                                   *p != '.' && *p != '-' && *p != ',')
                                       break;
                               host[i] = *p++;
                       }
                       host[i] = '\0';
                       continue;
               }
               if (*p == '!') {
                       p++;
                       while (isspace((unsigned char)*p))
                               p++;
                       if (*p == '\0' || *p == '*') {
                               strcpy(prog, "*");
                               continue;
                       }
                       for (i = 0; i < NAME_MAX; i++) {
                               if (!isprint((unsigned char)p[i]))
                                       break;
                               prog[i] = p[i];
                       }
                       prog[i] = '\0';
                       continue;
               }
               for (q = strchr(cline, '\0'); isspace((unsigned char)*--q);)
                       continue;
               *++q = '\0';
               if ((f = calloc(1, sizeof(*f))) == NULL) {
                       logerror("alloc failed");
                       die(0, 0, NULL);
               }
               if (!*f_ptr) *f_ptr = f; /* return first node */
               *nextp = f;
               nextp = &f->f_next;
               cfline(linenum, cline, f, prog, host);
       }
}

/*
*  INIT -- Initialize syslogd from configuration table
*/
void
/*ARGSUSED*/
init(int fd, short event, void *ev)
{
       FILE *cf;
       int i;
       struct filed *f, *newf, **nextp, *f2;
       char *p;
       sigset_t newmask, omask;
#ifndef DISABLE_TLS
       char *tls_status_msg = NULL;
       struct peer_cred *cred = NULL;
#endif /* !DISABLE_TLS */

       /* prevent recursive signals */
       BLOCK_SIGNALS(omask, newmask);

       DPRINTF((D_EVENT|D_CALL), "init\n");

       /*
        * be careful about dependencies and order of actions:
        * 1. flush buffer queues
        * 2. flush -sign SBs
        * 3. flush/delete buffer queue again, in case an SB got there
        * 4. close files/connections
        */

       /*
        *  flush any pending output
        */
       for (f = Files; f != NULL; f = f->f_next) {
               /* flush any pending output */
               if (f->f_prevcount)
                       fprintlog(f, NULL, NULL);
               SEND_QUEUE(f);
       }
       /* some actions only on SIGHUP and not on first start */
       if (Initialized) {
#ifndef DISABLE_SIGN
               sign_global_free();
#endif /* !DISABLE_SIGN */
#ifndef DISABLE_TLS
               free_incoming_tls_sockets();
#endif /* !DISABLE_TLS */
               Initialized = 0;
       }
       /*
        *  Close all open log files.
        */
       for (f = Files; f != NULL; f = f->f_next) {
               switch (f->f_type) {
               case F_FILE:
               case F_TTY:
               case F_CONSOLE:
                       (void)close(f->f_file);
                       break;
               case F_PIPE:
                       if (f->f_un.f_pipe.f_pid > 0) {
                               (void)close(f->f_file);
                               deadq_enter(f->f_un.f_pipe.f_pid,
                                   f->f_un.f_pipe.f_pname);
                       }
                       f->f_un.f_pipe.f_pid = 0;
                       break;
               case F_FORW:
                       if (f->f_un.f_forw.f_addr)
                               freeaddrinfo(f->f_un.f_forw.f_addr);
                       break;
#ifndef DISABLE_TLS
               case F_TLS:
                       free_tls_sslptr(f->f_un.f_tls.tls_conn);
                       break;
#endif /* !DISABLE_TLS */
               }
       }

       /*
        *  Close all open UDP sockets
        */
       if (finet) {
               for (i = 0; i < finet->fd; i++) {
                       if (close(finet[i+1].fd) < 0) {
                               logerror("close() failed");
                               die(0, 0, NULL);
                       }
                       DEL_EVENT(finet[i+1].ev);
                       FREEPTR(finet[i+1].ev);
               }
               FREEPTR(finet);
       }

       /* get FQDN and hostname/domain */
       FREEPTR(oldLocalFQDN);
       oldLocalFQDN = LocalFQDN;
       LocalFQDN = getLocalFQDN();
       if ((p = strchr(LocalFQDN, '.')) != NULL)
               (void)strlcpy(LocalHostName, LocalFQDN, 1+p-LocalFQDN);
       else
               (void)strlcpy(LocalHostName, LocalFQDN, sizeof(LocalHostName));

       /*
        *  Reset counter of forwarding actions
        */

       NumForwards=0;

       /* new destination list to replace Files */
       newf = NULL;
       nextp = &newf;

       /* open the configuration file */
       if ((cf = fopen(ConfFile, "r")) == NULL) {
               DPRINTF(D_FILE, "Cannot open `%s'\n", ConfFile);
               *nextp = (struct filed *)calloc(1, sizeof(*f));
               cfline(0, "*.ERR\t/dev/console", *nextp, "*", "*");
               (*nextp)->f_next = (struct filed *)calloc(1, sizeof(*f));
               cfline(0, "*.PANIC\t*", (*nextp)->f_next, "*", "*");
               Initialized = 1;
               RESTORE_SIGNALS(omask);
               return;
       }

#ifndef DISABLE_TLS
       /* init with new TLS_CTX
        * as far as I see one cannot change the cert/key of an existing CTX
        */
       FREE_SSL_CTX(tls_opt.global_TLS_CTX);

       free_cred_SLIST(&tls_opt.cert_head);
       free_cred_SLIST(&tls_opt.fprint_head);
#endif /* !DISABLE_TLS */

       /* read and close configuration file */
       read_config_file(cf, &newf);
       newf = *nextp;
       (void)fclose(cf);
       DPRINTF(D_MISC, "read_config_file() returned newf=%p\n", newf);

#define MOVE_QUEUE(dst, src) do {                               \
       struct buf_queue *buf;                                  \
       STAILQ_CONCAT(&dst->f_qhead, &src->f_qhead);            \
       STAILQ_FOREACH(buf, &dst->f_qhead, entries) {           \
             dst->f_qelements++;                               \
             dst->f_qsize += buf_queue_obj_size(buf);          \
       }                                                       \
       src->f_qsize = 0;                                       \
       src->f_qelements = 0;                                   \
} while (0)

       /*
        *  Free old log files.
        */
       for (f = Files; f != NULL;) {
               struct filed *ftmp;

               /* check if a new logfile is equal, if so pass the queue */
               for (f2 = newf; f2 != NULL; f2 = f2->f_next) {
                       if (f->f_type == f2->f_type
                           && ((f->f_type == F_PIPE
                           && !strcmp(f->f_un.f_pipe.f_pname,
                           f2->f_un.f_pipe.f_pname))
#ifndef DISABLE_TLS
                           || (f->f_type == F_TLS
                           && !strcmp(f->f_un.f_tls.tls_conn->hostname,
                           f2->f_un.f_tls.tls_conn->hostname)
                           && !strcmp(f->f_un.f_tls.tls_conn->port,
                           f2->f_un.f_tls.tls_conn->port))
#endif /* !DISABLE_TLS */
                           || (f->f_type == F_FORW
                           && !strcmp(f->f_un.f_forw.f_hname,
                           f2->f_un.f_forw.f_hname)))) {
                               DPRINTF(D_BUFFER, "move queue from f@%p "
                                   "to f2@%p\n", f, f2);
                               MOVE_QUEUE(f2, f);
                        }
               }
               message_queue_freeall(f);
               DELREF(f->f_prevmsg);
#ifndef DISABLE_TLS
               if (f->f_type == F_TLS)
                       free_tls_conn(f->f_un.f_tls.tls_conn);
#endif /* !DISABLE_TLS */
               FREEPTR(f->f_program);
               FREEPTR(f->f_host);
               DEL_EVENT(f->f_sq_event);

               ftmp = f->f_next;
               free((char *)f);
               f = ftmp;
       }
       Files = newf;
       Initialized = 1;

       if (Debug) {
               for (f = Files; f; f = f->f_next) {
                       for (i = 0; i <= LOG_NFACILITIES; i++)
                               if (f->f_pmask[i] == INTERNAL_NOPRI)
                                       printf("X ");
                               else
                                       printf("%d ", f->f_pmask[i]);
                       printf("%s: ", TypeInfo[f->f_type].name);
                       switch (f->f_type) {
                       case F_FILE:
                       case F_TTY:
                       case F_CONSOLE:
                       case F_FIFO:
                               printf("%s", f->f_un.f_fname);
                               break;

                       case F_FORW:
                               printf("%s", f->f_un.f_forw.f_hname);
                               break;
#ifndef DISABLE_TLS
                       case F_TLS:
                               printf("[%s]", f->f_un.f_tls.tls_conn->hostname);
                               break;
#endif /* !DISABLE_TLS */
                       case F_PIPE:
                               printf("%s", f->f_un.f_pipe.f_pname);
                               break;

                       case F_USERS:
                               for (i = 0;
                                   i < MAXUNAMES && *f->f_un.f_uname[i]; i++)
                                       printf("%s, ", f->f_un.f_uname[i]);
                               break;
                       }
                       if (f->f_program != NULL)
                               printf(" (%s)", f->f_program);
                       printf("\n");
               }
       }

       finet = socksetup(PF_UNSPEC, bindhostname);
       if (finet) {
               if (SecureMode) {
                       for (i = 0; i < finet->fd; i++) {
                               if (shutdown(finet[i+1].fd, SHUT_RD) < 0) {
                                       logerror("shutdown() failed");
                                       die(0, 0, NULL);
                               }
                       }
               } else
                       DPRINTF(D_NET, "Listening on inet and/or inet6 socket\n");
               DPRINTF(D_NET, "Sending on inet and/or inet6 socket\n");
       }

#ifndef DISABLE_TLS
       /* TLS setup -- after all local destinations opened  */
       DPRINTF(D_PARSE, "Parsed options: tls_ca: %s, tls_cadir: %s, "
           "tls_cert: %s, tls_key: %s, tls_verify: %s, "
           "bind: %s:%s, max. queue_lengths: %"
           PRId64 ", %" PRId64 ", %" PRId64 ", "
           "max. queue_sizes: %"
           PRId64 ", %" PRId64 ", %" PRId64 "\n",
           tls_opt.CAfile, tls_opt.CAdir,
           tls_opt.certfile, tls_opt.keyfile, tls_opt.x509verify,
           tls_opt.bindhost, tls_opt.bindport,
           TypeInfo[F_TLS].queue_length, TypeInfo[F_FILE].queue_length,
           TypeInfo[F_PIPE].queue_length,
           TypeInfo[F_TLS].queue_size, TypeInfo[F_FILE].queue_size,
           TypeInfo[F_PIPE].queue_size);
       SLIST_FOREACH(cred, &tls_opt.cert_head, entries) {
               DPRINTF(D_PARSE, "Accepting peer certificate "
                   "from file: \"%s\"\n", cred->data);
       }
       SLIST_FOREACH(cred, &tls_opt.fprint_head, entries) {
               DPRINTF(D_PARSE, "Accepting peer certificate with "
                   "fingerprint: \"%s\"\n", cred->data);
       }

       /* Note: The order of initialization is important because syslog-sign
        * should use the TLS cert for signing. -- So we check first if TLS
        * will be used and initialize it before starting -sign.
        *
        * This means that if we are a client without TLS destinations TLS
        * will not be initialized and syslog-sign will generate a new key.
        * -- Even if the user has set a usable tls_cert.
        * Is this the expected behaviour? The alternative would be to always
        * initialize the TLS structures, even if they will not be needed
        * (or only needed to read the DSA key for -sign).
        */

       /* Initialize TLS only if used */
       if (tls_opt.server)
               tls_status_msg = init_global_TLS_CTX();
       else
               for (f = Files; f; f = f->f_next) {
                       if (f->f_type != F_TLS)
                               continue;
                       tls_status_msg = init_global_TLS_CTX();
                       break;
               }

#endif /* !DISABLE_TLS */

#ifndef DISABLE_SIGN
       /* only initialize -sign if actually used */
       if (GlobalSign.sg == 0 || GlobalSign.sg == 1 || GlobalSign.sg == 2)
               (void)sign_global_init(Files);
       else if (GlobalSign.sg == 3)
               for (f = Files; f; f = f->f_next)
                       if (f->f_flags & FFLAG_SIGN) {
                               (void)sign_global_init(Files);
                               break;
                       }
#endif /* !DISABLE_SIGN */

#ifndef DISABLE_TLS
       if (tls_status_msg) {
               loginfo("%s", tls_status_msg);
               free(tls_status_msg);
       }
       DPRINTF((D_NET|D_TLS), "Preparing sockets for TLS\n");
       TLS_Listen_Set =
               socksetup_tls(PF_UNSPEC, tls_opt.bindhost, tls_opt.bindport);

       for (f = Files; f; f = f->f_next) {
               if (f->f_type != F_TLS)
                       continue;
               if (!tls_connect(f->f_un.f_tls.tls_conn)) {
                       logerror("Unable to connect to TLS server %s",
                           f->f_un.f_tls.tls_conn->hostname);
                       /* Reconnect after x seconds  */
                       schedule_event(&f->f_un.f_tls.tls_conn->event,
                           &((struct timeval){TLS_RECONNECT_SEC, 0}),
                           tls_reconnect, f->f_un.f_tls.tls_conn);
               }
       }
#endif /* !DISABLE_TLS */

       loginfo("restart");
       /*
        * Log a change in hostname, but only on a restart (we detect this
        * by checking to see if we're passed a kevent).
        */
       if (oldLocalFQDN && strcmp(oldLocalFQDN, LocalFQDN) != 0)
               loginfo("host name changed, \"%s\" to \"%s\"",
                   oldLocalFQDN, LocalFQDN);

       RESTORE_SIGNALS(omask);
}

/*
* Crack a configuration file line
*/
void
cfline(size_t linenum, const char *line, struct filed *f, const char *prog,
   const char *host)
{
       struct addrinfo hints, *res;
       int    error, i, pri, syncfile;
       const char   *p, *q;
       char *bp;
       char   buf[MAXLINE];
       struct stat sb;

       DPRINTF((D_CALL|D_PARSE),
               "cfline(%zu, \"%s\", f, \"%s\", \"%s\")\n",
               linenum, line, prog, host);

       errno = 0;      /* keep strerror() stuff out of logerror messages */

       /* clear out file entry */
       memset(f, 0, sizeof(*f));
       for (i = 0; i <= LOG_NFACILITIES; i++)
               f->f_pmask[i] = INTERNAL_NOPRI;
       STAILQ_INIT(&f->f_qhead);

       /*
        * There should not be any space before the log facility.
        * Check this is okay, complain and fix if it is not.
        */
       q = line;
       if (isblank((unsigned char)*line)) {
               errno = 0;
               logerror("Warning: `%s' space or tab before the log facility",
                   line);
               /* Fix: strip all spaces/tabs before the log facility */
               while (*q++ && isblank((unsigned char)*q))
                       /* skip blanks */;
               line = q;
       }

       /*
        * q is now at the first char of the log facility
        * There should be at least one tab after the log facility
        * Check this is okay, and complain and fix if it is not.
        */
       q = line + strlen(line);
       while (!isblank((unsigned char)*q) && (q != line))
               q--;
       if ((q == line) && strlen(line)) {
               /* No tabs or space in a non empty line: complain */
               errno = 0;
               logerror(
                   "Error: `%s' log facility or log target missing",
                   line);
               return;
       }

       /* save host name, if any */
       if (*host == '*')
               f->f_host = NULL;
       else {
               f->f_host = strdup(host);
               trim_anydomain(&f->f_host[1]);  /* skip +/- at beginning */
       }

       /* save program name, if any */
       if (*prog == '*')
               f->f_program = NULL;
       else
               f->f_program = strdup(prog);

       /* scan through the list of selectors */
       for (p = line; *p && !isblank((unsigned char)*p);) {
               int pri_done, pri_cmp, pri_invert;

               /* find the end of this facility name list */
               for (q = p; *q && !isblank((unsigned char)*q) && *q++ != '.'; )
                       continue;

               /* get the priority comparison */
               pri_cmp = 0;
               pri_done = 0;
               pri_invert = 0;
               if (*q == '!') {
                       pri_invert = 1;
                       q++;
               }
               while (! pri_done) {
                       switch (*q) {
                       case '<':
                               pri_cmp = PRI_LT;
                               q++;
                               break;
                       case '=':
                               pri_cmp = PRI_EQ;
                               q++;
                               break;
                       case '>':
                               pri_cmp = PRI_GT;
                               q++;
                               break;
                       default:
                               pri_done = 1;
                               break;
                       }
               }

               /* collect priority name */
               for (bp = buf; *q && !strchr("\t ,;", *q); )
                       *bp++ = *q++;
               *bp = '\0';

               /* skip cruft */
               while (strchr(",;", *q))
                       q++;

               /* decode priority name */
               if (*buf == '*') {
                       pri = LOG_PRIMASK + 1;
                       pri_cmp = PRI_LT | PRI_EQ | PRI_GT;
               } else {
                       pri = decode(buf, prioritynames);
                       if (pri < 0) {
                               errno = 0;
                               logerror("Unknown priority name `%s'", buf);
                               return;
                       }
               }
               if (pri_cmp == 0)
                       pri_cmp = UniquePriority ? PRI_EQ
                                                : PRI_EQ | PRI_GT;
               if (pri_invert)
                       pri_cmp ^= PRI_LT | PRI_EQ | PRI_GT;

               /* scan facilities */
               while (*p && !strchr("\t .;", *p)) {
                       for (bp = buf; *p && !strchr("\t ,;.", *p); )
                               *bp++ = *p++;
                       *bp = '\0';
                       if (*buf == '*')
                               for (i = 0; i < LOG_NFACILITIES; i++) {
                                       f->f_pmask[i] = pri;
                                       f->f_pcmp[i] = pri_cmp;
                               }
                       else {
                               i = decode(buf, facilitynames);
                               if (i < 0) {
                                       errno = 0;
                                       logerror("Unknown facility name `%s'",
                                           buf);
                                       return;
                               }
                               f->f_pmask[i >> 3] = pri;
                               f->f_pcmp[i >> 3] = pri_cmp;
                       }
                       while (*p == ',' || *p == ' ')
                               p++;
               }

               p = q;
       }

       /* skip to action part */
       while (isblank((unsigned char)*p))
               p++;

       /*
        * should this be "#ifndef DISABLE_SIGN" or is it a general option?
        * '+' before file destination: write with PRI field for later
        * verification
        */
       if (*p == '+') {
               f->f_flags |= FFLAG_FULL;
               p++;
       }
       if (*p == '-') {
               syncfile = 0;
               p++;
       } else
               syncfile = 1;

       switch (*p) {
       case '@':
#ifndef DISABLE_SIGN
               if (GlobalSign.sg == 3)
                       f->f_flags |= FFLAG_SIGN;
#endif /* !DISABLE_SIGN */
#ifndef DISABLE_TLS
               if (*(p+1) == '[') {
                       /* TLS destination */
                       if (!parse_tls_destination(p, f, linenum)) {
                               logerror("Unable to parse action %s", p);
                               break;
                       }
                       f->f_type = F_TLS;
                       break;
               }
#endif /* !DISABLE_TLS */
               (void)strlcpy(f->f_un.f_forw.f_hname, ++p,
                   sizeof(f->f_un.f_forw.f_hname));
               memset(&hints, 0, sizeof(hints));
               hints.ai_family = AF_UNSPEC;
               hints.ai_socktype = SOCK_DGRAM;
               hints.ai_protocol = 0;
               error = getaddrinfo(f->f_un.f_forw.f_hname, "syslog", &hints,
                   &res);
               if (error) {
                       errno = 0;
                       logerror("%s", gai_strerror(error));
                       break;
               }
               f->f_un.f_forw.f_addr = res;
               f->f_type = F_FORW;
               NumForwards++;
               break;

       case '/':
#ifndef DISABLE_SIGN
               if (GlobalSign.sg == 3)
                       f->f_flags |= FFLAG_SIGN;
#endif /* !DISABLE_SIGN */
               (void)strlcpy(f->f_un.f_fname, p, sizeof(f->f_un.f_fname));
               if ((f->f_file = open(p, O_WRONLY|O_APPEND|O_NONBLOCK, 0)) < 0)
               {
                       f->f_type = F_UNUSED;
                       logerror("%s", p);
                       break;
               }
               if (!fstat(f->f_file, &sb) && S_ISFIFO(sb.st_mode)) {
                       f->f_file = -1;
                       f->f_type = F_FIFO;
                       break;
               }

               if (isatty(f->f_file)) {
                       f->f_type = F_TTY;
                       if (strcmp(p, ctty) == 0)
                               f->f_type = F_CONSOLE;
               } else
                       f->f_type = F_FILE;

               if (syncfile)
                       f->f_flags |= FFLAG_SYNC;
               break;

       case '|':
#ifndef DISABLE_SIGN
               if (GlobalSign.sg == 3)
                       f->f_flags |= FFLAG_SIGN;
#endif
               f->f_un.f_pipe.f_pid = 0;
               (void) strlcpy(f->f_un.f_pipe.f_pname, p + 1,
                   sizeof(f->f_un.f_pipe.f_pname));
               f->f_type = F_PIPE;
               break;

       case '*':
               f->f_type = F_WALL;
               break;

       default:
               for (i = 0; i < MAXUNAMES && *p; i++) {
                       for (q = p; *q && *q != ','; )
                               q++;
                       (void)strncpy(f->f_un.f_uname[i], p, UT_NAMESIZE);
                       if ((q - p) > UT_NAMESIZE)
                               f->f_un.f_uname[i][UT_NAMESIZE] = '\0';
                       else
                               f->f_un.f_uname[i][q - p] = '\0';
                       while (*q == ',' || *q == ' ')
                               q++;
                       p = q;
               }
               f->f_type = F_USERS;
               break;
       }
}


/*
*  Decode a symbolic name to a numeric value
*/
int
decode(const char *name, CODE *codetab)
{
       CODE *c;
       char *p, buf[40];

       if (isdigit((unsigned char)*name))
               return atoi(name);

       for (p = buf; *name && p < &buf[sizeof(buf) - 1]; p++, name++) {
               if (isupper((unsigned char)*name))
                       *p = tolower((unsigned char)*name);
               else
                       *p = *name;
       }
       *p = '\0';
       for (c = codetab; c->c_name; c++)
               if (!strcmp(buf, c->c_name))
                       return c->c_val;

       return -1;
}

/*
* Retrieve the size of the kernel message buffer, via sysctl.
*/
int
getmsgbufsize(void)
{
#ifdef __NetBSD_Version__
       int msgbufsize, mib[2];
       size_t size;

       mib[0] = CTL_KERN;
       mib[1] = KERN_MSGBUFSIZE;
       size = sizeof msgbufsize;
       if (sysctl(mib, 2, &msgbufsize, &size, NULL, 0) == -1) {
               DPRINTF(D_MISC, "Couldn't get kern.msgbufsize\n");
               return 0;
       }
       return msgbufsize;
#else
       return MAXLINE;
#endif /* __NetBSD_Version__ */
}

/*
* Retrieve the hostname, via sysctl.
*/
char *
getLocalFQDN(void)
{
       int mib[2];
       char *hostname;
       size_t len;

       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;
               if (!qentry_tmp) return NULL;
               while ((qentry_tmp = STAILQ_NEXT(qentry_tmp, entries)) != NULL)
               {
                       if (LOG_PRI(qentry_tmp->msg->pri) == pri) {
                               /* save the successor, because qentry_tmp
                                * is probably deleted by the caller */
                               qentry_static = STAILQ_NEXT(qentry_tmp, entries);
                               return qentry_tmp;
                       }
               }
               /* nothing found in while loop --> next pri */
               if (--pri)
                       return find_qentry_to_delete(head, strategy, false);
               else
                       return NULL;
       } else /* strategy == PURGE_OLDEST or other value */ {
               qentry_tmp = qentry_static;
               qentry_static = STAILQ_NEXT(qentry_tmp, entries);
               return qentry_tmp;  /* is NULL on empty queue */
       }
}

/* note on TAILQ: newest message added at TAIL,
*                oldest to be removed is FIRST
*/
/*
* checks length of a destination's message queue
* if del_entries == 0 then assert queue length is
*   less or equal to configured number of queue elements
* otherwise del_entries tells how many entries to delete
*
* returns the number of removed queue elements
* (which not necessarily means free'd messages)
*
* strategy PURGE_OLDEST to delete oldest entry, e.g. after it was resent
* strategy PURGE_BY_PRIORITY to delete messages with lowest priority first,
*      this is much slower but might be desirable when unsent messages have
*      to be deleted, e.g. in call from domark()
*/
size_t
message_queue_purge(struct filed *f, size_t del_entries, int strategy)
{
       size_t removed = 0;
       struct buf_queue *qentry = NULL;

       DPRINTF((D_CALL|D_BUFFER), "purge_message_queue(%p, %zu, %d) with "
           "f_qelements=%zu and f_qsize=%zu\n",
           f, del_entries, strategy,
           f->f_qelements, f->f_qsize);

       /* reset state */
       (void)find_qentry_to_delete(&f->f_qhead, strategy, true);

       while (removed < del_entries
           || (TypeInfo[f->f_type].queue_length != -1
           && (size_t)TypeInfo[f->f_type].queue_length <= f->f_qelements)
           || (TypeInfo[f->f_type].queue_size != -1
           && (size_t)TypeInfo[f->f_type].queue_size <= f->f_qsize)) {
               qentry = find_qentry_to_delete(&f->f_qhead, strategy, 0);
               if (message_queue_remove(f, qentry))
                       removed++;
               else
                       break;
       }
       return removed;
}

/* run message_queue_purge() for all destinations to free memory */
size_t
message_allqueues_purge(void)
{
       size_t sum = 0;
       struct filed *f;

       for (f = Files; f; f = f->f_next)
               sum += message_queue_purge(f,
                   f->f_qelements/10, PURGE_BY_PRIORITY);

       DPRINTF(D_BUFFER,
           "message_allqueues_purge(): removed %zu buffer entries\n", sum);
       return sum;
}

/* run message_queue_purge() for all destinations to check limits */
size_t
message_allqueues_check(void)
{
       size_t sum = 0;
       struct filed *f;

       for (f = Files; f; f = f->f_next)
               sum += message_queue_purge(f, 0, PURGE_BY_PRIORITY);
       DPRINTF(D_BUFFER,
           "message_allqueues_check(): removed %zu buffer entries\n", sum);
       return sum;
}

struct buf_msg *
buf_msg_new(const size_t len)
{
       struct buf_msg *newbuf;

       CALLOC(newbuf, sizeof(*newbuf));

       if (len) { /* len = 0 is valid */
               MALLOC(newbuf->msg, len);
               newbuf->msgorig = newbuf->msg;
               newbuf->msgsize = len;
       }
       return NEWREF(newbuf);
}

void
buf_msg_free(struct buf_msg *buf)
{
       if (!buf)
               return;

       buf->refcount--;
       if (buf->refcount == 0) {
               FREEPTR(buf->timestamp);
               /* small optimizations: the host/recvhost may point to the
                * global HostName/FQDN. of course this must not be free()d
                * same goes for appname and include_pid
                */
               if (buf->recvhost != buf->host
                   && buf->recvhost != LocalHostName
                   && buf->recvhost != LocalFQDN
                   && buf->recvhost != oldLocalFQDN)
                       FREEPTR(buf->recvhost);
               if (buf->host != LocalHostName
                   && buf->host != LocalFQDN
                   && buf->host != oldLocalFQDN)
                       FREEPTR(buf->host);
               if (buf->prog != appname)
                       FREEPTR(buf->prog);
               if (buf->pid != include_pid)
                       FREEPTR(buf->pid);
               FREEPTR(buf->msgid);
               FREEPTR(buf->sd);
               FREEPTR(buf->msgorig);  /* instead of msg */
               FREEPTR(buf);
       }
}

size_t
buf_queue_obj_size(struct buf_queue *qentry)
{
       size_t sum = 0;

       if (!qentry)
               return 0;
       sum += sizeof(*qentry)
           + sizeof(*qentry->msg)
           + qentry->msg->msgsize
           + SAFEstrlen(qentry->msg->timestamp)+1
           + SAFEstrlen(qentry->msg->msgid)+1;
       if (qentry->msg->prog
           && qentry->msg->prog != include_pid)
               sum += strlen(qentry->msg->prog)+1;
       if (qentry->msg->pid
           && qentry->msg->pid != appname)
               sum += strlen(qentry->msg->pid)+1;
       if (qentry->msg->recvhost
           && qentry->msg->recvhost != LocalHostName
           && qentry->msg->recvhost != LocalFQDN
           && qentry->msg->recvhost != oldLocalFQDN)
               sum += strlen(qentry->msg->recvhost)+1;
       if (qentry->msg->host
           && qentry->msg->host != LocalHostName
           && qentry->msg->host != LocalFQDN
           && qentry->msg->host != oldLocalFQDN)
               sum += strlen(qentry->msg->host)+1;

       return sum;
}

bool
message_queue_remove(struct filed *f, struct buf_queue *qentry)
{
       if (!f || !qentry || !qentry->msg)
               return false;

       assert(!STAILQ_EMPTY(&f->f_qhead));
       STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries);
       f->f_qelements--;
       f->f_qsize -= buf_queue_obj_size(qentry);

       DPRINTF(D_BUFFER, "msg @%p removed from queue @%p, new qlen = %zu\n",
           qentry->msg, f, f->f_qelements);
       DELREF(qentry->msg);
       FREEPTR(qentry);
       return true;
}

/*
* returns *qentry on success and NULL on error
*/
struct buf_queue *
message_queue_add(struct filed *f, struct buf_msg *buffer)
{
       struct buf_queue *qentry;

       /* check on every call or only every n-th time? */
       message_queue_purge(f, 0, PURGE_BY_PRIORITY);

       while (!(qentry = malloc(sizeof(*qentry)))
           && message_queue_purge(f, 1, PURGE_OLDEST))
               continue;
       if (!qentry) {
               logerror("Unable to allocate memory");
               DPRINTF(D_BUFFER, "queue empty, no memory, msg dropped\n");
               return NULL;
       } else {
               qentry->msg = buffer;
               f->f_qelements++;
               f->f_qsize += buf_queue_obj_size(qentry);
               STAILQ_INSERT_TAIL(&f->f_qhead, qentry, entries);

               DPRINTF(D_BUFFER, "msg @%p queued @%p, qlen = %zu\n",
                   buffer, f, f->f_qelements);
               return qentry;
       }
}

void
message_queue_freeall(struct filed *f)
{
       struct buf_queue *qentry;

       if (!f) return;
       DPRINTF(D_MEM, "message_queue_freeall(f@%p) with f_qhead@%p\n", f,
           &f->f_qhead);

       while (!STAILQ_EMPTY(&f->f_qhead)) {
               qentry = STAILQ_FIRST(&f->f_qhead);
               STAILQ_REMOVE(&f->f_qhead, qentry, buf_queue, entries);
               DELREF(qentry->msg);
               FREEPTR(qentry);
       }

       f->f_qelements = 0;
       f->f_qsize = 0;
}

#ifndef DISABLE_TLS
/* utility function for tls_reconnect() */
struct filed *
get_f_by_conninfo(struct tls_conn_settings *conn_info)
{
       struct filed *f;

       for (f = Files; f; f = f->f_next) {
               if ((f->f_type == F_TLS) && f->f_un.f_tls.tls_conn == conn_info)
                       return f;
       }
       DPRINTF(D_TLS, "get_f_by_conninfo() called on invalid conn_info\n");
       return NULL;
}

/*
* Called on signal.
* Lets the admin reconnect without waiting for the reconnect timer expires.
*/
/*ARGSUSED*/
void
dispatch_force_tls_reconnect(int fd, short event, void *ev)
{
       struct filed *f;
       DPRINTF((D_TLS|D_CALL|D_EVENT), "dispatch_force_tls_reconnect()\n");
       for (f = Files; f; f = f->f_next) {
               if (f->f_type == F_TLS &&
                   f->f_un.f_tls.tls_conn->state == ST_NONE)
                       tls_reconnect(fd, event, f->f_un.f_tls.tls_conn);
       }
}
#endif /* !DISABLE_TLS */

/*
* return a timestamp in a static buffer,
* either format the timestamp given by parameter in_now
* or use the current time if in_now is NULL.
*/
char *
make_timestamp(time_t *in_now, bool iso, size_t tlen)
{
       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 = tv.tv_sec;
       }

       if (!iso) {
               strlcpy(timestamp, ctime(&mytime) + 4, sizeof(timestamp));
               timestamp[BSD_TIMESTAMPLEN] = '\0';
       } else {
               localtime_r(&mytime, &ltime);
               len += strftime(timestamp, sizeof(timestamp), "%FT%T", &ltime);
               snprintf(&timestamp[len], frac_digits + 2, ".%.*jd",
                   frac_digits, (intmax_t)tv.tv_usec);
               len += frac_digits + 1;
               tzlen = strftime(&timestamp[len], sizeof(timestamp) - len, "%z",
                   &ltime);
               len += tzlen;

               if (tzlen == 5) {
                       /* strftime gives "+0200", but we need "+02:00" */
                       timestamp[len + 2] = '\0';
                       timestamp[len + 1] = timestamp[len];
                       timestamp[len] = timestamp[len - 1];
                       timestamp[len - 1] = timestamp[len - 2];
                       timestamp[len - 2] = ':';
               }
       }

       switch (tlen) {
       case (size_t)-1:
               return timestamp;
       case 0:
               return strdup(timestamp);
       default:
               return strndup(timestamp, tlen);
       }
}

/* auxiliary 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;
}


#ifdef NDEBUG
/*
* -d also controls daemoniziation, so it makes sense even with
* NDEBUG, but if the user also tries to specify the logging details
* with an argument, warn them it's not compiled into this binary.
*/
void
set_debug(const char *level)
{
       Debug = D_DEFAULT;
       if (level == NULL)
               return;

       /* don't bother parsing the argument */
       fprintf(stderr,
               "%s: debug logging is not compiled\n",
               getprogname());
}

#else  /* !NDEBUG */
void
set_debug(const char *level)
{
       if (level == NULL) {
               Debug = D_DEFAULT; /* compat */
               return;
       }

       /* skip initial whitespace for consistency with strto*l */
       while (isspace((unsigned char)*level))
               ++level;

       /* accept ~num to mean "all except num" */
       bool invert = level[0] == '~';
       if (invert)
               ++level;

       errno = 0;
       char *endp = NULL;
       unsigned long bits = strtoul(level, &endp, 0);
       if (errno || endp == level || *endp != '\0') {
               fprintf(stderr, "%s: bad argument to -d\n", getprogname());
               usage();
       }
       if (invert)
               bits = ~bits;

       Debug = bits & D_ALL;

       /*
        * make it possible to use -d to stay in the foreground but
        * suppress all dbprintf output (there better be free bits in
        * typeof(Debug) that are not in D_ALL).
        */
       if (Debug == 0)
               Debug = ~D_ALL;
}
#endif  /* !NDEBUG */


#ifndef NDEBUG
void
dbprintf(const char *fname, const char *funname,
   size_t lnum, const char *fmt, ...)
{
       va_list ap;
       char *ts;

       ts = make_timestamp(NULL, true, (size_t)-1);
       printf("%s:%s:%s:%.4zu\t", ts, fname, funname, lnum);

       va_start(ap, fmt);
       vprintf(fmt, ap);
       va_end(ap);
}
#endif