journal-remote.c revision 07630cea1f3a845c09309f197ac7c4f11edd3b62
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Zbigniew Jędrzejewski-Szmek
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 <unistd.h>
#ifdef HAVE_GNUTLS
#endif
#include "sd-daemon.h"
#include "conf-parser.h"
#include "escape.h"
#include "fileio.h"
#include "journal-file.h"
#include "journal-remote-write.h"
#include "journald-native.h"
#include "macro.h"
#include "signal-util.h"
#include "socket-util.h"
#include "string-util.h"
#include "strv.h"
#include "journal-remote.h"
#define REMOTE_JOURNAL_PATH "/var/log/journal/remote"
static char* arg_getter = NULL;
static char* arg_listen_raw = NULL;
static char* arg_listen_http = NULL;
static char* arg_listen_https = NULL;
static int arg_compress = true;
static int arg_seal = false;
static char** arg_gnutls_log = NULL;
static char* arg_output = NULL;
static bool arg_trust_all = false;
/**********************************************************************
**********************************************************************
**********************************************************************/
int fd[2];
int r;
parent_pid = getpid();
if (child_pid < 0) {
return r;
}
/* In the child */
if (child_pid == 0) {
(void) reset_all_signal_handlers();
(void) reset_signal_mask();
if (r < 0) {
}
/* Make sure the child goes away when the parent dies */
/* Check whether our parent died before we were able
* to set the death signal */
if (getppid() != parent_pid)
}
if (r < 0)
return fd[0];
}
static int spawn_curl(const char* url) {
"-HAccept: application/vnd.fdo.journal",
"--silent",
"--show-error",
url);
int r;
if (r < 0)
return r;
}
int r;
if (r < 0)
return log_error_errno(r, "Failed to split getter option: %m");
if (r < 0)
return log_error_errno(r, "Failed to create command line: %m");
if (r < 0)
return r;
}
const char *output;
int r;
switch (arg_split_mode) {
case JOURNAL_WRITE_SPLIT_NONE:
break;
case JOURNAL_WRITE_SPLIT_HOST: {
_cleanup_free_ char *name;
if (!name)
return log_oom();
name);
if (r < 0)
return log_oom();
break;
}
default:
assert_not_reached("what?");
}
&w->metrics,
w->mmap,
if (r < 0)
log_error_errno(r, "Failed to open output journal %s: %m",
output);
else
return r;
}
/**********************************************************************
**********************************************************************
**********************************************************************/
static int init_writer_hashmap(RemoteServer *s) {
};
if (!s->writers)
return log_oom();
return 0;
}
const void *key;
int r;
switch(arg_split_mode) {
case JOURNAL_WRITE_SPLIT_NONE:
key = "one and only";
break;
case JOURNAL_WRITE_SPLIT_HOST:
break;
default:
assert_not_reached("what split mode?");
}
if (w)
writer_ref(w);
else {
w = writer_new(s);
if (!w)
return log_oom();
if (arg_split_mode == JOURNAL_WRITE_SPLIT_HOST) {
if (!w->hashmap_key)
return log_oom();
}
r = open_output(w, host);
if (r < 0)
return r;
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;
int fd,
void *userdata);
void *userdata);
void *userdata);
int fd,
void *userdata);
int fd,
void *userdata);
static int get_source_for_fd(RemoteServer *s,
int r;
/* This takes ownership of name, but only on success. */
return log_oom();
if (r < 0)
return log_warning_errno(r, "Failed to get writer for source %s: %m",
name);
return log_oom();
}
s->active++;
}
return 0;
}
assert(s);
if (source) {
/* this closes fd too */
s->active--;
}
return 0;
}
int r;
/* This takes ownership of name, even on failure, if own_name is true. */
assert(s);
if (!own_name) {
if (!name)
return log_oom();
}
if (r < 0) {
log_error_errno(r, "Failed to create source for fd:%d (%s): %m",
return r;
}
if (r == 0) {
/* Add additional source for buffer processing. It will be
* enabled later. */
if (r == 0)
} else if (r == -EPERM) {
if (r == 0)
}
if (r < 0) {
log_error_errno(r, "Failed to register event source for fd:%d: %m",
fd);
goto error;
}
if (r < 0) {
goto error;
}
return 1; /* work to do */
remove_source(s, fd);
return r;
}
int r;
if (r < 0)
return r;
if (r < 0)
return r;
fd_ = -1;
s->active ++;
return 0;
}
int fd;
if (fd < 0)
return fd;
return add_raw_socket(s, fd);
}
/**********************************************************************
**********************************************************************
**********************************************************************/
int r;
if (*connection_cls)
return 0;
if (r < 0)
return log_warning_errno(r, "Failed to get writer for source %s: %m",
hostname);
if (!source) {
return log_oom();
}
*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;
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,
RemoteSource *source) {
bool finished = false;
int r;
log_trace("%s: connection %p, %zu bytes",
if (*upload_data_size) {
if (r < 0)
return mhd_respond_oom(connection);
*upload_data_size = 0;
} else
finished = true;
for (;;) {
if (r == -EAGAIN)
break;
else if (r < 0) {
if (r == -E2BIG)
return mhd_respondf(connection,
"Entry is too large, maximum is %u bytes.\n",
else
return mhd_respondf(connection,
"Processing failed: %s.", strerror(-r));
}
}
if (!finished)
return MHD_YES;
/* The upload is finished */
if (remaining > 0) {
"Premature EOF. %zu bytes of trailing data not processed.",
}
};
static int request_handler(
void *cls,
struct MHD_Connection *connection,
const char *url,
const char *method,
const char *version,
const char *upload_data,
void **connection_cls) {
const char *header;
if (*connection_cls)
return process_http_upload(connection,
"Unsupported method.\n");
"Not found.\n");
MHD_HEADER_KIND, "Content-Type");
"Content-Type: application/vnd.fdo.journal"
" is required.\n");
{
const union MHD_ConnectionInfo *ci;
if (!ci) {
log_error("MHD_get_connection_info failed: cannot get remote fd");
"Cannot check remote address");
}
}
if (server->check_trust) {
if (r < 0)
return code;
} else {
if (r < 0)
"Cannot check remote hostname");
}
if (r == -ENOMEM)
return respond_oom(connection);
else if (r < 0)
strerror(-r));
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_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END},
{ MHD_OPTION_END}};
int opts_pos = 3;
int flags =
const union MHD_DaemonInfo *info;
int r, epoll_fd;
MHDDaemonWrapper *d;
r = fd_nonblock(fd, true);
if (r < 0)
if (key) {
{MHD_OPTION_HTTPS_MEM_KEY, 0, (char*) key};
{MHD_OPTION_HTTPS_MEM_CERT, 0, (char*) cert};
flags |= MHD_USE_SSL;
if (trust)
{MHD_OPTION_HTTPS_MEM_TRUST, 0, (char*) trust};
}
if (!d)
return log_oom();
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)",
if (!info) {
log_error("µhttp returned NULL daemon info");
r = -EOPNOTSUPP;
goto error;
}
if (epoll_fd < 0) {
log_error("µhttp epoll fd is invalid");
r = -EUCLEAN;
goto error;
}
dispatch_http_event, d);
if (r < 0) {
log_error_errno(r, "Failed to add event callback: %m");
goto error;
}
if (r < 0) {
log_error_errno(r, "Failed to set source name: %m");
goto error;
}
if (r < 0) {
log_oom();
goto error;
}
if (r < 0) {
log_error_errno(r, "Failed to add daemon to hashmap: %m");
goto error;
}
s->active ++;
return 0;
MHD_stop_daemon(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;
if (fd < 0)
return fd;
}
int fd,
void *userdata) {
MHDDaemonWrapper *d = userdata;
int r;
assert(d);
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);
if (r < 0)
return r;
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;
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);
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);
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);
log_error("Received fewer sockets than expected");
return -EBADFD;
}
if (fd == http_socket)
else if (fd == https_socket)
else
r = add_raw_socket(s, fd);
char *hostname;
if (r < 0)
return log_error_errno(r, "Failed to retrieve remote name: %m");
} else {
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to register socket (fd:%d): %m",
fd);
}
if (arg_url) {
if (arg_getter) {
} else {
}
if (fd < 0)
return fd;
hostname =
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) {
if (r < 0)
return r;
}
if (arg_listen_https) {
if (r < 0)
return r;
}
const char *output_name;
log_debug("Using standard input as source.");
fd = STDIN_FILENO;
output_name = "stdin";
} else {
if (fd < 0)
output_name = *file;
}
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. */
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);
free(d);
}
hashmap_free(s->daemons);
for (i = 0; i < s->sources_size; i++)
remove_source(s, i);
hashmap_free(s->writers);
sd_event_unref(s->events);
/* fds that we're listening on remain open... */
}
/**********************************************************************
**********************************************************************
**********************************************************************/
int fd,
RemoteServer *s) {
int r;
/* Returns 1 if there might be more data pending,
* 0 if data is currently exhausted, negative on error.
*/
log_debug("EOF reached with source fd:%d (%s)",
if (remaining > 0)
return 0;
} else if (r == -E2BIG) {
return 1;
} else if (r == -EAGAIN) {
return 0;
} else if (r < 0) {
log_debug_errno(r, "Closing connection: %m");
return 0;
} else
return 1;
}
void *userdata) {
int r;
/* Make sure event stays around even if source is destroyed */
if (r != 1)
/* No more data for now */
return r;
}
int fd,
void *userdata) {
int r;
if (r == 1)
/* Might have more data. We need to rerun the handler
* until we are sure the buffer is exhausted. */
return r;
}
void *userdata) {
}
int fd2, r;
if (fd2 < 0)
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");
return r;
}
if (r < 0) {
log_error_errno(r, "Resolving hostname failed: %m");
return r;
}
log_debug("Accepted %s %s connection from %s",
type,
a);
*hostname = b;
return fd2;
};
default:
log_error("Rejected %s connection with unsupported family %d",
return -EINVAL;
}
}
int fd,
void *userdata) {
RemoteServer *s = userdata;
int fd2;
SocketAddress addr = {
.size = sizeof(union sockaddr_union),
.type = SOCK_STREAM,
};
if (fd2 < 0)
return fd2;
}
/**********************************************************************
**********************************************************************
**********************************************************************/
static const char* const journal_write_split_mode_table[_JOURNAL_WRITE_SPLIT_MAX] = {
[JOURNAL_WRITE_SPLIT_NONE] = "none",
[JOURNAL_WRITE_SPLIT_HOST] = "host",
};
"Failed to parse split mode setting");
static int parse_config(void) {
const ConfigTableItem 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"
" --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"
" --cert=FILENAME SSL certificate in PEM format (default:\n"
" --trust=FILENAME|all SSL CA certificate or disable checking (default:\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"
}
enum {
ARG_VERSION = 0x100,
};
{}
};
int c, r;
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;
}
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;
}
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
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
break;
case ARG_KEY:
if (arg_key) {
log_error("Key file specified twice");
return -EINVAL;
}
if (!arg_key)
return log_oom();
break;
case ARG_CERT:
if (arg_cert) {
log_error("Certificate file specified twice");
return -EINVAL;
}
if (!arg_cert)
return log_oom();
break;
case ARG_TRUST:
if (arg_trust || arg_trust_all) {
log_error("Confusing trusted CA configuration");
return -EINVAL;
}
arg_trust_all = true;
else {
#ifdef HAVE_GNUTLS
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:
if (arg_split_mode == _JOURNAL_WRITE_SPLIT_INVALID) {
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
char *cat;
if (!cat)
return log_oom();
return log_oom();
}
break;
#else
log_error("Option --gnutls-log is not available.");
return -EINVAL;
#endif
}
case '?':
return -EINVAL;
default:
assert_not_reached("Unknown option code.");
}
|| sd_listen_fds(false) > 0;
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;
}
}
log_error("For SplitMode=none, output must be a file.");
return -EINVAL;
}
log_error("For SplitMode=host, output must be a directory.");
return -EINVAL;
}
log_debug("Full config: SplitMode=%s Key=%s Cert=%s Trust=%s",
return 1 /* work to do */;
}
int r;
if (r < 0)
return log_error_errno(r, "Failed to read key from file '%s': %m",
arg_key ?: PRIV_KEY_FILE);
if (r < 0)
return log_error_errno(r, "Failed to read certificate from file '%s': %m",
if (arg_trust_all)
log_info("Certificate checking disabled.");
else {
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file '%s': %m",
arg_trust ?: TRUST_FILE);
}
return 0;
}
RemoteServer s = {};
int r;
log_show_color(true);
r = parse_config();
if (r < 0)
return EXIT_FAILURE;
if (r <= 0)
return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
if (arg_listen_http || arg_listen_https) {
if (r < 0)
return EXIT_FAILURE;
}
if (arg_listen_https || https_socket >= 0)
return EXIT_FAILURE;
return EXIT_FAILURE;
r = sd_event_set_watchdog(s.events, true);
if (r < 0)
log_error_errno(r, "Failed to enable watchdog: %m");
else
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;
if (r < 0) {
log_error_errno(r, "Failed to run event loop: %m");
break;
}
}
sd_notifyf(false,
"STOPPING=1\n"
server_destroy(&s);
return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;
}