root/usr.sbin/syslogd/syslogd.c
/*      $NetBSD: syslogd.c,v 1.155 2026/07/03 10:18:05 msaitoh 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.155 2026/07/03 10:18:05 msaitoh 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.
 */
static char *linebuf, *klog_linebuf;
static 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);
        SLIST_INIT(&tls_opt.fprint_head);
        SLIST_INIT(&tls_opt.cert_head);
#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 EBUSY:
                                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);
                FREEPTR(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);
                FREEPTR(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);
                FREEPTR(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_cadir",             &tls_opt.CAdir},
                {"tls_ca",                &tls_opt.CAfile},
                {"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;
                FREEPTR(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);
                FREEPTR(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 element 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 != appname)
                sum += strlen(qentry->msg->prog)+1;
        if (qentry->msg->pid
            && qentry->msg->pid != include_pid)
                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