]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
journal-remote: split out µhttpd support and main() into a separate file
authorZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Wed, 16 May 2018 08:21:58 +0000 (10:21 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
Thu, 31 May 2018 11:04:18 +0000 (13:04 +0200)
This is in preparation to reusing the RemoteServer in other concepts.
I tried to keep changes to minimum:
- arg_* global variables are now passed as state in RemoteServer
- exported functions get the "journal_remote_" prefix
- some variables are renamed

In particular, there is an ugly global RemoveServer* variable. It was originally
added because µhttpd did not allow state to be passed to the callbacks. I'm not
sure if this has been remediated in µhttpd, but either way, this is not changed
here, the global variable is only renamed for clarity.

meson.build
src/journal-remote/journal-remote-main.c [new file with mode: 0644]
src/journal-remote/journal-remote.c
src/journal-remote/journal-remote.h
src/journal-remote/meson.build

index 83fed46cc7e9676e4cdfee1b4490dfe3e48f563e..ac1db5319da8c5cb206be55361c7d622911220bb 100644 (file)
@@ -2007,7 +2007,8 @@ if conf.get('ENABLE_REMOTE') == 1 and conf.get('HAVE_MICROHTTPD') == 1
         s_j_remote = executable('systemd-journal-remote',
                                 systemd_journal_remote_sources,
                                 include_directories : includes,
-                                link_with : [libshared],
+                                link_with : [libshared,
+                                             libsystemd_journal_remote],
                                 dependencies : [threads,
                                                 libmicrohttpd,
                                                 libgnutls,
diff --git a/src/journal-remote/journal-remote-main.c b/src/journal-remote/journal-remote-main.c
new file mode 100644 (file)
index 0000000..8fda9d1
--- /dev/null
@@ -0,0 +1,1145 @@
+/* SPDX-License-Identifier: LGPL-2.1+ */
+
+#include <getopt.h>
+#include <unistd.h>
+
+#include "sd-daemon.h"
+
+#include "conf-parser.h"
+#include "def.h"
+#include "fd-util.h"
+#include "fileio.h"
+#include "journal-remote-write.h"
+#include "journal-remote.h"
+#include "process-util.h"
+#include "signal-util.h"
+#include "socket-util.h"
+#include "stat-util.h"
+#include "string-table.h"
+#include "strv.h"
+
+#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem"
+#define CERT_FILE     CERTIFICATE_ROOT "/certs/journal-remote.pem"
+#define TRUST_FILE    CERTIFICATE_ROOT "/ca/trusted.pem"
+
+static char* arg_url = NULL;
+static char* arg_getter = NULL;
+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** arg_gnutls_log = NULL;
+
+static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID;
+static char* arg_output = NULL;
+
+static char *arg_key = NULL;
+static char *arg_cert = NULL;
+static char *arg_trust = NULL;
+static bool arg_trust_all = false;
+
+static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
+        [JOURNAL_WRITE_SPLIT_NONE] = "none",
+        [JOURNAL_WRITE_SPLIT_HOST] = "host",
+};
+
+DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode);
+static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode,
+                                journal_write_split_mode,
+                                JournalWriteSplitMode,
+                                "Failed to parse split mode setting");
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int spawn_child(const char* child, char** argv) {
+        pid_t child_pid;
+        int fd[2], r;
+
+        if (pipe(fd) < 0)
+                return log_error_errno(errno, "Failed to create pager pipe: %m");
+
+        r = safe_fork("(remote)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid);
+        if (r < 0) {
+                safe_close_pair(fd);
+                return r;
+        }
+
+        /* In the child */
+        if (r == 0) {
+                safe_close(fd[0]);
+
+                r = rearrange_stdio(STDIN_FILENO, fd[1], STDERR_FILENO);
+                if (r < 0) {
+                        log_error_errno(r, "Failed to dup pipe to stdout: %m");
+                        _exit(EXIT_FAILURE);
+                }
+
+                execvp(child, argv);
+                log_error_errno(errno, "Failed to exec child %s: %m", child);
+                _exit(EXIT_FAILURE);
+        }
+
+        safe_close(fd[1]);
+
+        r = fd_nonblock(fd[0], true);
+        if (r < 0)
+                log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m");
+
+        return fd[0];
+}
+
+static int spawn_curl(const 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_errno(r, "Failed to spawn curl: %m");
+        return r;
+}
+
+static int spawn_getter(const char *getter) {
+        int r;
+        _cleanup_strv_free_ char **words = NULL;
+
+        assert(getter);
+        r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES);
+        if (r < 0)
+                return log_error_errno(r, "Failed to split getter option: %m");
+
+        r = spawn_child(words[0], words);
+        if (r < 0)
+                log_error_errno(r, "Failed to spawn getter %s: %m", getter);
+
+        return r;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int null_timer_event_handler(sd_event_source *s,
+                                uint64_t usec,
+                                void *userdata);
+static int dispatch_http_event(sd_event_source *event,
+                               int fd,
+                               uint32_t revents,
+                               void *userdata);
+
+static int request_meta(void **connection_cls, int fd, char *hostname) {
+        RemoteSource *source;
+        Writer *writer;
+        int r;
+
+        assert(connection_cls);
+        if (*connection_cls)
+                return 0;
+
+        r = journal_remote_get_writer(journal_remote_server_global, hostname, &writer);
+        if (r < 0)
+                return log_warning_errno(r, "Failed to get writer for source %s: %m",
+                                         hostname);
+
+        source = source_new(fd, true, hostname, writer);
+        if (!source) {
+                writer_unref(writer);
+                return log_oom();
+        }
+
+        log_debug("Added RemoteSource as connection metadata %p", source);
+
+        *connection_cls = source;
+        return 0;
+}
+
+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;
+
+        if (s) {
+                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;
+        size_t remaining;
+        int r;
+
+        assert(source);
+
+        log_trace("%s: connection %p, %zu bytes",
+                  __func__, connection, *upload_data_size);
+
+        if (*upload_data_size) {
+                log_trace("Received %zu bytes", *upload_data_size);
+
+                r = journal_importer_push_data(&source->importer,
+                                               upload_data, *upload_data_size);
+                if (r < 0)
+                        return mhd_respond_oom(connection);
+
+                *upload_data_size = 0;
+        } else
+                finished = true;
+
+        for (;;) {
+                r = process_source(source,
+                                   journal_remote_server_global->compress,
+                                   journal_remote_server_global->seal);
+                if (r == -EAGAIN)
+                        break;
+                else if (r < 0) {
+                        log_warning("Failed to process data for connection %p", connection);
+                        if (r == -E2BIG)
+                                return mhd_respondf(connection,
+                                                    r, MHD_HTTP_PAYLOAD_TOO_LARGE,
+                                                    "Entry is too large, maximum is " STRINGIFY(DATA_SIZE_MAX) " bytes.");
+                        else
+                                return mhd_respondf(connection,
+                                                    r, MHD_HTTP_UNPROCESSABLE_ENTITY,
+                                                    "Processing failed: %m.");
+                }
+        }
+
+        if (!finished)
+                return MHD_YES;
+
+        /* The upload is finished */
+
+        remaining = journal_importer_bytes_remaining(&source->importer);
+        if (remaining > 0) {
+                log_warning("Premature EOF byte. %zu bytes lost.", remaining);
+                return mhd_respondf(connection,
+                                    0, MHD_HTTP_EXPECTATION_FAILED,
+                                    "Premature EOF. %zu bytes of trailing data not processed.",
+                                    remaining);
+        }
+
+        return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.");
+};
+
+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, fd;
+        _cleanup_free_ char *hostname = NULL;
+
+        assert(connection);
+        assert(connection_cls);
+        assert(url);
+        assert(method);
+
+        log_trace("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 mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.");
+
+        if (!streq(url, "/upload"))
+                return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
+
+        header = MHD_lookup_connection_value(connection,
+                                             MHD_HEADER_KIND, "Content-Type");
+        if (!header || !streq(header, "application/vnd.fdo.journal"))
+                return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
+                                   "Content-Type: application/vnd.fdo.journal is required.");
+
+        {
+                const union MHD_ConnectionInfo *ci;
+
+                ci = MHD_get_connection_info(connection,
+                                             MHD_CONNECTION_INFO_CONNECTION_FD);
+                if (!ci) {
+                        log_error("MHD_get_connection_info failed: cannot get remote fd");
+                        return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           "Cannot check remote address.");
+                }
+
+                fd = ci->connect_fd;
+                assert(fd >= 0);
+        }
+
+        if (journal_remote_server_global->check_trust) {
+                r = check_permissions(connection, &code, &hostname);
+                if (r < 0)
+                        return code;
+        } else {
+                r = getpeername_pretty(fd, false, &hostname);
+                if (r < 0)
+                        return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
+                                           "Cannot check remote hostname.");
+        }
+
+        assert(hostname);
+
+        r = request_meta(connection_cls, fd, hostname);
+        if (r == -ENOMEM)
+                return respond_oom(connection);
+        else if (r < 0)
+                return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m");
+
+        hostname = NULL;
+        return MHD_YES;
+}
+
+static int setup_microhttpd_server(RemoteServer *s,
+                                   int fd,
+                                   const char *key,
+                                   const char *cert,
+                                   const char *trust) {
+        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_CONNECTION_MEMORY_LIMIT, 128*1024},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END},
+                { MHD_OPTION_END}};
+        int opts_pos = 4;
+        int flags =
+                MHD_USE_DEBUG |
+                MHD_USE_DUAL_STACK |
+                MHD_USE_EPOLL |
+                MHD_USE_ITC;
+
+        const union MHD_DaemonInfo *info;
+        int r, epoll_fd;
+        MHDDaemonWrapper *d;
+
+        assert(fd >= 0);
+
+        r = fd_nonblock(fd, true);
+        if (r < 0)
+                return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd);
+
+/* MHD_OPTION_STRICT_FOR_CLIENT is introduced in microhttpd 0.9.54,
+ * and MHD_USE_PEDANTIC_CHECKS will be deprecated in future.
+ * If MHD_USE_PEDANTIC_CHECKS is '#define'd, then it is deprecated
+ * and we should use MHD_OPTION_STRICT_FOR_CLIENT. On the other hand,
+ * if MHD_USE_PEDANTIC_CHECKS is not '#define'd, then it is not
+ * deprecated yet and there exists an enum element with the same name.
+ * So we can safely use it. */
+#ifdef MHD_USE_PEDANTIC_CHECKS
+        opts[opts_pos++] = (struct MHD_OptionItem)
+                {MHD_OPTION_STRICT_FOR_CLIENT, 1};
+#else
+        flags |= MHD_USE_PEDANTIC_CHECKS;
+#endif
+
+        if (key) {
+                assert(cert);
+
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
+                opts[opts_pos++] = (struct MHD_OptionItem)
+                        {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
+
+                flags |= MHD_USE_TLS;
+
+                if (trust)
+                        opts[opts_pos++] = (struct MHD_OptionItem)
+                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
+        }
+
+        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)",
+                  key ? "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 = -EOPNOTSUPP;
+                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->io_event,
+                            epoll_fd, EPOLLIN,
+                            dispatch_http_event, d);
+        if (r < 0) {
+                log_error_errno(r, "Failed to add event callback: %m");
+                goto error;
+        }
+
+        r = sd_event_source_set_description(d->io_event, "io_event");
+        if (r < 0) {
+                log_error_errno(r, "Failed to set source name: %m");
+                goto error;
+        }
+
+        r = sd_event_add_time(s->events, &d->timer_event,
+                              CLOCK_MONOTONIC, (uint64_t) -1, 0,
+                              null_timer_event_handler, d);
+        if (r < 0) {
+                log_error_errno(r, "Failed to add timer_event: %m");
+                goto error;
+        }
+
+        r = sd_event_source_set_description(d->timer_event, "timer_event");
+        if (r < 0) {
+                log_error_errno(r, "Failed to set source name: %m");
+                goto error;
+        }
+
+        r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops);
+        if (r < 0) {
+                log_oom();
+                goto error;
+        }
+
+        r = hashmap_put(s->daemons, &d->fd, d);
+        if (r < 0) {
+                log_error_errno(r, "Failed to add daemon to hashmap: %m");
+                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,
+                                   const char *key,
+                                   const char *cert,
+                                   const char *trust) {
+        int fd;
+
+        fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC);
+        if (fd < 0)
+                return fd;
+
+        return setup_microhttpd_server(s, fd, key, cert, trust);
+}
+
+static int null_timer_event_handler(sd_event_source *timer_event,
+                                    uint64_t usec,
+                                    void *userdata) {
+        return dispatch_http_event(timer_event, 0, 0, userdata);
+}
+
+static int dispatch_http_event(sd_event_source *event,
+                               int fd,
+                               uint32_t revents,
+                               void *userdata) {
+        MHDDaemonWrapper *d = userdata;
+        int r;
+        MHD_UNSIGNED_LONG_LONG timeout = ULONG_LONG_MAX;
+
+        assert(d);
+
+        r = MHD_run(d->daemon);
+        if (r == MHD_NO) {
+                log_error("MHD_run failed!");
+                // XXX: unregister daemon
+                return -EINVAL;
+        }
+        if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO)
+                timeout = ULONG_LONG_MAX;
+
+        r = sd_event_source_set_time(d->timer_event, timeout);
+        if (r < 0) {
+                log_warning_errno(r, "Unable to set event loop timeout: %m, this may result in indefinite blocking!");
+                return 1;
+        }
+
+        r = sd_event_source_set_enabled(d->timer_event, SD_EVENT_ON);
+        if (r < 0)
+                log_warning_errno(r, "Unable to enable timer_event: %m, this may result in indefinite blocking!");
+
+        return 1; /* work to do */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int setup_signals(RemoteServer *s) {
+        int r;
+
+        assert(s);
+
+        assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0);
+
+        r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s);
+        if (r < 0)
+                return r;
+
+        r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s);
+        if (r < 0)
+                return r;
+
+        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 journal_remote_add_raw_socket(s, fd);
+}
+
+static int create_remoteserver(
+                RemoteServer *s,
+                const char* key,
+                const char* cert,
+                const char* trust) {
+
+        int r, n, fd;
+        char **file;
+
+        r = journal_remote_server_init(s, arg_output, arg_split_mode, arg_compress, arg_seal);
+        if (r < 0)
+                return r;
+
+        setup_signals(s);
+
+        n = sd_listen_fds(true);
+        if (n < 0)
+                return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
+        else
+                log_debug("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, true)) {
+                        log_debug("Received a listening socket (fd:%d)", fd);
+
+                        if (fd == http_socket)
+                                r = setup_microhttpd_server(s, fd, NULL, NULL, NULL);
+                        else if (fd == https_socket)
+                                r = setup_microhttpd_server(s, fd, key, cert, trust);
+                        else
+                                r = journal_remote_add_raw_socket(s, fd);
+                } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
+                        char *hostname;
+
+                        r = getpeername_pretty(fd, false, &hostname);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to retrieve remote name: %m");
+
+                        log_debug("Received a connection socket (fd:%d) from %s", fd, hostname);
+
+                        r = journal_remote_add_source(s, fd, hostname, true);
+                } else {
+                        log_error("Unknown socket passed on fd:%d", fd);
+
+                        return -EINVAL;
+                }
+
+                if (r < 0)
+                        return log_error_errno(r, "Failed to register socket (fd:%d): %m",
+                                               fd);
+        }
+
+        if (arg_getter) {
+                log_info("Spawning getter %s...", arg_getter);
+                fd = spawn_getter(arg_getter);
+                if (fd < 0)
+                        return fd;
+
+                r = journal_remote_add_source(s, fd, (char*) arg_output, false);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_url) {
+                const char *url;
+                char *hostname, *p;
+
+                if (!strstr(arg_url, "/entries")) {
+                        if (endswith(arg_url, "/"))
+                                url = strjoina(arg_url, "entries");
+                        else
+                                url = strjoina(arg_url, "/entries");
+                }
+                else
+                        url = strdupa(arg_url);
+
+                log_info("Spawning curl %s...", url);
+                fd = spawn_curl(url);
+                if (fd < 0)
+                        return fd;
+
+                hostname =
+                        startswith(arg_url, "https://") ?:
+                        startswith(arg_url, "http://") ?:
+                        arg_url;
+
+                hostname = strdupa(hostname);
+                if ((p = strchr(hostname, '/')))
+                        *p = '\0';
+                if ((p = strchr(hostname, ':')))
+                        *p = '\0';
+
+                r = journal_remote_add_source(s, fd, hostname, false);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_listen_raw) {
+                log_debug("Listening on a socket...");
+                r = setup_raw_socket(s, arg_listen_raw);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_listen_http) {
+                r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL);
+                if (r < 0)
+                        return r;
+        }
+
+        if (arg_listen_https) {
+                r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust);
+                if (r < 0)
+                        return r;
+        }
+
+        STRV_FOREACH(file, arg_files) {
+                const char *output_name;
+
+                if (streq(*file, "-")) {
+                        log_debug("Using standard input as source.");
+
+                        fd = STDIN_FILENO;
+                        output_name = "stdin";
+                } else {
+                        log_debug("Reading file %s...", *file);
+
+                        fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
+                        if (fd < 0)
+                                return log_error_errno(errno, "Failed to open %s: %m", *file);
+                        output_name = *file;
+                }
+
+                r = journal_remote_add_source(s, fd, (char*) output_name, false);
+                if (r < 0)
+                        return r;
+        }
+
+        if (s->active == 0) {
+                log_error("Zero sources specified");
+                return -EINVAL;
+        }
+
+        if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) {
+                /* In this case we know what the writer will be
+                   called, so we can create it and verify that we can
+                   create output as expected. */
+                r = journal_remote_get_writer(s, NULL, &s->_single_writer);
+                if (r < 0)
+                        return r;
+        }
+
+        return 0;
+}
+
+static int negative_fd(const char *spec) {
+        /* Return a non-positive number as its inverse, -EINVAL otherwise. */
+
+        int fd, r;
+
+        r = safe_atoi(spec, &fd);
+        if (r < 0)
+                return r;
+
+        if (fd > 0)
+                return -EINVAL;
+        else
+                return -fd;
+}
+
+static int parse_config(void) {
+        const ConfigTableItem items[] = {
+                { "Remote",  "Seal",                   config_parse_bool,             0, &arg_seal       },
+                { "Remote",  "SplitMode",              config_parse_write_split_mode, 0, &arg_split_mode },
+                { "Remote",  "ServerKeyFile",          config_parse_path,             0, &arg_key        },
+                { "Remote",  "ServerCertificateFile",  config_parse_path,             0, &arg_cert       },
+                { "Remote",  "TrustedCertificateFile", config_parse_path,             0, &arg_trust      },
+                {}};
+
+        return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf",
+                                        CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"),
+                                        "Remote\0", config_item_table_lookup, items,
+                                        CONFIG_PARSE_WARN, NULL);
+}
+
+static void help(void) {
+        printf("%s [OPTIONS...] {FILE|-}...\n\n"
+               "Write external journal events to journal file(s).\n\n"
+               "  -h --help                 Show this help\n"
+               "     --version              Show package version\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"
+               "     --listen-http=ADDR     Listen for HTTP connections at ADDR\n"
+               "     --listen-https=ADDR    Listen for HTTPS connections at ADDR\n"
+               "  -o --output=FILE|DIR      Write output to FILE or DIR/external-*.journal\n"
+               "     --compress[=BOOL]      XZ-compress the output journal (default: yes)\n"
+               "     --seal[=BOOL]          Use event sealing (default: no)\n"
+               "     --key=FILENAME         SSL key in PEM format (default:\n"
+               "                            \"" PRIV_KEY_FILE "\")\n"
+               "     --cert=FILENAME        SSL certificate in PEM format (default:\n"
+               "                            \"" CERT_FILE "\")\n"
+               "     --trust=FILENAME|all   SSL CA certificate or disable checking (default:\n"
+               "                            \"" TRUST_FILE "\")\n"
+               "     --gnutls-log=CATEGORY...\n"
+               "                            Specify a list of gnutls logging categories\n"
+               "     --split-mode=none|host How many output files to create\n"
+               "\n"
+               "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
+               , program_invocation_short_name);
+}
+
+static int parse_argv(int argc, char *argv[]) {
+        enum {
+                ARG_VERSION = 0x100,
+                ARG_URL,
+                ARG_LISTEN_RAW,
+                ARG_LISTEN_HTTP,
+                ARG_LISTEN_HTTPS,
+                ARG_GETTER,
+                ARG_SPLIT_MODE,
+                ARG_COMPRESS,
+                ARG_SEAL,
+                ARG_KEY,
+                ARG_CERT,
+                ARG_TRUST,
+                ARG_GNUTLS_LOG,
+        };
+
+        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   },
+                { "listen-http",  required_argument, NULL, ARG_LISTEN_HTTP  },
+                { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
+                { "output",       required_argument, NULL, 'o'              },
+                { "split-mode",   required_argument, NULL, ARG_SPLIT_MODE   },
+                { "compress",     optional_argument, NULL, ARG_COMPRESS     },
+                { "seal",         optional_argument, NULL, ARG_SEAL         },
+                { "key",          required_argument, NULL, ARG_KEY          },
+                { "cert",         required_argument, NULL, ARG_CERT         },
+                { "trust",        required_argument, NULL, ARG_TRUST        },
+                { "gnutls-log",   required_argument, NULL, ARG_GNUTLS_LOG   },
+                {}
+        };
+
+        int c, r;
+        bool type_a, type_b;
+
+        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:
+                        return version();
+
+                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_LISTEN_HTTP:
+                        if (arg_listen_http || http_socket >= 0) {
+                                log_error("cannot currently use --listen-http more than once");
+                                return -EINVAL;
+                        }
+
+                        r = negative_fd(optarg);
+                        if (r >= 0)
+                                http_socket = r;
+                        else
+                                arg_listen_http = optarg;
+                        break;
+
+                case ARG_LISTEN_HTTPS:
+                        if (arg_listen_https || https_socket >= 0) {
+                                log_error("cannot currently use --listen-https more than once");
+                                return -EINVAL;
+                        }
+
+                        r = negative_fd(optarg);
+                        if (r >= 0)
+                                https_socket = r;
+                        else
+                                arg_listen_https = optarg;
+
+                        break;
+
+                case ARG_KEY:
+                        if (arg_key) {
+                                log_error("Key file specified twice");
+                                return -EINVAL;
+                        }
+
+                        arg_key = strdup(optarg);
+                        if (!arg_key)
+                                return log_oom();
+
+                        break;
+
+                case ARG_CERT:
+                        if (arg_cert) {
+                                log_error("Certificate file specified twice");
+                                return -EINVAL;
+                        }
+
+                        arg_cert = strdup(optarg);
+                        if (!arg_cert)
+                                return log_oom();
+
+                        break;
+
+                case ARG_TRUST:
+                        if (arg_trust || arg_trust_all) {
+                                log_error("Confusing trusted CA configuration");
+                                return -EINVAL;
+                        }
+
+                        if (streq(optarg, "all"))
+                                arg_trust_all = true;
+                        else {
+#if HAVE_GNUTLS
+                                arg_trust = strdup(optarg);
+                                if (!arg_trust)
+                                        return log_oom();
+#else
+                                log_error("Option --trust is not available.");
+                                return -EINVAL;
+#endif
+                        }
+
+                        break;
+
+                case 'o':
+                        if (arg_output) {
+                                log_error("cannot use --output/-o more than once");
+                                return -EINVAL;
+                        }
+
+                        arg_output = optarg;
+                        break;
+
+                case ARG_SPLIT_MODE:
+                        arg_split_mode = journal_write_split_mode_from_string(optarg);
+                        if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) {
+                                log_error("Invalid split mode: %s", optarg);
+                                return -EINVAL;
+                        }
+                        break;
+
+                case ARG_COMPRESS:
+                        if (optarg) {
+                                r = parse_boolean(optarg);
+                                if (r < 0) {
+                                        log_error("Failed to parse --compress= parameter.");
+                                        return -EINVAL;
+                                }
+
+                                arg_compress = !!r;
+                        } else
+                                arg_compress = true;
+
+                        break;
+
+                case ARG_SEAL:
+                        if (optarg) {
+                                r = parse_boolean(optarg);
+                                if (r < 0) {
+                                        log_error("Failed to parse --seal= parameter.");
+                                        return -EINVAL;
+                                }
+
+                                arg_seal = !!r;
+                        } else
+                                arg_seal = true;
+
+                        break;
+
+                case ARG_GNUTLS_LOG: {
+#if HAVE_GNUTLS
+                        const char* p = optarg;
+                        for (;;) {
+                                _cleanup_free_ char *word = NULL;
+
+                                r = extract_first_word(&p, &word, ",", 0);
+                                if (r < 0)
+                                        return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m");
+
+                                if (r == 0)
+                                        break;
+
+                                if (strv_push(&arg_gnutls_log, word) < 0)
+                                        return log_oom();
+
+                                word = NULL;
+                        }
+                        break;
+#else
+                        log_error("Option --gnutls-log is not available.");
+                        return -EINVAL;
+#endif
+                }
+
+                case '?':
+                        return -EINVAL;
+
+                default:
+                        assert_not_reached("Unknown option code.");
+                }
+
+        if (optind < argc)
+                arg_files = argv + optind;
+
+        type_a = arg_getter || !strv_isempty(arg_files);
+        type_b = arg_url
+                || arg_listen_raw
+                || arg_listen_http || arg_listen_https
+                || sd_listen_fds(false) > 0;
+        if (type_a && type_b) {
+                log_error("Cannot use file input or --getter with "
+                          "--arg-listen-... or socket activation.");
+                return -EINVAL;
+        }
+        if (type_a) {
+                if (!arg_output) {
+                        log_error("Option --output must be specified with file input or --getter.");
+                        return -EINVAL;
+                }
+
+                if (!IN_SET(arg_split_mode, JOURNAL_WRITE_SPLIT_NONE, _JOURNAL_WRITE_SPLIT_INVALID)) {
+                        log_error("For active sources, only --split-mode=none is allowed.");
+                        return -EINVAL;
+                }
+
+                arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
+        }
+
+        if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID)
+                arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
+
+        if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE && arg_output) {
+                if (is_dir(arg_output, true) > 0) {
+                        log_error("For SplitMode=none, output must be a file.");
+                        return -EINVAL;
+                }
+                if (!endswith(arg_output, ".journal")) {
+                        log_error("For SplitMode=none, output file name must end with .journal.");
+                        return -EINVAL;
+                }
+        }
+
+        if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST
+            && arg_output && is_dir(arg_output, true) <= 0) {
+                log_error("For SplitMode=host, output must be a directory.");
+                return -EINVAL;
+        }
+
+        log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s",
+                  journal_write_split_mode_to_string(arg_split_mode),
+                  strna(arg_key),
+                  strna(arg_cert),
+                  strna(arg_trust));
+
+        return 1 /* work to do */;
+}
+
+static int load_certificates(char **key, char **cert, char **trust) {
+        int r;
+
+        r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read key from file '%s': %m",
+                                       arg_key ?: PRIV_KEY_FILE);
+
+        r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
+        if (r < 0)
+                return log_error_errno(r, "Failed to read certificate from file '%s': %m",
+                                       arg_cert ?: CERT_FILE);
+
+        if (arg_trust_all)
+                log_info("Certificate checking disabled.");
+        else {
+                r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
+                                               arg_trust ?: TRUST_FILE);
+        }
+
+        if ((arg_listen_raw || arg_listen_http) && *trust) {
+                log_error("Option --trust makes all non-HTTPS connections untrusted.");
+                return -EINVAL;
+        }
+
+        return 0;
+}
+
+int main(int argc, char **argv) {
+        RemoteServer s = {};
+        int r;
+        _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
+
+        log_show_color(true);
+        log_parse_environment();
+
+        r = parse_config();
+        if (r < 0)
+                return EXIT_FAILURE;
+
+        r = parse_argv(argc, argv);
+        if (r <= 0)
+                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+
+        if (arg_listen_http || arg_listen_https) {
+                r = setup_gnutls_logger(arg_gnutls_log);
+                if (r < 0)
+                        return EXIT_FAILURE;
+        }
+
+        if (arg_listen_https || https_socket >= 0)
+                if (load_certificates(&key, &cert, &trust) < 0)
+                        return EXIT_FAILURE;
+
+        if (create_remoteserver(&s, key, cert, trust) < 0)
+                return EXIT_FAILURE;
+
+        r = sd_event_set_watchdog(s.events, true);
+        if (r < 0)
+                log_error_errno(r, "Failed to enable watchdog: %m");
+        else
+                log_debug("Watchdog is %sd.", enable_disable(r > 0));
+
+        log_debug("%s running as pid "PID_FMT,
+                  program_invocation_short_name, getpid_cached());
+        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_errno(r, "Failed to run event loop: %m");
+                        break;
+                }
+        }
+
+        sd_notifyf(false,
+                   "STOPPING=1\n"
+                   "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count);
+        log_info("Finishing after writing %" PRIu64 " entries", s.event_count);
+
+        journal_remote_server_destroy(&s);
+
+        free(arg_key);
+        free(arg_cert);
+        free(arg_trust);
+
+        return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+}
index 81f30748e753fbf27b7059f6b44dab7ff66991af..9e9fe91786fb219826e469a251172c9106cd5647 100644 (file)
@@ -7,23 +7,19 @@
 
 #include <errno.h>
 #include <fcntl.h>
