Implement sending syslog messages over TLS.
authorbluhm <bluhm@openbsd.org>
Sun, 18 Jan 2015 19:37:59 +0000 (19:37 +0000)
committerbluhm <bluhm@openbsd.org>
Sun, 18 Jan 2015 19:37:59 +0000 (19:37 +0000)
OK reyk@

usr.sbin/syslogd/Makefile
usr.sbin/syslogd/evbuffer_tls.c [new file with mode: 0644]
usr.sbin/syslogd/evbuffer_tls.h [new file with mode: 0644]
usr.sbin/syslogd/syslogd.c

index 98add7b..236d683 100644 (file)
@@ -1,9 +1,9 @@
-#      $OpenBSD: Makefile,v 1.6 2014/10/05 18:14:01 bluhm Exp $
+#      $OpenBSD: Makefile,v 1.7 2015/01/18 19:37:59 bluhm Exp $
 
 PROG=  syslogd
-SRCS=  syslogd.c ttymsg.c privsep.c privsep_fdpass.c ringbuf.c
+SRCS=  syslogd.c ttymsg.c privsep.c privsep_fdpass.c ringbuf.c evbuffer_tls.c
 MAN=   syslogd.8 syslog.conf.5
-LDADD= -levent
-DPADD= ${LIBEVENT}
+LDADD= -levent -ltls -lssl -lcrypto
+DPADD= ${LIBEVENT} ${LIBTLS} ${LIBSSL} ${LIBCRYPTO}
 
 .include <bsd.prog.mk>
