journal-remote.c revision 7a855149eaf6dbd336d9defab5b4a9c70b75d5e6
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen This file is part of systemd.
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen Copyright 2012 Zbigniew Jędrzejewski-Szmek
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen systemd is free software; you can redistribute it and/or modify it
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen under the terms of the GNU Lesser General Public License as published by
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen the Free Software Foundation; either version 2.1 of the License, or
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen (at your option) any later version.
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen systemd is distributed in the hope that it will be useful, but
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen Lesser General Public License for more details.
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen You should have received a copy of the GNU Lesser General Public License
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
97f2d76d4f4dfab8b0629c09926a05a1e5621125Tom Gundersen#define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-remote.pem"
2ad8416dd057e7e3185169609ca3006e7649f576Zbigniew Jędrzejewski-Szmek#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-remote.pem"
2ad8416dd057e7e3185169609ca3006e7649f576Zbigniew Jędrzejewski-Szmek#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
2ad8416dd057e7e3185169609ca3006e7649f576Zbigniew Jędrzejewski-Szmekstatic char* arg_listen_raw = NULL;
2ad8416dd057e7e3185169609ca3006e7649f576Zbigniew Jędrzejewski-Szmekstatic char* arg_listen_http = NULL;
2ad8416dd057e7e3185169609ca3006e7649f576Zbigniew Jędrzejewski-Szmekstatic char* arg_listen_https = NULL;
5b9d4dc05560ddda89e48b6b39365824b15e1300Tom Gundersenstatic int arg_compress = true;
5b9d4dc05560ddda89e48b6b39365824b15e1300Tom Gundersenstatic int arg_seal = false;
5b9d4dc05560ddda89e48b6b39365824b15e1300Tom Gundersenstatic int http_socket = -1, https_socket = -1;
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersenstatic JournalWriteSplitMode arg_split_mode = JOURNAL_WRITE_SPLIT_HOST;
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersenstatic bool arg_trust_all = false;
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen/**********************************************************************
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen **********************************************************************
977085794d2996320e345433403de75f662b0622Tom Gundersen **********************************************************************/
f61942250a43a123580d7bbe5d7873dc5118ed97Tom Gundersenstatic int spawn_child(const char* child, char** argv) {
d2df0d0ed3a88e491405b403e6022e6619750130Tom Gundersen /* In the child */
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_error("Failed to dup pipe to stdout: %m");
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen /* Make sure the child goes away when the parent dies */
03e334a1c7dc8c20c38902aa039440763acc9b17Lennart Poettering if (prctl(PR_SET_PDEATHSIG, SIGTERM) < 0)
43b3a5ef61859f06cdbaf26765cab8e1adac4296Tom Gundersen /* Check whether our parent died before we were able
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen * to set the death signal */
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_error("Failed to exec child %s: %m", child);
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_warning("Failed to close write end of pipe: %m");
ed88bcfb7c15029f9fc95ee2380759a9eb782d46Zbigniew Jędrzejewski-Szmek log_error("Failed to spawn curl: %m");
ecb08ec6a5c52f2d940f3b8147e2a480affd46e1Zbigniew Jędrzejewski-Szmekstatic int spawn_getter(const char *getter, const char *url) {
e9f3d2d508bfd9fb5b54e82994bda365a71eb864Zbigniew Jędrzejewski-Szmek log_error("Failed to split getter option: %s", strerror(-r));
98a375f6d5cac24eb80d6d4e00699851324afdecTom Gundersen log_error("Failed to create command line: %s", strerror(-r));
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_error("Failed to spawn getter %s: %m", getter);
f8a0bb5285024b6ce372c3157e761e6543ebdcd2Andreas Henriksson#define filename_escape(s) xescape((s), "/ ")
f61942250a43a123580d7bbe5d7873dc5118ed97Tom Gundersenstatic int open_output(Writer *w, const char* host) {
b5884878a2874447b2a9f07f324a7cd909d96d48Lennart Poettering output = arg_output ?: REMOTE_JOURNAL_PATH "/remote.journal";
a2a5291b3f5ab6ed4c92f51d0fd10a03047380d8Zbigniew Jędrzejewski-Szmek case JOURNAL_WRITE_SPLIT_HOST: {
edf029b7fd9a5853a87d3ca99aac2922bb8a277eTom Gundersen r = asprintf(&_output, "%s/remote-%s.journal",
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_error("Failed to open output journal %s: %s",
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen log_info("Opened output file %s", w->journal->path);
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen/**********************************************************************
464cf22f17e0cf2d8bfa6d72b5e7a662d634f149Tom Gundersen **********************************************************************
464cf22f17e0cf2d8bfa6d72b5e7a662d634f149Tom Gundersen **********************************************************************/
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersenstatic int init_writer_hashmap(RemoteServer *s) {
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen static const struct {
edbb03e95a3c31bf719d5c6c46eec14d0bcb9c8fTom Gundersen [JOURNAL_WRITE_SPLIT_NONE] = {trivial_hash_func,
eb7040ec50fbfe5aad9eaf305bd442a4a235abaaTom Gundersen [JOURNAL_WRITE_SPLIT_HOST] = {string_hash_func,
b3e013148603aa670bc2c060ac63d48e54d76fc2Tom Gundersen assert(arg_split_mode >= 0 && arg_split_mode < (int) ELEMENTSOF(functions));
be32eb9b7fbcb22e4b648086d644135e38279633Tom Gundersen s->writers = hashmap_new(functions[arg_split_mode].hash_func,
be32eb9b7fbcb22e4b648086d644135e38279633Tom Gundersenstatic int get_writer(RemoteServer *s, const char *host,
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen const void *key;
04b67d49254d956d31bcfe80340fb9df7ed332d3Tom Gundersen if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
04b67d49254d956d31bcfe80340fb9df7ed332d3Tom Gundersen r = hashmap_put(s->writers, w->hashmap_key ?: key, w);
3c9b886068d99e5d3cbabcac32a4decf37244c54Tom Gundersen/**********************************************************************
3c9b886068d99e5d3cbabcac32a4decf37244c54Tom Gundersen **********************************************************************
04b67d49254d956d31bcfe80340fb9df7ed332d3Tom Gundersen **********************************************************************/
16b9b87aeee9353b5b8dae6089a69752422a5b09Tom Gundersen/* This should go away as soon as µhttpd allows state to be passed around. */
9bf3b53533cdc9b95c921b71da755401f223f765Lennart Poetteringstatic int dispatch_raw_source_event(sd_event_source *event,
16b9b87aeee9353b5b8dae6089a69752422a5b09Tom Gundersenstatic int dispatch_raw_connection_event(sd_event_source *event,
16b9b87aeee9353b5b8dae6089a69752422a5b09Tom Gundersenstatic int dispatch_http_event(sd_event_source *event,
16b9b87aeee9353b5b8dae6089a69752422a5b09Tom Gundersen if (!GREEDY_REALLOC0(s->sources, s->sources_size, fd + 1))
5fde13d748749f0e06e2e6cdd15f0980a79ea82cTom Gundersen log_warning("Failed to get writer for source %s: %s",
3e137a1b9a0eac2bf43d493d3302c3c959b6ccdbTom Gundersen s->sources[fd] = source_new(fd, false, name, writer);
755bde375f4db393ad06e73340bfcf4d0cf91bb2Lennart Poetteringstatic int remove_source(RemoteServer *s, int fd) {
755bde375f4db393ad06e73340bfcf4d0cf91bb2Lennart Poettering assert(fd >= 0 && fd < (ssize_t) s->sources_size);
43b3a5ef61859f06cdbaf26765cab8e1adac4296Tom Gundersen /* this closes fd too */
68ba38770640413b4fa06773447666eb88a38d4cTom Gundersenstatic int add_source(RemoteServer *s, int fd, char* name, bool own_name) {
5fde13d748749f0e06e2e6cdd15f0980a79ea82cTom Gundersen log_error("Failed to create source for fd:%d (%s): %s",
5fde13d748749f0e06e2e6cdd15f0980a79ea82cTom Gundersen r = sd_event_add_io(s->events, &source->event,
04b67d49254d956d31bcfe80340fb9df7ed332d3Tom Gundersen log_error("Failed to register event source for fd:%d: %s",
a669ea9860900d5cdebbc4cb9aaea72db7e28a02Tom Gundersenstatic int add_raw_socket(RemoteServer *s, int fd) {
5fde13d748749f0e06e2e6cdd15f0980a79ea82cTom Gundersen r = sd_event_add_io(s->events, &s->listen_event,
5fde13d748749f0e06e2e6cdd15f0980a79ea82cTom Gundersenstatic int setup_raw_socket(RemoteServer *s, const char *address) {
16b9b87aeee9353b5b8dae6089a69752422a5b09Tom Gundersen fd = make_socket_fd(LOG_INFO, address, SOCK_STREAM | SOCK_CLOEXEC);
d95b83b87d7d7c50e550f7128827f73a321c8934Tom Gundersen/**********************************************************************
d95b83b87d7d7c50e550f7128827f73a321c8934Tom Gundersen **********************************************************************
af6f0d422c521374ee6a2dd92df5935a5a476ae5Tom Gundersen **********************************************************************/
be32eb9b7fbcb22e4b648086d644135e38279633Tom Gundersenstatic RemoteSource *request_meta(void **connection_cls, int fd, char *hostname) {
847a8a5fed4d265dfa659917515c6f9bd1b8d5c4Tom Gundersen log_warning("Failed to get writer for source %s: %s",
847a8a5fed4d265dfa659917515c6f9bd1b8d5c4Tom Gundersen source = source_new(fd, true, hostname, writer);
be32eb9b7fbcb22e4b648086d644135e38279633Tom Gundersen log_debug("Added RemoteSource as connection metadata %p", source);
464cf22f17e0cf2d8bfa6d72b5e7a662d634f149Tom Gundersen log_debug("Cleaning up connection metadata %p", s);
static int process_http_upload(
const char *upload_data,
bool finished = false;
if (*upload_data_size) {
*upload_data_size = 0;
finished = true;
if (r == -E2BIG)
if (!finished)
return MHD_YES;
if (remaining > 0) {
static int request_handler(
void *cls,
const char *url,
const char *method,
const char *version,
const char *upload_data,
void **connection_cls) {
const char *header;
if (*connection_cls)
"Content-Type: application/vnd.fdo.journal"
if (!ci) {
return code;
return MHD_YES;
int fd,
const char *key,
const char *cert,
const char *trust) {
{ MHD_OPTION_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END}};
int flags =
int r, epoll_fd;
MHDDaemonWrapper *d;
if (key) {
if (trust)
return log_oom();
if (!d->daemon) {
r = -EINVAL;
goto error;
if (!info) {
r = -ENOTSUP;
goto error;
if (epoll_fd < 0) {
r = -EUCLEAN;
goto error;
dispatch_http_event, d);
goto error;
log_oom();
goto error;
goto error;
s->active ++;
free(d);
const char *address,
const char *key,
const char *cert,
const char *trust) {
int fd;
if (fd < 0)
return fd;
int fd,
void *userdata) {
assert(d);
if (r == MHD_NO) {
return -EINVAL;
void *userdata) {
assert(s);
assert(s);
int fd, r;
if (fd < 0)
return -EINVAL;
return fd;
const char* key,
const char* cert,
const char* trust) {
int r, n, fd;
char **file;
assert(s);
return -EINVAL;
setup_signals(s);
server = s;
n = sd_listen_fds(true);
strerror(-n));
return -EBADFD;
char *hostname;
return -EINVAL;
if (arg_url) {
if (arg_getter) {
if (fd < 0)
return fd;
hostname =
if (arg_listen_raw) {
if (arg_listen_http) {
if (arg_listen_https) {
const char *output_name;
if (fd < 0) {
return -errno;
if (s->active == 0) {
return -EINVAL;
r = init_writer_hashmap(s);
size_t i;
MHDDaemonWrapper *d;
free(d);
for (i = 0; i < s->sources_size; i++)
remove_source(s, i);
int fd,
void *userdata) {
if (remaining > 0)
} else if (r == -E2BIG) {
} else if (r == -EAGAIN) {
int fd2, r;
if (fd2 < 0) {
return -errno;
case AF_INET:
case AF_INET6: {
type,
*hostname = b;
return fd2;
return -EINVAL;
int fd,
void *userdata) {
int fd2, r;
char *hostname;
if (fd2 < 0)
return fd2;
static int parse_config(void) {
false, false, true, NULL);
static void help(void) {
help();
case ARG_VERSION:
case ARG_URL:
if (arg_url) {
return -EINVAL;
case ARG_GETTER:
if (arg_getter) {
return -EINVAL;
case ARG_LISTEN_RAW:
if (arg_listen_raw) {
return -EINVAL;
case ARG_LISTEN_HTTP:
return -EINVAL;
http_socket = r;
case ARG_LISTEN_HTTPS:
return -EINVAL;
https_socket = r;
case ARG_KEY:
if (arg_key) {
return -EINVAL;
if (!arg_key)
return log_oom();
case ARG_CERT:
if (arg_cert) {
return -EINVAL;
if (!arg_cert)
return log_oom();
case ARG_TRUST:
return -EINVAL;
arg_trust_all = true;
#ifdef HAVE_GNUTLS
if (!arg_trust)
return log_oom();
return -EINVAL;
if (arg_output) {
return -EINVAL;
case ARG_SPLIT_MODE:
return -EINVAL;
case ARG_COMPRESS:
if (optarg) {
return -EINVAL;
arg_compress = !!r;
arg_compress = true;
case ARG_SEAL:
if (optarg) {
return -EINVAL;
arg_seal = !!r;
arg_seal = true;
case ARG_GNUTLS_LOG: {
#ifdef HAVE_GNUTLS
char *cat;
if (!cat)
return log_oom();
return log_oom();
return -EINVAL;
return -EINVAL;
|| sd_listen_fds(false) > 0;
return -EINVAL;
if (type_a) {
if (!arg_output) {
return -EINVAL;
return -EINVAL;
return -EINVAL;
if (arg_trust_all)
#ifdef HAVE_GNUTLS
char **cat;
if (categories)
RemoteServer s = {};
log_show_color(true);
r = parse_config();
return EXIT_FAILURE;
return EXIT_FAILURE;
return EXIT_FAILURE;
return EXIT_FAILURE;
sd_notify(false,
while (s.active) {
if (r == SD_EVENT_FINISHED)
server_destroy(&s);