-#include <getopt.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
 #include <sys/prctl.h>
 #include <sys/socket.h>
-#include <unistd.h>
 #include <stdint.h>
 
 #include "sd-daemon.h"
 
 #include "alloc-util.h"
-#include "conf-parser.h"
 #include "def.h"
 #include "escape.h"
 #include "fd-util.h"
-#include "fileio.h"
 #include "journal-file.h"
 #include "journal-remote-write.h"
 #include "journal-remote.h"
 #include "macro.h"
 #include "parse-util.h"
 #include "process-util.h"
-#include "signal-util.h"
 #include "socket-util.h"
-#include "stat-util.h"
 #include "stdio-util.h"
-#include "string-table.h"
 #include "string-util.h"
 #include "strv.h"
 
 #define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
 
-#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem"
-#define CERT_FILE     CERTIFICATE_ROOT "/certs/journal-remote.pem"
-#define TRUST_FILE    CERTIFICATE_ROOT "/ca/trusted.pem"
-
-static char* arg_url = NULL;
-static char* arg_getter = NULL;
-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** arg_gnutls_log = NULL;
-
-static JournalWriteSplitMode arg_split_mode = _JOURNAL_WRITE_SPLIT_INVALID;
-static char* arg_output = NULL;
-
-static char *arg_key = NULL;
-static char *arg_cert = NULL;
-static char *arg_trust = NULL;
-static bool arg_trust_all = false;
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int spawn_child(const char* child, char** argv) {
-        pid_t child_pid;
-        int fd[2], r;
-
-        if (pipe(fd) < 0)
-                return log_error_errno(errno, "Failed to create pager pipe: %m");
-
-        r = safe_fork("(remote)", FORK_RESET_SIGNALS|FORK_DEATHSIG|FORK_LOG, &child_pid);
-        if (r < 0) {
-                safe_close_pair(fd);
-                return r;
-        }
-
-        /* In the child */
-        if (r == 0) {
-                safe_close(fd[0]);
-
-                r = rearrange_stdio(STDIN_FILENO, fd[1], STDERR_FILENO);
-                if (r < 0) {
-                        log_error_errno(r, "Failed to dup pipe to stdout: %m");
-                        _exit(EXIT_FAILURE);
-                }
-
-                execvp(child, argv);
-                log_error_errno(errno, "Failed to exec child %s: %m", child);
-                _exit(EXIT_FAILURE);
-        }
-
-        safe_close(fd[1]);
-
-        r = fd_nonblock(fd[0], true);
-        if (r < 0)
-                log_warning_errno(errno, "Failed to set child pipe to non-blocking: %m");
-
-        return fd[0];
-}
-
-static int spawn_curl(const 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_errno(r, "Failed to spawn curl: %m");
-        return r;
-}
-
-static int spawn_getter(const char *getter) {
-        int r;
-        _cleanup_strv_free_ char **words = NULL;
-
-        assert(getter);
-        r = strv_split_extract(&words, getter, WHITESPACE, EXTRACT_QUOTES);
-        if (r < 0)
-                return log_error_errno(r, "Failed to split getter option: %m");
-
-        r = spawn_child(words[0], words);
-        if (r < 0)
-                log_error_errno(r, "Failed to spawn getter %s: %m", getter);
-
-        return r;
-}
-
 #define filename_escape(s) xescape((s), "/ ")
 
