[systemd-commits] 16 commits - configure.ac Makefile.am Makefile-man.am man/systemctl.xml man/systemd-journal-remote.xml src/activate src/journal src/shared src/tmpfiles

Zbigniew Jędrzejewski-Szmek zbyszek at kemper.freedesktop.org
Sun Mar 16 22:58:23 PDT 2014


 Makefile-man.am                    |    4 
 Makefile.am                        |   37 +
 configure.ac                       |   15 
 man/systemctl.xml                  |   21 
 man/systemd-journal-remote.xml     |  314 +++++++++
 src/activate/activate.c            |   21 
 src/journal/coredump.c             |    2 
 src/journal/journal-gatewayd.c     |  151 ++--
 src/journal/journal-remote-parse.c |  429 ++++++++++++
 src/journal/journal-remote-parse.h |   61 +
 src/journal/journal-remote-write.c |  124 +++
 src/journal/journal-remote-write.h |   51 +
 src/journal/journal-remote.c       | 1243 +++++++++++++++++++++++++++++++++++++
 src/journal/journald-native.c      |   19 
 src/journal/journald-native.h      |    7 
 src/journal/journald-server.c      |   93 +-
 src/journal/journald.c             |    1 
 src/journal/microhttpd-util.c      |  233 ++++++
 src/journal/microhttpd-util.h      |   26 
 src/shared/mkdir.c                 |    2 
 src/shared/mkdir.h                 |    1 
 src/shared/socket-label.c          |   30 
 src/shared/socket-util.h           |    1 
 src/tmpfiles/tmpfiles.c            |   13 
 24 files changed, 2726 insertions(+), 173 deletions(-)

New commits:
commit 9029f64298cb70c12ecf638fddee7f41b056fcf1
Author: Jason St. John <jstjohn at purdue.edu>
Date:   Mon Mar 17 00:03:46 2014 -0400

    man: improve wording of systemctl's --after/--before
    
    Commit 4a77ca7 was an attempt at fixing the wording of --after and --before,
    but the new wording was unclear.
    
    Split the combined --after/--before section into a separate section for
    each, explicitly state what each option does, and add information about
    how these lists are generated.
    
    Reported-by: Andrey Borzenkov <arvidjaar at gmail.com>
    Reported-by: Lennart Poettering <lennart at poettering.net>

diff --git a/man/systemctl.xml b/man/systemctl.xml
index cd27c4a..77447dd 100644
--- a/man/systemctl.xml
+++ b/man/systemctl.xml
@@ -157,12 +157,27 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
 
       <varlistentry>
         <term><option>--after</option></term>
+
+        <listitem>
+          <para>With <command>list-dependencies</command>, show the
+          units that are ordered before the specified unit. In other
+          words, list the units that are in the <varname>After=</varname>
+          directive of the specified unit, have the specified unit in
+          their <varname>Before=</varname> directive, or are otherwise
+          implicit dependencies of the specified unit.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><option>--before</option></term>
 
         <listitem>
-          <para>Show after (before) which units the specified unit is started
-          with <command>list-dependencies</command>.
-          </para>
+          <para>With <command>list-dependencies</command>, show the
+          units that are ordered after the specified unit. In other
+          words, list the units that are in the <varname>Before=</varname>
+          directive of the specified unit, have the specified unit in
+          their <varname>After=</varname> directive, or otherwise depend
+          on the specified unit.</para>
         </listitem>
       </varlistentry>
 

commit 3f573096641cf3d350c8779971c644ca74aab93a
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Mar 17 00:13:48 2014 -0400

    build-sys: bump required µhttpd version
    
    MHD_USE_EPOLL_LINUX_ONLY, MHD_USE_DUAL_STACK are only available in
    next-but-last release.

diff --git a/configure.ac b/configure.ac
index 099fdac..9c8481e 100644
--- a/configure.ac
+++ b/configure.ac
@@ -700,7 +700,7 @@ AM_CONDITIONAL(HAVE_QRENCODE, [test "$have_qrencode" = "yes"])
 have_microhttpd=no
 AC_ARG_ENABLE(microhttpd, AS_HELP_STRING([--disable-microhttpd], [disable microhttpd support]))
 if test "x$enable_microhttpd" != "xno"; then
