1 /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
4 This file is part of systemd.
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
8 systemd is free software; you can redistribute it and/or modify it
9 under the terms of the GNU Lesser General Public License as published by
10 the Free Software Foundation; either version 2.1 of the License, or
11 (at your option) any later version.
13 systemd is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
16 Lesser General Public License for more details.
18 You should have received a copy of the GNU Lesser General Public License
19 along with systemd; If not, see <http://www.gnu.org/licenses/>.
23 #include <curl/curl.h>
28 #include "sd-daemon.h"
34 #include "conf-parser.h"
35 #include "journal-upload.h"
37 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
38 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
39 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
41 static const char* arg_url;
43 static void close_fd_input(Uploader *u);
45 static const char *arg_key = NULL;
46 static const char *arg_cert = NULL;
47 static const char *arg_trust = NULL;
49 static const char *arg_directory = NULL;
50 static char **arg_file = NULL;
51 static const char *arg_cursor = NULL;
52 static bool arg_after_cursor = false;
53 static int arg_journal_type = 0;
54 static const char *arg_machine = NULL;
55 static bool arg_merge = false;
56 static int arg_follow = -1;
57 static const char *arg_save_state = NULL;
59 #define SERVER_ANSWER_KEEP 2048
61 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
63 #define easy_setopt(curl, opt, value, level, cmd) \
65 code = curl_easy_setopt(curl, opt, value); \
68 "curl_easy_setopt " #opt " failed: %s", \
69 curl_easy_strerror(code)); \
74 static size_t output_callback(char *buf,
82 log_debug("The server answers (%zu bytes): %.*s",
83 size*nmemb, (int)(size*nmemb), buf);
85 if (nmemb && !u->answer) {
86 u->answer = strndup(buf, size*nmemb);
88 log_warning("Failed to store server answer (%zu bytes): %s",
89 size*nmemb, strerror(ENOMEM));
95 static int update_cursor_state(Uploader *u) {
96 _cleanup_free_ char *temp_path = NULL;
97 _cleanup_fclose_ FILE *f = NULL;
100 if (!u->state_file || !u->last_cursor)
103 r = fopen_temporary(u->state_file, &f, &temp_path);
108 "# This is private data. Do not parse.\n"
114 if (ferror(f) || rename(temp_path, u->state_file) < 0) {
116 unlink(u->state_file);
122 log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
127 static int load_cursor_state(Uploader *u) {
133 r = parse_env_file(u->state_file, NEWLINE,
134 "LAST_CURSOR", &u->last_cursor,
137 if (r < 0 && r != -ENOENT) {
138 log_error("Failed to read state file %s: %s",
139 u->state_file, strerror(-r));
148 int start_upload(Uploader *u,
149 size_t (*input_callback)(void *ptr,
157 assert(input_callback);
160 struct curl_slist *h;
162 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
166 h = curl_slist_append(h, "Transfer-Encoding: chunked");
168 curl_slist_free_all(h);
172 h = curl_slist_append(h, "Accept: text/plain");
174 curl_slist_free_all(h);
184 curl = curl_easy_init();
186 log_error("Call to curl_easy_init failed.");
190 /* tell it to POST to the URL */
191 easy_setopt(curl, CURLOPT_POST, 1L,
192 LOG_ERR, return -EXFULL);
194 easy_setopt(curl, CURLOPT_ERRORBUFFER, &u->error,
195 LOG_ERR, return -EXFULL);
197 /* set where to write to */
198 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
199 LOG_ERR, return -EXFULL);
201 easy_setopt(curl, CURLOPT_WRITEDATA, data,
202 LOG_ERR, return -EXFULL);
204 /* set where to read from */
205 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
206 LOG_ERR, return -EXFULL);
208 easy_setopt(curl, CURLOPT_READDATA, data,
209 LOG_ERR, return -EXFULL);
211 /* use our special own mime type and chunked transfer */
212 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
213 LOG_ERR, return -EXFULL);
215 /* enable verbose for easier tracing */
216 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
218 easy_setopt(curl, CURLOPT_USERAGENT,
219 "systemd-journal-upload " PACKAGE_STRING,
222 if (arg_key || startswith(u->url, "https://")) {
225 easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
226 LOG_ERR, return -EXFULL);
227 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
228 LOG_ERR, return -EXFULL);
231 if (arg_trust || startswith(u->url, "https://"))
232 easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
233 LOG_ERR, return -EXFULL);
235 if (arg_key || arg_trust)
236 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
241 /* truncate the potential old error message */
248 /* upload to this place */
249 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
251 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
252 curl_easy_strerror(code));
261 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
267 assert(nmemb <= SSIZE_MAX / size);
272 r = read(u->input, buf, size * nmemb);
273 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
278 u->uploading = false;
280 log_debug("Reached EOF");
284 log_error("Aborting transfer after read error on input: %m.");
285 return CURL_READFUNC_ABORT;
289 static void close_fd_input(Uploader *u) {
293 close_nointr(u->input);
298 static int dispatch_fd_input(sd_event_source *event,
307 if (revents & EPOLLHUP) {
308 log_debug("Received HUP");
313 if (!(revents & EPOLLIN)) {
314 log_warning("Unexpected poll event %"PRIu32".", revents);
319 log_warning("dispatch_fd_input called when uploading, ignoring.");
323 return start_upload(u, fd_input_callback, u);
326 static int open_file_for_upload(Uploader *u, const char *filename) {
329 if (streq(filename, "-"))
332 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
334 log_error("Failed to open %s: %m", filename);
342 r = sd_event_add_io(u->events, &u->input_event,
343 fd, EPOLLIN, dispatch_fd_input, u);
345 if (r != -EPERM || arg_follow > 0) {
346 log_error("Failed to register input event: %s", strerror(-r));
350 /* Normal files should just be consumed without polling. */
351 r = start_upload(u, fd_input_callback, u);
358 static int dispatch_sigterm(sd_event_source *event,
359 const struct signalfd_siginfo *si,
361 Uploader *u = userdata;
365 log_received_signal(LOG_INFO, si);
368 close_journal_input(u);
370 sd_event_exit(u->events, 0);
374 static int setup_signals(Uploader *u) {
380 assert_se(sigemptyset(&mask) == 0);
381 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
382 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
384 r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
388 r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
395 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
401 memzero(u, sizeof(Uploader));
404 if (!startswith(url, "http://") && !startswith(url, "https://"))
405 url = strappenda("https://", url);
407 u->url = strappend(url, "/upload");
411 u->state_file = state_file;
413 r = sd_event_default(&u->events);
415 log_error("sd_event_default failed: %s", strerror(-r));
419 r = setup_signals(u);
421 log_error("Failed to set up signals: %s", strerror(-r));
425 return load_cursor_state(u);
428 static void destroy_uploader(Uploader *u) {
431 curl_easy_cleanup(u->easy);
432 curl_slist_free_all(u->header);
435 free(u->last_cursor);
436 free(u->current_cursor);
440 u->input_event = sd_event_source_unref(u->input_event);
443 close_journal_input(u);
445 sd_event_source_unref(u->sigterm_event);
446 sd_event_source_unref(u->sigint_event);
447 sd_event_unref(u->events);
450 static int perform_upload(Uploader *u) {
456 code = curl_easy_perform(u->easy);
458 log_error("Upload to %s failed: %.*s",
460 u->error[0] ? (int) sizeof(u->error) : INT_MAX,
461 u->error[0] ? u->error : curl_easy_strerror(code));
465 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
467 log_error("Failed to retrieve response code: %s",
468 curl_easy_strerror(code));
473 log_error("Upload to %s failed with code %lu: %s",
474 u->url, status, strna(u->answer));
476 } else if (status < 200) {
477 log_error("Upload to %s finished with unexpected code %lu: %s",
478 u->url, status, strna(u->answer));
481 log_debug("Upload finished successfully with code %lu: %s",
482 status, strna(u->answer));
484 free(u->last_cursor);
485 u->last_cursor = u->current_cursor;
486 u->current_cursor = NULL;
488 return update_cursor_state(u);
491 static int parse_config(void) {
492 const ConfigTableItem items[] = {
493 { "Upload", "URL", config_parse_string, 0, &arg_url },
494 { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key },
495 { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
496 { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
499 return config_parse(NULL, PKGSYSCONFDIR "/journal-upload.conf", NULL,
501 config_item_table_lookup, items,
502 false, false, true, NULL);
505 static void help(void) {
506 printf("%s -u URL {FILE|-}...\n\n"
507 "Upload journal events to a remote server.\n\n"
508 " -h --help Show this help\n"
509 " --version Show package version\n"
510 " -u --url=URL Upload to this address\n"
511 " --key=FILENAME Specify key in PEM format\n"
512 " --cert=FILENAME Specify certificate in PEM format\n"
513 " --trust=FILENAME Specify CA certificate in PEM format\n"
514 " --system Use the system journal\n"
515 " --user Use the user journal for the current user\n"
516 " -m --merge Use all available journals\n"
517 " -M --machine=CONTAINER Operate on local container\n"
518 " -D --directory=PATH Use journal files from directory\n"
519 " --file=PATH Use this journal file\n"
520 " --cursor=CURSOR Start at the specified cursor\n"
521 " --after-cursor=CURSOR Start after the specified cursor\n"
522 " --follow[=BOOL] Do [not] wait for input\n"
523 " --save-state[=FILE] Save uploaded cursors (default \n"
525 " -h --help Show this help and exit\n"
526 " --version Print version string and exit\n"
527 , program_invocation_short_name);
530 static int parse_argv(int argc, char *argv[]) {
545 static const struct option options[] = {
546 { "help", no_argument, NULL, 'h' },
547 { "version", no_argument, NULL, ARG_VERSION },
548 { "url", required_argument, NULL, 'u' },
549 { "key", required_argument, NULL, ARG_KEY },
550 { "cert", required_argument, NULL, ARG_CERT },
551 { "trust", required_argument, NULL, ARG_TRUST },
552 { "system", no_argument, NULL, ARG_SYSTEM },
553 { "user", no_argument, NULL, ARG_USER },
554 { "merge", no_argument, NULL, 'm' },
555 { "machine", required_argument, NULL, 'M' },
556 { "directory", required_argument, NULL, 'D' },
557 { "file", required_argument, NULL, ARG_FILE },
558 { "cursor", required_argument, NULL, ARG_CURSOR },
559 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
560 { "follow", optional_argument, NULL, ARG_FOLLOW },
561 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
572 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
579 puts(PACKAGE_STRING);
580 puts(SYSTEMD_FEATURES);
585 log_error("cannot use more than one --url");
594 log_error("cannot use more than one --key");
603 log_error("cannot use more than one --cert");
612 log_error("cannot use more than one --trust");
620 arg_journal_type |= SD_JOURNAL_SYSTEM;
624 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
633 log_error("cannot use more than one --machine/-M");
637 arg_machine = optarg;
642 log_error("cannot use more than one --directory/-D");
646 arg_directory = optarg;
650 r = glob_extend(&arg_file, optarg);
652 log_error("Failed to add paths: %s", strerror(-r));
659 log_error("cannot use more than one --cursor/--after-cursor");
666 case ARG_AFTER_CURSOR:
668 log_error("cannot use more than one --cursor/--after-cursor");
673 arg_after_cursor = true;
678 r = parse_boolean(optarg);
680 log_error("Failed to parse --follow= parameter.");
691 arg_save_state = optarg ?: STATE_FILE;
695 log_error("Unknown option %s.", argv[optind-1]);
699 log_error("Missing argument to %s.", argv[optind-1]);
703 assert_not_reached("Unhandled option code.");
707 log_error("Required --url/-u option missing.");
711 if (!!arg_key != !!arg_cert) {
712 log_error("Options --key and --cert must be used together.");
716 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
717 log_error("Input arguments make no sense with journal input.");
724 static int open_journal(sd_journal **j) {
728 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
730 r = sd_journal_open_files(j, (const char**) arg_file, 0);
731 else if (arg_machine)
732 r = sd_journal_open_container(j, arg_machine, 0);
734 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
736 log_error("Failed to open %s: %s",
737 arg_directory ? arg_directory : arg_file ? "files" : "journal",
742 int main(int argc, char **argv) {
747 log_show_color(true);
748 log_parse_environment();
754 r = parse_argv(argc, argv);
758 r = setup_uploader(&u, arg_url, arg_save_state);
762 sd_event_set_watchdog(u.events, true);
764 log_debug("%s running as pid "PID_FMT,
765 program_invocation_short_name, getpid());
767 use_journal = optind >= argc;
770 r = open_journal(&j);
773 r = open_journal_for_upload(&u, j,
774 arg_cursor ?: u.last_cursor,
775 arg_cursor ? arg_after_cursor : true,
783 "STATUS=Processing input...");
790 r = check_journal_input(&u);
791 } else if (u.input < 0 && !use_journal) {
795 log_debug("Using %s as input.", argv[optind]);
796 r = open_file_for_upload(&u, argv[optind++]);
801 r = sd_event_get_state(u.events);
804 if (r == SD_EVENT_FINISHED)
808 r = perform_upload(&u);
813 r = sd_event_run(u.events, u.timeout);
815 log_error("Failed to run event loop: %s", strerror(-r));
821 sd_notify(false, "STATUS=Shutting down...");
822 destroy_uploader(&u);
825 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;