-static int open_output(Writer *w, const char* host) {
-        _cleanup_free_ char *_output = NULL;
-        const char *output;
+static int open_output(RemoteServer *s, Writer *w, const char* host) {
+        _cleanup_free_ char *_filename = NULL;
+        const char *filename;
         int r;
 
-        switch (arg_split_mode) {
+        switch (s->split_mode) {
         case JOURNAL_WRITE_SPLIT_NONE:
-                output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal";
+                filename = s->output;
                 break;
 
         case JOURNAL_WRITE_SPLIT_HOST: {
@@ -156,13 +55,11 @@ static int open_output(Writer *w, const char* host) {
                 if (!name)
                         return log_oom();
 
-                r = asprintf(&_output, "%s/remote-%s.journal",
-                             arg_output ?: REMOTE_JOURNAL_PATH,
-                             name);
+                r = asprintf(&_filename, "%s/remote-%s.journal", s->output, name);
                 if (r < 0)
                         return log_oom();
 
-                output = _output;
+                filename = _filename;
                 break;
         }
 
@@ -170,18 +67,17 @@ static int open_output(Writer *w, const char* host) {
                 assert_not_reached("what?");
         }
 
-        r = journal_file_open_reliably(output,
+        r = journal_file_open_reliably(filename,
                                        O_RDWR|O_CREAT, 0640,
-                                       arg_compress, (uint64_t) -1, arg_seal,
+                                       s->compress, (uint64_t) -1, s->seal,
                                        &w->metrics,
                                        w->mmap, NULL,
                                        NULL, &w->journal);
         if (r < 0)
-                log_error_errno(r, "Failed to open output journal %s: %m",
-                                output);
-        else
-                log_debug("Opened output file %s", w->journal->path);
-        return r;
+                return log_error_errno(r, "Failed to open output journal %s: %m", filename);
+
+        log_debug("Opened output file %s", w->journal->path);
+        return 0;
 }
 
 /**********************************************************************
@@ -189,26 +85,27 @@ static int open_output(Writer *w, const char* host) {
  **********************************************************************/
 
 static int init_writer_hashmap(RemoteServer *s) {
-        static const struct hash_ops *hash_ops[] = {
+        static const struct hash_ops* const hash_ops[] = {
                 [JOURNAL_WRITE_SPLIT_NONE] = NULL,
                 [JOURNAL_WRITE_SPLIT_HOST] = &string_hash_ops,
         };
 
-        assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(hash_ops));
+        assert(s);
+        assert(s->split_mode >= 0 && s->split_mode < (int) ELEMENTSOF(hash_ops));
 
-        s->writers = hashmap_new(hash_ops[arg_split_mode]);
+        s->writers = hashmap_new(hash_ops[s->split_mode]);
         if (!s->writers)
                 return log_oom();
 
         return 0;
 }
 
-static int get_writer(RemoteServer *s, const char *host, Writer **writer) {
+int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer) {
         _cleanup_(writer_unrefp) Writer *w = NULL;
         const void *key;
         int r;
 
-        switch(arg_split_mode) {
+        switch(s->split_mode) {
         case JOURNAL_WRITE_SPLIT_NONE:
                 key = "one and only";
                 break;
@@ -230,13 +127,13 @@ static int get_writer(RemoteServer *s, const char *host, Writer **writer) {
                 if (!w)
                         return log_oom();
 
-                if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
+                if (s->split_mode == JOURNAL_WRITE_SPLIT_HOST) {
                         w->hashmap_key = strdup(key);
                         if (!w->hashmap_key)
                                 return log_oom();
                 }
 
-                r = open_output(w, host);
+                r = open_output(s, w, host);
                 if (r < 0)
                         return r;
 
@@ -255,7 +152,7 @@ static int get_writer(RemoteServer *s, const char *host, Writer **writer) {
  **********************************************************************/
 
 /* This should go away as soon as µhttpd allows state to be passed around. */
-static RemoteServer *server;
+RemoteServer *journal_remote_server_global;
 
 static int dispatch_raw_source_event(sd_event_source *event,
                                      int fd,
@@ -269,13 +166,6 @@ static int dispatch_raw_connection_event(sd_event_source *event,
                                          int fd,
                                          uint32_t revents,
                                          void *userdata);
-static int null_timer_event_handler(sd_event_source *s,
-                                uint64_t usec,
-                                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, char *name, RemoteSource **source) {
@@ -290,7 +180,7 @@ static int get_source_for_fd(RemoteServer *s,
         if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
                 return log_oom();
 
-        r = get_writer(s, name, &writer);
+        r = journal_remote_get_writer(s, name, &writer);
         if (r < 0)
                 return log_warning_errno(r, "Failed to get writer for source %s: %m",
                                          name);
@@ -326,8 +216,7 @@ static int remove_source(RemoteServer *s, int fd) {
         return 0;
 }
 
-static int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
-
+int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name) {
         RemoteSource *source = NULL;
         int r;
 
@@ -387,7 +276,7 @@ static int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
         return r;
 }
 
-static int add_raw_socket(RemoteServer *s, int fd) {
+int journal_remote_add_raw_socket(RemoteServer *s, int fd) {
         int r;
         _cleanup_close_ int fd_ = fd;
         char name[STRLEN("raw-socket-") + DECIMAL_STR_MAX(int) + 1];
@@ -411,602 +300,45 @@ static int add_raw_socket(RemoteServer *s, int fd) {
         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);
-}
-
 /**********************************************************************
  **********************************************************************
  **********************************************************************/
 
-static int request_meta(void **connection_cls, int fd, char *hostname) {
-        RemoteSource *source;
-        Writer *writer;
-        int r;
-
-        assert(connection_cls);
-        if (*connection_cls)
-                return 0;
-
-        r = get_writer(server, hostname, &writer);
-        if (r < 0)
-                return log_warning_errno(r, "Failed to get writer for source %s: %m",
-                                         hostname);
-
-        source = source_new(fd, true, hostname, writer);
-        if (!source) {
-                writer_unref(writer);
-                return log_oom();
-        }
-
-        log_debug("Added RemoteSource as connection metadata %p", source);
-
-        *connection_cls = source;
-        return 0;
-}
-
-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;
-
-        if (s) {
-                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;
-        size_t remaining;
-        int r;
-
-        assert(source);
-
-        log_trace("%s: connection %p, %zu bytes",
-                  __func__, connection, *upload_data_size);
-
-        if (*upload_data_size) {
-                log_trace("Received %zu bytes", *upload_data_size);
-
-                r = journal_importer_push_data(&source->importer,
-                                               upload_data, *upload_data_size);
-                if (r < 0)
-                        return mhd_respond_oom(connection);
-
-                *upload_data_size = 0;
-        } else
-                finished = true;
-
-        for (;;) {
-                r = process_source(source, arg_compress, arg_seal);
-                if (r == -EAGAIN)
-                        break;
-                else if (r < 0) {
-                        log_warning("Failed to process data for connection %p", connection);
-                        if (r == -E2BIG)
-                                return mhd_respondf(connection,
-                                                    r, MHD_HTTP_PAYLOAD_TOO_LARGE,
-                                                    "Entry is too large, maximum is " STRINGIFY(DATA_SIZE_MAX) " bytes.");
-                        else
-                                return mhd_respondf(connection,
-                                                    r, MHD_HTTP_UNPROCESSABLE_ENTITY,
-                                                    "Processing failed: %m.");
-                }
-        }
-
-        if (!finished)
-                return MHD_YES;
-
-        /* The upload is finished */
+int journal_remote_server_init(
+                RemoteServer *s,
+                const char *output,
+                JournalWriteSplitMode split_mode,
+                bool compress,
+                bool seal) {
 
-        remaining = journal_importer_bytes_remaining(&source->importer);
-        if (remaining > 0) {
-                log_warning("Premature EOF byte. %zu bytes lost.", remaining);
-                return mhd_respondf(connection,
-                                    0, MHD_HTTP_EXPECTATION_FAILED,
-                                    "Premature EOF. %zu bytes of trailing data not processed.",
-                                    remaining);
-        }
-
-        return mhd_respond(connection, MHD_HTTP_ACCEPTED, "OK.");
-};
-
-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, fd;
-        _cleanup_free_ char *hostname = NULL;
-
-        assert(connection);
-        assert(connection_cls);
-        assert(url);
-        assert(method);
-
-        log_trace("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 mhd_respond(connection, MHD_HTTP_NOT_ACCEPTABLE, "Unsupported method.");
-
-        if (!streq(url, "/upload"))
-                return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.");
-
-        header = MHD_lookup_connection_value(connection,
-                                             MHD_HEADER_KIND, "Content-Type");
-        if (!header || !streq(header, "application/vnd.fdo.journal"))
-                return mhd_respond(connection, MHD_HTTP_UNSUPPORTED_MEDIA_TYPE,
-                                   "Content-Type: application/vnd.fdo.journal is required.");
-
-        {
-                const union MHD_ConnectionInfo *ci;
-
-                ci = MHD_get_connection_info(connection,
-                                             MHD_CONNECTION_INFO_CONNECTION_FD);
-                if (!ci) {
-                        log_error("MHD_get_connection_info failed: cannot get remote fd");
-                        return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           "Cannot check remote address.");
-                }
-
-                fd = ci->connect_fd;
-                assert(fd >= 0);
-        }
-
-        if (server->check_trust) {
-                r = check_permissions(connection, &code, &hostname);
-                if (r < 0)
-                        return code;
-        } else {
-                r = getpeername_pretty(fd, false, &hostname);
-                if (r < 0)
-                        return mhd_respond(connection, MHD_HTTP_INTERNAL_SERVER_ERROR,
-                                           "Cannot check remote hostname.");
-        }
-
-        assert(hostname);
-
-        r = request_meta(connection_cls, fd, hostname);
-        if (r == -ENOMEM)
-                return respond_oom(connection);
-        else if (r < 0)
-                return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "%m");
-
-        hostname = NULL;
-        return MHD_YES;
-}
-
-static int setup_microhttpd_server(RemoteServer *s,
-                                   int fd,
-                                   const char *key,
-                                   const char *cert,
-                                   const char *trust) {
-        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_CONNECTION_MEMORY_LIMIT, 128*1024},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END},
-                { MHD_OPTION_END}};
-        int opts_pos = 4;
-        int flags =
-                MHD_USE_DEBUG |
-                MHD_USE_DUAL_STACK |
-                MHD_USE_EPOLL |
-                MHD_USE_ITC;
-
-        const union MHD_DaemonInfo *info;
-        int r, epoll_fd;
-        MHDDaemonWrapper *d;
-
-        assert(fd >= 0);
-
-        r = fd_nonblock(fd, true);
-        if (r < 0)
-                return log_error_errno(r, "Failed to make fd:%d nonblocking: %m", fd);
-
-/* MHD_OPTION_STRICT_FOR_CLIENT is introduced in microhttpd 0.9.54,
- * and MHD_USE_PEDANTIC_CHECKS will be deprecated in future.
- * If MHD_USE_PEDANTIC_CHECKS is '#define'd, then it is deprecated
- * and we should use MHD_OPTION_STRICT_FOR_CLIENT. On the other hand,
- * if MHD_USE_PEDANTIC_CHECKS is not '#define'd, then it is not
- * deprecated yet and there exists an enum element with the same name.
- * So we can safely use it. */
-#ifdef MHD_USE_PEDANTIC_CHECKS
-        opts[opts_pos++] = (struct MHD_OptionItem)
-                {MHD_OPTION_STRICT_FOR_CLIENT, 1};
-#else
-        flags |= MHD_USE_PEDANTIC_CHECKS;
-#endif
-
-        if (key) {
-                assert(cert);
-
-                opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
-                opts[opts_pos++] = (struct MHD_OptionItem)
-                        {MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
-
-                flags |= MHD_USE_TLS;
-
-                if (trust)
-                        opts[opts_pos++] = (struct MHD_OptionItem)
-                                {MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
-        }
-
-        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)",
-                  key ? "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 = -EOPNOTSUPP;
-                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->io_event,
-                            epoll_fd, EPOLLIN,
-                            dispatch_http_event, d);
-        if (r < 0) {
-                log_error_errno(r, "Failed to add event callback: %m");
-                goto error;
-        }
-
-        r = sd_event_source_set_description(d->io_event, "io_event");
-        if (r < 0) {
-                log_error_errno(r, "Failed to set source name: %m");
-                goto error;
-        }
-
-        r = sd_event_add_time(s->events, &d->timer_event,
-                              CLOCK_MONOTONIC, (uint64_t) -1, 0,
-                              null_timer_event_handler, d);
-        if (r < 0) {
-                log_error_errno(r, "Failed to add timer_event: %m");
-                goto error;
-        }
-
-        r = sd_event_source_set_description(d->timer_event, "timer_event");
-        if (r < 0) {
-                log_error_errno(r, "Failed to set source name: %m");
-                goto error;
-        }
-
-        r = hashmap_ensure_allocated(&s->daemons, &uint64_hash_ops);
-        if (r < 0) {
-                log_oom();
-                goto error;
-        }
-
-        r = hashmap_put(s->daemons, &d->fd, d);
-        if (r < 0) {
-                log_error_errno(r, "Failed to add daemon to hashmap: %m");
-                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,
-                                   const char *key,
-                                   const char *cert,
-                                   const char *trust) {
-        int fd;
-
-        fd = make_socket_fd(LOG_DEBUG, address, SOCK_STREAM, SOCK_CLOEXEC);
-        if (fd < 0)
-                return fd;
-
-        return setup_microhttpd_server(s, fd, key, cert, trust);
-}
-
-static int null_timer_event_handler(sd_event_source *timer_event,
-                                    uint64_t usec,
-                                    void *userdata) {
-        return dispatch_http_event(timer_event, 0, 0, userdata);
-}
-
-static int dispatch_http_event(sd_event_source *event,
-                               int fd,
-                               uint32_t revents,
-                               void *userdata) {
-        MHDDaemonWrapper *d = userdata;
-        int r;
-        MHD_UNSIGNED_LONG_LONG timeout = ULONG_LONG_MAX;
-
-        assert(d);
-
-        r = MHD_run(d->daemon);
-        if (r == MHD_NO) {
-                log_error("MHD_run failed!");
-                // XXX: unregister daemon
-                return -EINVAL;
-        }
-        if (MHD_get_timeout(d->daemon, &timeout) == MHD_NO)
-                timeout = ULONG_LONG_MAX;
-
-        r = sd_event_source_set_time(d->timer_event, timeout);
-        if (r < 0) {
-                log_warning_errno(r, "Unable to set event loop timeout: %m, this may result in indefinite blocking!");
-                return 1;
-        }
-
-        r = sd_event_source_set_enabled(d->timer_event, SD_EVENT_ON);
-        if (r < 0)
-                log_warning_errno(r, "Unable to enable timer_event: %m, this may result in indefinite blocking!");
-
-        return 1; /* work to do */
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static int setup_signals(RemoteServer *s) {
         int r;
 
         assert(s);
 
-        assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, -1) >= 0);
+        assert(journal_remote_server_global == NULL);
+        journal_remote_server_global = s;
 
-        r = sd_event_add_signal(s->events, &s->sigterm_event, SIGTERM, NULL, s);
-        if (r < 0)
-                return r;
+        s->split_mode = split_mode;
+        s->compress = compress;
+        s->seal = seal;
 
-        r = sd_event_add_signal(s->events, &s->sigint_event, SIGINT, NULL, s);
-        if (r < 0)
-                return r;
-
-        return 0;
-}
-
-static int negative_fd(const char *spec) {
-        /* Return a non-positive number as its inverse, -EINVAL otherwise. */
-
-        int fd, r;
-
-        r = safe_atoi(spec, &fd);
-        if (r < 0)
-                return r;
-
-        if (fd > 0)
-                return -EINVAL;
+        if (output)
+                s->output = output;
+        else if (split_mode == JOURNAL_WRITE_SPLIT_NONE)
+                s->output = REMOTE_JOURNAL_PATH "/remote.journal";
+        else if (split_mode == JOURNAL_WRITE_SPLIT_HOST)
+                s->output = REMOTE_JOURNAL_PATH;
         else
-                return -fd;
-}
-
-static int remoteserver_init(RemoteServer *s,
-                             const char* key,
-                             const char* cert,
-                             const char* trust) {
-        int r, n, fd;
-        char **file;
-
-        assert(s);
-
-        if ((arg_listen_raw || arg_listen_http) && trust) {
-                log_error("Option --trust makes all non-HTTPS connections untrusted.");
-                return -EINVAL;
-        }
+                assert_not_reached("bad split mode");
 
         r = sd_event_default(&s->events);
         if (r < 0)
                 return log_error_errno(r, "Failed to allocate event loop: %m");
 
-        setup_signals(s);
-
-        assert(server == NULL);
-        server = s;
-
         r = init_writer_hashmap(s);
         if (r < 0)
                 return r;
 
-        n = sd_listen_fds(true);
-        if (n < 0)
-                return log_error_errno(n, "Failed to read listening file descriptors from environment: %m");
-        else
-                log_debug("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, true)) {
-                        log_debug("Received a listening socket (fd:%d)", fd);
-
-                        if (fd == http_socket)
-                                r = setup_microhttpd_server(s, fd, NULL, NULL, NULL);
-                        else if (fd == https_socket)
-                                r = setup_microhttpd_server(s, fd, key, cert, trust);
-                        else
-                                r = add_raw_socket(s, fd);
-                } else if (sd_is_socket(fd, AF_UNSPEC, 0, false)) {
-                        char *hostname;
-
-                        r = getpeername_pretty(fd, false, &hostname);
-                        if (r < 0)
-                                return log_error_errno(r, "Failed to retrieve remote name: %m");
-
-                        log_debug("Received a connection socket (fd:%d) from %s", fd, hostname);
-
-                        r = add_source(s, fd, hostname, true);
-                } else {
-                        log_error("Unknown socket passed on fd:%d", fd);
-
-                        return -EINVAL;
-                }
-
-                if (r < 0)
-                        return log_error_errno(r, "Failed to register socket (fd:%d): %m",
-                                               fd);
-        }
-
-        if (arg_getter) {
-                log_info("Spawning getter %s...", arg_getter);
-                fd = spawn_getter(arg_getter);
-                if (fd < 0)
-                        return fd;
-
-                r = add_source(s, fd, (char*) arg_output, false);
-                if (r < 0)
-                        return r;
-        }
-
-        if (arg_url) {
-                const char *url;
-                char *hostname, *p;
-
-                if (!strstr(arg_url, "/entries")) {
-                        if (endswith(arg_url, "/"))
-                                url = strjoina(arg_url, "entries");
-                        else
-                                url = strjoina(arg_url, "/entries");
-                }
-                else
-                        url = strdupa(arg_url);
-
-                log_info("Spawning curl %s...", url);
-                fd = spawn_curl(url);
-                if (fd < 0)
-                        return fd;
-
-                hostname =
-                        startswith(arg_url, "https://") ?:
-                        startswith(arg_url, "http://") ?:
-                        arg_url;
-
-                hostname = strdupa(hostname);
-                if ((p = strchr(hostname, '/')))
-                        *p = '\0';
-                if ((p = strchr(hostname, ':')))
-                        *p = '\0';
-
-                r = add_source(s, fd, hostname, false);
-                if (r < 0)
-                        return r;
-        }
-
-        if (arg_listen_raw) {
-                log_debug("Listening on a socket...");
-                r = setup_raw_socket(s, arg_listen_raw);
-                if (r < 0)
-                        return r;
-        }
-
-        if (arg_listen_http) {
-                r = setup_microhttpd_socket(s, arg_listen_http, NULL, NULL, NULL);
-                if (r < 0)
-                        return r;
-        }
-
-        if (arg_listen_https) {
-                r = setup_microhttpd_socket(s, arg_listen_https, key, cert, trust);
-                if (r < 0)
-                        return r;
-        }
-
-        STRV_FOREACH(file, arg_files) {
-                const char *output_name;
-
-                if (streq(*file, "-")) {
-                        log_debug("Using standard input as source.");
-
-                        fd = STDIN_FILENO;
-                        output_name = "stdin";
-                } else {
-                        log_debug("Reading file %s...", *file);
-
-                        fd = open(*file, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NONBLOCK);
-                        if (fd < 0)
-                                return log_error_errno(errno, "Failed to open %s: %m", *file);
-                        output_name = *file;
-                }
-
-                r = add_source(s, fd, (char*) output_name, false);
-                if (r < 0)
-                        return r;
-        }
-
-        if (s->active == 0) {
-                log_error("Zero sources specified");
-                return -EINVAL;
-        }
-
-        if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE) {
-                /* In this case we know what the writer will be
-                   called, so we can create it and verify that we can
-                   create output as expected. */
-                r = get_writer(s, NULL, &s->_single_writer);
-                if (r < 0)
-                        return r;
-        }
-
         return 0;
 }
 
@@ -1017,7 +349,7 @@ static void MHDDaemonWrapper_free(MHDDaemonWrapper *d) {
         free(d);
 }
 
-static void server_destroy(RemoteServer *s) {
+RemoteServer* journal_remote_server_destroy(RemoteServer *s) {
         size_t i;
 
         hashmap_free_with_destructor(s->daemons, MHDDaemonWrapper_free);
@@ -1035,7 +367,11 @@ static void server_destroy(RemoteServer *s) {
         sd_event_source_unref(s->listen_event);
         sd_event_unref(s->events);
 
+        if (s == journal_remote_server_global)
+                journal_remote_server_global = NULL;
+
         /* fds that we're listening on remain open... */
+        return NULL;
 }
 
 /**********************************************************************
@@ -1058,7 +394,7 @@ static int handle_raw_source(sd_event_source *event,
         source = s->sources[fd];
         assert(source->importer.fd == fd);
 
-        r = process_source(source, arg_compress, arg_seal);
+        r = process_source(source, s->compress, s->seal);
         if (journal_importer_eof(&source->importer)) {
                 size_t remaining;
 
@@ -1078,7 +414,7 @@ static int handle_raw_source(sd_event_source *event,
                 return 0;
         } else if (r < 0) {
                 log_debug_errno(r, "Closing connection: %m");
-                remove_source(server, fd);
+                remove_source(s, fd);
                 return 0;
         } else
                 return 1;
@@ -1092,7 +428,7 @@ static int dispatch_raw_source_until_block(sd_event_source *event,
         /* Make sure event stays around even if source is destroyed */
         sd_event_source_ref(event);
 
-        r = handle_raw_source(event, source->importer.fd, EPOLLIN, server);
+        r = handle_raw_source(event, source->importer.fd, EPOLLIN, journal_remote_server_global);
         if (r != 1)
                 /* No more data for now */
                 sd_event_source_set_enabled(event, SD_EVENT_OFF);
@@ -1112,7 +448,7 @@ static int dispatch_raw_source_event(sd_event_source *event,
         assert(source->event);
         assert(source->buffer_event);
 
-        r = handle_raw_source(event, fd, EPOLLIN, server);
+        r = handle_raw_source(event, fd, EPOLLIN, journal_remote_server_global);
         if (r == 1)
                 /* Might have more data. We need to rerun the handler
                  * until we are sure the buffer is exhausted. */
@@ -1125,7 +461,7 @@ static int dispatch_blocking_source_event(sd_event_source *event,
                                           void *userdata) {
         RemoteSource *source = userdata;
 
-        return handle_raw_source(event, source->importer.fd, EPOLLIN, server);
+        return handle_raw_source(event, source->importer.fd, EPOLLIN, journal_remote_server_global);
 }
 
 static int accept_connection(const char* type, int fd,
@@ -1191,439 +527,5 @@ static int dispatch_raw_connection_event(sd_event_source *event,
         if (fd2 < 0)
                 return fd2;
 
-        return add_source(s, fd2, hostname, true);
-}
-
-/**********************************************************************
- **********************************************************************
- **********************************************************************/
-
-static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
-        [JOURNAL_WRITE_SPLIT_NONE] = "none",
-        [JOURNAL_WRITE_SPLIT_HOST] = "host",
-};
-
-DEFINE_PRIVATE_STRING_TABLE_LOOKUP(journal_write_split_mode, JournalWriteSplitMode);
-static DEFINE_CONFIG_PARSE_ENUM(config_parse_write_split_mode,
-                                journal_write_split_mode,
-                                JournalWriteSplitMode,
-                                "Failed to parse split mode setting");
-
-static int parse_config(void) {
-        const ConfigTableItem items[] = {
-                { "Remote",  "Seal",                   config_parse_bool,             0, &arg_seal       },
-                { "Remote",  "SplitMode",              config_parse_write_split_mode, 0, &arg_split_mode },
-                { "Remote",  "ServerKeyFile",          config_parse_path,             0, &arg_key        },
-                { "Remote",  "ServerCertificateFile",  config_parse_path,             0, &arg_cert       },
-                { "Remote",  "TrustedCertificateFile", config_parse_path,             0, &arg_trust      },
-                {}};
-
-        return config_parse_many_nulstr(PKGSYSCONFDIR "/journal-remote.conf",
-                                        CONF_PATHS_NULSTR("systemd/journal-remote.conf.d"),
-                                        "Remote\0", config_item_table_lookup, items,
-                                        CONFIG_PARSE_WARN, NULL);
-}
-
-static void help(void) {
-        printf("%s [OPTIONS...] {FILE|-}...\n\n"
-               "Write external journal events to journal file(s).\n\n"
-               "  -h --help                 Show this help\n"
-               "     --version              Show package version\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"
-               "     --listen-http=ADDR     Listen for HTTP connections at ADDR\n"
-               "     --listen-https=ADDR    Listen for HTTPS connections at ADDR\n"
-               "  -o --output=FILE|DIR      Write output to FILE or DIR/external-*.journal\n"
-               "     --compress[=BOOL]      XZ-compress the output journal (default: yes)\n"
-               "     --seal[=BOOL]          Use event sealing (default: no)\n"
-               "     --key=FILENAME         SSL key in PEM format (default:\n"
-               "                            \"" PRIV_KEY_FILE "\")\n"
-               "     --cert=FILENAME        SSL certificate in PEM format (default:\n"
-               "                            \"" CERT_FILE "\")\n"
-               "     --trust=FILENAME|all   SSL CA certificate or disable checking (default:\n"
-               "                            \"" TRUST_FILE "\")\n"
-               "     --gnutls-log=CATEGORY...\n"
-               "                            Specify a list of gnutls logging categories\n"
-               "     --split-mode=none|host How many output files to create\n"
-               "\n"
-               "Note: file descriptors from sd_listen_fds() will be consumed, too.\n"
-               , program_invocation_short_name);
-}
-
-static int parse_argv(int argc, char *argv[]) {
-        enum {
-                ARG_VERSION = 0x100,
-                ARG_URL,
-                ARG_LISTEN_RAW,
-                ARG_LISTEN_HTTP,
-                ARG_LISTEN_HTTPS,
-                ARG_GETTER,
-                ARG_SPLIT_MODE,
-                ARG_COMPRESS,
-                ARG_SEAL,
-                ARG_KEY,
-                ARG_CERT,
-                ARG_TRUST,
-                ARG_GNUTLS_LOG,
-        };
-
-        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   },
-                { "listen-http",  required_argument, NULL, ARG_LISTEN_HTTP  },
-                { "listen-https", required_argument, NULL, ARG_LISTEN_HTTPS },
-                { "output",       required_argument, NULL, 'o'              },
-                { "split-mode",   required_argument, NULL, ARG_SPLIT_MODE   },
-                { "compress",     optional_argument, NULL, ARG_COMPRESS     },
-                { "seal",         optional_argument, NULL, ARG_SEAL         },
-                { "key",          required_argument, NULL, ARG_KEY          },
-                { "cert",         required_argument, NULL, ARG_CERT         },
-                { "trust",        required_argument, NULL, ARG_TRUST        },
-                { "gnutls-log",   required_argument, NULL, ARG_GNUTLS_LOG   },
-                {}
-        };
-
-        int c, r;
-        bool type_a, type_b;
-
-        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:
-                        return version();
-
-                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_LISTEN_HTTP:
-                        if (arg_listen_http || http_socket >= 0) {
-                                log_error("cannot currently use --listen-http more than once");
-                                return -EINVAL;
-                        }
-
-                        r = negative_fd(optarg);
-                        if (r >= 0)
-                                http_socket = r;
-                        else
-                                arg_listen_http = optarg;
-                        break;
-
-                case ARG_LISTEN_HTTPS:
-                        if (arg_listen_https || https_socket >= 0) {
-                                log_error("cannot currently use --listen-https more than once");
-                                return -EINVAL;
-                        }
-
-                        r = negative_fd(optarg);
-                        if (r >= 0)
-                                https_socket = r;
-                        else
-                                arg_listen_https = optarg;
-
-                        break;
-
-                case ARG_KEY:
-                        if (arg_key) {
-                                log_error("Key file specified twice");
-                                return -EINVAL;
-                        }
-
-                        arg_key = strdup(optarg);
-                        if (!arg_key)
-                                return log_oom();
-
-                        break;
-
-                case ARG_CERT:
-                        if (arg_cert) {
-                                log_error("Certificate file specified twice");
-                                return -EINVAL;
-                        }
-
-                        arg_cert = strdup(optarg);
-                        if (!arg_cert)
-                                return log_oom();
-
-                        break;
-
-                case ARG_TRUST:
-                        if (arg_trust || arg_trust_all) {
-                                log_error("Confusing trusted CA configuration");
-                                return -EINVAL;
-                        }
-
-                        if (streq(optarg, "all"))
-                                arg_trust_all = true;
-                        else {
-#if HAVE_GNUTLS
-                                arg_trust = strdup(optarg);
-                                if (!arg_trust)
-                                        return log_oom();
-#else
-                                log_error("Option --trust is not available.");
-                                return -EINVAL;
-#endif
-                        }
-
-                        break;
-
-                case 'o':
-                        if (arg_output) {
-                                log_error("cannot use --output/-o more than once");
-                                return -EINVAL;
-                        }
-
-                        arg_output = optarg;
-                        break;
-
-                case ARG_SPLIT_MODE:
-                        arg_split_mode = journal_write_split_mode_from_string(optarg);
-                        if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) {
-                                log_error("Invalid split mode: %s", optarg);
-                                return -EINVAL;
-                        }
-                        break;
-
-                case ARG_COMPRESS:
-                        if (optarg) {
-                                r = parse_boolean(optarg);
-                                if (r < 0) {
-                                        log_error("Failed to parse --compress= parameter.");
-                                        return -EINVAL;
-                                }
-
-                                arg_compress = !!r;
-                        } else
-                                arg_compress = true;
-
-                        break;
-
-                case ARG_SEAL:
-                        if (optarg) {
-                                r = parse_boolean(optarg);
-                                if (r < 0) {
-                                        log_error("Failed to parse --seal= parameter.");
-                                        return -EINVAL;
-                                }
-
-                                arg_seal = !!r;
-                        } else
-                                arg_seal = true;
-
-                        break;
-
-                case ARG_GNUTLS_LOG: {
-#if HAVE_GNUTLS
-                        const char* p = optarg;
-                        for (;;) {
-                                _cleanup_free_ char *word = NULL;
-
-                                r = extract_first_word(&p, &word, ",", 0);
-                                if (r < 0)
-                                        return log_error_errno(r, "Failed to parse --gnutls-log= argument: %m");
-
-                                if (r == 0)
-                                        break;
-
-                                if (strv_push(&arg_gnutls_log, word) < 0)
-                                        return log_oom();
-
-                                word = NULL;
-                        }
-                        break;
-#else
-                        log_error("Option --gnutls-log is not available.");
-                        return -EINVAL;
-#endif
-                }
-
-                case '?':
-                        return -EINVAL;
-
-                default:
-                        assert_not_reached("Unknown option code.");
-                }
-
-        if (optind < argc)
-                arg_files = argv + optind;
-
-        type_a = arg_getter || !strv_isempty(arg_files);
-        type_b = arg_url
-                || arg_listen_raw
-                || arg_listen_http || arg_listen_https
-                || sd_listen_fds(false) > 0;
-        if (type_a && type_b) {
-                log_error("Cannot use file input or --getter with "
-                          "--arg-listen-... or socket activation.");
-                return -EINVAL;
-        }
-        if (type_a) {
-                if (!arg_output) {
-                        log_error("Option --output must be specified with file input or --getter.");
-                        return -EINVAL;
-                }
-
-                if (!IN_SET(arg_split_mode, JOURNAL_WRITE_SPLIT_NONE, _JOURNAL_WRITE_SPLIT_INVALID)) {
-                        log_error("For active sources, only --split-mode=none is allowed.");
-                        return -EINVAL;
-                }
-
-                arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
-        }
-
-        if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID)
-                arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
-
-        if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE && arg_output) {
-                if (is_dir(arg_output, true) > 0) {
-                        log_error("For SplitMode=none, output must be a file.");
-                        return -EINVAL;
-                }
-                if (!endswith(arg_output, ".journal")) {
-                        log_error("For SplitMode=none, output file name must end with .journal.");
-                        return -EINVAL;
-                }
-        }
-
-        if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST
-            && arg_output && is_dir(arg_output, true) <= 0) {
-                log_error("For SplitMode=host, output must be a directory.");
-                return -EINVAL;
-        }
-
-        log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s",
-                  journal_write_split_mode_to_string(arg_split_mode),
-                  strna(arg_key),
-                  strna(arg_cert),
-                  strna(arg_trust));
-
-        return 1 /* work to do */;
-}
-
-static int load_certificates(char **key, char **cert, char **trust) {
-        int r;
-
-        r = read_full_file(arg_key ?: PRIV_KEY_FILE, key, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to read key from file '%s': %m",
-                                       arg_key ?: PRIV_KEY_FILE);
-
-        r = read_full_file(arg_cert ?: CERT_FILE, cert, NULL);
-        if (r < 0)
-                return log_error_errno(r, "Failed to read certificate from file '%s': %m",
-                                       arg_cert ?: CERT_FILE);
-
-        if (arg_trust_all)
-                log_info("Certificate checking disabled.");
-        else {
-                r = read_full_file(arg_trust ?: TRUST_FILE, trust, NULL);
-                if (r < 0)
-                        return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
-                                               arg_trust ?: TRUST_FILE);
-        }
-
-        return 0;
-}
-
-int main(int argc, char **argv) {
-        RemoteServer s = {};
-        int r;
-        _cleanup_free_ char *key = NULL, *cert = NULL, *trust = NULL;
-
-        log_show_color(true);
-        log_parse_environment();
-
-        r = parse_config();
-        if (r < 0)
-                return EXIT_FAILURE;
-
-        r = parse_argv(argc, argv);
-        if (r <= 0)
-                return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
-
-        if (arg_listen_http || arg_listen_https) {
-                r = setup_gnutls_logger(arg_gnutls_log);
-                if (r < 0)
-                        return EXIT_FAILURE;
-        }
-
-        if (arg_listen_https || https_socket >= 0)
-                if (load_certificates(&key, &cert, &trust) < 0)
-                        return EXIT_FAILURE;
-
-        if (remoteserver_init(&s, key, cert, trust) < 0)
-                return EXIT_FAILURE;
-
-        r = sd_event_set_watchdog(s.events, true);
-        if (r < 0)
-                log_error_errno(r, "Failed to enable watchdog: %m");
-        else
-                log_debug("Watchdog is %sd.", enable_disable(r > 0));
-
-        log_debug("%s running as pid "PID_FMT,
-                  program_invocation_short_name, getpid_cached());
-        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_errno(r, "Failed to run event loop: %m");
-                        break;
-                }
-        }
-
-        sd_notifyf(false,
-                   "STOPPING=1\n"
-                   "STATUS=Shutting down after writing %" PRIu64 " entries...", s.event_count);
-        log_info("Finishing after writing %" PRIu64 " entries", s.event_count);
-
-        server_destroy(&s);
-
-        free(arg_key);
-        free(arg_cert);
-        free(arg_trust);
-
-        return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
+        return journal_remote_add_source(s, fd2, hostname, true);
 }