diff --git a/usr.sbin/syslogd/evbuffer_tls.c b/usr.sbin/syslogd/evbuffer_tls.c
new file mode 100644 (file)
index 0000000..d593489
--- /dev/null
@@ -0,0 +1,357 @@
+/*     $OpenBSD: evbuffer_tls.c,v 1.1 2015/01/18 19:37:59 bluhm Exp $ */
+
+/*
+ * Copyright (c) 2002-2004 Niels Provos <provos@citi.umich.edu>
+ * Copyright (c) 2014-2015 Alexander Bluhm <bluhm@openbsd.org>
+ * 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. The name of the author may not be used to endorse or promote products
+ *    derived from this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR 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/types.h>
+#include <sys/time.h>
+#include <sys/ioctl.h>
+
+#include <errno.h>
+#include <event.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+#include <tls.h>
+
+#include "evbuffer_tls.h"
+
+/* prototypes */
+
+void bufferevent_read_pressure_cb(struct evbuffer *, size_t, size_t, void *);
+int evtls_read(struct evbuffer *, int, int, struct tls *);
+int evtls_write(struct evbuffer *, int, struct tls *);
+
+static int
+bufferevent_add(struct event *ev, int timeout)
+{
+       struct timeval tv, *ptv = NULL;
+
+       if (timeout) {
+               timerclear(&tv);
+               tv.tv_sec = timeout;
+               ptv = &tv;
+       }
+
+       return (event_add(ev, ptv));
+}
+
+static void
+buffertls_readcb(int fd, short event, void *arg)
+{
+       struct buffertls *buftls = arg;
+       struct bufferevent *bufev = buftls->bt_bufev;
+       struct tls *ctx = buftls->bt_ctx;
+       int res = 0;
+       short what = EVBUFFER_READ;
+       size_t len;
+       int howmuch = -1;
+
+       if (event == EV_TIMEOUT) {
+               what |= EVBUFFER_TIMEOUT;
+               goto error;
+       }
+
+       /*
+        * If we have a high watermark configured then we don't want to
+        * read more data than would make us reach the watermark.
+        */
+       if (bufev->wm_read.high != 0) {
+               howmuch = bufev->wm_read.high - EVBUFFER_LENGTH(bufev->input);
+               /* we might have lowered the watermark, stop reading */
+               if (howmuch <= 0) {
+                       struct evbuffer *buf = bufev->input;
+                       event_del(&bufev->ev_read);
+                       evbuffer_setcb(buf,
+                           bufferevent_read_pressure_cb, bufev);
+                       return;
+               }
+       }
+
+       res = evtls_read(bufev->input, fd, howmuch, ctx);
+       switch (res) {
+       case TLS_READ_AGAIN:
+               event_set(&bufev->ev_read, fd, EV_READ, buffertls_readcb,
+                   buftls);
+               goto reschedule;
+       case TLS_WRITE_AGAIN:
+               event_set(&bufev->ev_read, fd, EV_WRITE, buffertls_readcb,
+                   buftls);
+               goto reschedule;
+       case -1:
+               if (errno == EAGAIN || errno == EINTR)
+                       goto reschedule;
+               /* error case */
+               what |= EVBUFFER_ERROR;
+               break;
+       case 0:
+               /* eof case */
+               what |= EVBUFFER_EOF;
+               break;
+       }
+       if (res <= 0)
+               goto error;
+
+       event_set(&bufev->ev_read, fd, EV_READ, buffertls_readcb, buftls);
+       bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+
+       /* See if this callbacks meets the water marks */
+       len = EVBUFFER_LENGTH(bufev->input);
+       if (bufev->wm_read.low != 0 && len < bufev->wm_read.low)
+               return;
+       if (bufev->wm_read.high != 0 && len >= bufev->wm_read.high) {
+               struct evbuffer *buf = bufev->input;
+               event_del(&bufev->ev_read);
+
+               /* Now schedule a callback for us when the buffer changes */
+               evbuffer_setcb(buf, bufferevent_read_pressure_cb, bufev);
+       }
+
+       /* Invoke the user callback - must always be called last */
+       if (bufev->readcb != NULL)
+               (*bufev->readcb)(bufev, bufev->cbarg);
+       return;
+
+ reschedule:
+       bufferevent_add(&bufev->ev_read, bufev->timeout_read);
+       return;
+
+ error:
+       (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+buffertls_writecb(int fd, short event, void *arg)
+{
+       struct buffertls *buftls = arg;
+       struct bufferevent *bufev = buftls->bt_bufev;
+       struct tls *ctx = buftls->bt_ctx;
+       int res = 0;
+       short what = EVBUFFER_WRITE;
+
+       if (event == EV_TIMEOUT) {
+               what |= EVBUFFER_TIMEOUT;
+               goto error;
+       }
+
+       if (EVBUFFER_LENGTH(bufev->output) != 0) {
+               res = evtls_write(bufev->output, fd, ctx);
+               switch (res) {
+               case TLS_READ_AGAIN:
+                       event_set(&bufev->ev_write, fd, EV_READ,
+                           buffertls_writecb, buftls);
+                       goto reschedule;
+               case TLS_WRITE_AGAIN:
+                       event_set(&bufev->ev_write, fd, EV_WRITE,
+                           buffertls_writecb, buftls);
+                       goto reschedule;
+               case -1:
+                       if (errno == EAGAIN || errno == EINTR ||
+                           errno == EINPROGRESS)
+                               goto reschedule;
+                       /* error case */
+                       what |= EVBUFFER_ERROR;
+                       break;
+               case 0:
+                       /* eof case */
+                       what |= EVBUFFER_EOF;
+                       break;
+               }
+               if (res <= 0)
+                       goto error;
+       }
+
+       event_set(&bufev->ev_write, fd, EV_WRITE, buffertls_writecb, buftls);
+       if (EVBUFFER_LENGTH(bufev->output) != 0)
+               bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+
+       /*
+        * Invoke the user callback if our buffer is drained or below the
+        * low watermark.
+        */
+       if (bufev->writecb != NULL &&
+           EVBUFFER_LENGTH(bufev->output) <= bufev->wm_write.low)
+               (*bufev->writecb)(bufev, bufev->cbarg);
+
+       return;
+
+ reschedule:
+       if (EVBUFFER_LENGTH(bufev->output) != 0)
+               bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+       return;
+
+ error:
+       (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+static void
+buffertls_connectcb(int fd, short event, void *arg)
+{
+       struct buffertls *buftls = arg;
+       struct bufferevent *bufev = buftls->bt_bufev;
+       struct tls *ctx = buftls->bt_ctx;
+       const char *hostname = buftls->bt_hostname;
+       int res = 0;
+       short what = EVBUFFER_CONNECT;
+
+       if (event == EV_TIMEOUT) {
+               what |= EVBUFFER_TIMEOUT;
+               goto error;
+       }
+
+       res = tls_connect_socket(ctx, fd, hostname);
+       switch (res) {
+       case TLS_READ_AGAIN:
+               event_set(&bufev->ev_write, fd, EV_READ,
+                   buffertls_connectcb, buftls);
+               goto reschedule;
+       case TLS_WRITE_AGAIN:
+               event_set(&bufev->ev_write, fd, EV_WRITE,
+                   buffertls_connectcb, buftls);
+               goto reschedule;
+       case -1:
+               if (errno == EAGAIN || errno == EINTR ||
+                   errno == EINPROGRESS)
+                       goto reschedule;
+               /* error case */
+               what |= EVBUFFER_ERROR;
+               break;
+       }
+       if (res < 0)
+               goto error;
+
+       /*
+        * There might be data available in the tls layer.  Try
+        * an read operation and setup the callbacks.  Call the read
+        * callback after enabling the write callback to give the
+        * read error handler a chance to disable the write event.
+        */
+       event_set(&bufev->ev_write, fd, EV_WRITE, buffertls_writecb, buftls);
+       if (EVBUFFER_LENGTH(bufev->output) != 0)
+               bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+       buffertls_readcb(fd, 0, buftls);
+
+       return;
+
+ reschedule:
+       bufferevent_add(&bufev->ev_write, bufev->timeout_write);
+       return;
+
+ error:
+       (*bufev->errorcb)(bufev, what, bufev->cbarg);
+}
+
+void
+buffertls_set(struct buffertls *buftls, struct bufferevent *bufev,
+    struct tls *ctx, int fd)
+{
+       bufferevent_setfd(bufev, fd);
+       event_set(&bufev->ev_read, fd, EV_READ, buffertls_readcb, buftls);
+       event_set(&bufev->ev_write, fd, EV_WRITE, buffertls_writecb, buftls);
+       buftls->bt_bufev = bufev;
+       buftls->bt_ctx = ctx;
+}
+
+void
+buffertls_connect(struct buffertls *buftls, int fd, const char *hostname)
+{
+       struct bufferevent *bufev = buftls->bt_bufev;
+
+       event_del(&bufev->ev_read);
+       event_del(&bufev->ev_write);
+
+       buftls->bt_hostname = hostname;
+       buffertls_connectcb(fd, 0, buftls);
+}
+
+/*
+ * Reads data from a file descriptor into a buffer.
+ */
+
+#define EVBUFFER_MAX_READ      4096
+
+int
+evtls_read(struct evbuffer *buf, int fd, int howmuch, struct tls *ctx)
+{
+       u_char *p;
+       size_t len, oldoff = buf->off;
+       int n = EVBUFFER_MAX_READ;
+
+       if (ioctl(fd, FIONREAD, &n) == -1 || n <= 0) {
+               n = EVBUFFER_MAX_READ;
+       } else if (n > EVBUFFER_MAX_READ && n > howmuch) {
+               /*
+                * It's possible that a lot of data is available for
+                * reading.  We do not want to exhaust resources
+                * before the reader has a chance to do something
+                * about it.  If the reader does not tell us how much
+                * data we should read, we artifically limit it.
+                */
+               if ((size_t)n > buf->totallen << 2)
+                       n = buf->totallen << 2;
+               if (n < EVBUFFER_MAX_READ)
+                       n = EVBUFFER_MAX_READ;
+       }
+       if (howmuch < 0 || howmuch > n)
+               howmuch = n;
+
+       /* If we don't have FIONREAD, we might waste some space here */
+       if (evbuffer_expand(buf, howmuch) == -1)
+               return (-1);
+
+       /* We can append new data at this point */
+       p = buf->buffer + buf->off;
+
+       n = tls_read(ctx, p, howmuch, &len);
+       if (n < 0 || len == 0)
+               return (n);
+
+       buf->off += len;
+
+       /* Tell someone about changes in this buffer */
+       if (buf->off != oldoff && buf->cb != NULL)
+               (*buf->cb)(buf, oldoff, buf->off, buf->cbarg);
+
+       return (len);
+}
+
+int
+evtls_write(struct evbuffer *buffer, int fd, struct tls *ctx)
+{
+       size_t len;
+       int n;
+
+       n = tls_write(ctx, buffer->buffer, buffer->off, &len);
+       if (n < 0 || len == 0)
+               return (n);
+
+       evbuffer_drain(buffer, len);
+
+       return (len);
+}
diff --git a/usr.sbin/syslogd/evbuffer_tls.h b/usr.sbin/syslogd/evbuffer_tls.h
new file mode 100644 (file)
index 0000000..ba81f80
--- /dev/null
@@ -0,0 +1,37 @@
+/*     $OpenBSD: evbuffer_tls.h,v 1.1 2015/01/18 19:37:59 bluhm Exp $ */
+
+/*
+ * Copyright (c) 2014-2015 Alexander Bluhm <bluhm@openbsd.org>
+ *
+ * Permission to use, copy, modify, and distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#ifndef _EVBUFFER_TLS_H_
+#define _EVBUFFER_TLS_H_
+
+#define EVBUFFER_CONNECT       0x80
+
+struct bufferevent;
+struct tls;
+
+struct buffertls {
+       struct bufferevent      *bt_bufev;
+       struct tls              *bt_ctx;
+       const char              *bt_hostname;
+};
+
+void   buffertls_set(struct buffertls *, struct bufferevent *, struct tls *,
+    int);
+void   buffertls_connect(struct buffertls *, int, const char *);
+
+#endif /* _EVBUFFER_TLS_H_ */
index c94936e..915cba5 100644 (file)
@@ -1,4 +1,4 @@
-/*     $OpenBSD: syslogd.c,v 1.142 2015/01/16 06:40:21 deraadt Exp $   */
+/*     $OpenBSD: syslogd.c,v 1.143 2015/01/18 19:37:59 bluhm Exp $     */
 
 /*
  * Copyright (c) 1983, 1988, 1993, 1994
@@ -50,7 +50,7 @@
  * extensive changes by Ralph Campbell
  * more extensive changes by Eric Allman (again)
  * memory buffer logging by Damien Miller
- * IPv6, libevent, sending via TCP by Alexander Bluhm
+ * IPv6, libevent, sending over TCP and TLS by Alexander Bluhm
  */
 
 #define        MAXLINE         1024            /* maximum line length */
@@ -91,6 +91,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
+#include <tls.h>
 #include <unistd.h>
 #include <limits.h>
 #include <utmp.h>
 #include <sys/syslog.h>
 
 #include "syslogd.h"
+#include "evbuffer_tls.h"
 
 char *ConfFile = _PATH_LOGCONF;
 const char ctty[] = _PATH_CONSOLE;
@@ -135,9 +137,11 @@ struct filed {
                struct {
                        char    f_loghost[1+4+3+1+HOST_NAME_MAX+1+1+NI_MAXSERV];
                                /* @proto46://[hostname]:servname\0 */
-                       struct sockaddr_storage f_addr;
+                       struct sockaddr_storage  f_addr;
+                       struct buffertls         f_buftls;
                        struct bufferevent      *f_bufev;
-                       int     f_reconnectwait;
+                       struct tls              *f_ctx;
+                       int                      f_reconnectwait;
                } f_forw;               /* forwarding address */
                char    f_fname[PATH_MAX];
                struct {
@@ -182,11 +186,12 @@ int       repeatinterval[] = { 30, 120, 600 };    /* # of secs before flush */
 #define F_MEMBUF       7               /* memory buffer */
 #define F_PIPE         8               /* pipe to external program */
 #define F_FORWTCP      9               /* remote machine via TCP */
+#define F_FORWTLS      10              /* remote machine via TLS */
 
 char   *TypeNames[] = {
        "UNUSED",       "FILE",         "TTY",          "CONSOLE",
        "FORWUDP",      "USERS",        "WALL",         "MEMBUF",
-       "PIPE",         "FORWTCP",
+       "PIPE",         "FORWTCP",      "FORWTLS",
 };
 
 SIMPLEQ_HEAD(filed_list, filed) Files;
@@ -271,6 +276,7 @@ void         tcp_readcb(struct bufferevent *, void *);
 void    tcp_writecb(struct bufferevent *, void *);
 void    tcp_errorcb(struct bufferevent *, short, void *);
 void    tcp_connectcb(int, short, void *);
+struct tls *tls_socket(struct filed *);
 void    die_signalcb(int, short, void *);
 void    mark_timercb(int, short, void *);
 void    init_signalcb(int, short, void *);
@@ -715,8 +721,7 @@ tcp_readcb(struct bufferevent *bufev, void *arg)
         * Drop data received from the forward log server.
         */
        dprintf("loghost \"%s\" did send %zu bytes back\n",
-           f->f_un.f_forw.f_loghost,
-           EVBUFFER_LENGTH(f->f_un.f_forw.f_bufev->input));
+           f->f_un.f_forw.f_loghost, EVBUFFER_LENGTH(bufev->input));
        evbuffer_drain(bufev->input, -1);
 }
 
@@ -745,10 +750,16 @@ tcp_errorcb(struct bufferevent *bufev, short event, void *arg)
        else
                snprintf(ebuf, sizeof(ebuf),
                    "syslogd: loghost \"%s\" connection error: %s",
-                   f->f_un.f_forw.f_loghost, strerror(errno));
+                   f->f_un.f_forw.f_loghost, f->f_un.f_forw.f_ctx ?
+                   tls_error(f->f_un.f_forw.f_ctx) : strerror(errno));
        dprintf("%s\n", ebuf);
 
        /* The SIGHUP handler may also close the socket, so invalidate it. */
+       if (f->f_un.f_forw.f_ctx) {
+               tls_close(f->f_un.f_forw.f_ctx);
+               tls_free(f->f_un.f_forw.f_ctx);
+               f->f_un.f_forw.f_ctx = NULL;
+       }
        close(f->f_file);
        f->f_file = -1;
 
@@ -768,6 +779,7 @@ tcp_connectcb(int fd, short event, void *arg)
 {
        struct filed            *f = arg;
        struct bufferevent      *bufev = f->f_un.f_forw.f_bufev;
+       struct tls              *ctx;
        struct timeval           to;
        int                      s;
 
@@ -780,8 +792,9 @@ tcp_connectcb(int fd, short event, void *arg)
 
        if ((s = tcp_socket(f)) == -1)
                goto retry;
+       dprintf("tcp connect callback: socket success, event %#x\n", event);
+       f->f_file = s;
 
-       dprintf("tcp connect callback: success, event %#x\n", event);
        bufferevent_setfd(bufev, s);
        bufferevent_setcb(bufev, tcp_readcb, tcp_writecb, tcp_errorcb, f);
        /*
@@ -789,7 +802,20 @@ tcp_connectcb(int fd, short event, void *arg)
         * the socket to detect connection close and errors.
         */
        bufferevent_enable(bufev, EV_READ|EV_WRITE);
-       f->f_file = s;
+
+       if (f->f_type == F_FORWTLS) {
+               if ((ctx = tls_socket(f)) == NULL) {
+                       close(f->f_file);
+                       f->f_file = -1;
+                       goto retry;
+               }
+               dprintf("tcp connect callback: TLS context success\n");
+               f->f_un.f_forw.f_ctx = ctx;
+
+               buffertls_set(&f->f_un.f_forw.f_buftls, bufev, ctx, s);
+               /* XXX no host given */
+               buffertls_connect(&f->f_un.f_forw.f_buftls, s, NULL);
+       }
 
        return;
 
@@ -808,6 +834,46 @@ tcp_connectcb(int fd, short event, void *arg)
        evtimer_add(&bufev->ev_write, &to);
 }
 
+struct tls *
+tls_socket(struct filed *f)
+{
+       static struct tls_config *config;
+       struct tls      *ctx;
+       char             ebuf[100];
+
+       if (config == NULL) {
+               if (tls_init() < 0) {
+                       snprintf(ebuf, sizeof(ebuf), "tls_init \"%s\"",
+                           f->f_un.f_forw.f_loghost);
+                       logerror(ebuf);
+                       return (NULL);
+               }
+               if ((config = tls_config_new()) == NULL) {
+                       snprintf(ebuf, sizeof(ebuf), "tls_config_new \"%s\"",
+                           f->f_un.f_forw.f_loghost);
+                       logerror(ebuf);
+                       return (NULL);
+               }
+               /* XXX No verify for now, ca certs are outside of privsep. */
+               tls_config_insecure_noverifyhost(config);
+               tls_config_insecure_noverifycert(config);
+       }
+       if ((ctx = tls_client()) == NULL) {
+               snprintf(ebuf, sizeof(ebuf), "tls_client \"%s\"",
+                   f->f_un.f_forw.f_loghost);
+               logerror(ebuf);
+               return (NULL);
+       }
+       if (tls_configure(ctx, config) < 0) {
+               snprintf(ebuf, sizeof(ebuf), "tls_configure \"%s\": %s",
+                   f->f_un.f_forw.f_loghost, tls_error(ctx));
+               logerror(ebuf);
+               tls_free(ctx);
+               return (NULL);
+       }
+       return (ctx);
+}
+
 void
 usage(void)
 {
@@ -1114,6 +1180,7 @@ fprintlog(struct filed *f, int flags, char *msg)
                break;
 
        case F_FORWTCP:
+       case F_FORWTLS:
                dprintf(" %s\n", f->f_un.f_forw.f_loghost);
                if (EVBUFFER_LENGTH(f->f_un.f_forw.f_bufev->output) >=
                    MAX_TCPBUF)
@@ -1402,6 +1469,12 @@ init(void)
                        fprintlog(f, 0, (char *)NULL);
 
                switch (f->f_type) {
+               case F_FORWTLS:
+                       if (f->f_un.f_forw.f_ctx) {
+                               tls_close(f->f_un.f_forw.f_ctx);
+                               tls_free(f->f_un.f_forw.f_ctx);
+                       }
+                       /* FALLTHROUGH */
                case F_FORWTCP:
                        /* XXX Save messages in output buffer for reconnect. */
                        bufferevent_free(f->f_un.f_forw.f_bufev);
@@ -1542,6 +1615,7 @@ init(void)
 
                        case F_FORWUDP:
                        case F_FORWTCP:
+                       case F_FORWTLS:
                                printf("%s", f->f_un.f_forw.f_loghost);
                                break;
 
@@ -1604,9 +1678,10 @@ cfline(char *line, char *prog)
 {
        int i, pri;
        size_t rb_len;
-       char *bp, *p, *q, *proto, *host, *port;
+       char *bp, *p, *q, *proto, *host, *port, *ipproto;
        char buf[MAXLINE], ebuf[100];
        struct filed *xf, *f, *d;
+       struct timeval to;
 
        dprintf("cfline(\"%s\", f, \"%s\")\n", line, prog);
 
@@ -1714,11 +1789,13 @@ cfline(char *line, char *prog)
                }
                if (proto == NULL)
                        proto = "udp";
+               ipproto = proto;
                if (strcmp(proto, "udp") == 0) {
                        if (fd_udp == -1)
                                proto = "udp6";
                        if (fd_udp6 == -1)
                                proto = "udp4";
+                       ipproto = proto;
                } else if (strcmp(proto, "udp4") == 0) {
                        if (fd_udp == -1) {
                                snprintf(ebuf, sizeof(ebuf), "no udp4 \"%s\"",
@@ -1736,6 +1813,12 @@ cfline(char *line, char *prog)
                } else if (strcmp(proto, "tcp") == 0 ||
                    strcmp(proto, "tcp4") == 0 || strcmp(proto, "tcp6") == 0) {
                        ;
+               } else if (strcmp(proto, "tls") == 0) {
+                       ipproto = "tcp";
+               } else if (strcmp(proto, "tls4") == 0) {
+                       ipproto = "tcp4";
+               } else if (strcmp(proto, "tls6") == 0) {
+                       ipproto = "tcp6";
                } else {
                        snprintf(ebuf, sizeof(ebuf), "bad protocol \"%s\"",
                            f->f_un.f_forw.f_loghost);
@@ -1749,14 +1832,15 @@ cfline(char *line, char *prog)
                        break;
                }
                if (port == NULL)
-                       port = "syslog";
+                       port = strncmp(proto, "tls", 3) == 0 ?
+                           "syslog-tls" : "syslog";
                if (strlen(port) >= NI_MAXSERV) {
                        snprintf(ebuf, sizeof(ebuf), "port too long \"%s\"",
                            f->f_un.f_forw.f_loghost);
                        logerror(ebuf);
                        break;
                }
-               if (priv_getaddrinfo(proto, host, port,
+               if (priv_getaddrinfo(ipproto, host, port,
                    (struct sockaddr*)&f->f_un.f_forw.f_addr,
                    sizeof(f->f_un.f_forw.f_addr)) != 0) {
                        snprintf(ebuf, sizeof(ebuf), "bad hostname \"%s\"",
@@ -1775,7 +1859,7 @@ cfline(char *line, char *prog)
                                break;
                        }
                        f->f_type = F_FORWUDP;
-               } else if (strncmp(proto, "tcp", 3) == 0) {
+               } else if (strncmp(ipproto, "tcp", 3) == 0) {
                        if ((f->f_un.f_forw.f_bufev = bufferevent_new(-1,
                            tcp_readcb, tcp_writecb, tcp_errorcb, f)) == NULL) {
                                snprintf(ebuf, sizeof(ebuf),
@@ -1784,8 +1868,23 @@ cfline(char *line, char *prog)
                                logerror(ebuf);
                                break;
                        }
-                       f->f_type = F_FORWTCP;
-                       tcp_connectcb(-1, 0, f);
+                       if (strncmp(proto, "tls", 3) == 0) {
+                               f->f_type = F_FORWTLS;
+                       } else {
+                               f->f_type = F_FORWTCP;
+                       }
+                       /*
+                        * If we try to connect to a TLS server immediately
+                        * syslogd gets an SIGPIPE as the signal handlers have
+                        * not been set up.  Delay the connection until the
+                        * event loop is started.  We can reuse the write event
+                        * for that as bufferevent is still disabled.
+                        */
+                       to.tv_sec = 0;
+                       to.tv_usec = 1;
+                       evtimer_set(&f->f_un.f_forw.f_bufev->ev_write,
+                           tcp_connectcb, f);
+                       evtimer_add(&f->f_un.f_forw.f_bufev->ev_write, &to);
                }
                break;