journal-upload.c revision 2cf4172a71860c6e44edd27a3b68047ae062d7fc
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering This file is part of systemd.
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering Copyright 2014 Zbigniew Jędrzejewski-Szmek
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering systemd is free software; you can redistribute it and/or modify it
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering under the terms of the GNU Lesser General Public License as published by
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering the Free Software Foundation; either version 2.1 of the License, or
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering (at your option) any later version.
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering systemd is distributed in the hope that it will be useful, but
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering WITHOUT ANY WARRANTY; without even the implied warranty of
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering Lesser General Public License for more details.
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering You should have received a copy of the GNU Lesser General Public License
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering along with systemd; If not, see <http://www.gnu.org/licenses/>.
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering#define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering#define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering#define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poetteringstatic bool arg_after_cursor = false;
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poetteringstatic bool arg_merge = false;
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering#define STATE_FILE "/var/lib/systemd/journal-upload/state"
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering#define easy_setopt(curl, opt, value, level, cmd) \
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering "curl_easy_setopt " #opt " failed: %s", \
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen log_debug("The server answers (%zu bytes): %.*s",
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_warning_errno(ENOMEM, "Failed to store server answer (%zu bytes): %m",
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poetteringstatic int check_cursor_updating(Uploader *u) {
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering return log_error_errno(r, "Cannot create parent directory of state file %s: %m",
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = fopen_temporary(u->state_file, &f, &temp_path);
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen return log_error_errno(r, "Cannot save state to %s: %m",
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering r = fopen_temporary(u->state_file, &f, &temp_path);
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering "# This is private data. Do not parse.\n"
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering "LAST_CURSOR=%s\n",
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering if (ferror(f) || rename(temp_path, u->state_file) < 0) {
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen log_error_errno(r, "Failed to save state %s: %m", u->state_file);
9d12709626bccc0cae677a7035f62efe6aabb4abLennart Poettering r = parse_env_file(u->state_file, NEWLINE,
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering log_debug("State file %s is not present.", u->state_file);
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering else if (r < 0)
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering return log_error_errno(r, "Failed to read state file %s: %m",
aa1936ea1a89c2bb968ba33e3274898a4eeae771Lennart Poettering log_debug("Last cursor was %s", u->last_cursor);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering h = curl_slist_append(h, "Transfer-Encoding: chunked");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering h = curl_slist_append(h, "Accept: text/plain");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("Call to curl_easy_init failed.");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering /* tell it to POST to the URL */
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering /* set where to write to */
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering easy_setopt(curl, CURLOPT_WRITEDATA, data,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering /* set where to read from */
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
9d12709626bccc0cae677a7035f62efe6aabb4abLennart Poettering easy_setopt(curl, CURLOPT_READDATA, data,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering /* use our special own mime type and chunked transfer */
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers /* enable verbose for easier tracing */
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers if (arg_key || startswith(u->url, "https://")) {
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers else if (arg_trust || startswith(u->url, "https://"))
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering /* truncate the potential old error message */
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers /* upload to this place */
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers log_error("curl_easy_setopt CURLOPT_URL failed: %s",
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersenstatic size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers if (r == 0) {
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen log_error_errno(errno, "Aborting transfer after read error on input: %m.");
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersenstatic int dispatch_fd_input(sd_event_source *event,
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers log_warning("Unexpected poll event %"PRIu32".", revents);
9f6eb1cd58f2ddf2eb6ba0e4de056e13d938af75Kay Sievers log_warning("dispatch_fd_input called when uploading, ignoring.");
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersenstatic int open_file_for_upload(Uploader *u, const char *filename) {
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering return log_error_errno(errno, "Failed to open %s: %m", filename);
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen r = sd_event_add_io(u->events, &u->input_event,
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen return log_error_errno(r, "Failed to register input event: %m");
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen /* Normal files should just be consumed without polling. */
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poetteringstatic int dispatch_sigterm(sd_event_source *event,
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
34a6778fb9d1065f3fbb8e2243b9f0f25d1d18f1Zbigniew Jędrzejewski-Szmekstatic int setup_uploader(Uploader *u, const char *url, const char *state_file) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering u->url = strjoin(proto, url, "/upload", NULL);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering return log_error_errno(r, "sd_event_default failed: %m");
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering return log_error_errno(r, "Failed to set up signals: %m");
04d39279245834494baccfdb9349db8bf80abd13Lennart Poetteringstatic void destroy_uploader(Uploader *u) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering u->input_event = sd_event_source_unref(u->input_event);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering u->url, (int) sizeof(u->error), u->error);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_error("Failed to retrieve response code: %s",
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_error("Upload to %s failed with code %lu: %s",
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_error("Upload to %s finished with unexpected code %lu: %s",
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_debug("Upload finished successfully with code %lu: %s",
04d39279245834494baccfdb9349db8bf80abd13Lennart Poetteringstatic int parse_config(void) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "Upload", "URL", config_parse_string, 0, &arg_url },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering return config_parse_many(PKGSYSCONFDIR "/journal-upload.conf",
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering "Upload\0", config_item_table_lookup, items,
04d39279245834494baccfdb9349db8bf80abd13Lennart Poetteringstatic void help(void) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering "Upload journal events to a remote server.\n\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -h --help Show this help\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --version Show package version\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -u --url=URL Upload to this address (default port "
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --key=FILENAME Specify key in PEM format (default:\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --cert=FILENAME Specify certificate in PEM format (default:\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --system Use the system journal\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --user Use the user journal for the current user\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -m --merge Use all available journals\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -M --machine=CONTAINER Operate on local container\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -D --directory=PATH Use journal files from directory\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --file=PATH Use this journal file\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --cursor=CURSOR Start at the specified cursor\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --after-cursor=CURSOR Start after the specified cursor\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --follow[=BOOL] Do [not] wait for input\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --save-state[=FILE] Save uploaded cursors (default \n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " -h --help Show this help and exit\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering " --version Print version string and exit\n"
04d39279245834494baccfdb9349db8bf80abd13Lennart Poetteringstatic int parse_argv(int argc, char *argv[]) {
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "version", no_argument, NULL, ARG_VERSION },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "key", required_argument, NULL, ARG_KEY },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "cert", required_argument, NULL, ARG_CERT },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "trust", required_argument, NULL, ARG_TRUST },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "system", no_argument, NULL, ARG_SYSTEM },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "machine", required_argument, NULL, 'M' },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "directory", required_argument, NULL, 'D' },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "file", required_argument, NULL, ARG_FILE },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "cursor", required_argument, NULL, ARG_CURSOR },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "follow", optional_argument, NULL, ARG_FOLLOW },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering return 0 /* done */;
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering return 0 /* done */;
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_error("cannot use more than one --url");
04d39279245834494baccfdb9349db8bf80abd13Lennart Poettering log_error("cannot use more than one --key");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("cannot use more than one --cert");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("cannot use more than one --trust");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering arg_journal_type |= SD_JOURNAL_CURRENT_USER;
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("cannot use more than one --machine/-M");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("cannot use more than one --directory/-D");
a7893c6b28772edbc7e1fea3c209caa54d465648Lennart Poettering return log_error_errno(r, "Failed to add paths: %m");
a7893c6b28772edbc7e1fea3c209caa54d465648Lennart Poettering log_error("cannot use more than one --cursor/--after-cursor");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("cannot use more than one --cursor/--after-cursor");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("Failed to parse --follow= parameter.");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("Unknown option %s.", argv[optind-1]);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("Missing argument to %s.", argv[optind-1]);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering assert_not_reached("Unhandled option code.");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error("Required --url/-u option missing.");
a7893c6b28772edbc7e1fea3c209caa54d465648Lennart Poettering log_error("Options --key and --cert must be used together.");
d21ed1ead18d16d35c30299a69d3366847f8a039Lennart Poettering if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
a7893c6b28772edbc7e1fea3c209caa54d465648Lennart Poettering log_error("Input arguments make no sense with journal input.");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = sd_journal_open_files(j, (const char**) arg_file, 0);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = sd_journal_open_container(j, arg_machine, 0);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_error_errno(r, "Failed to open %s: %m",
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering arg_directory ? arg_directory : arg_file ? "files" : "journal");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = setup_uploader(&u, arg_url, arg_save_state);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering program_invocation_short_name, getpid());
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering "STATUS=Processing input...");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering while (true) {
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering } else if (u.input < 0 && !use_journal) {
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering log_debug("Using %s as input.", argv[optind]);
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering r = open_file_for_upload(&u, argv[optind++]);
84f6181c2ac99a0514ca5e0c8fc8c8e284caf789Lennart Poettering log_error_errno(r, "Failed to run event loop: %m");
a1da85830bfaa77b9eb9c54693e5573559c97e50Tom Gundersen "STOPPING=1\n"
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering "STATUS=Shutting down...");
1ee306e1248866617c96ed9f4263f375588ad838Lennart Poettering return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;