index f8df72ec55d625151e42f120a342ecd109c644cc..72741c7fbad410ab33bf04130947d2c1cc7e4d65 100644 (file)
@@ -36,6 +36,27 @@ struct RemoteServer {
         Writer *_single_writer;
         uint64_t event_count;
 
-        bool check_trust;
         Hashmap *daemons;
+
+        const char *output;                    /* either the output file or directory */
+
+        JournalWriteSplitMode split_mode;
+        bool compress;
+        bool seal;
+        bool check_trust;
 };
+extern RemoteServer *journal_remote_server_global;
+
+int journal_remote_server_init(
+                RemoteServer *s,
+                const char *output,
+                JournalWriteSplitMode split_mode,
+                bool compress,
+                bool seal);
+
+int journal_remote_get_writer(RemoteServer *s, const char *host, Writer **writer);
+
+int journal_remote_add_source(RemoteServer *s, int fd, char* name, bool own_name);
+int journal_remote_add_raw_socket(RemoteServer *s, int fd);
+
+RemoteServer* journal_remote_server_destroy(RemoteServer *s);
index c5a3dff635613953aae674bcda783cda0ee42974..99b9003f3b7fa21daca8da4bb5521d88ba37d2be 100644 (file)
@@ -8,15 +8,26 @@ systemd_journal_upload_sources = files('''
         journal-upload-journal.c
 '''.split())
 
+libsystemd_journal_remote = static_library(
+        'systemd-journal-remote',
+        'journal-remote-parse.h',
+        'journal-remote-parse.c',
+        'journal-remote-write.h',
+        'journal-remote-write.c',
+        'journal-remote.h',
+        'journal-remote.c',
+        'microhttpd-util.h',
+        'microhttpd-util.c',
+        include_directories : includes,
+        dependencies : [threads,
+                        libmicrohttpd,
+                        libgnutls,
+                        libxz,
+                        liblz4],
+        install : false)
+
 systemd_journal_remote_sources = files('''
-        journal-remote-parse.h
-        journal-remote-parse.c
-        journal-remote-write.h
-        journal-remote-write.c
-        journal-remote.h
-        journal-remote.c
-        microhttpd-util.h
-        microhttpd-util.c
+        journal-remote-main.c
 '''.split())
 
 systemd_journal_gatewayd_sources = files('''