-        PKG_CHECK_MODULES(MICROHTTPD, [libmicrohttpd >= 0.9.5],
+        PKG_CHECK_MODULES(MICROHTTPD, [libmicrohttpd >= 0.9.33],
                 [AC_DEFINE(HAVE_MICROHTTPD, 1, [Define if microhttpd is available]) have_microhttpd=yes], have_microhttpd=no)
         if test "x$have_microhttpd" = xno -a "x$enable_microhttpd" = xyes; then
                 AC_MSG_ERROR([*** microhttpd support requested but libraries not found])

commit 8a8d55f2c104cf99efe5f244fc4857a15e9f60de
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 16 22:33:35 2014 -0400

    journal-remote: implement inheriting http(s) sockets
    
    Now --listen-http=-3 --listen-https=-4 can be used to spawn a µhttpd
    server on those two ports, in http and https modes respectively.
    As before, --listen-http=3 --listen-https=4 will launch µhttpd servers
    on ports 3 and 4.

diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
index ef123ce..40bef05 100644
--- a/man/systemd-journal-remote.xml
+++ b/man/systemd-journal-remote.xml
@@ -143,14 +143,19 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <term><option>--listen-http=<replaceable>ADDRESS</replaceable></option></term>
         <term><option>--listen-https=<replaceable>ADDRESS</replaceable></option></term>
 
-        <listitem><para><replaceable>ADDRESS</replaceable> must be an
+        <listitem><para><replaceable>ADDRESS</replaceable> must be
+        either a negative integer, in which case it will be
+        interpreted as the (negated) file descriptor number, or an
         address suitable for <option>ListenStream=</option> (c.f.
         <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
-        HTTP or HTTPS server will be spawned on this port,
-        respectively, for the first and second options. Currenntly
-        Only POST requests to <filename>/upload</filename> with
-        <literal>Content-Type: application/vnd.fdo.journal</literal>
-        are supported.</para>
+        In the first case, matching file descriptor must be inherited
+        through
+        <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>.
+        In the second case, an HTTP or HTTPS server will be spawned on
+        this port, respectively for <option>--listen-http</option> and
+        <option>--listen-https</option>. Currenntly Only POST requests
+        to <filename>/upload</filename> with <literal>Content-Type:
+        application/vnd.fdo.journal</literal> are supported.</para>
         </listitem>
       </varlistentry>
 
@@ -160,10 +165,15 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
         <listitem><para><command>systemd-journal-remote</command>
         supports the
         <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
-        protocol. Open sockets inherited through socket
-        activation behave like those opened with
-        <option>--listen-raw=</option> described above.
-        </para>
+        protocol. Open sockets inherited through socket activation
+        behave like those opened with <option>--listen-raw=</option>
+        described above, unless they are specified as an argument in
+        <option>--listen-http=-<replaceable>n</replaceable></option>
+        or
+        <option>--listen-https=-<replaceable>n</replaceable></option>
+        above. In this later case, an HTTP or HTTPS server will be
+        spawned using this descriptor and connections must be made
+        over the HTTP protocol.</para>
         </listitem>
       </varlistentry>
 
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
index 7840183..6d7ba6c 100644
--- a/src/journal/journal-remote.c
+++ b/src/journal/journal-remote.c
@@ -57,12 +57,13 @@
 static char* arg_output = NULL;
 static char* arg_url = NULL;
 static char* arg_getter = NULL;
-static bool arg_stdin = false;
 static char* arg_listen_raw = NULL;
 static char* arg_listen_http = NULL;
 static char* arg_listen_https = NULL;
+static char** arg_files = NULL;
 static int arg_compress = true;
 static int arg_seal = false;
+static int http_socket = -1, https_socket = -1;
 
 static char *key_pem = NULL;
 static char *cert_pem = NULL;
@@ -336,12 +337,8 @@ static int add_source(RemoteServer *s, int fd, const char* name) {
         return r;
 }
 
-static int setup_raw_socket(RemoteServer *s, const char *address) {
-        int fd, r;
-
-        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
-        if (fd < 0)
-                return fd;
+static int add_raw_socket(RemoteServer *s, int fd) {
+        int r;
 
         r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
                             dispatch_raw_connection_event, s);
@@ -354,6 +351,16 @@ static int setup_raw_socket(RemoteServer *s, const char *address) {
         return 0;
 }
 
+static int setup_raw_socket(RemoteServer *s, const char *address) {
+        int fd;
+
+        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        return add_raw_socket(s, fd);
+}
+
 /**********************************************************************
  **********************************************************************
  **********************************************************************/
@@ -671,9 +678,24 @@ static int setup_signals(RemoteServer *s) {
         return 0;
 }
 
+static int fd_fd(const char *spec) {
+        int fd, r;
+
+        r = safe_atoi(spec, &fd);
+        if (r < 0)
+                return r;
+
+        if (fd >= 0)
+                return -ENOENT;
+
+        return -fd;
+}
+
+
 static int remoteserver_init(RemoteServer *s) {
         int r, n, fd;
         const char *output_name = NULL;
+        char **file;
 
         assert(s);
 
@@ -692,18 +714,38 @@ static int remoteserver_init(RemoteServer *s) {
         } else
                 log_info("Received %d descriptors", n);
 
+        if (MAX(http_socket, https_socket) >= SD_LISTEN_FDS_START + n) {
+                log_error("Received fewer sockets than expected");
+                return -EBADFD;
+        }
+
         for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
                 if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
-                        assert_not_reached("not implemented");
+                        log_info("Received a listening socket (fd:%d)", fd);
+
+                        if (fd == http_socket)
+                                r = setup_microhttpd_server(s, fd, false);
+                        else if (fd == https_socket)
+                                r = setup_microhttpd_server(s, fd, true);
+                        else
+                                r = add_raw_socket(s, fd);
                 } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
                         log_info("Received a connection socket (fd:%d)", fd);
 
                         r = add_source(s, fd, NULL);
-                        output_name = "socket";
                 } else {
                         log_error("Unknown socket passed on fd:%d", fd);
+
                         return -EINVAL;
                 }
+
+                if(r < 0) {
+                        log_error("Failed to register socket (fd:%d): %s",
+                                  fd, strerror(-r));
+                        return r;
+                }
+
+                output_name = "socket";
         }
 
         if (arg_url) {
@@ -757,13 +799,26 @@ static int remoteserver_init(RemoteServer *s) {
                 output_name = arg_listen_https;
         }
 
-        if (arg_stdin) {
-                log_info("Reading standard input...");
-                r = add_source(s, STDIN_FILENO, "stdin");
+        STRV_FOREACH(file, arg_files) {
+                if (streq(*file, "-")) {
+                        log_info("Reading standard input...");
+
+                        fd = STDIN_FILENO;
+                        output_name = "stdin";
+                } else {
+                        log_info("Reading file %s...", *file);
+
+                        fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                        if (fd < 0) {
+                                log_error("Failed to open %s: %m", *file);
+                                return -errno;
+                        }
+                        output_name = *file;
+                }
+
+                r = add_source(s, fd, output_name);
                 if (r < 0)
                         return r;
-
-                output_name = "stdin";
         }
 
         if (s->active == 0) {
@@ -771,7 +826,7 @@ static int remoteserver_init(RemoteServer *s) {
                 return -EINVAL;
         }
 
-        if (!!n + !!arg_url + !!arg_listen_raw + !!arg_stdin > 1)
+        if (!!n + !!arg_url + !!arg_listen_raw + !!arg_files)
                 output_name = "multiple";
 
         r = writer_init(&s->writer);
@@ -907,7 +962,7 @@ static int dispatch_raw_connection_event(sd_event_source *event,
  **********************************************************************/
 
 static int help(void) {
-        printf("%s [OPTIONS...]\n\n"
+        printf("%s [OPTIONS...] {FILE|-}...\n\n"
                "Write external journal events to a journal file.\n\n"
                "Options:\n"
                "  --url=URL            Read events from systemd-journal-gatewayd at URL\n"
@@ -915,7 +970,6 @@ static int help(void) {
                "  --listen-raw=ADDR    Listen for connections at ADDR\n"
                "  --listen-http=ADDR   Listen for HTTP connections at ADDR\n"
                "  --listen-https=ADDR  Listen for HTTPS connections at ADDR\n"
-               "  --stdin              Read events from standard input\n"
                "  -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
                "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
                "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
@@ -935,7 +989,6 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_LISTEN_RAW,
                 ARG_LISTEN_HTTP,
                 ARG_LISTEN_HTTPS,
-                ARG_STDIN,
                 ARG_GETTER,
                 ARG_COMPRESS,
                 ARG_NO_COMPRESS,
@@ -954,7 +1007,6 @@ static int parse_argv(int argc, char *argv[]) {
                 { "listen-raw",   required_argument, NULL, ARG_LISTEN_RAW   },
                 { "listen-http",  required_argument, NULL, ARG_LISTEN_HTTP  },
                 { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
-                { "stdin",        no_argument,       NULL, ARG_STDIN        },
                 { "output",       required_argument, NULL, 'o'              },
                 { "compress",     no_argument,       NULL, ARG_COMPRESS     },
                 { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
@@ -1010,21 +1062,41 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_LISTEN_HTTP:
-                        if (arg_listen_http) {
+                        if (arg_listen_http || http_socket >= 0) {
                                 log_error("cannot currently use --listen-http more than once");
                                 return -EINVAL;
                         }
 
-                        arg_listen_http = optarg;
+                        r = fd_fd(optarg);
+                        if (r >= 0)
+                                http_socket = r;
+                        else if (r == -ENOENT)
+                                arg_listen_http = optarg;
+                        else {
+                                log_error("Invalid port/fd specification %s: %s",
+                                          optarg, strerror(-r));
+                                return -EINVAL;
+                        }
+
                         break;
 
                 case ARG_LISTEN_HTTPS:
-                        if (arg_listen_https) {
+                        if (arg_listen_https || https_socket >= 0) {
                                 log_error("cannot currently use --listen-https more than once");
                                 return -EINVAL;
                         }
 
-                        arg_listen_https = optarg;
+                        r = fd_fd(optarg);
+                        if (r >= 0)
+                                https_socket = r;
+                        else if (r == -ENOENT)
+                                arg_listen_https = optarg;
+                        else {
+                                log_error("Invalid port/fd specification %s: %s",
+                                          optarg, strerror(-r));
+                                return -EINVAL;
+                        }
+
                         break;
 
                 case ARG_KEY:
@@ -1070,10 +1142,6 @@ static int parse_argv(int argc, char *argv[]) {
                         log_error("Option --trust is not available.");
 #endif
 
-                case ARG_STDIN:
-                        arg_stdin = true;
-                        break;
-
                 case 'o':
                         if (arg_output) {
                                 log_error("cannot use --output/-o more than once");
@@ -1109,10 +1177,8 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
-        if (optind < argc) {
-                log_error("This program takes no positional arguments");
-                return -EINVAL;
-        }
+        if (optind < argc)
+                arg_files = argv + optind;
 
         return 1 /* work to do */;
 }

commit e7216d112aff3ba4ce196db95b86d77d5a1b234e
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 16 20:05:50 2014 -0400

    microhttpd-util: use static buffer for static messages
    
    Most of the messages we send do not require a allocating and
    freeing a buffer, to optimize this by using const strings.
    
    Also, rename respond_error to mhd_respond*, since it is used
    not only for errors.
    
    Make use of information from printf to avoid one extra call to
    strlen.

diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index d47b27e..9a88676 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -450,20 +450,20 @@ static int request_handler_entries(
 
         r = open_journal(m);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
 
         if (request_parse_accept(m, connection) < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
 
         if (request_parse_range(m, connection) < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
 
         if (request_parse_arguments(m, connection) < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
 
         if (m->discrete) {
                 if (!m->cursor)
-                        return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
+                        return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
 
                 m->n_entries = 1;
                 m->n_entries_set = true;
@@ -476,7 +476,7 @@ static int request_handler_entries(
         else if (m->n_skip < 0)
                 r = sd_journal_seek_tail(m->journal);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
 
         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
         if (!response)
@@ -612,14 +612,14 @@ static int request_handler_fields(
 
         r = open_journal(m);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
 
         if (request_parse_accept(m, connection) < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
 
         r = sd_journal_query_unique(m->journal, field);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
+                return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
 
         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
         if (!response)
@@ -678,10 +678,10 @@ static int request_handler_file(
 
         fd = open(path, O_RDONLY|O_CLOEXEC);
         if (fd < 0)
-                return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
+                return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
 
         if (fstat(fd, &st) < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
 
         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
         if (!response)
@@ -745,15 +745,15 @@ static int request_handler_machine(
 
         r = open_journal(m);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
 
         r = sd_id128_get_machine(&mid);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
 
         r = sd_id128_get_boot(&bid);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
 
         hostname = gethostname_malloc();
         if (!hostname)
@@ -761,11 +761,11 @@ static int request_handler_machine(
 
         r = sd_journal_get_usage(m->journal, &usage);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
 
         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
         if (r < 0)
-                return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
+                return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
 
         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
 
@@ -822,8 +822,8 @@ static int request_handler(
         assert(method);
 
         if (!streq(method, "GET"))
-                return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                     "Unsupported method.\n");
+                return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                                   "Unsupported method.\n");
 
 
         if (!*connection_cls) {
@@ -853,7 +853,7 @@ static int request_handler(
         if (streq(url, "/machine"))
                 return request_handler_machine(connection, *connection_cls);
 
-        return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
+        return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
 }
 
 static int help(void) {
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
index 1a1ca2c..7840183 100644
--- a/src/journal/journal-remote.c
+++ b/src/journal/journal-remote.c
@@ -411,7 +411,7 @@ static int process_http_upload(
                 if (r < 0) {
                         log_error("Failed to store received data of size %zu: %s",
                                   *upload_data_size, strerror(-r));
-                        return respond_oom_internal(connection);
+                        return mhd_respond_oom(connection);
                 }
                 *upload_data_size = 0;
         } else
@@ -425,8 +425,8 @@ static int process_http_upload(
                         break;
                 else if (r < 0) {
                         log_warning("Failed to process data for connection %p", connection);
-                        return respond_error(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
-                                             "Processing failed: %s", strerror(-r));
+                        return mhd_respondf(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
+                                            "Processing failed: %s", strerror(-r));
                 }
         }
 
@@ -437,11 +437,11 @@ static int process_http_upload(
 
         if (source_non_empty(source)) {
                 log_warning("EOF reached with incomplete data");
-                return respond_error(connection, MHD_HTTP_EXPECTATION_FAILED,
-                                     "Trailing data not processed.");
+                return mhd_respond(connection, MHD_HTTP_EXPECTATION_FAILED,
+                                   "Trailing data not processed.");
         }
 
-        return respond_error(connection, MHD_HTTP_ACCEPTED, "OK.\n");
+        return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.\n");
 };
 
 static int request_handler(
@@ -470,19 +470,19 @@ static int request_handler(
                                            *connection_cls);
 
         if (!streq(method, "POST"))
-                return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
-                                     "Unsupported method.\n");
+                return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                                   "Unsupported method.\n");
 
         if (!streq(url, "/upload"))
-                return respond_error(connection, MHD_HTTP_NOT_FOUND,
-                                     "Not found.\n");
+                return mhd_respond(connection, MHD_HTTP_NOT_FOUND,
+                                   "Not found.\n");
 
         header = MHD_lookup_connection_value(connection,
                                              MHD_HEADER_KIND, "Content-Type");
         if (!header || !streq(header, "application/vnd.fdo.journal"))
-                return respond_error(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
-                                     "Content-Type: application/vnd.fdo.journal"
-                                     " is required.\n");
+                return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
+                                   "Content-Type: application/vnd.fdo.journal"
+                                   " is required.\n");
 
         if (trust_pem) {
                 r = check_permissions(connection, &code);
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
index f7f12e1..250be93 100644
--- a/src/journal/microhttpd-util.c
+++ b/src/journal/microhttpd-util.c
@@ -48,31 +48,45 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
 }
 
 
-int respond_oom_internal(struct MHD_Connection *connection) {
-        const char *m = "Out of memory.\n";
-
+static int mhd_respond_internal(struct MHD_Connection *connection,
+                                enum MHD_RequestTerminationCode code,
+                                char *buffer,
+                                size_t size,
+                                enum MHD_ResponseMemoryMode mode) {
         struct MHD_Response *response;
-        int ret;
+        int r;
 
         assert(connection);
 
-        response = MHD_create_response_from_buffer(strlen(m), (char*) m, MHD_RESPMEM_PERSISTENT);
+        response = MHD_create_response_from_buffer(size, buffer, mode);
         if (!response)
                 return MHD_NO;
 
+        log_debug("Queing response %u: %s", code, buffer);
         MHD_add_response_header(response, "Content-Type", "text/plain");
-        ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
+        r = MHD_queue_response(connection, code, response);
         MHD_destroy_response(response);
 
-        return ret;
+        return r;
 }
 
-_printf_(3,4)
-int respond_error(struct MHD_Connection *connection,
-                  unsigned code,
-                  const char *format, ...) {
+int mhd_respond(struct MHD_Connection *connection,
+                enum MHD_RequestTerminationCode code,
+                const char *message) {
+
+        return mhd_respond_internal(connection, code,
+                                    (char*) message, strlen(message),
+                                    MHD_RESPMEM_PERSISTENT);
+}
+
+int mhd_respond_oom(struct MHD_Connection *connection) {
+        return mhd_respond(connection, MHD_HTTP_SERVICE_UNAVAILABLE,  "Out of memory.\n");
+}
+
+int mhd_respondf(struct MHD_Connection *connection,
+                 enum MHD_RequestTerminationCode code,
+                 const char *format, ...) {
 
-        struct MHD_Response *response;
         char *m;
         int r;
         va_list ap;
@@ -87,17 +101,9 @@ int respond_error(struct MHD_Connection *connection,
         if (r < 0)
                 return respond_oom(connection);
 
-        response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
-        if (!response) {
+        r = mhd_respond_internal(connection, code, m, r, MHD_RESPMEM_MUST_FREE);
+        if (r == MHD_NO)
                 free(m);
-                return respond_oom(connection);
-        }
-
-        log_debug("Queing response %u: %s", code, m);
-        MHD_add_response_header(response, "Content-Type", "text/plain");
-        r = MHD_queue_response(connection, code, response);
-        MHD_destroy_response(response);
-
         return r;
 }
 
@@ -229,8 +235,8 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
         if (!ci) {
                 log_error("MHD_get_connection_info failed: session is unencrypted");
-                *code = respond_error(connection, MHD_HTTP_FORBIDDEN,
-                                      "Encrypted connection is required");
+                *code = mhd_respond(connection, MHD_HTTP_FORBIDDEN,
+                                    "Encrypted connection is required");
                 return -EPERM;
         }
         session = ci->tls_session;
@@ -238,15 +244,15 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
 
         r = get_client_cert(session, &client_cert);
         if (r < 0) {
-                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
-                                      "Authorization through certificate is required");
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Authorization through certificate is required");
                 return -EPERM;
         }
 
         r = get_auth_dn(client_cert, &buf);
         if (r < 0) {
-                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
-                                      "Failed to determine distinguished name from certificate");
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Failed to determine distinguished name from certificate");
                 return -EPERM;
         }
 
@@ -255,8 +261,8 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
         r = verify_cert_authorized(session);
         if (r < 0) {
                 log_warning("Client is not authorized");
-                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
-                                      "Client certificate not signed by recognized authority");
+                *code = mhd_respond(connection, MHD_HTTP_UNAUTHORIZED,
+                                    "Client certificate not signed by recognized authority");
         }
         return r;
 }
diff --git a/src/journal/microhttpd-util.h b/src/journal/microhttpd-util.h
index cd14ac4..df4d003 100644
--- a/src/journal/microhttpd-util.h
+++ b/src/journal/microhttpd-util.h
@@ -28,14 +28,18 @@
 
 void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
 
-int respond_oom_internal(struct MHD_Connection *connection);
-
 /* respond_oom() must be usable with return, hence this form. */
-#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
+#define respond_oom(connection) log_oom(), mhd_respond_oom(connection)
+
+int mhd_respondf(struct MHD_Connection *connection,
+                 unsigned code,
+                 const char *format, ...) _printf_(3,4);
+
+int mhd_respond(struct MHD_Connection *connection,
+                unsigned code,
+                const char *message);
 
-int respond_error(struct MHD_Connection *connection,
-                  unsigned code,
-                  const char *format, ...);
+int mhd_respond_oom(struct MHD_Connection *connection);
 
 int check_permissions(struct MHD_Connection *connection, int *code);
 

commit cc64d0175a3c2c974709e9962c00fbe04d74c43f
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Mar 15 15:58:03 2014 -0400

    journal-remote: HTTP(s) support
    
    The whole tool is made dependent on µhttpd availability. It should be
    easy to make the µhttpd parts conditional, but since transfer over
    HTTP seems to be the primary use case, currently this is not done.
    
    Current implementation uses nested epoll loops: sd-event is used for
    the external event loop, and µhttpd uses epoll in its own
    loop. Unfortunately µhttpd does not expose enough information to add
    the descriptors it uses to the external event loop. This means that
    starvation of other events is possible, if one of the inner µhttpd
    loops is constantly busy. This means that µhttpd servers should not
    be mixed with other sources.
    
    The TLS authentication parts haven't been really tested properly, and
    should not be take too seriously.

diff --git a/Makefile-man.am b/Makefile-man.am
index eefd5ea..8a4ee9d 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -68,7 +68,6 @@ MANPAGES += \
 	man/systemd-halt.service.8 \
 	man/systemd-inhibit.1 \
 	man/systemd-initctl.service.8 \
-	man/systemd-journal-remote.8 \
 	man/systemd-journald.service.8 \
 	man/systemd-machine-id-setup.1 \
 	man/systemd-notify.1 \
@@ -1135,7 +1134,8 @@ endif
 
 if HAVE_MICROHTTPD
 MANPAGES += \
-	man/systemd-journal-gatewayd.service.8
+	man/systemd-journal-gatewayd.service.8 \
+	man/systemd-journal-remote.8
 MANPAGES_ALIAS += \
 	man/systemd-journal-gatewayd.8 \
 	man/systemd-journal-gatewayd.socket.8
diff --git a/Makefile.am b/Makefile.am
index 923e3c4..4f94587 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3130,6 +3130,10 @@ systemd_cat_SOURCES = \
 systemd_cat_LDADD = \
 	libsystemd-journal-core.la
 
+if HAVE_MICROHTTPD
+rootlibexec_PROGRAMS += \
+	systemd-journal-remote
+
 systemd_journal_remote_SOURCES = \
 	src/journal/journal-remote-parse.h \
 	src/journal/journal-remote-parse.c \
@@ -3141,6 +3145,23 @@ systemd_journal_remote_LDADD = \
 	libsystemd-internal.la \
 	libsystemd-journal-core.la
 
+systemd_journal_remote_SOURCES += \
+	src/journal/microhttpd-util.h \
+	src/journal/microhttpd-util.c
+
+systemd_journal_remote_CFLAGS = \
+	$(AM_CFLAGS) \
+	$(MICROHTTPD_CFLAGS)
+
+systemd_journal_remote_LDADD += \
+	$(MICROHTTPD_LIBS)
+
+if HAVE_GNUTLS
+systemd_journal_remote_LDADD += \
+	$(GNUTLS_LIBS)
+endif
+endif
+
 # using _CFLAGS = in the conditional below would suppress AM_CFLAGS
 journalctl_CFLAGS = \
 	$(AM_CFLAGS)
@@ -3391,8 +3412,7 @@ noinst_LTLIBRARIES += \
 	libsystemd-journal-internal.la
 
 rootlibexec_PROGRAMS += \
-	systemd-journald \
-	systemd-journal-remote
+	systemd-journald
 
 rootbin_PROGRAMS += \
 	journalctl
diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
index 036d0bc..ef123ce 100644
--- a/man/systemd-journal-remote.xml
+++ b/man/systemd-journal-remote.xml
@@ -21,7 +21,7 @@ You should have received a copy of the GNU Lesser General Public License
 along with systemd; If not, see <http://www.gnu.org/licenses/>.
 -->
 
-<refentry id="systemd-journal-remote">
+<refentry id="systemd-journal-remote" conditional='HAVE_MICROHTTPD'>
 
   <refentryinfo>
     <title>systemd-journal-remote</title>
@@ -130,8 +130,8 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       <varlistentry>
         <term><option>--listen-raw=<replaceable>ADDRESS</replaceable></option></term>
 
-        <listitem><para><option>ADDRESS</option> must be an address
-        suitable for <option>ListenStream=</option> (c.f.
+        <listitem><para><replaceable>ADDRESS</replaceable> must be an
+        address suitable for <option>ListenStream=</option> (c.f.
         <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
         <command>systemd-journal-remote</command> will listen on this
         socket for connections. Each connection is expected to be a
@@ -140,6 +140,21 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>.
       </varlistentry>
 
       <varlistentry>
+        <term><option>--listen-http=<replaceable>ADDRESS</replaceable></option></term>
+        <term><option>--listen-https=<replaceable>ADDRESS</replaceable></option></term>
+
+        <listitem><para><replaceable>ADDRESS</replaceable> must be an
+        address suitable for <option>ListenStream=</option> (c.f.
+        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+        HTTP or HTTPS server will be spawned on this port,
+        respectively, for the first and second options. Currenntly
+        Only POST requests to <filename>/upload</filename> with
+        <literal>Content-Type: application/vnd.fdo.journal</literal>
+        are supported.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
         <term><varname>$LISTEN_FDS</varname></term>
 
         <listitem><para><command>systemd-journal-remote</command>
diff --git a/src/journal/journal-remote-parse.c b/src/journal/journal-remote-parse.c
index ee2260c..c961844 100644
--- a/src/journal/journal-remote-parse.c
+++ b/src/journal/journal-remote-parse.c
@@ -100,6 +100,20 @@ static int get_line(RemoteSource *source, char **line, size_t *size) {
         return 1;
 }
 
+int push_data(RemoteSource *source, const char *data, size_t size) {
+        assert(source);
+        assert(source->state != STATE_EOF);
+
+        if (!GREEDY_REALLOC(source->buf, source->size,
+                            source->filled + size))
+                return log_oom();
+
+        memcpy(source->buf + source->filled, data, size);
+        source->filled += size;
+
+        return 0;
+}
+
 static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
         int n;
         char *newbuf = NULL;
diff --git a/src/journal/journal-remote-parse.h b/src/journal/journal-remote-parse.h
index 3bda97e..c1506d1 100644
--- a/src/journal/journal-remote-parse.h
+++ b/src/journal/journal-remote-parse.h
@@ -57,4 +57,5 @@ static inline int source_non_empty(RemoteSource *source) {
 
 void source_free(RemoteSource *source);
 int process_data(RemoteSource *source);
+int push_data(RemoteSource *source, const char *data, size_t size);
 int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal);
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
index f8979da..1a1ca2c 100644
--- a/src/journal/journal-remote.c
+++ b/src/journal/journal-remote.c
@@ -41,6 +41,13 @@
 #include "build.h"
 #include "macro.h"
 #include "strv.h"
+#include "fileio.h"
+#include "socket-util.h"
+#include "microhttpd-util.h"
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
 
 #include "journal-remote-parse.h"
 #include "journal-remote-write.h"
@@ -52,9 +59,15 @@ static char* arg_url = NULL;
 static char* arg_getter = NULL;
 static bool arg_stdin = false;
 static char* arg_listen_raw = NULL;
+static char* arg_listen_http = NULL;
+static char* arg_listen_https = NULL;
 static int arg_compress = true;
 static int arg_seal = false;
 
+static char *key_pem = NULL;
+static char *cert_pem = NULL;
+static char *trust_pem = NULL;
+
 /**********************************************************************
  **********************************************************************
  **********************************************************************/
@@ -201,6 +214,17 @@ static int open_output(Writer *s, const char* url) {
         return r;
 }
 
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+typedef struct MHDDaemonWrapper {
+        uint64_t fd;
+        struct MHD_Daemon *daemon;
+
+        sd_event_source *event;
+} MHDDaemonWrapper;
+
 typedef struct RemoteServer {
         RemoteSource **sources;
         ssize_t sources_size;
@@ -210,8 +234,13 @@ typedef struct RemoteServer {
         sd_event_source *sigterm_event, *sigint_event, *listen_event;
 
         Writer writer;
+
+        Hashmap *daemons;
 } RemoteServer;
 
+/* This should go away as soon as µhttpd allows state to be passed around. */
+static RemoteServer *server;
+
 static int dispatch_raw_source_event(sd_event_source *event,
                                      int fd,
                                      uint32_t revents,
@@ -220,6 +249,10 @@ static int dispatch_raw_connection_event(sd_event_source *event,
                                          int fd,
                                          uint32_t revents,
                                          void *userdata);
+static int dispatch_http_event(sd_event_source *event,
+                               int fd,
+                               uint32_t revents,
+                               void *userdata);
 
 static int get_source_for_fd(RemoteServer *s, int fd, RemoteSource **source) {
         assert(fd >= 0);
@@ -325,6 +358,285 @@ static int setup_raw_socket(RemoteServer *s, const char *address) {
  **********************************************************************
  **********************************************************************/
 
+static RemoteSource *request_meta(void **connection_cls) {
+        RemoteSource *source;
+
+        assert(connection_cls);
+        if (*connection_cls)
+                return *connection_cls;
+
+        source = new0(RemoteSource, 1);
+        if (!source)
+                return NULL;
+        source->fd = -1;
+
+        log_debug("Added RemoteSource as connection metadata %p", source);
+
+        *connection_cls = source;
+        return source;
+}
+
+static void request_meta_free(void *cls,
+                              struct MHD_Connection *connection,
+                              void **connection_cls,
+                              enum MHD_RequestTerminationCode toe) {
+        RemoteSource *s;
+
+        assert(connection_cls);
+        s = *connection_cls;
+
+        log_debug("Cleaning up connection metadata %p", s);
+        source_free(s);
+        *connection_cls = NULL;
+}
+
+static int process_http_upload(
+                struct MHD_Connection *connection,
+                const char *upload_data,
+                size_t *upload_data_size,
+                RemoteSource *source) {
+
+        bool finished = false;
+        int r;
+
+        assert(source);
+
+        log_debug("request_handler_upload: connection %p, %zu bytes",
+                  connection, *upload_data_size);
+
+        if (*upload_data_size) {
+                log_info("Received %zu bytes", *upload_data_size);
+
+                r = push_data(source, upload_data, *upload_data_size);
+                if (r < 0) {
+                        log_error("Failed to store received data of size %zu: %s",
+                                  *upload_data_size, strerror(-r));
+                        return respond_oom_internal(connection);
+                }
+                *upload_data_size = 0;
+        } else
+                finished = true;
+
+        while (true) {
+                r = process_source(source, &server->writer, arg_compress, arg_seal);
+                if (r == -E2BIG)
+                        log_warning("Entry too big, skipped");
+                else if (r == -EAGAIN || r == -EWOULDBLOCK)
+                        break;
+                else if (r < 0) {
+                        log_warning("Failed to process data for connection %p", connection);
+                        return respond_error(connection, MHD_HTTP_UNPROCESSABLE_ENTITY,
+                                             "Processing failed: %s", strerror(-r));
+                }
+        }
+
+        if (!finished)
+                return MHD_YES;
+
+        /* The upload is finished */
+
+        if (source_non_empty(source)) {
+                log_warning("EOF reached with incomplete data");
+                return respond_error(connection, MHD_HTTP_EXPECTATION_FAILED,
+                                     "Trailing data not processed.");
+        }
+
+        return respond_error(connection, MHD_HTTP_ACCEPTED, "OK.\n");
+};
+
+static int request_handler(
+                void *cls,
+                struct MHD_Connection *connection,
+                const char *url,
+                const char *method,
+                const char *version,
+                const char *upload_data,
+                size_t *upload_data_size,
+                void **connection_cls) {
+
+        const char *header;
+        int r ,code;
+
+        assert(connection);
+        assert(connection_cls);
+        assert(url);
+        assert(method);
+
+        log_debug("Handling a connection %s %s %s", method, url, version);
+
+        if (*connection_cls)
+                return process_http_upload(connection,
+                                           upload_data, upload_data_size,
+                                           *connection_cls);
+
+        if (!streq(method, "POST"))
+                return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
+                                     "Unsupported method.\n");
+
+        if (!streq(url, "/upload"))
+                return respond_error(connection, MHD_HTTP_NOT_FOUND,
+                                     "Not found.\n");
+
+        header = MHD_lookup_connection_value(connection,
+                                             MHD_HEADER_KIND, "Content-Type");
+        if (!header || !streq(header, "application/vnd.fdo.journal"))
+                return respond_error(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
+                                     "Content-Type: application/vnd.fdo.journal"
+                                     " is required.\n");
+
+        if (trust_pem) {
+                r = check_permissions(connection, &code);
+                if (r < 0)
+                        return code;
+        }
+
+        if (!request_meta(connection_cls))
+                return respond_oom(connection);
+        return MHD_YES;
+}
+
+static int setup_microhttpd_server(RemoteServer *s, int fd, bool https) {
+        struct MHD_OptionItem opts[] = {
+                { MHD_OPTION_NOTIFY_COMPLETED, (intptr_t) request_meta_free},
+                { MHD_OPTION_EXTERNAL_LOGGER, (intptr_t) microhttpd_logger},
+                { MHD_OPTION_LISTEN_SOCKET, fd},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END}};
+        int opts_pos = 3;
+        int flags =
+                MHD_USE_DEBUG |
+                MHD_USE_PEDANTIC_CHECKS |
+                MHD_USE_EPOLL_LINUX_ONLY |
+                MHD_USE_DUAL_STACK;
+
+        const union MHD_DaemonInfo *info;
+        int r, epoll_fd;
+        MHDDaemonWrapper *d;
+
+        assert(fd >= 0);
+
+        r = fd_nonblock(fd, true);
+        if (r < 0) {
+                log_error("Failed to make fd:%d nonblocking: %s", fd, strerror(-r));
+                return r;
+        }
+
+        if (https) {
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
+
+                flags |= MHD_USE_SSL;
+
+                if (trust_pem)
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
+        }
+
+        d = new(MHDDaemonWrapper, 1);
+        if (!d)
+                return log_oom();
+
+        d->fd = (uint64_t) fd;
+
+        d->daemon = MHD_start_daemon(flags, 0,
+                                     NULL, NULL,
+                                     request_handler, NULL,
+                                     MHD_OPTION_ARRAY, opts,
+                                     MHD_OPTION_END);
+        if (!d->daemon) {
+                log_error("Failed to start µhttp daemon");
+                r = -EINVAL;
+                goto error;
+        }
+
+        log_debug("Started MHD %s daemon on fd:%d (wrapper @ %p)",
+                  https ? "HTTPS" : "HTTP", fd, d);
+
+
+        info = MHD_get_daemon_info(d->daemon, MHD_DAEMON_INFO_EPOLL_FD_LINUX_ONLY);
+        if (!info) {
+                log_error("µhttp returned NULL daemon info");
+                r = -ENOTSUP;
+                goto error;
+        }
+
+        epoll_fd = info->listen_fd;
+        if (epoll_fd < 0) {
+                log_error("µhttp epoll fd is invalid");
+                r = -EUCLEAN;
+                goto error;
+        }
+
+        r = sd_event_add_io(s->events, &d->event,
+                            epoll_fd, EPOLLIN, dispatch_http_event, d);
+        if (r < 0) {
+                log_error("Failed to add event callback: %s", strerror(-r));
+                goto error;
+        }
+
+        r = hashmap_ensure_allocated(&s->daemons, uint64_hash_func, uint64_compare_func);
+        if (r < 0) {
+                log_oom();
+                goto error;
+        }
+
+        r = hashmap_put(s->daemons, &d->fd, d);
+        if (r < 0) {
+                log_error("Failed to add daemon to hashmap: %s", strerror(-r));
+                goto error;
+        }
+
+        s->active ++;
+        return 0;
+
+error:
+        MHD_stop_daemon(d->daemon);
+        free(d->daemon);
+        free(d);
+        return r;
+}
+
+static int setup_microhttpd_socket(RemoteServer *s,
+                                   const char *address,
+                                   bool https) {
+        int fd;
+
+        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        return setup_microhttpd_server(s, fd, https);
+}
+
+static int dispatch_http_event(sd_event_source *event,
+                               int fd,
+                               uint32_t revents,
+                               void *userdata) {
+        MHDDaemonWrapper *d = userdata;
+        int r;
+
+        assert(d);
+
+        log_info("%s", __func__);
+
+        r = MHD_run(d->daemon);
+        if (r == MHD_NO) {
+                log_error("MHD_run failed!");
+                // XXX: unregister daemon
+                return -EINVAL;
+        }
+
+        return 1; /* work to do */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
 static int dispatch_sigterm(sd_event_source *event,
                             const struct signalfd_siginfo *si,
                             void *userdata) {
@@ -369,6 +681,9 @@ static int remoteserver_init(RemoteServer *s) {
 
         setup_signals(s);
 
+        assert(server == NULL);
+        server = s;
+
         n = sd_listen_fds(true);
         if (n < 0) {
                 log_error("Failed to read listening file descriptors from environment: %s",
@@ -426,6 +741,22 @@ static int remoteserver_init(RemoteServer *s) {
                 output_name = arg_listen_raw;
         }
 
+        if (arg_listen_http) {
+                r = setup_microhttpd_socket(s, arg_listen_http, false);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_http;
+        }
+
+        if (arg_listen_https) {
+                r = setup_microhttpd_socket(s, arg_listen_https, true);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_https;
+        }
+
         if (arg_stdin) {
                 log_info("Reading standard input...");
                 r = add_source(s, STDIN_FILENO, "stdin");
@@ -454,11 +785,20 @@ static int remoteserver_init(RemoteServer *s) {
 static int server_destroy(RemoteServer *s) {
         int r;
         ssize_t i;
+        MHDDaemonWrapper *d;
 
         r = writer_close(&s->writer);
 
+        while ((d = hashmap_steal_first(s->daemons))) {
+                MHD_stop_daemon(d->daemon);
+                sd_event_source_unref(d->event);
+                free(d);
+        }
+
+        hashmap_free(s->daemons);
+
         assert(s->sources_size == 0 || s->sources);
-        for(i = 0; i < s->sources_size; i++)
+        for (i = 0; i < s->sources_size; i++)
                 remove_source(s, i);
 
         free(s->sources);
@@ -506,56 +846,61 @@ static int dispatch_raw_source_event(sd_event_source *event,
         return r;
 }
 
-static int dispatch_raw_connection_event(sd_event_source *event,
-                                         int fd,
-                                         uint32_t revents,
-                                         void *userdata) {
-        RemoteServer *s = userdata;
-
-        SocketAddress addr = {
-                .size = sizeof(union sockaddr_union),
-                .type = SOCK_STREAM,
-        };
+static int accept_connection(const char* type, int fd, SocketAddress *addr) {
         int fd2, r;
 
-        log_debug("Accepting new connection on fd:%d", fd);
-        fd2 = accept4(fd, &addr.sockaddr.sa, &addr.size, SOCK_NONBLOCK|SOCK_CLOEXEC);
+        log_debug("Accepting new %s connection on fd:%d", type, fd);
+        fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
         if (fd2 < 0) {
                 log_error("accept() on fd:%d failed: %m", fd);
                 return -errno;
         }
 
-        switch(socket_address_family(&addr)) {
+        switch(socket_address_family(addr)) {
         case AF_INET:
         case AF_INET6: {
                 char* _cleanup_free_ a = NULL;
 
-                r = socket_address_print(&addr, &a);
+                r = socket_address_print(addr, &a);
                 if (r < 0) {
                         log_error("socket_address_print(): %s", strerror(-r));
                         close(fd2);
                         return r;
                 }
 
-                log_info("Accepted %s connection from %s",
-                         socket_address_family(&addr) == AF_INET ? "IP" : "IPv6",
+                log_info("Accepted %s %s connection from %s",
+                         type,
+                         socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
                          a);
-                break;
+
+                return fd2;
         };
         default:
-                log_error("Connection with unsupported family %d",
-                          socket_address_family(&addr));
+                log_error("Rejected %s connection with unsupported family %d",
+                          type, socket_address_family(addr));
                 close(fd2);
+
                 return -EINVAL;
         }
+}
 
-        r = add_source(s, fd2, NULL);
-        if (r < 0)
-                log_error("failed to create source from fd:%d: %s", fd2, strerror(-r));
+static int dispatch_raw_connection_event(sd_event_source *event,
+                                         int fd,
+                                         uint32_t revents,
+                                         void *userdata) {
+        RemoteServer *s = userdata;
+        int fd2;
+        SocketAddress addr = {
+                .size = sizeof(union sockaddr_union),
+                .type = SOCK_STREAM,
+        };
 
-        return r;
-}
+        fd2 = accept_connection("raw", fd, &addr);
+        if (fd2 < 0)
+                return fd2;
 
+        return add_source(s, fd2, NULL);
+}
 
 /**********************************************************************
  **********************************************************************
@@ -568,6 +913,8 @@ static int help(void) {
                "  --url=URL            Read events from systemd-journal-gatewayd at URL\n"
                "  --getter=COMMAND     Read events from the output of COMMAND\n"
                "  --listen-raw=ADDR    Listen for connections at ADDR\n"
+               "  --listen-http=ADDR   Listen for HTTP connections at ADDR\n"
+               "  --listen-https=ADDR  Listen for HTTPS connections at ADDR\n"
                "  --stdin              Read events from standard input\n"
                "  -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
                "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
@@ -586,12 +933,17 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_VERSION = 0x100,
                 ARG_URL,
                 ARG_LISTEN_RAW,
+                ARG_LISTEN_HTTP,
+                ARG_LISTEN_HTTPS,
                 ARG_STDIN,
                 ARG_GETTER,
                 ARG_COMPRESS,
                 ARG_NO_COMPRESS,
                 ARG_SEAL,
                 ARG_NO_SEAL,
+                ARG_KEY,
+                ARG_CERT,
+                ARG_TRUST,
         };
 
         static const struct option options[] = {
@@ -600,16 +952,21 @@ static int parse_argv(int argc, char *argv[]) {
                 { "url",          required_argument, NULL, ARG_URL          },
                 { "getter",       required_argument, NULL, ARG_GETTER       },
                 { "listen-raw",   required_argument, NULL, ARG_LISTEN_RAW   },
+                { "listen-http",  required_argument, NULL, ARG_LISTEN_HTTP  },
+                { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
                 { "stdin",        no_argument,       NULL, ARG_STDIN        },
                 { "output",       required_argument, NULL, 'o'              },
                 { "compress",     no_argument,       NULL, ARG_COMPRESS     },
                 { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
                 { "seal",         no_argument,       NULL, ARG_SEAL         },
                 { "no-seal",      no_argument,       NULL, ARG_NO_SEAL      },
+                { "key",          required_argument, NULL, ARG_KEY          },
+                { "cert",         required_argument, NULL, ARG_CERT         },
+                { "trust",        required_argument, NULL, ARG_TRUST        },
                 {}
         };
 
-        int c;
+        int c, r;
 
         assert(argc >= 0);
         assert(argv);
@@ -652,6 +1009,67 @@ static int parse_argv(int argc, char *argv[]) {
                         arg_listen_raw = optarg;
                         break;
 
+                case ARG_LISTEN_HTTP:
+                        if (arg_listen_http) {
+                                log_error("cannot currently use --listen-http more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_listen_http = optarg;
+                        break;
+
+                case ARG_LISTEN_HTTPS:
+                        if (arg_listen_https) {
+                                log_error("cannot currently use --listen-https more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_listen_https = optarg;
+                        break;
+
+                case ARG_KEY:
+                        if (key_pem) {
+                                log_error("Key file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &key_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read key file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(key_pem);
+                        break;
+
+                case ARG_CERT:
+                        if (cert_pem) {
+                                log_error("Certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &cert_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(cert_pem);
+                        break;
+
+                case ARG_TRUST:
+#ifdef HAVE_GNUTLS
+                        if (trust_pem) {
+                                log_error("CA certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &trust_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read CA certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(trust_pem);
+                        break;
+#else
+                        log_error("Option --trust is not available.");
+#endif
+
                 case ARG_STDIN:
                         arg_stdin = true;
                         break;
@@ -686,6 +1104,11 @@ static int parse_argv(int argc, char *argv[]) {
                         return -EINVAL;
                 }
 
+        if (arg_listen_https && !(key_pem && cert_pem)) {
+                log_error("Options --key and --cert must be used when https sources are specified");
+                return -EINVAL;
+        }
+
         if (optind < argc) {
                 log_error("This program takes no positional arguments");
                 return -EINVAL;
@@ -694,6 +1117,18 @@ static int parse_argv(int argc, char *argv[]) {
         return 1 /* work to do */;
 }
 
+static int setup_gnutls_logger(void) {
+        if (!arg_listen_http && !arg_listen_https)
+                return 0;
+
+#ifdef HAVE_GNUTLS
+        gnutls_global_set_log_function(log_func_gnutls);
+        gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
+#endif
+
+        return 0;
+}
+
 int main(int argc, char **argv) {
         RemoteServer s = {};
         int r, r2;
@@ -706,6 +1141,10 @@ int main(int argc, char **argv) {
         if (r <= 0)
                 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
 
+        r = setup_gnutls_logger();
+        if (r < 0)
+                return EXIT_FAILURE;
+
         if (remoteserver_init(&s) < 0)
                 return EXIT_FAILURE;
 
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
index 17135ab..f7f12e1 100644
--- a/src/journal/microhttpd-util.c
+++ b/src/journal/microhttpd-util.c
@@ -49,13 +49,14 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
 
 
 int respond_oom_internal(struct MHD_Connection *connection) {
+        const char *m = "Out of memory.\n";
+
         struct MHD_Response *response;
-        const char m[] = "Out of memory.\n";
         int ret;
 
         assert(connection);
 
-        response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
+        response = MHD_create_response_from_buffer(strlen(m), (char*) m, MHD_RESPMEM_PERSISTENT);
         if (!response)
                 return MHD_NO;
 
@@ -92,7 +93,7 @@ int respond_error(struct MHD_Connection *connection,
                 return respond_oom(connection);
         }
 
-        log_debug("queing response %u: %s", code, m);
+        log_debug("Queing response %u: %s", code, m);
         MHD_add_response_header(response, "Content-Type", "text/plain");
         r = MHD_queue_response(connection, code, response);
         MHD_destroy_response(response);
@@ -227,8 +228,10 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
         ci = MHD_get_connection_info(connection,
                                      MHD_CONNECTION_INFO_GNUTLS_SESSION);
         if (!ci) {
-                log_error("MHD_get_connection_info failed");
-                return -EINVAL;
+                log_error("MHD_get_connection_info failed: session is unencrypted");
+                *code = respond_error(connection, MHD_HTTP_FORBIDDEN,
+                                      "Encrypted connection is required");
+                return -EPERM;
         }
         session = ci->tls_session;
         assert(session);
@@ -247,11 +250,11 @@ int check_permissions(struct MHD_Connection *connection, int *code) {
                 return -EPERM;
         }
 
-        log_info("Connection from %s", buf);
+        log_info("Connection from DN %s", buf);
 
         r = verify_cert_authorized(session);
         if (r < 0) {
-                log_error("Client is not authorized");
+                log_warning("Client is not authorized");
                 *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
                                       "Client certificate not signed by recognized authority");
         }

commit fdfccdbc985944a57017a25f44dd6acc1a937bab
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Nov 1 23:08:03 2012 +0100

    journal-remote: tool to receive messages over the network

diff --git a/Makefile-man.am b/Makefile-man.am
index 346bc63..eefd5ea 100644
--- a/Makefile-man.am
+++ b/Makefile-man.am
@@ -68,6 +68,7 @@ MANPAGES += \
 	man/systemd-halt.service.8 \
 	man/systemd-inhibit.1 \
 	man/systemd-initctl.service.8 \
+	man/systemd-journal-remote.8 \
 	man/systemd-journald.service.8 \
 	man/systemd-machine-id-setup.1 \
 	man/systemd-notify.1 \
@@ -1462,6 +1463,7 @@ EXTRA_DIST += \
 	man/systemd-inhibit.xml \
 	man/systemd-initctl.service.xml \
 	man/systemd-journal-gatewayd.service.xml \
+	man/systemd-journal-remote.xml \
 	man/systemd-journald.service.xml \
 	man/systemd-localed.service.xml \
 	man/systemd-logind.service.xml \
diff --git a/Makefile.am b/Makefile.am
index fed8561..923e3c4 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3130,6 +3130,17 @@ systemd_cat_SOURCES = \
 systemd_cat_LDADD = \
 	libsystemd-journal-core.la
 
+systemd_journal_remote_SOURCES = \
+	src/journal/journal-remote-parse.h \
+	src/journal/journal-remote-parse.c \
+	src/journal/journal-remote-write.h \
+	src/journal/journal-remote-write.c \
+	src/journal/journal-remote.c
+
+systemd_journal_remote_LDADD = \
+	libsystemd-internal.la \
+	libsystemd-journal-core.la
+
 # using _CFLAGS = in the conditional below would suppress AM_CFLAGS
 journalctl_CFLAGS = \
 	$(AM_CFLAGS)
@@ -3380,7 +3391,8 @@ noinst_LTLIBRARIES += \
 	libsystemd-journal-internal.la
 
 rootlibexec_PROGRAMS += \
-	systemd-journald
+	systemd-journald \
+	systemd-journal-remote
 
 rootbin_PROGRAMS += \
 	journalctl
diff --git a/man/systemd-journal-remote.xml b/man/systemd-journal-remote.xml
new file mode 100644
index 0000000..036d0bc
--- /dev/null
+++ b/man/systemd-journal-remote.xml
@@ -0,0 +1,289 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+This file is part of systemd.
+
+Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+systemd is free software; you can redistribute it and/or modify it
+under the terms of the GNU Lesser General Public License as published by
+the Free Software Foundation; either version 2.1 of the License, or
+(at your option) any later version.
+
+systemd is distributed in the hope that it will be useful, but
+WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+Lesser General Public License for more details.
+
+You should have received a copy of the GNU Lesser General Public License
+along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-journal-remote">
+
+  <refentryinfo>
+    <title>systemd-journal-remote</title>
+    <productname>systemd</productname>
+
+    <authorgroup>
+      <author>
+        <contrib>Developer</contrib>
+        <firstname>Zbigniew</firstname>
+        <surname>Jędrzejewski-Szmek</surname>
+        <email>zbyszek at in.waw.pl</email>
+      </author>
+    </authorgroup>
+  </refentryinfo>
+
+  <refmeta>
+    <refentrytitle>systemd-journal-remote</refentrytitle>
+    <manvolnum>8</manvolnum>
+  </refmeta>
+
+  <refnamediv>
+    <refname>systemd-journal-remote</refname>
+    <refpurpose>Stream journal messages over the network</refpurpose>
+  </refnamediv>
+
+  <refsynopsisdiv>
+    <cmdsynopsis>
+      <command>systemd-journal-remote</command>
+      <arg choice="opt" rep="repeat">OPTIONS</arg>
+      <arg choice="opt" rep="norepeat">-o/--output=DIR|FILE</arg>
+      <arg choice="opt" rep="repeat">SOURCES</arg>
+    </cmdsynopsis>
+  </refsynopsisdiv>
+
+  <refsect1>
+    <title>Description</title>
+
+    <para>
+      <filename>systemd-journal-remote</filename> is a command to
+      receive serialized journal events and store them to the journal.
+      Input streams must be in the
+      <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">
+        Journal Export Format
+      </ulink>,
+      i.e. like the output from
+      <command>journalctl --output=export</command>.
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>Sources</title>
+
+    <para>
+      Sources can be either "active"
+      (<command>systemd-journal-remote</command> requests and pulls
+      the data), or "passive"
+      (<command>systemd-journal-remote</command> waits for a
+      connection and than receives events pushed by the other side).
+    </para>
+
+    <para>
+      <command>systemd-journal-remote</command> can read more than one
+      event stream at a time. They will be interleaved in the output
+      file. In case of "active" connections, each "source" is one
+      stream, and in case of "passive" connections each connection can
+      result in a separate stream. Sockets can be configured in
+      "accept" mode (i.e. only one connection), or "listen" mode (i.e.
+      multiple connections, each resulting in a stream).
+    </para>
+
+    <para>
+      When there are no more connections, and no more can be created
+      (there are no listening sockets), then
+      <command>systemd-journal-remote</command> will exit.
+    </para>
+
+    <para>Active sources can be specified in the following
+    ways:</para>
+
+    <variablelist>
+      <varlistentry>
+        <listitem><para>When <option>-</option> is given as a
+        positional argument, events will be read from standard input.
+        Other positional arguments will be treated as filenames
+        to open and read from.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--url=<replaceable>ADDRESS</replaceable></option></term>
+
+        <listitem><para>With the
+        <option>--url=<replaceable>ADDRESS</replaceable></option> option,
+        events will be retrieved using HTTP from
+        <replaceable>ADDRESS</replaceable>. This URL should refer to the
+        root of a remote
+        <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+        instance (e.g. <ulink>http://some.host:19531/</ulink> or
+        <ulink>https://some.host:19531/</ulink>).</para></listitem>
+      </varlistentry>
+    </variablelist>
+
+    <para>Passive sources can be specified in the following
+    ways:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--listen-raw=<replaceable>ADDRESS</replaceable></option></term>
+
+        <listitem><para><option>ADDRESS</option> must be an address
+        suitable for <option>ListenStream=</option> (c.f.
+        <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+        <command>systemd-journal-remote</command> will listen on this
+        socket for connections. Each connection is expected to be a
+        stream of journal events.</para>
+        </listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><varname>$LISTEN_FDS</varname></term>
+
+        <listitem><para><command>systemd-journal-remote</command>
+        supports the
+        <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
+        protocol. Open sockets inherited through socket
+        activation behave like those opened with
+        <option>--listen-raw=</option> described above.
+        </para>
+        </listitem>
+      </varlistentry>
+
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Sinks</title>
+
+    <para>The location of the output journal can be specified
+    with <option>-o</option> or <option>--output=</option>.
+    </para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--output=<replaceable>FILE</replaceable></option></term>
+
+        <listitem><para>Will write to this journal. The filename must
+        end with <filename>.journal</filename>. The file will be
+        created if it does not exist. When necessary (journal file
+        full, or corrupted) the file will be renamed following normal
+        journald rules and a new journal file will be created in it's
+        stead. </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--output=<replaceable>DIR</replaceable></option></term>
+
+        <listitem><para>Will create journal files underneath directory
+        <replaceable>DIR</replaceable>. The directory must exist. When
+        necessary (journal files over size, or corrupted) journal
+        files will be rotated following normal journald rules. Names
+        of files underneath <replaceable>DIR</replaceable> will be
+        generated using the rules described below.</para></listitem>
+      </varlistentry>
+    </variablelist>
+
+    <para>If <option>--output=</option> is not used, output directory
+    <filename>/var/log/journal/<replaceable>machine-id</replaceable>/</filename>
+    will be used, where <replaceable>machine-id</replaceable> is the
+    identifier of the current system (see
+    <citerefentry><refentrytitle>machine-id</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+    In case the output file is not specified, journal files will be
+    created underneath the selected directory. Files will be called
+    <filename>remote-<replaceable>variable</replaceable>.journal</filename>,
+    where the <replaceable>variable</replaceable> part is generated
+    based on what passive and active sources are specified. It is
+    recommended to give a full output filename.</para>
+
+    <para>In case of "active" sources, if the hostname is known it
+    will be used in the <replaceable>variable</replaceable> part.
+    Otherwise, local address and port number will be used, or
+    <literal>stdin</literal> for events passed over standard
+    input, and <literal>multiple</literal> if more than one source
+    is specified.</para>
+  </refsect1>
+
+  <refsect1>
+    <title>Options</title>
+
+    <para>The following options are understood:</para>
+
+    <variablelist>
+      <varlistentry>
+        <term><option>--help</option></term>
+        <term><option>-h</option></term>
+
+        <listitem><para>Print a short help
+        text and exit.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--version</option></term>
+
+        <listitem><para>Print a short version
+        string and exit.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--compress</option></term>
+        <term><option>--no-compress</option></term>
+
+        <listitem><para>Compress or not, respectively, the data in the
+        journal using XZ.</para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--seal</option></term>
+        <term><option>--no-seal</option></term>
+
+        <listitem><para>Periodically sign or not, respectively, the
+        data in the journal using Forward Secure Sealing.
+        </para></listitem>
+      </varlistentry>
+
+      <varlistentry>
+        <term><option>--getter=<replaceable>PROG --option1 --option2</replaceable></option></term>
+
+        <listitem><para>Program to invoke to retrieve data. Journal
+        event stream must be generated on standard output.</para>
+
+        <para>Examples:</para>
+
+        <programlisting>--getter='curl "-HAccept: application/vnd.fdo.journal" https://some.host:19531/'</programlisting>
+
+        <programlisting>--getter='wget --header="Accept: application/vnd.fdo.journal" -O- https://some.host:19531/'</programlisting>
+        </listitem>
+      </varlistentry>
+    </variablelist>
+  </refsect1>
+
+  <refsect1>
+    <title>Examples</title>
+    <para>Copy local journal events to a different journal directory:
+    <programlisting>
+journalctl -o export | systemd-journal-remote -o /tmp/dir -
+    </programlisting>
+    </para>
+
+    <para>Retrieve events from a remote
+    <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+    instance and store them in
+    <filename>/var/log/journal/some.host/remote-some~host.journal</filename>:
+    <programlisting>
+systemd-journal-remote --url http://some.host:19531/
+    </programlisting>
+    </para>
+  </refsect1>
+
+  <refsect1>
+    <title>See Also</title>
+    <para>
+      <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+      <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.
+    </para>
+  </refsect1>
+</refentry>
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index ac16a7c..d47b27e 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -78,6 +78,7 @@ static const char* const mime_types[_OUTPUT_MODE_MAX] = {
 static RequestMeta *request_meta(void **connection_cls) {
         RequestMeta *m;
 
+        assert(connection_cls);
         if (*connection_cls)
                 return *connection_cls;
 
diff --git a/src/journal/journal-remote-parse.c b/src/journal/journal-remote-parse.c
new file mode 100644
index 0000000..ee2260c
--- /dev/null
+++ b/src/journal/journal-remote-parse.c
@@ -0,0 +1,415 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-remote-parse.h"
+#include "journald-native.h"
+
+#define LINE_CHUNK 1024u
+
+void source_free(RemoteSource *source) {
+        if (!source)
+                return;
+
+        if (source->fd >= 0) {
+                log_debug("Closing fd:%d (%s)", source->fd, source->name);
+                close(source->fd);
+        }
+        free(source->name);
+        free(source->buf);
+        iovw_free_contents(&source->iovw);
+        free(source);
+}
+
+static int get_line(RemoteSource *source, char **line, size_t *size) {
+        ssize_t n, remain;
+        char *c;
+        char *newbuf = NULL;
+        size_t newsize = 0;
+
+        assert(source);
+        assert(source->state == STATE_LINE);
+        assert(source->filled <= source->size);
+        assert(source->buf == NULL || source->size > 0);
+
+        c = memchr(source->buf, '\n', source->filled);
+        if (c != NULL)
+                goto docopy;
+
+ resize:
+        if (source->size - source->filled < LINE_CHUNK) {
+                // XXX: add check for maximum line length
+
+                if (!GREEDY_REALLOC(source->buf, source->size,
+                                    source->filled + LINE_CHUNK))
+                        return log_oom();
+        }
+        assert(source->size - source->filled >= LINE_CHUNK);
+
+        n = read(source->fd, source->buf + source->filled,
+                 source->size - source->filled);
+        if (n < 0) {
+                if (errno != EAGAIN && errno != EWOULDBLOCK)
+                        log_error("read(%d, ..., %zd): %m", source->fd,
+                                  source->size - source->filled);
+                return -errno;
+        } else if (n == 0)
+                return 0;
+
+        c = memchr(source->buf + source->filled, '\n', n);
+        source->filled += n;
+
+        if (c == NULL)
+                goto resize;
+
+ docopy:
+        *line = source->buf;
+        *size = c + 1 - source->buf;
+
+        /* Check if something remains */
+        remain = source->buf + source->filled - c - 1;
+        assert(remain >= 0);
+        if (remain) {
+                newsize = MAX(remain, LINE_CHUNK);
+                newbuf = malloc(newsize);
+                if (!newbuf)
+                        return log_oom();
+                memcpy(newbuf, c + 1, remain);
+        }
+        source->buf = newbuf;
+        source->size = newsize;
+        source->filled = remain;
+
+        return 1;
+}
+
+static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
+        int n;
+        char *newbuf = NULL;
+        size_t newsize = 0, remain;
+
+        assert(source);
+        assert(source->state == STATE_DATA_START ||
+               source->state == STATE_DATA ||
+               source->state == STATE_DATA_FINISH);
+        assert(size <= DATA_SIZE_MAX);
+        assert(source->filled <= source->size);
+        assert(source->buf != NULL || source->size == 0);
+        assert(source->buf == NULL || source->size > 0);
+        assert(data);
+
+        while(source->filled < size) {
+                if (!GREEDY_REALLOC(source->buf, source->size, size))
+                        return log_oom();
+
+                n = read(source->fd, source->buf + source->filled,
+                         source->size - source->filled);
+                if (n < 0) {
+                        if (errno != EAGAIN && errno != EWOULDBLOCK)
+                                log_error("read(%d, ..., %zd): %m", source->fd,
+                                          source->size - source->filled);
+                        return -errno;
+                } else if (n == 0)
+                        return 0;
+
+                source->filled += n;
+        }
+
+        *data = source->buf;
+
+        /* Check if something remains */
+        assert(size <= source->filled);
+        remain = source->filled - size;
+        if (remain) {
+                newsize = MAX(remain, LINE_CHUNK);
+                newbuf = malloc(newsize);
+                if (!newbuf)
+                        return log_oom();
+                memcpy(newbuf, source->buf + size, remain);
+        }
+        source->buf = newbuf;
+        source->size = newsize;
+        source->filled = remain;
+
+        return 1;
+}
+
+static int get_data_size(RemoteSource *source) {
+        int r;
+        void _cleanup_free_ *data = NULL;
+
+        assert(source);
+        assert(source->state == STATE_DATA_START);
+        assert(source->data_size == 0);
+
+        r = fill_fixed_size(source, &data, sizeof(uint64_t));
+        if (r <= 0)
+                return r;
+
+        source->data_size = le64toh( *(uint64_t *) data );
+        if (source->data_size > DATA_SIZE_MAX) {
+                log_error("Stream declares field with size %zu > %u == DATA_SIZE_MAX",
+                          source->data_size, DATA_SIZE_MAX);
+                return -EINVAL;
+        }
+        if (source->data_size == 0)
+                log_warning("Binary field with zero length");
+
+        return 1;
+}
+
+static int get_data_data(RemoteSource *source, void **data) {
+        int r;
+
+        assert(source);
+        assert(data);
+        assert(source->state == STATE_DATA);
+
+        r = fill_fixed_size(source, data, source->data_size);
+        if (r <= 0)
+                return r;
+
+        return 1;
+}
+
+static int get_data_newline(RemoteSource *source) {
+        int r;
+        char _cleanup_free_ *data = NULL;
+
+        assert(source);
+        assert(source->state == STATE_DATA_FINISH);
+
+        r = fill_fixed_size(source, (void**) &data, 1);
+        if (r <= 0)
+                return r;
+
+        assert(data);
+        if (*data != '\n') {
+                log_error("expected newline, got '%c'", *data);
+                return -EINVAL;
+        }
+
+        return 1;
+}
+
+static int process_dunder(RemoteSource *source, char *line, size_t n) {
+        const char *timestamp;
+        int r;
+
+        assert(line);
+        assert(n > 0);
+        assert(line[n-1] == '\n');
+
+        /* XXX: is it worth to support timestamps in extended format?
+         * We don't produce them, but who knows... */
+
+        timestamp = startswith(line, "__CURSOR=");
+        if (timestamp)
+                /* ignore __CURSOR */
+                return 1;
+
+        timestamp = startswith(line, "__REALTIME_TIMESTAMP=");
+        if (timestamp) {
+                long long unsigned x;
+                line[n-1] = '\0';
+                r = safe_atollu(timestamp, &x);
+                if (r < 0)
+                        log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp);
+                else
+                        source->ts.realtime = x;
+                return r < 0 ? r : 1;
+        }
+
+        timestamp = startswith(line, "__MONOTONIC_TIMESTAMP=");
+        if (timestamp) {
+                long long unsigned x;
+                line[n-1] = '\0';
+                r = safe_atollu(timestamp, &x);
+                if (r < 0)
+                        log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp);
+                else
+                        source->ts.monotonic = x;
+                return r < 0 ? r : 1;
+        }
+
+        timestamp = startswith(line, "__");
+        if (timestamp) {
+                log_notice("Unknown dunder line %s", line);
+                return 1;
+        }
+
+        /* no dunder */
+        return 0;
+}
+
+int process_data(RemoteSource *source) {
+        int r;
+
+        switch(source->state) {
+        case STATE_LINE: {
+                char *line, *sep;
+                size_t n;
+
+                assert(source->data_size == 0);
+
+                r = get_line(source, &line, &n);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return r;
+                }
+                assert(n > 0);
+                assert(line[n-1] == '\n');
+
+                if (n == 1) {
+                        log_debug("Received empty line, event is ready");
+                        free(line);
+                        return 1;
+                }
+
+                r = process_dunder(source, line, n);
+                if (r != 0) {
+                        free(line);
+                        return r < 0 ? r : 0;
+                }
+
+                /* MESSAGE=xxx\n
+                   or
+                   COREDUMP\n
+                   LLLLLLLL0011223344...\n
+                */
+                sep = memchr(line, '=', n);
+                if (sep)
+                        /* chomp newline */
+                        n--;
+                else
+                        /* replace \n with = */
+                        line[n-1] = '=';
+                log_debug("Received: %.*s", (int) n, line);
+
+                r = iovw_put(&source->iovw, line, n);
+                if (r < 0) {
+                        log_error("Failed to put line in iovect");
+                        free(line);
+                        return r;
+                }
+
+                if (!sep)
+                        source->state = STATE_DATA_START;
+                return 0; /* continue */
+        }
+
+        case STATE_DATA_START:
+                assert(source->data_size == 0);
+
+                r = get_data_size(source);
+                log_debug("get_data_size() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                source->state = source->data_size > 0 ?
+                        STATE_DATA : STATE_DATA_FINISH;
+
+                return 0; /* continue */
+
+        case STATE_DATA: {
+                void *data;
+
+                assert(source->data_size > 0);
+
+                r = get_data_data(source, &data);
+                log_debug("get_data_data() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                assert(data);
+
+                r = iovw_put(&source->iovw, data, source->data_size);
+                if (r < 0) {
+                        log_error("failed to put binary buffer in iovect");
+                        return r;
+                }
+
+                source->state = STATE_DATA_FINISH;
+
+                return 0; /* continue */
+        }
+
+        case STATE_DATA_FINISH:
+                r = get_data_newline(source);
+                log_debug("get_data_newline() -> %d", r);
+                if (r < 0)
+                        return r;
+                if (r == 0) {
+                        source->state = STATE_EOF;
+                        return 0;
+                }
+
+                source->data_size = 0;
+                source->state = STATE_LINE;
+
+                return 0; /* continue */
+        default:
+                assert_not_reached("wtf?");
+        }
+}
+
+int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal) {
+        int r;
+
+        assert(source);
+        assert(writer);
+
+        r = process_data(source);
+        if (r <= 0)
+                return r;
+
+        /* We have a full event */
+        log_info("Received a full event from source@%p fd:%d (%s)",
+                 source, source->fd, source->name);
+
+        if (!source->iovw.count) {
+                log_warning("Entry with no payload, skipping");
+                goto freeing;
+        }
+
+        assert(source->iovw.iovec);
+        assert(source->iovw.count);
+
+        r = writer_write(writer, &source->iovw, &source->ts, compress, seal);
+        if (r < 0)
+                log_error("Failed to write entry of %zu bytes: %s",
+                          iovw_size(&source->iovw), strerror(-r));
+        else
+                r = 1;
+
+ freeing:
+        iovw_free_contents(&source->iovw);
+        return r;
+}
diff --git a/src/journal/journal-remote-parse.h b/src/journal/journal-remote-parse.h
new file mode 100644
index 0000000..3bda97e
--- /dev/null
+++ b/src/journal/journal-remote-parse.h
@@ -0,0 +1,60 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include "sd-event.h"
+#include "journal-remote-write.h"
+
+typedef enum {
+        STATE_LINE = 0,    /* waiting to read, or reading line */
+        STATE_DATA_START,  /* reading binary data header */
+        STATE_DATA,        /* reading binary data */
+        STATE_DATA_FINISH, /* expecting newline */
+        STATE_EOF,         /* done */
+} source_state;
+
+typedef struct RemoteSource {
+        char* name;
+        int fd;
+
+        char *buf;
+        size_t size;
+        size_t filled;
+        size_t data_size;
+
+        struct iovec_wrapper iovw;
+
+        source_state state;
+        dual_timestamp ts;
+
+        sd_event_source *event;
+} RemoteSource;
+
+static inline int source_non_empty(RemoteSource *source) {
+        assert(source);
+
+        return source->filled > 0;
+}
+
+void source_free(RemoteSource *source);
+int process_data(RemoteSource *source);
+int process_source(RemoteSource *source, Writer *writer, bool compress, bool seal);
diff --git a/src/journal/journal-remote-write.c b/src/journal/journal-remote-write.c
new file mode 100644
index 0000000..4d142bd
--- /dev/null
+++ b/src/journal/journal-remote-write.c
@@ -0,0 +1,124 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-remote-write.h"
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
+        if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+                return log_oom();
+
+        iovw->iovec[iovw->count++] = (struct iovec) {data, len};
+        return 0;
+}
+
+void iovw_free_contents(struct iovec_wrapper *iovw) {
+        for (size_t j = 0; j < iovw->count; j++)
+                free(iovw->iovec[j].iov_base);
+        free(iovw->iovec);
+        iovw->iovec = NULL;
+        iovw->size_bytes = iovw->count = 0;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+        size_t n = 0, i;
+
+        for(i = 0; i < iovw->count; i++)
+                n += iovw->iovec[i].iov_len;
+
+        return n;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int do_rotate(JournalFile **f, bool compress, bool seal) {
+        int r = journal_file_rotate(f, compress, seal);
+        if (r < 0) {
+                if (*f)
+                        log_error("Failed to rotate %s: %s", (*f)->path,
+                                  strerror(-r));
+                else
+                        log_error("Failed to create rotated journal: %s",
+                                  strerror(-r));
+        }
+
+        return r;
+}
+
+int writer_init(Writer *s) {
+        assert(s);
+
+        s->journal = NULL;
+
+        memset(&s->metrics, 0xFF, sizeof(s->metrics));
+
+        s->mmap = mmap_cache_new();
+        if (!s->mmap)
+                return log_oom();
+
+        s->seqnum = 0;
+
+        return 0;
+}
+
+int writer_close(Writer *s) {
+        if (s->journal)
+                journal_file_close(s->journal);
+        if (s->mmap)
+                mmap_cache_unref(s->mmap);
+        return 0;
+}
+
+int writer_write(Writer *s,
+                 struct iovec_wrapper *iovw,
+                 dual_timestamp *ts,
+                 bool compress,
+                 bool seal) {
+        int r;
+
+        assert(s);
+        assert(iovw);
+        assert(iovw->count > 0);
+
+        if (journal_file_rotate_suggested(s->journal, 0)) {
+                log_info("%s: Journal header limits reached or header out-of-date, rotating",
+                         s->journal->path);
+                r = do_rotate(&s->journal, compress, seal);
+                if (r < 0)
+                        return r;
+        }
+
+        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
+                                      &s->seqnum, NULL, NULL);
+        if (r >= 0)
+                return 1;
+
+        log_info("%s: Write failed, rotating", s->journal->path);
+        r = do_rotate(&s->journal, compress, seal);
+        if (r < 0)
+                return r;
+
+        log_debug("Retrying write.");
+        r = journal_file_append_entry(s->journal, ts, iovw->iovec, iovw->count,
+                                      &s->seqnum, NULL, NULL);
+        return r < 0 ? r : 1;
+}
diff --git a/src/journal/journal-remote-write.h b/src/journal/journal-remote-write.h
new file mode 100644
index 0000000..8798216
--- /dev/null
+++ b/src/journal/journal-remote-write.h
@@ -0,0 +1,51 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#pragma once
+
+#include <stdlib.h>
+
+#include "journal-file.h"
+
+struct iovec_wrapper {
+        struct iovec *iovec;
+        size_t size_bytes;
+        size_t count;
+};
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
+void iovw_free_contents(struct iovec_wrapper *iovw);
+size_t iovw_size(struct iovec_wrapper *iovw);
+
+typedef struct Writer {
+        JournalFile *journal;
+        JournalMetrics metrics;
+        MMapCache *mmap;
+        uint64_t seqnum;
+} Writer;
+
+int writer_init(Writer *s);
+int writer_close(Writer *s);
+int writer_write(Writer *s,
+                 struct iovec_wrapper *iovw,
+                 dual_timestamp *ts,
+                 bool compress,
+                 bool seal);
diff --git a/src/journal/journal-remote.c b/src/journal/journal-remote.c
new file mode 100644
index 0000000..f8979da
--- /dev/null
+++ b/src/journal/journal-remote.c
@@ -0,0 +1,738 @@
+/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
+
+/***
+  This file is part of systemd.
+
+  Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+  systemd is free software; you can redistribute it and/or modify it
+  under the terms of the GNU Lesser General Public License as published by
+  the Free Software Foundation; either version 2.1 of the License, or
+  (at your option) any later version.
+
+  systemd is distributed in the hope that it will be useful, but
+  WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+  Lesser General Public License for more details.
+
+  You should have received a copy of the GNU Lesser General Public License
+  along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+#include <getopt.h>
+
+#include "sd-daemon.h"
+#include "sd-event.h"
+#include "journal-file.h"
+#include "journald-native.h"
+#include "socket-util.h"
+#include "mkdir.h"
+#include "build.h"
+#include "macro.h"
+#include "strv.h"
+
+#include "journal-remote-parse.h"
+#include "journal-remote-write.h"
+
+#define REMOTE_JOURNAL_PATH "/var/log/journal/" SD_ID128_FORMAT_STR "/remote-%s.journal"
+
+static char* arg_output = NULL;
+static char* arg_url = NULL;
+static char* arg_getter = NULL;
+static bool arg_stdin = false;
+static char* arg_listen_raw = NULL;
+static int arg_compress = true;
+static int arg_seal = false;
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int spawn_child(const char* child, char** argv) {
+        int fd[2];
+        pid_t parent_pid, child_pid;
+        int r;
+
+        if (pipe(fd) < 0) {
+                log_error("Failed to create pager pipe: %m");
+                return -errno;
+        }
+
+        parent_pid = getpid();
+
+        child_pid = fork();
+        if (child_pid < 0) {
+                r = -errno;
+                log_error("Failed to fork: %m");
+                close_pipe(fd);
+                return r;
+        }
+
+        /* In the child */
+        if (child_pid == 0) {
+                r = dup2(fd[1], STDOUT_FILENO);
+                if (r < 0) {
+                        log_error("Failed to dup pipe to stdout: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                r = close_pipe(fd);
+                if (r < 0)
+                        log_warning("Failed to close pipe fds: %m");
+
+                /* Make sure the child goes away when the parent dies */
+                if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+                        _exit(EXIT_FAILURE);
+
+                /* Check whether our parent died before we were able
+                 * to set the death signal */
+                if (getppid() != parent_pid)
+                        _exit(EXIT_SUCCESS);
+
+                execvp(child, argv);
+                log_error("Failed to exec child %s: %m", child);
+                _exit(EXIT_FAILURE);
+        }
+
+        r = close(fd[1]);
+        if (r < 0)
+                log_warning("Failed to close write end of pipe: %m");
+
+        return fd[0];
+}
+
+static int spawn_curl(char* url) {
+        char **argv = STRV_MAKE("curl",
+                                "-HAccept: application/vnd.fdo.journal",
+                                "--silent",
+                                "--show-error",
+                                url);
+        int r;
+
+        r = spawn_child("curl", argv);
+        if (r < 0)
+                log_error("Failed to spawn curl: %m");
+        return r;
+}
+
+static int spawn_getter(char *getter, char *url) {
+        int r;
+        char _cleanup_strv_free_ **words = NULL, **words2 = NULL;
+
+        assert(getter);
+        words = strv_split_quoted(getter);
+        if (!words)
+                return log_oom();
+
+        r = spawn_child(words[0], words);
+        if (r < 0)
+                log_error("Failed to spawn getter %s: %m", getter);
+
+        return r;
+}
+
+static int open_output(Writer *s, const char* url) {
+        char _cleanup_free_ *name, *output = NULL;
+        char *c;
+        int r;
+
+        assert(url);
+        name = strdup(url);
+        if (!name)
+                return log_oom();
+
+        for(c = name; *c; c++) {
+                if (*c == '/' || *c == ':' || *c == ' ')
+                        *c = '~';
+                else if (*c == '?') {
+                        *c = '\0';
+                        break;
+                }
+        }
+
+        if (!arg_output) {
+                sd_id128_t machine;
+                r = sd_id128_get_machine(&machine);
+                if (r < 0) {
+                        log_error("failed to determine machine ID128: %s", strerror(-r));
+                        return r;
+                }
+
+                r = asprintf(&output, REMOTE_JOURNAL_PATH,
+                             SD_ID128_FORMAT_VAL(machine), name);
+                if (r < 0)
+                        return log_oom();
+        } else {
+                r = is_dir(arg_output);
+                if (r > 0) {
+                        r = asprintf(&output,
+                                     "%s/remote-%s.journal", arg_output, name);
+                        if (r < 0)
+                                return log_oom();
+                } else {
+                        output = strdup(arg_output);
+                        if (!output)
+                                return log_oom();
+                }
+        }
+
+        r = journal_file_open_reliably(output,
+                                       O_RDWR|O_CREAT, 0640,
+                                       arg_compress, arg_seal,
+                                       &s->metrics,
+                                       s->mmap,
+                                       NULL, &s->journal);
+        if (r < 0)
+                log_error("Failed to open output journal %s: %s",
+                          arg_output, strerror(-r));
+        else
+                log_info("Opened output file %s", s->journal->path);
+        return r;
+}
+
+typedef struct RemoteServer {
+        RemoteSource **sources;
+        ssize_t sources_size;
+        ssize_t active;
+
+        sd_event *events;
+        sd_event_source *sigterm_event, *sigint_event, *listen_event;
+
+        Writer writer;
+} RemoteServer;
+
+static int dispatch_raw_source_event(sd_event_source *event,
+                                     int fd,
+                                     uint32_t revents,
+                                     void *userdata);
+static int dispatch_raw_connection_event(sd_event_source *event,
+                                         int fd,
+                                         uint32_t revents,
+                                         void *userdata);
+
+static int get_source_for_fd(RemoteServer *s, int fd, RemoteSource **source) {
+        assert(fd >= 0);
+        assert(source);
+
+        if (!GREEDY_REALLOC0_T(s->sources, s->sources_size, fd + 1))
+                return log_oom();
+
+        if (s->sources[fd] == NULL) {
+                s->sources[fd] = new0(RemoteSource, 1);
+                if (!s->sources[fd])
+                        return log_oom();
+                s->sources[fd]->fd = -1;
+                s->active++;
+        }
+
+        *source = s->sources[fd];
+        return 0;
+}
+
+static int remove_source(RemoteServer *s, int fd) {
+        RemoteSource *source;
+
+        assert(s);
+        assert(fd >= 0);
+        assert(fd < s->sources_size);
+
+        source = s->sources[fd];
+        if (source) {
+                source_free(source);
+                s->sources[fd] = NULL;
+                s->active--;
+        }
+
+        close(fd);
+
+        return 0;
+}
+
+static int add_source(RemoteServer *s, int fd, const char* name) {
+        RemoteSource *source = NULL;
+        char *realname;
+        int r;
+
+        assert(s);
+        assert(fd >= 0);
+
+        if (name) {
+                realname = strdup(name);
+                if (!realname)
+                        return log_oom();
+        } else {
+                r = asprintf(&realname, "fd:%d", fd);
+                if (r < 0)
+                        return log_oom();
+        }
+
+        log_debug("Creating source for fd:%d (%s)", fd, name);
+
+        r = get_source_for_fd(s, fd, &source);
+        if (r < 0) {
+                log_error("Failed to create source for fd:%d (%s)", fd, name);
+                return r;
+        }
+        assert(source);
+        assert(source->fd < 0);
+        source->fd = fd;
+
+        r = sd_event_add_io(s->events, &source->event,
+                            fd, EPOLLIN, dispatch_raw_source_event, s);
+        if (r < 0) {
+                log_error("Failed to register event source for fd:%d: %s",
+                          fd, strerror(-r));
+                goto error;
+        }
+
+        return 1; /* work to do */
+
+ error:
+        remove_source(s, fd);
+        return r;
+}
+
+static int setup_raw_socket(RemoteServer *s, const char *address) {
+        int fd, r;
+
+        fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        r = sd_event_add_io(s->events, &s->listen_event, fd, EPOLLIN,
+                            dispatch_raw_connection_event, s);
+        if (r < 0) {
+                close(fd);
+                return r;
+        }
+
+        s->active ++;
+        return 0;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int dispatch_sigterm(sd_event_source *event,
+                            const struct signalfd_siginfo *si,
+                            void *userdata) {
+        RemoteServer *s = userdata;
+
+        assert(s);
+
+        log_received_signal(LOG_INFO, si);
+
+        sd_event_exit(s->events, 0);
+        return 0;
+}
+
+static int setup_signals(RemoteServer *s) {
+        sigset_t mask;
+        int r;
+
+        assert(s);
+
+        assert_se(sigemptyset(&mask) == 0);
+        sigset_add_many(&mask, SIGINT, SIGTERM, -1);
+        assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
+
+        r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, dispatch_sigterm, s);
+        if (r < 0)
+                return r;
+
+        r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, dispatch_sigterm, s);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
+static int remoteserver_init(RemoteServer *s) {
+        int r, n, fd;
+        const char *output_name = NULL;
+
+        assert(s);
+
+        sd_event_default(&s->events);
+
+        setup_signals(s);
+
+        n = sd_listen_fds(true);
+        if (n < 0) {
+                log_error("Failed to read listening file descriptors from environment: %s",
+                          strerror(-n));
+                return n;
+        } else
+                log_info("Received %d descriptors", n);
+
+        for (fd = SD_LISTEN_FDS_START; fd < SD_LISTEN_FDS_START + n; fd++) {
+                if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
+                        assert_not_reached("not implemented");
+                } else if (sd_is_socket(fd, AF_UNSPEC, 0, true)) {
+                        log_info("Received a connection socket (fd:%d)", fd);
+
+                        r = add_source(s, fd, NULL);
+                        output_name = "socket";
+                } else {
+                        log_error("Unknown socket passed on fd:%d", fd);
+                        return -EINVAL;
+                }
+        }
+
+        if (arg_url) {
+                char _cleanup_free_ *url = NULL;
+                char _cleanup_strv_free_ **urlv = strv_new(arg_url, "/entries", NULL);
+                if (!urlv)
+                        return log_oom();
+                url = strv_join(urlv, "");
+                if (!url)
+                        return log_oom();
+
+                if (arg_getter) {
+                        log_info("Spawning getter %s...", url);
+                        fd = spawn_getter(arg_getter, url);
+                } else {
+                        log_info("Spawning curl %s...", url);
+                        fd = spawn_curl(url);
+                }
+                if (fd < 0)
+                        return fd;
+
+                r = add_source(s, fd, arg_url);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_url;
+        }
+
+        if (arg_listen_raw) {
+                log_info("Listening on a socket...");
+                r = setup_raw_socket(s, arg_listen_raw);
+                if (r < 0)
+                        return r;
+
+                output_name = arg_listen_raw;
+        }
+
+        if (arg_stdin) {
+                log_info("Reading standard input...");
+                r = add_source(s, STDIN_FILENO, "stdin");
+                if (r < 0)
+                        return r;
+
+                output_name = "stdin";
+        }
+
+        if (s->active == 0) {
+                log_error("Zarro sources specified");
+                return -EINVAL;
+        }
+
+        if (!!n + !!arg_url + !!arg_listen_raw + !!arg_stdin > 1)
+                output_name = "multiple";
+
+        r = writer_init(&s->writer);
+        if (r < 0)
+                return r;
+
+        r = open_output(&s->writer, output_name);
+        return r;
+}
+
+static int server_destroy(RemoteServer *s) {
+        int r;
+        ssize_t i;
+
+        r = writer_close(&s->writer);
+
+        assert(s->sources_size == 0 || s->sources);
+        for(i = 0; i < s->sources_size; i++)
+                remove_source(s, i);
+
+        free(s->sources);
+
+        sd_event_source_unref(s->sigterm_event);
+        sd_event_source_unref(s->sigint_event);
+        sd_event_source_unref(s->listen_event);
+        sd_event_unref(s->events);
+
+        /* fds that we're listening on remain open... */
+
+        return r;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int dispatch_raw_source_event(sd_event_source *event,
+                                     int fd,
+                                     uint32_t revents,
+                                     void *userdata) {
+
+        RemoteServer *s = userdata;
+        RemoteSource *source;
+        int r;
+
+        assert(fd < s->sources_size);
+        source = s->sources[fd];
+        assert(source->fd == fd);
+
+        r = process_source(source, &s->writer, arg_compress, arg_seal);
+        if (source->state == STATE_EOF) {
+                log_info("EOF reached with source fd:%d (%s)",
+                         source->fd, source->name);
+                if (source_non_empty(source))
+                        log_warning("EOF reached with incomplete data");
+                remove_source(s, source->fd);
+                log_info("%zd active source remaining", s->active);
+        } else if (r == -E2BIG) {
+                log_error("Entry too big, skipped");
+                r = 1;
+        }
+
+        return r;
+}
+
+static int dispatch_raw_connection_event(sd_event_source *event,
+                                         int fd,
+                                         uint32_t revents,
+                                         void *userdata) {
+        RemoteServer *s = userdata;
+
+        SocketAddress addr = {
+                .size = sizeof(union sockaddr_union),
+                .type = SOCK_STREAM,
+        };
+        int fd2, r;
+
+        log_debug("Accepting new connection on fd:%d", fd);
+        fd2 = accept4(fd, &addr.sockaddr.sa, &addr.size, SOCK_NONBLOCK|SOCK_CLOEXEC);
+        if (fd2 < 0) {
+                log_error("accept() on fd:%d failed: %m", fd);
+                return -errno;
+        }
+
+        switch(socket_address_family(&addr)) {
+        case AF_INET:
+        case AF_INET6: {
+                char* _cleanup_free_ a = NULL;
+
+                r = socket_address_print(&addr, &a);
+                if (r < 0) {
+                        log_error("socket_address_print(): %s", strerror(-r));
+                        close(fd2);
+                        return r;
+                }
+
+                log_info("Accepted %s connection from %s",
+                         socket_address_family(&addr) == AF_INET ? "IP" : "IPv6",
+                         a);
+                break;
+        };
+        default:
+                log_error("Connection with unsupported family %d",
+                          socket_address_family(&addr));
+                close(fd2);
+                return -EINVAL;
+        }
+
+        r = add_source(s, fd2, NULL);
+        if (r < 0)
+                log_error("failed to create source from fd:%d: %s", fd2, strerror(-r));
+
+        return r;
+}
+
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int help(void) {
+        printf("%s [OPTIONS...]\n\n"
+               "Write external journal events to a journal file.\n\n"
+               "Options:\n"
+               "  --url=URL            Read events from systemd-journal-gatewayd at URL\n"
+               "  --getter=COMMAND     Read events from the output of COMMAND\n"
+               "  --listen-raw=ADDR    Listen for connections at ADDR\n"
+               "  --stdin              Read events from standard input\n"
+               "  -o --output=FILE|DIR Write output to FILE or DIR/external-*.journal\n"
+               "  --[no-]compress      Use XZ-compression in the output journal (default: yes)\n"
+               "  --[no-]seal          Use Event sealing in the output journal (default: no)\n"
+               "  -h --help            Show this help and exit\n"
+               "  --version            Print version string and exit\n"
+               "\n"
+               "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
+               , program_invocation_short_name);
+
+        return 0;
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_URL,
+                ARG_LISTEN_RAW,
+                ARG_STDIN,
+                ARG_GETTER,
+                ARG_COMPRESS,
+                ARG_NO_COMPRESS,
+                ARG_SEAL,
+                ARG_NO_SEAL,
+        };
+
+        static const struct option options[] = {
+                { "help",         no_argument,       NULL, 'h'              },
+                { "version",      no_argument,       NULL, ARG_VERSION      },
+                { "url",          required_argument, NULL, ARG_URL          },
+                { "getter",       required_argument, NULL, ARG_GETTER       },
+                { "listen-raw",   required_argument, NULL, ARG_LISTEN_RAW   },
+                { "stdin",        no_argument,       NULL, ARG_STDIN        },
+                { "output",       required_argument, NULL, 'o'              },
+                { "compress",     no_argument,       NULL, ARG_COMPRESS     },
+                { "no-compress",  no_argument,       NULL, ARG_NO_COMPRESS  },
+                { "seal",         no_argument,       NULL, ARG_SEAL         },
+                { "no-seal",      no_argument,       NULL, ARG_NO_SEAL      },
+                {}
+        };
+
+        int c;
+
+        assert(argc >= 0);
+        assert(argv);
+
+        while ((c = getopt_long(argc, argv, "ho:", options, NULL)) >= 0)
+                switch(c) {
+                case 'h':
+                        help();
+                        return 0 /* done */;
+
+                case ARG_VERSION:
+                        puts(PACKAGE_STRING);
+                        puts(SYSTEMD_FEATURES);
+                        return 0 /* done */;
+
+                case ARG_URL:
+                        if (arg_url) {
+                                log_error("cannot currently set more than one --url");
+                                return -EINVAL;
+                        }
+
+                        arg_url = optarg;
+                        break;
+
+                case ARG_GETTER:
+                        if (arg_getter) {
+                                log_error("cannot currently use --getter more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_getter = optarg;
+                        break;
+
+                case ARG_LISTEN_RAW:
+                        if (arg_listen_raw) {
+                                log_error("cannot currently use --listen-raw more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_listen_raw = optarg;
+                        break;
+
+                case ARG_STDIN:
+                        arg_stdin = true;
+                        break;
+
+                case 'o':
+                        if (arg_output) {
+                                log_error("cannot use --output/-o more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_output = optarg;
+                        break;
+
+                case ARG_COMPRESS:
+                        arg_compress = true;
+                        break;
+                case ARG_NO_COMPRESS:
+                        arg_compress = false;
+                        break;
+                case ARG_SEAL:
+                        arg_seal = true;
+                        break;
+                case ARG_NO_SEAL:
+                        arg_seal = false;
+                        break;
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        log_error("Unknown option code %c", c);
+                        return -EINVAL;
+                }
+
+        if (optind < argc) {
+                log_error("This program takes no positional arguments");
+                return -EINVAL;
+        }
+
+        return 1 /* work to do */;
+}
+
+int main(int argc, char **argv) {
+        RemoteServer s = {};
+        int r, r2;
+
+        log_set_max_level(LOG_DEBUG);
+        log_show_color(true);
+        log_parse_environment();
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+        if (remoteserver_init(&s) < 0)
+                return EXIT_FAILURE;
+
+        log_debug("%s running as pid %lu",
+                  program_invocation_short_name, (unsigned long) getpid());
+        sd_notify(false,
+                  "READY=1\n"
+                  "STATUS=Processing requests...");
+
+        while (s.active) {
+                r = sd_event_get_state(s.events);
+                if (r < 0)
+                        break;
+                if (r == SD_EVENT_FINISHED)
+                        break;
+
+                r = sd_event_run(s.events, -1);
+                if (r < 0) {
+                        log_error("Failed to run event loop: %s", strerror(-r));
+                        break;
+                }
+        }
+
+        log_info("Finishing after writing %" PRIu64 " entries", s.writer.seqnum);
+        r2 = server_destroy(&s);
+
+        sd_notify(false, "STATUS=Shutting down...");
+
+        return r >= 0 && r2 >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}

commit f12be7e8ca278a5a207d0fd051acec700b804a7a
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Dec 1 11:12:05 2012 +0100

    journal-gatewayd: check if certificate is signed by CA
    
    If --trust=ca.crt is used, only clients presenting certificates signed
    by the ca will be allowed to proceed. No hostname matching is
    performed, so any client wielding a signed certificate will be
    authorized.
    
    Error functions are moved from journal-gateway to microhttp-util and
    made non-static, since now they are used in two source files.

diff --git a/configure.ac b/configure.ac
index 9e0739a..099fdac 100644
--- a/configure.ac
+++ b/configure.ac
@@ -712,7 +712,7 @@ AM_CONDITIONAL(HAVE_MICROHTTPD, [test "$have_microhttpd" = "yes"])
 have_gnutls=no
 AC_ARG_ENABLE(gnutls, AS_HELP_STRING([--disable-gnutls], [disable gnutls support]))
 if test "x$enable_gnutls" != "xno"; then
-        PKG_CHECK_MODULES(GNUTLS, [gnutls],
+        PKG_CHECK_MODULES(GNUTLS, [gnutls >= 3.1.4],
                 [AC_DEFINE(HAVE_GNUTLS, 1, [Define if gnutls is available]) have_gnutls=yes], have_gnutls=no)
         if test "x$have_gnutls" = xno -a "x$enable_gnutls" = xyes; then
                 AC_MSG_ERROR([*** gnutls support requested but libraries not found])
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index c9a2438..ac16a7c 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -27,6 +27,10 @@
 
 #include <microhttpd.h>
 
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#endif
+
 #include "log.h"
 #include "util.h"
 #include "sd-journal.h"
@@ -38,6 +42,10 @@
 #include "build.h"
 #include "fileio.h"
 
+static char *key_pem = NULL;
+static char *cert_pem = NULL;
+static char *trust_pem = NULL;
+
 typedef struct RequestMeta {
         sd_journal *journal;
 
@@ -111,60 +119,6 @@ static int open_journal(RequestMeta *m) {
         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
 }
 
-static int respond_oom_internal(struct MHD_Connection *connection) {
-        struct MHD_Response *response;
-        const char m[] = "Out of memory.\n";
-        int ret;
-
-        assert(connection);
-
-        response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
-        if (!response)
-                return MHD_NO;
-
-        MHD_add_response_header(response, "Content-Type", "text/plain");
-        ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
-        MHD_destroy_response(response);
-
-        return ret;
-}
-
-#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
-
-_printf_(3,4)
-static int respond_error(
-                struct MHD_Connection *connection,
-                unsigned code,
-                const char *format, ...) {
-
-        struct MHD_Response *response;
-        char *m;
-        int r;
-        va_list ap;
-
-        assert(connection);
-        assert(format);
-
-        va_start(ap, format);
-        r = vasprintf(&m, format, ap);
-        va_end(ap);
-
-        if (r < 0)
-                return respond_oom(connection);
-
-        response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
-        if (!response) {
-                free(m);
-                return respond_oom(connection);
-        }
-
-        MHD_add_response_header(response, "Content-Type", "text/plain");
-        r = MHD_queue_response(connection, code, response);
-        MHD_destroy_response(response);
-
-        return r;
-}
-
 static ssize_t request_reader_entries(
                 void *cls,
                 uint64_t pos,
@@ -859,6 +813,7 @@ static int request_handler(
                 const char *upload_data,
                 size_t *upload_data_size,
                 void **connection_cls) {
+        int r, code;
 
         assert(connection);
         assert(connection_cls);
@@ -876,6 +831,12 @@ static int request_handler(
                 return MHD_YES;
         }
 
+        if (trust_pem) {
+                r = check_permissions(connection, &code);
+                if (r < 0)
+                        return code;
+        }
+
         if (streq(url, "/"))
                 return request_handler_redirect(connection, "/browse");
 
@@ -908,10 +869,6 @@ static int help(void) {
         return 0;
 }
 
-static char *key_pem = NULL;
-static char *cert_pem = NULL;
-static char *trust_pem = NULL;
-
 static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
@@ -973,6 +930,7 @@ static int parse_argv(int argc, char *argv[]) {
                         break;
 
                 case ARG_TRUST:
+#ifdef HAVE_GNUTLS
                         if (trust_pem) {
                                 log_error("CA certificate file specified twice");
                                 return -EINVAL;
@@ -984,6 +942,9 @@ static int parse_argv(int argc, char *argv[]) {
                         }
                         assert(trust_pem);
                         break;
+#else
+                        log_error("Option --trust is not available.");
+#endif
 
                 case '?':
                         return -EINVAL;
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
index b07ae6d..17135ab 100644
--- a/src/journal/microhttpd-util.c
+++ b/src/journal/microhttpd-util.c
@@ -3,6 +3,7 @@
 /***
   This file is part of systemd.
 
+  Copyright 2012 Lennart Poettering
   Copyright 2012 Zbigniew Jędrzejewski-Szmek
 
   systemd is free software; you can redistribute it and/or modify it
@@ -21,12 +22,18 @@
 
 #include <stddef.h>
 #include <stdio.h>
+#include <string.h>
 
 #include "microhttpd-util.h"
 #include "log.h"
 #include "macro.h"
 #include "util.h"
 
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#endif
+
 void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
         _cleanup_free_ char *f = NULL;
 
@@ -40,6 +47,59 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
         REENABLE_WARNING;
 }
 
+
+int respond_oom_internal(struct MHD_Connection *connection) {
+        struct MHD_Response *response;
+        const char m[] = "Out of memory.\n";
+        int ret;
+
+        assert(connection);
+
+        response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
+        if (!response)
+                return MHD_NO;
+
+        MHD_add_response_header(response, "Content-Type", "text/plain");
+        ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
+        MHD_destroy_response(response);
+
+        return ret;
+}
+
+_printf_(3,4)
+int respond_error(struct MHD_Connection *connection,
+                  unsigned code,
+                  const char *format, ...) {
+
+        struct MHD_Response *response;
+        char *m;
+        int r;
+        va_list ap;
+
+        assert(connection);
+        assert(format);
+
+        va_start(ap, format);
+        r = vasprintf(&m, format, ap);
+        va_end(ap);
+
+        if (r < 0)
+                return respond_oom(connection);
+
+        response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
+        if (!response) {
+                free(m);
+                return respond_oom(connection);
+        }
+
+        log_debug("queing response %u: %s", code, m);
+        MHD_add_response_header(response, "Content-Type", "text/plain");
+        r = MHD_queue_response(connection, code, response);
+        MHD_destroy_response(response);
+
+        return r;
+}
+
 #ifdef HAVE_GNUTLS
 
 static int log_level_map[] = {
@@ -73,4 +133,133 @@ void log_func_gnutls(int level, const char *message) {
         log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
 }
 
+static int verify_cert_authorized(gnutls_session_t session) {
+        unsigned status;
+        gnutls_certificate_type_t type;
+        gnutls_datum_t out;
+        int r;
+
+        r = gnutls_certificate_verify_peers2(session, &status);
+        if (r < 0) {
+                log_error("gnutls_certificate_verify_peers2 failed: %s", strerror(-r));
+                return r;
+        }
+
+        type = gnutls_certificate_type_get(session);
+        r = gnutls_certificate_verification_status_print(status, type, &out, 0);
+        if (r < 0) {
+                log_error("gnutls_certificate_verification_status_print failed: %s", strerror(-r));
+                return r;
+        }
+
+        log_info("Certificate status: %s", out.data);
+
+        return status == 0 ? 0 : -EPERM;
+}
+
+static int get_client_cert(gnutls_session_t session, gnutls_x509_crt_t *client_cert) {
+        const gnutls_datum_t *pcert;
+        unsigned listsize;
+        gnutls_x509_crt_t cert;
+        int r;
+
+        assert(session);
+        assert(client_cert);
+
+        pcert = gnutls_certificate_get_peers(session, &listsize);
+        if (!pcert || !listsize) {
+                log_error("Failed to retrieve certificate chain");
+                return -EINVAL;
+        }
+
+        r = gnutls_x509_crt_init(&cert);
+        if (r < 0) {
+                log_error("Failed to initialize client certificate");
+                return r;
+        }
+
+        /* Note that by passing values between 0 and listsize here, you
+           can get access to the CA's certs */
+        r = gnutls_x509_crt_import(cert, &pcert[0], GNUTLS_X509_FMT_DER);
+        if (r < 0) {
+                log_error("Failed to import client certificate");
+                gnutls_x509_crt_deinit(cert);
+                return r;
+        }
+
+        *client_cert = cert;
+        return 0;
+}
+
+static int get_auth_dn(gnutls_x509_crt_t client_cert, char **buf) {
+        size_t len = 0;
+        int r;
+
+        assert(buf);
+        assert(*buf == NULL);
+
+        r = gnutls_x509_crt_get_dn(client_cert, NULL, &len);
+        if (r != GNUTLS_E_SHORT_MEMORY_BUFFER) {
+                log_error("gnutls_x509_crt_get_dn failed");
+                return r;
+        }
+
+        *buf = malloc(len);
+        if (!*buf)
+                return log_oom();
+
+        gnutls_x509_crt_get_dn(client_cert, *buf, &len);
+        return 0;
+}
+
+int check_permissions(struct MHD_Connection *connection, int *code) {
+        const union MHD_ConnectionInfo *ci;
+        gnutls_session_t session;
+        gnutls_x509_crt_t client_cert;
+        char _cleanup_free_ *buf = NULL;
+        int r;
+
+        assert(connection);
+        assert(code);
+
+        *code = 0;
+
+        ci = MHD_get_connection_info(connection,
+                                     MHD_CONNECTION_INFO_GNUTLS_SESSION);
+        if (!ci) {
+                log_error("MHD_get_connection_info failed");
+                return -EINVAL;
+        }
+        session = ci->tls_session;
+        assert(session);
+
+        r = get_client_cert(session, &client_cert);
+        if (r < 0) {
+                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
+                                      "Authorization through certificate is required");
+                return -EPERM;
+        }
+
+        r = get_auth_dn(client_cert, &buf);
+        if (r < 0) {
+                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
+                                      "Failed to determine distinguished name from certificate");
+                return -EPERM;
+        }
+
+        log_info("Connection from %s", buf);
+
+        r = verify_cert_authorized(session);
+        if (r < 0) {
+                log_error("Client is not authorized");
+                *code = respond_error(connection, MHD_HTTP_UNAUTHORIZED,
+                                      "Client certificate not signed by recognized authority");
+        }
+        return r;
+}
+
+#else
+int check_permissions(struct MHD_Connection *connection, int *code) {
+        return -EPERM;
+}
 #endif
diff --git a/src/journal/microhttpd-util.h b/src/journal/microhttpd-util.h
index 4afe0a2..cd14ac4 100644
--- a/src/journal/microhttpd-util.h
+++ b/src/journal/microhttpd-util.h
@@ -22,14 +22,24 @@
 #pragma once
 
 #include <stdarg.h>
+#include <microhttpd.h>
 
 #include "macro.h"
 
 void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
 
-#ifdef HAVE_GNUTLS
-#include <gnutls/gnutls.h>
+int respond_oom_internal(struct MHD_Connection *connection);
+
+/* respond_oom() must be usable with return, hence this form. */
+#define respond_oom(connection) log_oom(), respond_oom_internal(connection)
+
+int respond_error(struct MHD_Connection *connection,
+                  unsigned code,
+                  const char *format, ...);
 
+int check_permissions(struct MHD_Connection *connection, int *code);
+
+#ifdef HAVE_GNUTLS
 void log_func_gnutls(int level, const char *message);
 
 /* This is additionally filtered by our internal log level, so it

commit cafc7f91306ea17ace4a6c3d76d81c8780c87452
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Wed Nov 28 23:08:35 2012 +0100

    journal-gatewayd: log to journal from gnutls
    
    Prefix "gnutls: " is added. Some semi-random mapping of gnutls levels
    to syslog levels is done, but since gnutls levels seem to be used
    rather loosely, most end up as debug.

diff --git a/Makefile.am b/Makefile.am
index 9e01cd5..fed8561 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -3437,6 +3437,11 @@ systemd_journal_gatewayd_LDADD = \
 	libsystemd-shared.la \
 	$(MICROHTTPD_LIBS)
 
+if HAVE_GNUTLS
+systemd_journal_gatewayd_LDADD += \
+	$(GNUTLS_LIBS)
+endif
+
 systemd_journal_gatewayd_CFLAGS = \
 	$(AM_CFLAGS) \
 	$(MICROHTTPD_CFLAGS)
diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index 862ee79..c9a2438 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -1024,6 +1024,11 @@ int main(int argc, char *argv[]) {
         if (r == 0)
                 return EXIT_SUCCESS;
 
+#ifdef HAVE_GNUTLS
+        gnutls_global_set_log_function(log_func_gnutls);
+        gnutls_global_set_log_level(GNUTLS_LOG_LEVEL);
+#endif
+
         n = sd_listen_fds(1);
         if (n < 0) {
                 log_error("Failed to determine passed sockets: %s", strerror(-n));
diff --git a/src/journal/microhttpd-util.c b/src/journal/microhttpd-util.c
index 3844f7a..b07ae6d 100644
--- a/src/journal/microhttpd-util.c
+++ b/src/journal/microhttpd-util.c
@@ -39,3 +39,38 @@ void microhttpd_logger(void *arg, const char *fmt, va_list ap) {
         log_metav(LOG_INFO, NULL, 0, NULL, f, ap);
         REENABLE_WARNING;
 }
+
+#ifdef HAVE_GNUTLS
+
+static int log_level_map[] = {
+        LOG_DEBUG,
+        LOG_WARNING, /* gnutls session audit */
+        LOG_DEBUG,   /* gnutls debug log */
+        LOG_WARNING, /* gnutls assert log */
+        LOG_INFO,    /* gnutls handshake log */
+        LOG_DEBUG,   /* gnutls record log */
+        LOG_DEBUG,   /* gnutls dtls log */
+        LOG_DEBUG,
+        LOG_DEBUG,
+        LOG_DEBUG,
+        LOG_DEBUG,   /* gnutls hard log */
+        LOG_DEBUG,   /* gnutls read log */
+        LOG_DEBUG,   /* gnutls write log */
+        LOG_DEBUG,   /* gnutls io log */
+        LOG_DEBUG,   /* gnutls buffers log */
+};
+
+void log_func_gnutls(int level, const char *message) {
+        int ourlevel;
+
+        assert_se(message);
+
+        if (0 <= level && level < (int) ELEMENTSOF(log_level_map))
+                ourlevel = log_level_map[level];
+        else
+                level = LOG_DEBUG;
+
+        log_meta(ourlevel, NULL, 0, NULL, "gnutls: %s", message);
+}
+
+#endif
diff --git a/src/journal/microhttpd-util.h b/src/journal/microhttpd-util.h
index 74d1668..4afe0a2 100644
--- a/src/journal/microhttpd-util.h
+++ b/src/journal/microhttpd-util.h
@@ -26,3 +26,15 @@
 #include "macro.h"
 
 void microhttpd_logger(void *arg, const char *fmt, va_list ap) _printf_(2, 0);
+
+#ifdef HAVE_GNUTLS
+#include <gnutls/gnutls.h>
+
+void log_func_gnutls(int level, const char *message);
+
+/* This is additionally filtered by our internal log level, so it
+ * should be set fairly high to capture all potentially interesting
+ * events without overwhelming detail.
+ */
+#define GNUTLS_LOG_LEVEL 6
+#endif

commit 6031319956b4b1d13799373bfda3e8690f6fa874
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Wed Nov 28 12:45:31 2012 +0100

    build-sys: add check on gnutls

diff --git a/configure.ac b/configure.ac
index ae5a6b5..9e0739a 100644
--- a/configure.ac
+++ b/configure.ac
@@ -709,6 +709,18 @@ fi
 AM_CONDITIONAL(HAVE_MICROHTTPD, [test "$have_microhttpd" = "yes"])
 
 # ------------------------------------------------------------------------------
+have_gnutls=no
+AC_ARG_ENABLE(gnutls, AS_HELP_STRING([--disable-gnutls], [disable gnutls support]))
+if test "x$enable_gnutls" != "xno"; then
+        PKG_CHECK_MODULES(GNUTLS, [gnutls],
+                [AC_DEFINE(HAVE_GNUTLS, 1, [Define if gnutls is available]) have_gnutls=yes], have_gnutls=no)
+        if test "x$have_gnutls" = xno -a "x$enable_gnutls" = xyes; then
+                AC_MSG_ERROR([*** gnutls support requested but libraries not found])
+        fi
+fi
+AM_CONDITIONAL(HAVE_GNUTLS, [test "$have_gnutls" = "yes"])
+
+# ------------------------------------------------------------------------------
 have_binfmt=no
 AC_ARG_ENABLE(binfmt, AS_HELP_STRING([--disable-binfmt], [disable binfmt tool]))
 if test "x$enable_binfmt" != "xno"; then
@@ -1132,6 +1144,7 @@ AC_MSG_RESULT([
         QRENCODE:                ${have_qrencode}
         MICROHTTPD:              ${have_microhttpd}
         CHKCONFIG:               ${have_chkconfig}
+        GNUTLS:                  ${have_gnutls}
         binfmt:                  ${have_binfmt}
         vconsole:                ${have_vconsole}
         readahead:               ${have_readahead}

commit e5ebe12b770bbb7bf73177517c339dc3601a5efc
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Nov 26 23:02:14 2012 +0100

    journal-gatewayd: ask clients to provide certificates
    
    A certificate authority certificate will be presented to clients,
    causing them to present their client certificate, if it is signed by
    this authority (default behaviour of most clients). No certificate
    checking is actually performed.

diff --git a/src/journal/journal-gatewayd.c b/src/journal/journal-gatewayd.c
index 7e97a35..862ee79 100644
--- a/src/journal/journal-gatewayd.c
+++ b/src/journal/journal-gatewayd.c
@@ -900,8 +900,9 @@ static int help(void) {
                "HTTP server for journal events.\n\n"
                "  -h --help           Show this help\n"
                "     --version        Show package version\n"
-               "     --cert=CERT.PEM  Specify server certificate in PEM format\n"
-               "     --key=KEY.PEM    Specify server key in PEM format\n",
+               "     --cert=CERT.PEM  Server certificate in PEM format\n"
+               "     --key=KEY.PEM    Server key in PEM format\n"
+               "     --trust=CERT.PEM Certificat authority certificate in PEM format\n",
                program_invocation_short_name);
 
         return 0;
@@ -909,12 +910,14 @@ static int help(void) {
 
 static char *key_pem = NULL;
 static char *cert_pem = NULL;
+static char *trust_pem = NULL;
 
 static int parse_argv(int argc, char *argv[]) {
         enum {
                 ARG_VERSION = 0x100,
                 ARG_KEY,
                 ARG_CERT,
+                ARG_TRUST,
         };
 
         int r, c;
@@ -924,6 +927,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "version", no_argument,       NULL, ARG_VERSION },
                 { "key",     required_argument, NULL, ARG_KEY     },
                 { "cert",    required_argument, NULL, ARG_CERT    },
+                { "trust",   required_argument, NULL, ARG_TRUST   },
                 {}
         };
 
@@ -968,6 +972,19 @@ static int parse_argv(int argc, char *argv[]) {
                         assert(cert_pem);
                         break;
 
+                case ARG_TRUST:
+                        if (trust_pem) {
+                                log_error("CA certificate file specified twice");
+                                return -EINVAL;
+                        }
+                        r = read_full_file(optarg, &trust_pem, NULL);
+                        if (r < 0) {
+                                log_error("Failed to read CA certificate file: %s", strerror(-r));
+                                return r;
+                        }
+                        assert(trust_pem);
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -985,6 +1002,11 @@ static int parse_argv(int argc, char *argv[]) {
                 return -EINVAL;
         }
 
+        if (trust_pem && !key_pem) {
+                log_error("CA certificate can only be used with certificate file");
+                return -EINVAL;
+        }
+
         return 1;
 }
 
@@ -1018,6 +1040,7 @@ int main(int argc, char *argv[]) {
                         { MHD_OPTION_END, 0, NULL },
                         { MHD_OPTION_END, 0, NULL },
                         { MHD_OPTION_END, 0, NULL },
+                        { MHD_OPTION_END, 0, NULL },
                         { MHD_OPTION_END, 0, NULL }};
                 int opts_pos = 2;
                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
@@ -1033,6 +1056,11 @@ int main(int argc, char *argv[]) {
                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
                         flags |= MHD_USE_SSL;
                 }
+                if (trust_pem) {
+                        assert(flags & MHD_USE_SSL);
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
+                }
 
                 d = MHD_start_daemon(flags, 19531,
                                      NULL, NULL,

commit e0aa3726103448097e5ad7cc6f427e142103a321
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sun Mar 9 21:33:04 2014 -0400

    activate: export make_socket_fd
    
    Also improve logging to print out the parsed address on error.

diff --git a/src/activate/activate.c b/src/activate/activate.c
index 23c484c..8c58273 100644
--- a/src/activate/activate.c
+++ b/src/activate/activate.c
@@ -59,25 +59,6 @@ static int add_epoll(int epoll_fd, int fd) {
         return 0;
 }
 
-static int make_socket_fd(const char* address, int flags) {
-        SocketAddress a;
-        int fd, r;
-
-        r = socket_address_parse(&a, address);
-        if (r < 0) {
-                log_error("Failed to parse socket: %s", strerror(-r));
-                return r;
-        }
-
-        fd = socket_address_listen(&a, flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT, NULL, false, false, 0755, 0644, NULL);
-        if (fd < 0) {
-                log_error("Failed to listen: %s", strerror(-r));
-                return fd;
-        }
-
-        return fd;
-}
-
 static int open_sockets(int *epoll_fd, bool accept) {
         char **address;
         int n, fd, r;
@@ -119,7 +100,7 @@ static int open_sockets(int *epoll_fd, bool accept) {
 
         STRV_FOREACH(address, arg_listen) {
 
-                fd = make_socket_fd(*address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC));
+                fd = make_socket_fd(LOG_DEBUG, *address, SOCK_STREAM | (arg_accept*SOCK_CLOEXEC));
                 if (fd < 0) {
                         log_open();
                         log_error("Failed to open '%s': %s", *address, strerror(-fd));
diff --git a/src/shared/socket-label.c b/src/shared/socket-label.c
index c8be17a..1e78dd2 100644
--- a/src/shared/socket-label.c
+++ b/src/shared/socket-label.c
@@ -143,3 +143,33 @@ int socket_address_listen(
 
         return r;
 }
+
+int make_socket_fd(int log_level, const char* address, int flags) {
+        SocketAddress a;
+        int fd, r;
+
+        r = socket_address_parse(&a, address);
+        if (r < 0) {
+                log_error("Failed to parse socket: %s", strerror(-r));
+                return r;
+        }
+
+        fd = socket_address_listen(&a, flags, SOMAXCONN, SOCKET_ADDRESS_DEFAULT,
+                                   NULL, false, false, 0755, 0644, NULL);
+        if (fd < 0 || log_get_max_level() >= log_level) {
+                char _cleanup_free_ *p = NULL;
+
+                r = socket_address_print(&a, &p);
+                if (r < 0) {
+                        log_error("socket_address_print(): %s", strerror(-r));
+                        return r;
+                }
+
+                if (fd < 0)
+                        log_error("Failed to listen on %s: %s", p, strerror(-r));
+                else
+                        log_full(log_level, "Listening on %s", p);
+        }
+
+        return fd;
+}
diff --git a/src/shared/socket-util.h b/src/shared/socket-util.h
index 84ebc30..efaaf82 100644
--- a/src/shared/socket-util.h
+++ b/src/shared/socket-util.h
@@ -84,6 +84,7 @@ int socket_address_listen(
                 mode_t directory_mode,
                 mode_t socket_mode,
                 const char *label);
+int make_socket_fd(int log_level, const char* address, int flags);
 
 bool socket_address_is(const SocketAddress *a, const char *s, int type);
 bool socket_address_is_netlink(const SocketAddress *a, const char *s);

commit 6cf487afad4ef4706b5d6d9ba5df24cac68e687a
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Fri Nov 2 15:05:31 2012 +0100

    shared: export is_dir

diff --git a/src/shared/mkdir.c b/src/shared/mkdir.c
index b35551e..ba083d6 100644
--- a/src/shared/mkdir.c
+++ b/src/shared/mkdir.c
@@ -58,7 +58,7 @@ int mkdir_safe(const char *path, mode_t mode, uid_t uid, gid_t gid) {
         return mkdir_safe_internal(path, mode, uid, gid, mkdir);
 }
 
-static int is_dir(const char* path) {
+int is_dir(const char* path) {
         struct stat st;
 
         if (stat(path, &st) < 0)
diff --git a/src/shared/mkdir.h b/src/shared/mkdir.h
index eb73902..f1bf4c0 100644
--- a/src/shared/mkdir.h
+++ b/src/shared/mkdir.h
@@ -41,3 +41,4 @@ typedef int (*mkdir_func_t)(const char *pathname, mode_t mode);
 int mkdir_safe_internal(const char *path, mode_t mode, uid_t uid, gid_t gid, mkdir_func_t _mkdir);
 int mkdir_parents_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir);
 int mkdir_p_internal(const char *prefix, const char *path, mode_t mode, mkdir_func_t _mkdir);
+int is_dir(const char *path);
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index 52f8037..74a0127 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -542,7 +542,7 @@ static int recursive_relabel_children(Item *i, const char *path) {
 
         for (;;) {
                 struct dirent *de;
-                bool is_dir;
+                bool dir;
                 int r;
                 _cleanup_free_ char *entry_path = NULL;
 
@@ -567,18 +567,17 @@ static int recursive_relabel_children(Item *i, const char *path) {
                 }
 
                 if (de->d_type == DT_UNKNOWN) {
-                        struct stat st;
-
-                        if (lstat(entry_path, &st) < 0) {
+                        r = is_dir(entry_path);
+                        if (r < 0) {
                                 if (ret == 0 && errno != ENOENT)
                                         ret = -errno;
                                 continue;
                         }
 
-                        is_dir = S_ISDIR(st.st_mode);
+                        dir = r;
 
                 } else
-                        is_dir = de->d_type == DT_DIR;
+                        dir = de->d_type == DT_DIR;
 
                 r = item_set_perms(i, entry_path);
                 if (r < 0) {
@@ -587,7 +586,7 @@ static int recursive_relabel_children(Item *i, const char *path) {
                         continue;
                 }
 
-                if (is_dir) {
+                if (dir) {
                         r = recursive_relabel_children(i, entry_path);
                         if (r < 0 && ret == 0)
                                 ret = r;

commit d18d46ecea80a7f07415edb9264af6a254fd70bb
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Sat Nov 3 20:13:46 2012 +0100

    journal: export valid_user_field and size defines
    
    In preparation for use elsewhere.

diff --git a/src/journal/coredump.c b/src/journal/coredump.c
index 733373b..29342de 100644
--- a/src/journal/coredump.c
+++ b/src/journal/coredump.c
@@ -36,12 +36,14 @@
 #include "mkdir.h"
 #include "special.h"
 #include "cgroup-util.h"
+#include "journald-native.h"
 
 /* Few programs have less than 3MiB resident */
 #define COREDUMP_MIN_START (3*1024*1024)
 /* Make sure to not make this larger than the maximum journal entry
  * size. See ENTRY_SIZE_MAX in journald-native.c. */
 #define COREDUMP_MAX (767*1024*1024)
+assert_cc(COREDUMP_MAX <= ENTRY_SIZE_MAX);
 
 enum {
         ARG_PID = 1,
diff --git a/src/journal/journald-native.c b/src/journal/journald-native.c
index 0509c1e..c54f647 100644
--- a/src/journal/journald-native.c
+++ b/src/journal/journald-native.c
@@ -33,12 +33,7 @@
 #include "journald-syslog.h"
 #include "journald-wall.h"
 
-/* Make sure not to make this smaller than the maximum coredump
- * size. See COREDUMP_MAX in coredump.c */
-#define ENTRY_SIZE_MAX (1024*1024*768)
-#define DATA_SIZE_MAX (1024*1024*768)
-
-static bool valid_user_field(const char *p, size_t l) {
+bool valid_user_field(const char *p, size_t l, bool allow_protected) {
         const char *a;
 
         /* We kinda enforce POSIX syntax recommendations for
@@ -56,7 +51,7 @@ static bool valid_user_field(const char *p, size_t l) {
                 return false;
 
         /* Variables starting with an underscore are protected */
-        if (p[0] == '_')
+        if (!allow_protected && p[0] == '_')
                 return false;
 
         /* Don't allow digits as first character */
@@ -65,9 +60,9 @@ static bool valid_user_field(const char *p, size_t l) {
 
         /* Only allow A-Z0-9 and '_' */
         for (a = p; a < p + l; a++)
-                if (!((*a >= 'A' && *a <= 'Z') ||
-                      (*a >= '0' && *a <= '9') ||
-                      *a == '_'))
+                if ((*a < 'A' || *a > 'Z') &&
+                    (*a < '0' || *a > '9') &&
+                    *a != '_')
                         return false;
 
         return true;
@@ -139,7 +134,7 @@ void server_process_native_message(
 
                 q = memchr(p, '=', e - p);
                 if (q) {
-                        if (valid_user_field(p, q - p)) {
+                        if (valid_user_field(p, q - p, false)) {
                                 size_t l;
 
                                 l = e - p;
@@ -239,7 +234,7 @@ void server_process_native_message(
                         k[e - p] = '=';
                         memcpy(k + (e - p) + 1, e + 1 + sizeof(uint64_t), l);
 
-                        if (valid_user_field(p, e - p)) {
+                        if (valid_user_field(p, e - p, false)) {
                                 iovec[n].iov_base = k;
                                 iovec[n].iov_len = (e - p) + 1 + l;
                                 n++;
diff --git a/src/journal/journald-native.h b/src/journal/journald-native.h
index 16c09f5..bf02fee 100644
--- a/src/journal/journald-native.h
+++ b/src/journal/journald-native.h
@@ -23,6 +23,13 @@
 
 #include "journald-server.h"
 
+/* Make sure not to make this smaller than the maximum coredump
+ * size. See COREDUMP_MAX in coredump.c */
+#define ENTRY_SIZE_MAX (1024*1024*768)
+#define DATA_SIZE_MAX (1024*1024*768)
+
+bool valid_user_field(const char *p, size_t l, bool allow_protected);
+
 void server_process_native_message(Server *s, const void *buffer, size_t buffer_size, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len);
 
 void server_process_native_file(Server *s, int fd, struct ucred *ucred, struct timeval *tv, const char *label, size_t label_len);

commit 63c8666b824e8762ffb73647e1caee165dfbc868
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Nov 1 22:36:52 2012 +0100

    journal: extract duplicated code to a function

diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 0ab8c70..ef39d0a 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -364,6 +364,20 @@ void server_sync(Server *s) {
         s->sync_scheduled = false;
 }
 
+static void do_vacuum(Server *s, char *ids, JournalFile *f, const char* path,
+                      JournalMetrics *metrics) {
+        char *p;
+        int r;
+
+        if (!f)
+                return;
+
+        p = strappenda(path, ids);
+        r = journal_directory_vacuum(p, metrics->max_use, s->max_retention_usec, &s->oldest_file_usec);
+        if (r < 0 && r != -ENOENT)
+                log_error("Failed to vacuum %s: %s", p, strerror(-r));
+}
+
 void server_vacuum(Server *s) {
         char ids[33];
         sd_id128_t machine;
@@ -378,24 +392,10 @@ void server_vacuum(Server *s) {
                 log_error("Failed to get machine ID: %s", strerror(-r));
                 return;
         }
-
         sd_id128_to_string(machine, ids);
 
-        if (s->system_journal) {
-                char *p = strappenda("/var/log/journal/", ids);
-
-                r = journal_directory_vacuum(p, s->system_metrics.max_use, s->max_retention_usec, &s->oldest_file_usec);
-                if (r < 0 && r != -ENOENT)
-                        log_error("Failed to vacuum %s: %s", p, strerror(-r));
-        }
-
-        if (s->runtime_journal) {
-                char *p = strappenda("/run/log/journal/", ids);
-
-                r = journal_directory_vacuum(p, s->runtime_metrics.max_use, s->max_retention_usec, &s->oldest_file_usec);
-                if (r < 0 && r != -ENOENT)
-                        log_error("Failed to vacuum %s: %s", p, strerror(-r));
-        }
+        do_vacuum(s, ids, s->system_journal, "/var/log/journal/", &s->system_metrics);
+        do_vacuum(s, ids, s->runtime_journal, "/run/log/journal/", &s->runtime_metrics);
 
         s->cached_available_space_timestamp = 0;
 }

commit fc55baee9964a118afbddbf82b8e667a0ad80b99
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Thu Nov 1 22:26:22 2012 +0100

    journal: extract duplicated code to a function

diff --git a/src/journal/journald-server.c b/src/journal/journald-server.c
index 5befe93..0ab8c70 100644
--- a/src/journal/journald-server.c
+++ b/src/journal/journald-server.c
@@ -295,6 +295,27 @@ static JournalFile* find_journal(Server *s, uid_t uid) {
         return f;
 }
 
+static int do_rotate(Server *s, JournalFile **f, const char* name,
+                     bool seal, uint32_t uid) {
+        int r;
+        assert(s);
+
+        if (!*f)
+                return -EINVAL;
+
+        r = journal_file_rotate(f, s->compress, seal);
+        if (r < 0)
+                if (*f)
+                        log_error("Failed to rotate %s: %s",
+                                  (*f)->path, strerror(-r));
+                else
+                        log_error("Failed to create new %s journal: %s",
+                                  name, strerror(-r));
+        else
+                server_fix_perms(s, *f, uid);
+        return r;
+}
+
 void server_rotate(Server *s) {
         JournalFile *f;
         void *k;
@@ -303,42 +324,16 @@ void server_rotate(Server *s) {
 
         log_debug("Rotating...");
 
-        if (s->runtime_journal) {
-                r = journal_file_rotate(&s->runtime_journal, s->compress, false);
-                if (r < 0)
-                        if (s->runtime_journal)
-                                log_error("Failed to rotate %s: %s", s->runtime_journal->path, strerror(-r));
-                        else
-                                log_error("Failed to create new runtime journal: %s", strerror(-r));
-                else
-                        server_fix_perms(s, s->runtime_journal, 0);
-        }
-
-        if (s->system_journal) {
-                r = journal_file_rotate(&s->system_journal, s->compress, s->seal);
-                if (r < 0)
-                        if (s->system_journal)
-                                log_error("Failed to rotate %s: %s", s->system_journal->path, strerror(-r));
-                        else
-                                log_error("Failed to create new system journal: %s", strerror(-r));
-
-                else
-                        server_fix_perms(s, s->system_journal, 0);
-        }
+        do_rotate(s, &s->runtime_journal, "runtime", false, 0);
+        do_rotate(s, &s->system_journal, "system", s->seal, 0);
 
         HASHMAP_FOREACH_KEY(f, k, s->user_journals, i) {
-                r = journal_file_rotate(&f, s->compress, s->seal);
-                if (r < 0)
-                        if (f)
-                                log_error("Failed to rotate %s: %s", f->path, strerror(-r));
-                        else {
-                                log_error("Failed to create user journal: %s", strerror(-r));
-                                hashmap_remove(s->user_journals, k);
-                        }
-                else {
+                r = do_rotate(s, &f, "user", s->seal, PTR_TO_UINT32(k));
+                if (r >= 0)
                         hashmap_replace(s->user_journals, k, f);
-                        server_fix_perms(s, f, PTR_TO_UINT32(k));
-                }
+                else if (!f)
+                        /* Old file has been closed and deallocated */
+                        hashmap_remove(s->user_journals, k);
         }
 }
 

commit 2fc74bf4336eb7a7e40c0b355d19966cd97d4b3c
Author: Zbigniew Jędrzejewski-Szmek <zbyszek at in.waw.pl>
Date:   Mon Mar 10 21:19:23 2014 -0400

    journald: remove stray reset of error return value

diff --git a/src/journal/journald.c b/src/journal/journald.c
index 37896d0..c8c0801 100644
--- a/src/journal/journald.c
+++ b/src/journal/journald.c
@@ -110,7 +110,6 @@ int main(int argc, char *argv[]) {
                 r = sd_event_run(server.event, t);
                 if (r < 0) {
                         log_error("Failed to run event loop: %s", strerror(-r));
-                        r = -errno;
                         goto finish;
                 }
 



More information about the systemd-commits mailing list