summaryrefslogtreecommitdiff
path: root/src/grp-journal/grp-remote/systemd-journal-remote
diff options
context:
space:
mode:
Diffstat (limited to 'src/grp-journal/grp-remote/systemd-journal-remote')
l---------src/grp-journal/grp-remote/systemd-journal-remote/GNUmakefile1
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/Makefile84
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c507
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h69
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c165
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h70
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c1598
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in6
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml120
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h53
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in30
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket15
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers8
-rw-r--r--src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml324
14 files changed, 3050 insertions, 0 deletions
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/GNUmakefile b/src/grp-journal/grp-remote/systemd-journal-remote/GNUmakefile
new file mode 120000
index 0000000000..0f24a727ed
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/GNUmakefile
@@ -0,0 +1 @@
+GNUmakefile \ No newline at end of file
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/Makefile b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile
new file mode 100644
index 0000000000..8bb2c3871d
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/Makefile
@@ -0,0 +1,84 @@
+# -*- Mode: makefile; indent-tabs-mode: t -*-
+#
+# This file is part of systemd.
+#
+# Copyright 2010-2012 Lennart Poettering
+# Copyright 2010-2012 Kay Sievers
+# Copyright 2013 Zbigniew Jędrzejewski-Szmek
+# Copyright 2013 David Strauss
+# Copyright 2016 Luke Shumaker
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+#
+# systemd is distributed in the hope that it will be useful, but
+# WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+# Lesser General Public License for more details.
+#
+# You should have received a copy of the GNU Lesser General Public License
+# along with systemd; If not, see <http://www.gnu.org/licenses/>.
+include $(dir $(lastword $(MAKEFILE_LIST)))/../../../../config.mk
+include $(topsrcdir)/build-aux/Makefile.head.mk
+
+ifneq ($(HAVE_MICROHTTPD),)
+rootlibexec_PROGRAMS += \
+ systemd-journal-remote
+
+systemd_journal_remote_SOURCES = \
+ src/journal-remote/journal-remote-parse.h \
+ src/journal-remote/journal-remote-parse.c \
+ src/journal-remote/journal-remote-write.h \
+ src/journal-remote/journal-remote-write.c \
+ src/journal-remote/journal-remote.h \
+ src/journal-remote/journal-remote.c
+
+systemd_journal_remote_LDADD = \
+ libjournal-core.la
+
+systemd_journal_remote_SOURCES += \
+ src/journal-remote/microhttpd-util.h \
+ src/journal-remote/microhttpd-util.c
+
+systemd_journal_remote_CFLAGS = \
+ $(MICROHTTPD_CFLAGS)
+
+systemd_journal_remote_LDADD += \
+ $(MICROHTTPD_LIBS)
+
+ifneq ($(ENABLE_TMPFILES),)
+dist_tmpfiles_DATA += \
+ tmpfiles.d/systemd-remote.conf
+endif # ENABLE_TMPFILES
+
+ifneq ($(HAVE_GNUTLS),)
+systemd_journal_remote_LDADD += \
+ $(GNUTLS_LIBS)
+endif # HAVE_GNUTLS
+
+# systemd-journal-remote make sense mostly with full crypto stack
+dist_systemunit_DATA += \
+ units/systemd-journal-remote.socket
+
+nodist_systemunit_DATA += \
+ units/systemd-journal-remote.service
+
+journal-remote-install-hook: journal-install-hook
+ -$(MKDIR_P) $(DESTDIR)/var/log/journal/remote
+ -chown 0:0 $(DESTDIR)/var/log/journal/remote
+ -chmod 755 $(DESTDIR)/var/log/journal/remote
+
+INSTALL_EXEC_HOOKS += journal-remote-install-hook
+
+nodist_pkgsysconf_DATA += \
+ src/journal-remote/journal-remote.conf
+
+EXTRA_DIST += \
+ units/systemd-journal-remote.service.in \
+ src/journal-remote/journal-remote.conf.in \
+ src/journal-remote/log-generator.py
+endif # HAVE_MICROHTTPD
+
+include $(topsrcdir)/build-aux/Makefile.tail.mk
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c
new file mode 100644
index 0000000000..fdfa692214
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.c
@@ -0,0 +1,507 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "journal-core/journald-native.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/string-util.h"
+
+#include "journal-remote-parse.h"
+
+#define LINE_CHUNK 8*1024u
+
+void source_free(RemoteSource *source) {
+ if (!source)
+ return;
+
+ if (source->fd >= 0 && !source->passive_fd) {
+ log_debug("Closing fd:%d (%s)", source->fd, source->name);
+ safe_close(source->fd);
+ }
+
+ free(source->name);
+ free(source->buf);
+ iovw_free_contents(&source->iovw);
+
+ log_debug("Writer ref count %i", source->writer->n_ref);
+ writer_unref(source->writer);
+
+ sd_event_source_unref(source->event);
+ sd_event_source_unref(source->buffer_event);
+
+ free(source);
+}
+
+/**
+ * Initialize zero-filled source with given values. On success, takes
+ * ownerhship of fd and writer, otherwise does not touch them.
+ */
+RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer) {
+
+ RemoteSource *source;
+
+ log_debug("Creating source for %sfd:%d (%s)",
+ passive_fd ? "passive " : "", fd, name);
+
+ assert(fd >= 0);
+
+ source = new0(RemoteSource, 1);
+ if (!source)
+ return NULL;
+
+ source->fd = fd;
+ source->passive_fd = passive_fd;
+ source->name = name;
+ source->writer = writer;
+
+ return source;
+}
+
+static char* realloc_buffer(RemoteSource *source, size_t size) {
+ char *b, *old = source->buf;
+
+ b = GREEDY_REALLOC(source->buf, source->size, size);
+ if (!b)
+ return NULL;
+
+ iovw_rebase(&source->iovw, old, source->buf);
+
+ return b;
+}
+
+static int get_line(RemoteSource *source, char **line, size_t *size) {
+ ssize_t n;
+ char *c = NULL;
+
+ assert(source);
+ assert(source->state == STATE_LINE);
+ assert(source->offset <= source->filled);
+ assert(source->filled <= source->size);
+ assert(source->buf == NULL || source->size > 0);
+ assert(source->fd >= 0);
+
+ for (;;) {
+ if (source->buf) {
+ size_t start = MAX(source->scanned, source->offset);
+
+ c = memchr(source->buf + start, '\n',
+ source->filled - start);
+ if (c != NULL)
+ break;
+ }
+
+ source->scanned = source->filled;
+ if (source->scanned >= DATA_SIZE_MAX) {
+ log_error("Entry is bigger than %u bytes.", DATA_SIZE_MAX);
+ return -E2BIG;
+ }
+
+ if (source->passive_fd)
+ /* we have to wait for some data to come to us */
+ return -EAGAIN;
+
+ /* We know that source->filled is at most DATA_SIZE_MAX, so if
+ we reallocate it, we'll increase the size at least a bit. */
+ assert_cc(DATA_SIZE_MAX < ENTRY_SIZE_MAX);
+ if (source->size - source->filled < LINE_CHUNK &&
+ !realloc_buffer(source, MIN(source->filled + LINE_CHUNK, ENTRY_SIZE_MAX)))
+ return log_oom();
+
+ assert(source->buf);
+ assert(source->size - source->filled >= LINE_CHUNK ||
+ source->size == ENTRY_SIZE_MAX);
+
+ n = read(source->fd,
+ source->buf + source->filled,
+ source->size - source->filled);
+ if (n < 0) {
+ if (errno != EAGAIN)
+ log_error_errno(errno, "read(%d, ..., %zu): %m",
+ source->fd,
+ source->size - source->filled);
+ return -errno;
+ } else if (n == 0)
+ return 0;
+
+ source->filled += n;
+ }
+
+ *line = source->buf + source->offset;
+ *size = c + 1 - source->buf - source->offset;
+ source->offset += *size;
+
+ return 1;
+}
+
+int push_data(RemoteSource *source, const char *data, size_t size) {
+ assert(source);
+ assert(source->state != STATE_EOF);
+
+ if (!realloc_buffer(source, source->filled + size)) {
+ log_error("Failed to store received data of size %zu "
+ "(in addition to existing %zu bytes with %zu filled): %s",
+ size, source->size, source->filled, strerror(ENOMEM));
+ return -ENOMEM;
+ }
+
+ memcpy(source->buf + source->filled, data, size);
+ source->filled += size;
+
+ return 0;
+}
+
+static int fill_fixed_size(RemoteSource *source, void **data, size_t size) {
+
+ assert(source);
+ assert(source->state == STATE_DATA_START ||
+ source->state == STATE_DATA ||
+ source->state == STATE_DATA_FINISH);
+ assert(size <= DATA_SIZE_MAX);
+ assert(source->offset <= source->filled);
+ assert(source->filled <= source->size);
+ assert(source->buf != NULL || source->size == 0);
+ assert(source->buf == NULL || source->size > 0);
+ assert(source->fd >= 0);
+ assert(data);
+
+ while (source->filled - source->offset < size) {
+ int n;
+
+ if (source->passive_fd)
+ /* we have to wait for some data to come to us */
+ return -EAGAIN;
+
+ if (!realloc_buffer(source, source->offset + size))
+ return log_oom();
+
+ n = read(source->fd, source->buf + source->filled,
+ source->size - source->filled);
+ if (n < 0) {
+ if (errno != EAGAIN)
+ log_error_errno(errno, "read(%d, ..., %zu): %m", source->fd,
+ source->size - source->filled);
+ return -errno;
+ } else if (n == 0)
+ return 0;
+
+ source->filled += n;
+ }
+
+ *data = source->buf + source->offset;
+ source->offset += size;
+
+ return 1;
+}
+
+static int get_data_size(RemoteSource *source) {
+ int r;
+ void *data;
+
+ assert(source);
+ assert(source->state == STATE_DATA_START);
+ assert(source->data_size == 0);
+
+ r = fill_fixed_size(source, &data, sizeof(uint64_t));
+ if (r <= 0)
+ return r;
+
+ source->data_size = le64toh( *(uint64_t *) data );
+ if (source->data_size > DATA_SIZE_MAX) {
+ log_error("Stream declares field with size %zu > DATA_SIZE_MAX = %u",
+ source->data_size, DATA_SIZE_MAX);
+ return -EINVAL;
+ }
+ if (source->data_size == 0)
+ log_warning("Binary field with zero length");
+
+ return 1;
+}
+
+static int get_data_data(RemoteSource *source, void **data) {
+ int r;
+
+ assert(source);
+ assert(data);
+ assert(source->state == STATE_DATA);
+
+ r = fill_fixed_size(source, data, source->data_size);
+ if (r <= 0)
+ return r;
+
+ return 1;
+}
+
+static int get_data_newline(RemoteSource *source) {
+ int r;
+ char *data;
+
+ assert(source);
+ assert(source->state == STATE_DATA_FINISH);
+
+ r = fill_fixed_size(source, (void**) &data, 1);
+ if (r <= 0)
+ return r;
+
+ assert(data);
+ if (*data != '\n') {
+ log_error("expected newline, got '%c'", *data);
+ return -EINVAL;
+ }
+
+ return 1;
+}
+
+static int process_dunder(RemoteSource *source, char *line, size_t n) {
+ const char *timestamp;
+ int r;
+
+ assert(line);
+ assert(n > 0);
+ assert(line[n-1] == '\n');
+
+ /* XXX: is it worth to support timestamps in extended format?
+ * We don't produce them, but who knows... */
+
+ timestamp = startswith(line, "__CURSOR=");
+ if (timestamp)
+ /* ignore __CURSOR */
+ return 1;
+
+ timestamp = startswith(line, "__REALTIME_TIMESTAMP=");
+ if (timestamp) {
+ long long unsigned x;
+ line[n-1] = '\0';
+ r = safe_atollu(timestamp, &x);
+ if (r < 0)
+ log_warning("Failed to parse __REALTIME_TIMESTAMP: '%s'", timestamp);
+ else
+ source->ts.realtime = x;
+ return r < 0 ? r : 1;
+ }
+
+ timestamp = startswith(line, "__MONOTONIC_TIMESTAMP=");
+ if (timestamp) {
+ long long unsigned x;
+ line[n-1] = '\0';
+ r = safe_atollu(timestamp, &x);
+ if (r < 0)
+ log_warning("Failed to parse __MONOTONIC_TIMESTAMP: '%s'", timestamp);
+ else
+ source->ts.monotonic = x;
+ return r < 0 ? r : 1;
+ }
+
+ timestamp = startswith(line, "__");
+ if (timestamp) {
+ log_notice("Unknown dunder line %s", line);
+ return 1;
+ }
+
+ /* no dunder */
+ return 0;
+}
+
+static int process_data(RemoteSource *source) {
+ int r;
+
+ switch(source->state) {
+ case STATE_LINE: {
+ char *line, *sep;
+ size_t n = 0;
+
+ assert(source->data_size == 0);
+
+ r = get_line(source, &line, &n);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return r;
+ }
+ assert(n > 0);
+ assert(line[n-1] == '\n');
+
+ if (n == 1) {
+ log_trace("Received empty line, event is ready");
+ return 1;
+ }
+
+ r = process_dunder(source, line, n);
+ if (r != 0)
+ return r < 0 ? r : 0;
+
+ /* MESSAGE=xxx\n
+ or
+ COREDUMP\n
+ LLLLLLLL0011223344...\n
+ */
+ sep = memchr(line, '=', n);
+ if (sep) {
+ /* chomp newline */
+ n--;
+
+ r = iovw_put(&source->iovw, line, n);
+ if (r < 0)
+ return r;
+ } else {
+ /* replace \n with = */
+ line[n-1] = '=';
+
+ source->field_len = n;
+ source->state = STATE_DATA_START;
+
+ /* we cannot put the field in iovec until we have all data */
+ }
+
+ log_trace("Received: %.*s (%s)", (int) n, line, sep ? "text" : "binary");
+
+ return 0; /* continue */
+ }
+
+ case STATE_DATA_START:
+ assert(source->data_size == 0);
+
+ r = get_data_size(source);
+ // log_debug("get_data_size() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ source->state = source->data_size > 0 ?
+ STATE_DATA : STATE_DATA_FINISH;
+
+ return 0; /* continue */
+
+ case STATE_DATA: {
+ void *data;
+ char *field;
+
+ assert(source->data_size > 0);
+
+ r = get_data_data(source, &data);
+ // log_debug("get_data_data() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ assert(data);
+
+ field = (char*) data - sizeof(uint64_t) - source->field_len;
+ memmove(field + sizeof(uint64_t), field, source->field_len);
+
+ r = iovw_put(&source->iovw, field + sizeof(uint64_t), source->field_len + source->data_size);
+ if (r < 0)
+ return r;
+
+ source->state = STATE_DATA_FINISH;
+
+ return 0; /* continue */
+ }
+
+ case STATE_DATA_FINISH:
+ r = get_data_newline(source);
+ // log_debug("get_data_newline() -> %d", r);
+ if (r < 0)
+ return r;
+ if (r == 0) {
+ source->state = STATE_EOF;
+ return 0;
+ }
+
+ source->data_size = 0;
+ source->state = STATE_LINE;
+
+ return 0; /* continue */
+ default:
+ assert_not_reached("wtf?");
+ }
+}
+
+int process_source(RemoteSource *source, bool compress, bool seal) {
+ size_t remain, target;
+ int r;
+
+ assert(source);
+ assert(source->writer);
+
+ r = process_data(source);
+ if (r <= 0)
+ return r;
+
+ /* We have a full event */
+ log_trace("Received full event from source@%p fd:%d (%s)",
+ source, source->fd, source->name);
+
+ if (!source->iovw.count) {
+ log_warning("Entry with no payload, skipping");
+ goto freeing;
+ }
+
+ assert(source->iovw.iovec);
+ assert(source->iovw.count);
+
+ r = writer_write(source->writer, &source->iovw, &source->ts, compress, seal);
+ if (r < 0)
+ log_error_errno(r, "Failed to write entry of %zu bytes: %m",
+ iovw_size(&source->iovw));
+ else
+ r = 1;
+
+ freeing:
+ iovw_free_contents(&source->iovw);
+
+ /* possibly reset buffer position */
+ remain = source->filled - source->offset;
+
+ if (remain == 0) /* no brainer */
+ source->offset = source->scanned = source->filled = 0;
+ else if (source->offset > source->size - source->filled &&
+ source->offset > remain) {
+ memcpy(source->buf, source->buf + source->offset, remain);
+ source->offset = source->scanned = 0;
+ source->filled = remain;
+ }
+
+ target = source->size;
+ while (target > 16 * LINE_CHUNK && source->filled < target / 2)
+ target /= 2;
+ if (target < source->size) {
+ char *tmp;
+
+ tmp = realloc(source->buf, target);
+ if (!tmp)
+ log_warning("Failed to reallocate buffer to (smaller) size %zu",
+ target);
+ else {
+ log_debug("Reallocated buffer from %zu to %zu bytes",
+ source->size, target);
+ source->buf = tmp;
+ source->size = target;
+ }
+ }
+
+ return r;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h
new file mode 100644
index 0000000000..4f47ea89d6
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-parse.h
@@ -0,0 +1,69 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-event.h>
+
+#include "journal-remote-write.h"
+
+typedef enum {
+ STATE_LINE = 0, /* waiting to read, or reading line */
+ STATE_DATA_START, /* reading binary data header */
+ STATE_DATA, /* reading binary data */
+ STATE_DATA_FINISH, /* expecting newline */
+ STATE_EOF, /* done */
+} source_state;
+
+typedef struct RemoteSource {
+ char *name;
+ int fd;
+ bool passive_fd;
+
+ char *buf;
+ size_t size; /* total size of the buffer */
+ size_t offset; /* offset to the beginning of live data in the buffer */
+ size_t scanned; /* number of bytes since the beginning of data without a newline */
+ size_t filled; /* total number of bytes in the buffer */
+
+ size_t field_len; /* used for binary fields: the field name length */
+ size_t data_size; /* and the size of the binary data chunk being processed */
+
+ struct iovec_wrapper iovw;
+
+ source_state state;
+ dual_timestamp ts;
+
+ Writer *writer;
+
+ sd_event_source *event;
+ sd_event_source *buffer_event;
+} RemoteSource;
+
+RemoteSource* source_new(int fd, bool passive_fd, char *name, Writer *writer);
+
+static inline size_t source_non_empty(RemoteSource *source) {
+ assert(source);
+
+ return source->filled;
+}
+
+void source_free(RemoteSource *source);
+int push_data(RemoteSource *source, const char *data, size_t size);
+int process_source(RemoteSource *source, bool compress, bool seal);
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c
new file mode 100644
index 0000000000..99b02602ea
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.c
@@ -0,0 +1,165 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "systemd-basic/alloc-util.h"
+
+#include "journal-remote.h"
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len) {
+ if (!GREEDY_REALLOC(iovw->iovec, iovw->size_bytes, iovw->count + 1))
+ return log_oom();
+
+ iovw->iovec[iovw->count++] = (struct iovec) {data, len};
+ return 0;
+}
+
+void iovw_free_contents(struct iovec_wrapper *iovw) {
+ iovw->iovec = mfree(iovw->iovec);
+ iovw->size_bytes = iovw->count = 0;
+}
+
+size_t iovw_size(struct iovec_wrapper *iovw) {
+ size_t n = 0, i;
+
+ for (i = 0; i < iovw->count; i++)
+ n += iovw->iovec[i].iov_len;
+
+ return n;
+}
+
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new) {
+ size_t i;
+
+ for (i = 0; i < iovw->count; i++)
+ iovw->iovec[i].iov_base = (char*) iovw->iovec[i].iov_base - old + new;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int do_rotate(JournalFile **f, bool compress, bool seal) {
+ int r = journal_file_rotate(f, compress, seal, NULL);
+ if (r < 0) {
+ if (*f)
+ log_error_errno(r, "Failed to rotate %s: %m", (*f)->path);
+ else
+ log_error_errno(r, "Failed to create rotated journal: %m");
+ }
+
+ return r;
+}
+
+Writer* writer_new(RemoteServer *server) {
+ Writer *w;
+
+ w = new0(Writer, 1);
+ if (!w)
+ return NULL;
+
+ memset(&w->metrics, 0xFF, sizeof(w->metrics));
+
+ w->mmap = mmap_cache_new();
+ if (!w->mmap)
+ return mfree(w);
+
+ w->n_ref = 1;
+ w->server = server;
+
+ return w;
+}
+
+Writer* writer_free(Writer *w) {
+ if (!w)
+ return NULL;
+
+ if (w->journal) {
+ log_debug("Closing journal file %s.", w->journal->path);
+ journal_file_close(w->journal);
+ }
+
+ if (w->server && w->hashmap_key)
+ hashmap_remove(w->server->writers, w->hashmap_key);
+
+ free(w->hashmap_key);
+
+ if (w->mmap)
+ mmap_cache_unref(w->mmap);
+
+ return mfree(w);
+}
+
+Writer* writer_unref(Writer *w) {
+ if (w && (-- w->n_ref <= 0))
+ writer_free(w);
+
+ return NULL;
+}
+
+Writer* writer_ref(Writer *w) {
+ if (w)
+ assert_se(++ w->n_ref >= 2);
+
+ return w;
+}
+
+int writer_write(Writer *w,
+ struct iovec_wrapper *iovw,
+ dual_timestamp *ts,
+ bool compress,
+ bool seal) {
+ int r;
+
+ assert(w);
+ assert(iovw);
+ assert(iovw->count > 0);
+
+ if (journal_file_rotate_suggested(w->journal, 0)) {
+ log_info("%s: Journal header limits reached or header out-of-date, rotating",
+ w->journal->path);
+ r = do_rotate(&w->journal, compress, seal);
+ if (r < 0)
+ return r;
+ }
+
+ r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count,
+ &w->seqnum, NULL, NULL);
+ if (r >= 0) {
+ if (w->server)
+ w->server->event_count += 1;
+ return 1;
+ }
+
+ log_debug_errno(r, "%s: Write failed, rotating: %m", w->journal->path);
+ r = do_rotate(&w->journal, compress, seal);
+ if (r < 0)
+ return r;
+ else
+ log_debug("%s: Successfully rotated journal", w->journal->path);
+
+ log_debug("Retrying write.");
+ r = journal_file_append_entry(w->journal, ts, iovw->iovec, iovw->count,
+ &w->seqnum, NULL, NULL);
+ if (r < 0)
+ return r;
+
+ if (w->server)
+ w->server->event_count += 1;
+ return 1;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h
new file mode 100644
index 0000000000..a61434ca75
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote-write.h
@@ -0,0 +1,70 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include "sd-journal/journal-file.h"
+
+typedef struct RemoteServer RemoteServer;
+
+struct iovec_wrapper {
+ struct iovec *iovec;
+ size_t size_bytes;
+ size_t count;
+};
+
+int iovw_put(struct iovec_wrapper *iovw, void* data, size_t len);
+void iovw_free_contents(struct iovec_wrapper *iovw);
+size_t iovw_size(struct iovec_wrapper *iovw);
+void iovw_rebase(struct iovec_wrapper *iovw, char *old, char *new);
+
+typedef struct Writer {
+ JournalFile *journal;
+ JournalMetrics metrics;
+
+ MMapCache *mmap;
+ RemoteServer *server;
+ char *hashmap_key;
+
+ uint64_t seqnum;
+
+ int n_ref;
+} Writer;
+
+Writer* writer_new(RemoteServer* server);
+Writer* writer_free(Writer *w);
+
+Writer* writer_ref(Writer *w);
+Writer* writer_unref(Writer *w);
+
+DEFINE_TRIVIAL_CLEANUP_FUNC(Writer*, writer_unref);
+#define _cleanup_writer_unref_ _cleanup_(writer_unrefp)
+
+int writer_write(Writer *s,
+ struct iovec_wrapper *iovw,
+ dual_timestamp *ts,
+ bool compress,
+ bool seal);
+
+typedef enum JournalWriteSplitMode {
+ JOURNAL_WRITE_SPLIT_NONE,
+ JOURNAL_WRITE_SPLIT_HOST,
+ _JOURNAL_WRITE_SPLIT_MAX,
+ _JOURNAL_WRITE_SPLIT_INVALID = -1
+} JournalWriteSplitMode;
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c
new file mode 100644
index 0000000000..476f4d27a8
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.c
@@ -0,0 +1,1598 @@
+/***
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <errno.h>
+#include <fcntl.h>
+#include <getopt.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/prctl.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include <systemd/sd-daemon.h>
+
+#include "journal-core/journald-native.h"
+#include "sd-journal/journal-file.h"
+#include "systemd-basic/alloc-util.h"
+#include "systemd-basic/def.h"
+#include "systemd-basic/escape.h"
+#include "systemd-basic/fd-util.h"
+#include "systemd-basic/fileio.h"
+#include "systemd-basic/macro.h"
+#include "systemd-basic/parse-util.h"
+#include "systemd-basic/signal-util.h"
+#include "systemd-basic/socket-util.h"
+#include "systemd-basic/stat-util.h"
+#include "systemd-basic/stdio-util.h"
+#include "systemd-basic/string-table.h"
+#include "systemd-basic/string-util.h"
+#include "systemd-basic/strv.h"
+#include "systemd-shared/conf-parser.h"
+
+#include "journal-remote-write.h"
+#include "journal-remote.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_HOST;
+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) {
+ int fd[2];
+ pid_t parent_pid, child_pid;
+ int r;
+
+ if (pipe(fd) < 0)
+ return log_error_errno(errno, "Failed to create pager pipe: %m");
+
+ parent_pid = getpid();
+
+ child_pid = fork();
+ if (child_pid < 0) {
+ r = log_error_errno(errno, "Failed to fork: %m");
+ safe_close_pair(fd);
+ return r;
+ }
+
+ /* In the child */
+ if (child_pid == 0) {
+
+ (void) reset_all_signal_handlers();
+ (void) reset_signal_mask();
+
+ r = dup2(fd[1], STDOUT_FILENO);
+ if (r < 0) {
+ log_error_errno(errno, "Failed to dup pipe to stdout: %m");
+ _exit(EXIT_FAILURE);
+ }
+
+ safe_close_pair(fd);
+
+ /* Make sure the child goes away when the parent dies */
+ if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
+ _exit(EXIT_FAILURE);
+
+ /* Check whether our parent died before we were able
+ * to set the death signal */
+ if (getppid() != parent_pid)
+ _exit(EXIT_SUCCESS);
+
+ execvp(child, argv);
+ log_error_errno(errno, "Failed to exec child %s: %m", child);
+ _exit(EXIT_FAILURE);
+ }
+
+ r = close(fd[1]);
+ if (r < 0)
+ log_warning_errno(errno, "Failed to close write end of pipe: %m");
+
+ 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;
+ int r;
+
+ switch (arg_split_mode) {
+ case JOURNAL_WRITE_SPLIT_NONE:
+ output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal";
+ break;
+
+ case JOURNAL_WRITE_SPLIT_HOST: {
+ _cleanup_free_ char *name;
+
+ assert(host);
+
+ name = filename_escape(host);
+ if (!name)
+ return log_oom();
+
+ r = asprintf(&_output, "%s/remote-%s.journal",
+ arg_output ?: REMOTE_JOURNAL_PATH,
+ name);
+ if (r < 0)
+ return log_oom();
+
+ output = _output;
+ break;
+ }
+
+ default:
+ assert_not_reached("what?");
+ }
+
+ r = journal_file_open_reliably(output,
+ O_RDWR|O_CREAT, 0640,
+ arg_compress, arg_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;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int init_writer_hashmap(RemoteServer *s) {
+ static const struct hash_ops *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));
+
+ s->writers = hashmap_new(hash_ops[arg_split_mode]);
+ if (!s->writers)
+ return log_oom();
+
+ return 0;
+}
+
+static int get_writer(RemoteServer *s, const char *host,
+ Writer **writer) {
+ const void *key;
+ _cleanup_writer_unref_ Writer *w = NULL;
+ int r;
+
+ switch(arg_split_mode) {
+ case JOURNAL_WRITE_SPLIT_NONE:
+ key = "one and only";
+ break;
+
+ case JOURNAL_WRITE_SPLIT_HOST:
+ assert(host);
+ key = host;
+ break;
+
+ default:
+ assert_not_reached("what split mode?");
+ }
+
+ w = hashmap_get(s->writers, key);
+ if (w)
+ writer_ref(w);
+ else {
+ w = writer_new(s);
+ if (!w)
+ return log_oom();
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
+ w->hashmap_key = strdup(key);
+ if (!w->hashmap_key)
+ return log_oom();
+ }
+
+ r = open_output(w, host);
+ if (r < 0)
+ return r;
+
+ r = hashmap_put(s->writers, w->hashmap_key ?: key, w);
+ if (r < 0)
+ return r;
+ }
+
+ *writer = w;
+ w = NULL;
+ return 0;
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+/* This should go away as soon as µhttpd allows state to be passed around. */
+static RemoteServer *server;
+
+static int dispatch_raw_source_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int dispatch_raw_source_until_block(sd_event_source *event,
+ void *userdata);
+static int dispatch_blocking_source_event(sd_event_source *event,
+ void *userdata);
+static int dispatch_raw_connection_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+static int dispatch_http_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata);
+
+static int get_source_for_fd(RemoteServer *s,
+ int fd, char *name, RemoteSource **source) {
+ Writer *writer;
+ int r;
+
+ /* This takes ownership of name, but only on success. */
+
+ assert(fd >= 0);
+ assert(source);
+
+ if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
+ return log_oom();
+
+ r = get_writer(s, name, &writer);
+ if (r < 0)
+ return log_warning_errno(r, "Failed to get writer for source %s: %m",
+ name);
+
+ if (s->sources[fd] == NULL) {
+ s->sources[fd] = source_new(fd, false, name, writer);
+ if (!s->sources[fd]) {
+ writer_unref(writer);
+ return log_oom();
+ }
+
+ s->active++;
+ }
+
+ *source = s->sources[fd];
+ return 0;
+}
+
+static int remove_source(RemoteServer *s, int fd) {
+ RemoteSource *source;
+
+ assert(s);
+ assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+
+ source = s->sources[fd];
+ if (source) {
+ /* this closes fd too */
+ source_free(source);
+ s->sources[fd] = NULL;
+ s->active--;
+ }
+
+ return 0;
+}
+
+static int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
+
+ RemoteSource *source = NULL;
+ int r;
+
+ /* This takes ownership of name, even on failure, if own_name is true. */
+
+ assert(s);
+ assert(fd >= 0);
+ assert(name);
+
+ if (!own_name) {
+ name = strdup(name);
+ if (!name)
+ return log_oom();
+ }
+
+ r = get_source_for_fd(s, fd, name, &source);
+ if (r < 0) {
+ log_error_errno(r, "Failed to create source for fd:%d (%s): %m",
+ fd, name);
+ free(name);
+ return r;
+ }
+
+ r = sd_event_add_io(s->events, &source->event,
+ fd, EPOLLIN|EPOLLRDHUP|EPOLLPRI,
+ dispatch_raw_source_event, source);
+ if (r == 0) {
+ /* Add additional source for buffer processing. It will be
+ * enabled later. */
+ r = sd_event_add_defer(s->events, &source->buffer_event,
+ dispatch_raw_source_until_block, source);
+ if (r == 0)
+ sd_event_source_set_enabled(source->buffer_event, SD_EVENT_OFF);
+ } else if (r == -EPERM) {
+ log_debug("Falling back to sd_event_add_defer for fd:%d (%s)", fd, name);
+ r = sd_event_add_defer(s->events, &source->event,
+ dispatch_blocking_source_event, source);
+ if (r == 0)
+ sd_event_source_set_enabled(source->event, SD_EVENT_ON);
+ }
+ if (r < 0) {
+ log_error_errno(r, "Failed to register event source for fd:%d: %m",
+ fd);
+ goto error;
+ }
+
+ r = sd_event_source_set_description(source->event, name);
+ if (r < 0) {
+ log_error_errno(r, "Failed to set source name for fd:%d: %m", fd);
+ goto error;
+ }
+
+ return 1; /* work to do */
+
+ error:
+ remove_source(s, fd);
+ return r;
+}
+
+static int add_raw_socket(RemoteServer *s, int fd) {
+ int r;
+ _cleanup_close_ int fd_ = fd;
+ char name[sizeof("raw-socket-")-1 + DECIMAL_STR_MAX(int) + 1];
+
+ assert(fd >= 0);
+
+ r = sd_event_add_io(s->events, &s->listen_event,
+ fd, EPOLLIN,
+ dispatch_raw_connection_event, s);
+ if (r < 0)
+ return r;
+
+ xsprintf(name, "raw-socket-%d", fd);
+
+ r = sd_event_source_set_description(s->listen_event, name);
+ if (r < 0)
+ return r;
+
+ fd_ = -1;
+ s->active++;
+ 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 = push_data(source, 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_REQUEST_ENTITY_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 = source_non_empty(source);
+ 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}};
+ int opts_pos = 4;
+ int flags =
+ MHD_USE_DEBUG |
+ MHD_USE_DUAL_STACK |
+ MHD_USE_EPOLL_LINUX_ONLY |
+ MHD_USE_PEDANTIC_CHECKS |
+ MHD_USE_PIPE_FOR_SHUTDOWN;
+
+ 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);
+
+ 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_SSL;
+
+ 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->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->event, "epoll-fd");
+ 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 dispatch_http_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ MHDDaemonWrapper *d = userdata;
+ int r;
+
+ assert(d);
+
+ r = MHD_run(d->daemon);
+ if (r == MHD_NO) {
+ log_error("MHD_run failed!");
+ // XXX: unregister daemon
+ return -EINVAL;
+ }
+
+ return 1; /* work to do */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int 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 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 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;
+ }
+
+ 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;
+}
+
+static void server_destroy(RemoteServer *s) {
+ size_t i;
+ MHDDaemonWrapper *d;
+
+ while ((d = hashmap_steal_first(s->daemons))) {
+ MHD_stop_daemon(d->daemon);
+ sd_event_source_unref(d->event);
+ free(d);
+ }
+
+ hashmap_free(s->daemons);
+
+ assert(s->sources_size == 0 || s->sources);
+ for (i = 0; i < s->sources_size; i++)
+ remove_source(s, i);
+ free(s->sources);
+
+ writer_unref(s->_single_writer);
+ hashmap_free(s->writers);
+
+ sd_event_source_unref(s->sigterm_event);
+ sd_event_source_unref(s->sigint_event);
+ sd_event_source_unref(s->listen_event);
+ sd_event_unref(s->events);
+
+ /* fds that we're listening on remain open... */
+}
+
+/**********************************************************************
+ **********************************************************************
+ **********************************************************************/
+
+static int handle_raw_source(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ RemoteServer *s) {
+
+ RemoteSource *source;
+ int r;
+
+ /* Returns 1 if there might be more data pending,
+ * 0 if data is currently exhausted, negative on error.
+ */
+
+ assert(fd >= 0 && fd < (ssize_t) s->sources_size);
+ source = s->sources[fd];
+ assert(source->fd == fd);
+
+ r = process_source(source, arg_compress, arg_seal);
+ if (source->state == STATE_EOF) {
+ size_t remaining;
+
+ log_debug("EOF reached with source fd:%d (%s)",
+ source->fd, source->name);
+
+ remaining = source_non_empty(source);
+ if (remaining > 0)
+ log_notice("Premature EOF. %zu bytes lost.", remaining);
+ remove_source(s, source->fd);
+ log_debug("%zu active sources remaining", s->active);
+ return 0;
+ } else if (r == -E2BIG) {
+ log_notice_errno(E2BIG, "Entry too big, skipped");
+ return 1;
+ } else if (r == -EAGAIN) {
+ return 0;
+ } else if (r < 0) {
+ log_debug_errno(r, "Closing connection: %m");
+ remove_source(server, fd);
+ return 0;
+ } else
+ return 1;
+}
+
+static int dispatch_raw_source_until_block(sd_event_source *event,
+ void *userdata) {
+ RemoteSource *source = userdata;
+ int r;
+
+ /* Make sure event stays around even if source is destroyed */
+ sd_event_source_ref(event);
+
+ r = handle_raw_source(event, source->fd, EPOLLIN, server);
+ if (r != 1)
+ /* No more data for now */
+ sd_event_source_set_enabled(event, SD_EVENT_OFF);
+
+ sd_event_source_unref(event);
+
+ return r;
+}
+
+static int dispatch_raw_source_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ RemoteSource *source = userdata;
+ int r;
+
+ assert(source->event);
+ assert(source->buffer_event);
+
+ r = handle_raw_source(event, fd, EPOLLIN, server);
+ if (r == 1)
+ /* Might have more data. We need to rerun the handler
+ * until we are sure the buffer is exhausted. */
+ sd_event_source_set_enabled(source->buffer_event, SD_EVENT_ON);
+
+ return r;
+}
+
+static int dispatch_blocking_source_event(sd_event_source *event,
+ void *userdata) {
+ RemoteSource *source = userdata;
+
+ return handle_raw_source(event, source->fd, EPOLLIN, server);
+}
+
+static int accept_connection(const char* type, int fd,
+ SocketAddress *addr, char **hostname) {
+ int fd2, r;
+
+ log_debug("Accepting new %s connection on fd:%d", type, fd);
+ fd2 = accept4(fd, &addr->sockaddr.sa, &addr->size, SOCK_NONBLOCK|SOCK_CLOEXEC);
+ if (fd2 < 0)
+ return log_error_errno(errno, "accept() on fd:%d failed: %m", fd);
+
+ switch(socket_address_family(addr)) {
+ case AF_INET:
+ case AF_INET6: {
+ _cleanup_free_ char *a = NULL;
+ char *b;
+
+ r = socket_address_print(addr, &a);
+ if (r < 0) {
+ log_error_errno(r, "socket_address_print(): %m");
+ close(fd2);
+ return r;
+ }
+
+ r = socknameinfo_pretty(&addr->sockaddr, addr->size, &b);
+ if (r < 0) {
+ log_error_errno(r, "Resolving hostname failed: %m");
+ close(fd2);
+ return r;
+ }
+
+ log_debug("Accepted %s %s connection from %s",
+ type,
+ socket_address_family(addr) == AF_INET ? "IP" : "IPv6",
+ a);
+
+ *hostname = b;
+
+ return fd2;
+ };
+ default:
+ log_error("Rejected %s connection with unsupported family %d",
+ type, socket_address_family(addr));
+ close(fd2);
+
+ return -EINVAL;
+ }
+}
+
+static int dispatch_raw_connection_event(sd_event_source *event,
+ int fd,
+ uint32_t revents,
+ void *userdata) {
+ RemoteServer *s = userdata;
+ int fd2;
+ SocketAddress addr = {
+ .size = sizeof(union sockaddr_union),
+ .type = SOCK_STREAM,
+ };
+ char *hostname = NULL;
+
+ fd2 = accept_connection("raw", fd, &addr, &hostname);
+ 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,
+ false, 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 {
+#ifdef 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: {
+#ifdef 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;
+ }
+
+ arg_split_mode = JOURNAL_WRITE_SPLIT_NONE;
+ }
+
+ if (arg_split_mode == JOURNAL_WRITE_SPLIT_NONE
+ && arg_output && is_dir(arg_output, true) > 0) {
+ log_error("For SplitMode=none, output must be a file.");
+ 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());
+ 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;
+}
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in
new file mode 100644
index 0000000000..7122d63362
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.in
@@ -0,0 +1,6 @@
+[Remote]
+# Seal=false
+# SplitMode=host
+# ServerKeyFile=@CERTIFICATEROOT@/private/journal-remote.pem
+# ServerCertificateFile=@CERTIFICATEROOT@/certs/journal-remote.pem
+# TrustedCertificateFile=@CERTIFICATEROOT@/ca/trusted.pem
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml
new file mode 100644
index 0000000000..f7ac8c46e0
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.conf.xml
@@ -0,0 +1,120 @@
+<?xml version='1.0'?> <!--*-nxml-*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+ "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2015 Chris Morgan
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="journal-remote.conf" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+ <refentryinfo>
+ <title>journal-remote.conf</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Chris</firstname>
+ <surname>Morgan</surname>
+ <email>chmorgan@gmail.com</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>journal-remote.conf</refentrytitle>
+ <manvolnum>5</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>journal-remote.conf</refname>
+ <refname>journal-remote.conf.d</refname>
+ <refpurpose>Configuration files for the service accepting remote journal uploads</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <para><filename>/etc/systemd/journal-remote.conf</filename></para>
+ <para><filename>/etc/systemd/journal-remote.conf.d/*.conf</filename></para>
+ <para><filename>/run/systemd/journal-remote.conf.d/*.conf</filename></para>
+ <para><filename>/usr/lib/systemd/journal-remote.conf.d/*.conf</filename></para>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>These files configure various parameters of
+ <citerefentry><refentrytitle>systemd-journal-remote.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>.</para>
+ </refsect1>
+
+ <xi:include href="standard-conf.xml" xpointer="main-conf" />
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>All options are configured in the
+ <literal>[Remote]</literal> section:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><varname>Seal=</varname></term>
+
+ <listitem><para>Periodically sign the data in the journal using Forward Secure Sealing.
+ </para></listitem>
+ </varlistentry>
+
+
+ <varlistentry>
+ <term><varname>SplitMode=</varname></term>
+
+ <listitem><para>One of <literal>host</literal> or <literal>none</literal>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>ServerKeyFile=</varname></term>
+
+ <listitem><para>SSL key in PEM format.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>ServerCertificateFile=</varname></term>
+
+ <listitem><para>SSL CA certificate in PEM format.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>TrustedCertificateFile=</varname></term>
+
+ <listitem><para>SSL CA certificate.</para></listitem>
+ </varlistentry>
+
+ </variablelist>
+
+ </refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd-journal-remote</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+
+</refentry>
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h
new file mode 100644
index 0000000000..1c090ccdfc
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/journal-remote.h
@@ -0,0 +1,53 @@
+#pragma once
+
+/***
+ This file is part of systemd.
+
+ Copyright 2014 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+***/
+
+#include <systemd/sd-event.h>
+
+#include "systemd-basic/hashmap.h"
+#include "systemd-microhttpd/microhttpd-util.h"
+
+#include "journal-remote-parse.h"
+#include "journal-remote-write.h"
+
+typedef struct MHDDaemonWrapper MHDDaemonWrapper;
+
+struct MHDDaemonWrapper {
+ uint64_t fd;
+ struct MHD_Daemon *daemon;
+
+ sd_event_source *event;
+};
+
+struct RemoteServer {
+ RemoteSource **sources;
+ size_t sources_size;
+ size_t active;
+
+ sd_event *events;
+ sd_event_source *sigterm_event, *sigint_event, *listen_event;
+
+ Hashmap *writers;
+ Writer *_single_writer;
+ uint64_t event_count;
+
+ bool check_trust;
+ Hashmap *daemons;
+};
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in
new file mode 100644
index 0000000000..753dd6c158
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.service.in
@@ -0,0 +1,30 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Journal Remote Sink Service
+Documentation=man:systemd-journal-remote(8) man:journal-remote.conf(5)
+Requires=systemd-journal-remote.socket
+
+[Service]
+ExecStart=@rootlibexecdir@/systemd-journal-remote --listen-https=-3 --output=/var/log/journal/remote/
+User=systemd-journal-remote
+Group=systemd-journal-remote
+WatchdogSec=3min
+PrivateTmp=yes
+PrivateDevices=yes
+PrivateNetwork=yes
+ProtectSystem=full
+ProtectHome=yes
+ProtectControlGroups=yes
+ProtectKernelTunables=yes
+MemoryDenyWriteExecute=yes
+RestrictRealtime=yes
+RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
+
+[Install]
+Also=systemd-journal-remote.socket
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket
new file mode 100644
index 0000000000..076dcae8a3
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.socket
@@ -0,0 +1,15 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+[Unit]
+Description=Journal Remote Sink Socket
+
+[Socket]
+ListenStream=19532
+
+[Install]
+WantedBy=sockets.target
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers
new file mode 100644
index 0000000000..ca20c24896
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.sysusers
@@ -0,0 +1,8 @@
+# This file is part of systemd.
+#
+# systemd is free software; you can redistribute it and/or modify it
+# under the terms of the GNU Lesser General Public License as published by
+# the Free Software Foundation; either version 2.1 of the License, or
+# (at your option) any later version.
+
+u systemd-journal-remote - "systemd Journal Remote"
diff --git a/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml
new file mode 100644
index 0000000000..ee2d5c2486
--- /dev/null
+++ b/src/grp-journal/grp-remote/systemd-journal-remote/systemd-journal-remote.xml
@@ -0,0 +1,324 @@
+<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*-->
+<!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN"
+"http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd">
+
+<!--
+ This file is part of systemd.
+
+ Copyright 2012 Zbigniew Jędrzejewski-Szmek
+
+ systemd is free software; you can redistribute it and/or modify it
+ under the terms of the GNU Lesser General Public License as published by
+ the Free Software Foundation; either version 2.1 of the License, or
+ (at your option) any later version.
+
+ systemd is distributed in the hope that it will be useful, but
+ WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ Lesser General Public License for more details.
+
+ You should have received a copy of the GNU Lesser General Public License
+ along with systemd; If not, see <http://www.gnu.org/licenses/>.
+-->
+
+<refentry id="systemd-journal-remote" conditional='HAVE_MICROHTTPD'
+ xmlns:xi="http://www.w3.org/2001/XInclude">
+
+ <refentryinfo>
+ <title>systemd-journal-remote</title>
+ <productname>systemd</productname>
+
+ <authorgroup>
+ <author>
+ <contrib>Developer</contrib>
+ <firstname>Zbigniew</firstname>
+ <surname>Jędrzejewski-Szmek</surname>
+ <email>zbyszek@in.waw.pl</email>
+ </author>
+ </authorgroup>
+ </refentryinfo>
+
+ <refmeta>
+ <refentrytitle>systemd-journal-remote</refentrytitle>
+ <manvolnum>8</manvolnum>
+ </refmeta>
+
+ <refnamediv>
+ <refname>systemd-journal-remote</refname>
+ <refpurpose>Receive journal messages over the network</refpurpose>
+ </refnamediv>
+
+ <refsynopsisdiv>
+ <cmdsynopsis>
+ <command>systemd-journal-remote</command>
+ <arg choice="opt" rep="repeat">OPTIONS</arg>
+ <arg choice="opt" rep="norepeat">-o/--output=<replaceable>DIR</replaceable>|<replaceable>FILE</replaceable></arg>
+ <arg choice="opt" rep="repeat">SOURCES</arg>
+ </cmdsynopsis>
+ </refsynopsisdiv>
+
+ <refsect1>
+ <title>Description</title>
+
+ <para>
+ <filename>systemd-journal-remote</filename> is a command to
+ receive serialized journal events and store them to the journal.
+ Input streams are in the
+ <ulink url="http://www.freedesktop.org/wiki/Software/systemd/export">
+ Journal Export Format
+ </ulink>,
+ i.e. like the output from
+ <command>journalctl --output=export</command>. For transport over
+ the network, this serialized stream is usually carried over an
+ HTTPS connection.
+ </para>
+ </refsect1>
+
+ <refsect1>
+ <title>Sources</title>
+
+ <para>
+ Sources can be either "active"
+ (<command>systemd-journal-remote</command> requests and pulls
+ the data), or "passive"
+ (<command>systemd-journal-remote</command> waits for a
+ connection and then receives events pushed by the other side).
+ </para>
+
+ <para>
+ <command>systemd-journal-remote</command> can read more than one
+ event stream at a time. They will be interleaved in the output
+ file. In case of "active" connections, each "source" is one
+ stream, and in case of "passive" connections, each connection can
+ result in a separate stream. Sockets can be configured in
+ "accept" mode (i.e. only one connection), or "listen" mode (i.e.
+ multiple connections, each resulting in a stream).
+ </para>
+
+ <para>
+ When there are no more connections, and no more can be created
+ (there are no listening sockets), then
+ <command>systemd-journal-remote</command> will exit.
+ </para>
+
+ <para>Active sources can be specified in the following
+ ways:</para>
+
+ <variablelist>
+ <varlistentry>
+ <listitem><para>When <option>-</option> is given as a
+ positional argument, events will be read from standard input.
+ Other positional arguments will be treated as filenames
+ to open and read from.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--url=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para>With the
+ <option>--url=<replaceable>ADDRESS</replaceable></option> option,
+ events will be retrieved using HTTP from
+ <replaceable>ADDRESS</replaceable>. This URL should refer to the
+ root of a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance, e.g. http://some.host:19531/ or
+ https://some.host:19531/.</para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>Passive sources can be specified in the following
+ ways:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--listen-raw=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para><replaceable>ADDRESS</replaceable> must be an
+ address suitable for <option>ListenStream=</option> (cf.
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ <command>systemd-journal-remote</command> will listen on this
+ socket for connections. Each connection is expected to be a
+ stream of journal events.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--listen-http=<replaceable>ADDRESS</replaceable></option></term>
+ <term><option>--listen-https=<replaceable>ADDRESS</replaceable></option></term>
+
+ <listitem><para><replaceable>ADDRESS</replaceable> must be
+ either a negative integer, in which case it will be
+ interpreted as the (negated) file descriptor number, or an
+ address suitable for <option>ListenStream=</option> (c.f.
+ <citerefentry><refentrytitle>systemd.socket</refentrytitle><manvolnum>5</manvolnum></citerefentry>).
+ In the first case, matching file descriptor must be inherited
+ through
+ <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>.
+ In the second case, an HTTP or HTTPS server will be spawned on
+ this port, respectively for <option>--listen-http</option> and
+ <option>--listen-https</option>. Currently, only POST requests
+ to <filename>/upload</filename> with <literal>Content-Type:
+ application/vnd.fdo.journal</literal> are supported.</para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><varname>$LISTEN_FDS</varname></term>
+
+ <listitem><para><command>systemd-journal-remote</command>
+ supports the
+ <varname>$LISTEN_FDS</varname>/<varname>$LISTEN_PID</varname>
+ protocol. Open sockets inherited through socket activation
+ behave like those opened with <option>--listen-raw=</option>
+ described above, unless they are specified as an argument in
+ <option>--listen-http=-<replaceable>n</replaceable></option>
+ or
+ <option>--listen-https=-<replaceable>n</replaceable></option>
+ above. In the latter case, an HTTP or HTTPS server will be
+ spawned using this descriptor and connections must be made
+ over the HTTP protocol.</para>
+ </listitem>
+ </varlistentry>
+
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Sinks</title>
+
+ <para>The location of the output journal can be specified
+ with <option>-o</option> or <option>--output=</option>. For "active"
+ sources, this option is required.
+ </para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--output=<replaceable>FILE</replaceable></option></term>
+
+ <listitem><para>Will write to this journal file. The filename
+ must end with <filename>.journal</filename>. The file will be
+ created if it does not exist. If necessary (journal file full,
+ or corrupted), the file will be renamed following normal
+ journald rules and a new journal file will be created in its
+ stead.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--output=<replaceable>DIR</replaceable></option></term>
+
+ <listitem><para>Will create journal files underneath directory
+ <replaceable>DIR</replaceable>. The directory must exist. If
+ necessary (journal files over size, or corrupted), journal
+ files will be rotated following normal journald rules. Names
+ of files underneath <replaceable>DIR</replaceable> will be
+ generated using the rules described below.</para></listitem>
+ </varlistentry>
+ </variablelist>
+
+ <para>If <option>--output=</option> is not used, the output
+ directory <filename>/var/log/journal/remote/</filename> will be
+ used. In case the output file is not specified, journal files
+ will be created underneath the selected directory. Files will be
+ called
+ <filename>remote-<replaceable>hostname</replaceable>.journal</filename>,
+ where the <replaceable>hostname</replaceable> part is the
+ escaped hostname of the source endpoint of the connection, or the
+ numerical address if the hostname cannot be determined.</para>
+
+ <para>In case of "active" sources, the output file name must
+ always be given explicitly.</para>
+ </refsect1>
+
+ <refsect1>
+ <title>Options</title>
+
+ <para>The following options are understood:</para>
+
+ <variablelist>
+ <varlistentry>
+ <term><option>--split-mode</option></term>
+
+ <listitem><para>One of <constant>none</constant> or
+ <constant>host</constant>. For the first, only one output
+ journal file is used. For the latter, a separate output file
+ is used, based on the hostname of the other endpoint of a
+ connection.</para>
+
+ <para>In case of "active" sources, the output file name must
+ always be given explicitly and only <constant>none</constant>
+ is allowed.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--compress</option> [<replaceable>BOOL</replaceable>]</term>
+
+ <listitem><para>If this is set to <literal>yes</literal> then compress
+ the data in the journal using XZ. The default is <literal>yes</literal>.
+ </para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--seal</option> [<replaceable>BOOL</replaceable>]</term>
+
+ <listitem><para>If this is set to <literal>yes</literal> then
+ periodically sign the data in the journal using Forward Secure Sealing.
+ The default is <literal>no</literal>.</para></listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term><option>--getter=<replaceable>PROG --option1 --option2</replaceable></option></term>
+
+ <listitem><para>Program to invoke to retrieve data. The journal
+ event stream must be generated on standard output.</para>
+
+ <para>Examples:</para>
+
+ <programlisting>--getter='curl "-HAccept: application/vnd.fdo.journal" https://some.host:19531/'</programlisting>
+
+ <programlisting>--getter='wget --header="Accept: application/vnd.fdo.journal" -O- https://some.host:19531/'</programlisting>
+ </listitem>
+ </varlistentry>
+
+ <xi:include href="standard-options.xml" xpointer="help" />
+ <xi:include href="standard-options.xml" xpointer="version" />
+ </variablelist>
+ </refsect1>
+
+ <refsect1>
+ <title>Examples</title>
+ <para>Copy local journal events to a different journal directory:
+ <programlisting>
+journalctl -o export | systemd-journal-remote -o /tmp/dir -
+ </programlisting>
+ </para>
+
+ <para>Retrieve all available events from a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance and store them in
+ <filename>/var/log/journal/remote/remote-some.host.journal</filename>:
+ <programlisting>
+systemd-journal-remote --url http://some.host:19531/
+ </programlisting>
+ </para>
+
+ <para>Retrieve current boot events and wait for new events from a remote
+ <citerefentry><refentrytitle>systemd-journal-gatewayd</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ instance, and store them in
+ <filename>/var/log/journal/remote/remote-some.host.journal</filename>:
+ <programlisting>
+systemd-journal-remote --url http://some.host:19531/entries?boot&amp;follow
+ </programlisting>
+ </para>
+</refsect1>
+
+ <refsect1>
+ <title>See Also</title>
+ <para>
+ <citerefentry><refentrytitle>systemd-journal-upload</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>journalctl</refentrytitle><manvolnum>1</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journald.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>,
+ <citerefentry><refentrytitle>systemd-journal-gatewayd.service</refentrytitle><manvolnum>8</manvolnum></citerefentry>
+ <citerefentry><refentrytitle>journal-remote.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>
+ </para>
+ </refsect1>
+</refentry>