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"
35 #include "conf-parser.h"
36 #include "journal-upload.h"
38 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
39 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
40 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
41 #define DEFAULT_PORT 19532
43 static const char* arg_url;
45 static void close_fd_input(Uploader *u);
47 static const char *arg_key = NULL;
48 static const char *arg_cert = NULL;
49 static const char *arg_trust = NULL;
51 static const char *arg_directory = NULL;
52 static char **arg_file = NULL;
53 static const char *arg_cursor = NULL;
54 static bool arg_after_cursor = false;
55 static int arg_journal_type = 0;
56 static const char *arg_machine = NULL;
57 static bool arg_merge = false;
58 static int arg_follow = -1;
59 static const char *arg_save_state = NULL;
61 #define SERVER_ANSWER_KEEP 2048
63 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
65 #define easy_setopt(curl, opt, value, level, cmd) \
67 code = curl_easy_setopt(curl, opt, value); \
70 "curl_easy_setopt " #opt " failed: %s", \
71 curl_easy_strerror(code)); \
76 static size_t output_callback(char *buf,
84 log_debug("The server answers (%zu bytes): %.*s",
85 size*nmemb, (int)(size*nmemb), buf);
87 if (nmemb && !u->answer) {
88 u->answer = strndup(buf, size*nmemb);
90 log_warning("Failed to store server answer (%zu bytes): %s",
91 size*nmemb, strerror(ENOMEM));
97 static int check_cursor_updating(Uploader *u) {
98 _cleanup_free_ char *temp_path = NULL;
99 _cleanup_fclose_ FILE *f = NULL;
105 r = mkdir_parents(u->state_file, 0755);
107 log_error("Cannot create parent directory of state file %s: %s",
108 u->state_file, strerror(-r));
112 r = fopen_temporary(u->state_file, &f, &temp_path);
114 log_error("Cannot save state to %s: %s",
115 u->state_file, strerror(-r));
123 static int update_cursor_state(Uploader *u) {
124 _cleanup_free_ char *temp_path = NULL;
125 _cleanup_fclose_ FILE *f = NULL;
128 if (!u->state_file || !u->last_cursor)
131 r = fopen_temporary(u->state_file, &f, &temp_path);
136 "# This is private data. Do not parse.\n"
142 if (ferror(f) || rename(temp_path, u->state_file) < 0) {
144 unlink(u->state_file);
150 log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
155 static int load_cursor_state(Uploader *u) {
161 r = parse_env_file(u->state_file, NEWLINE,
162 "LAST_CURSOR", &u->last_cursor,
166 log_debug("State file %s is not present.", u->state_file);
168 log_error("Failed to read state file %s: %s",
169 u->state_file, strerror(-r));
172 log_debug("Last cursor was %s", u->last_cursor);
179 int start_upload(Uploader *u,
180 size_t (*input_callback)(void *ptr,
188 assert(input_callback);
191 struct curl_slist *h;
193 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
197 h = curl_slist_append(h, "Transfer-Encoding: chunked");
199 curl_slist_free_all(h);
203 h = curl_slist_append(h, "Accept: text/plain");
205 curl_slist_free_all(h);
215 curl = curl_easy_init();
217 log_error("Call to curl_easy_init failed.");
221 /* tell it to POST to the URL */
222 easy_setopt(curl, CURLOPT_POST, 1L,
223 LOG_ERR, return -EXFULL);
225 easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
226 LOG_ERR, return -EXFULL);
228 /* set where to write to */
229 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
230 LOG_ERR, return -EXFULL);
232 easy_setopt(curl, CURLOPT_WRITEDATA, data,
233 LOG_ERR, return -EXFULL);
235 /* set where to read from */
236 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
237 LOG_ERR, return -EXFULL);
239 easy_setopt(curl, CURLOPT_READDATA, data,
240 LOG_ERR, return -EXFULL);
242 /* use our special own mime type and chunked transfer */
243 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
244 LOG_ERR, return -EXFULL);
246 /* enable verbose for easier tracing */
247 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
249 easy_setopt(curl, CURLOPT_USERAGENT,
250 "systemd-journal-upload " PACKAGE_STRING,
253 if (arg_key || startswith(u->url, "https://")) {
254 easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
255 LOG_ERR, return -EXFULL);
256 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
257 LOG_ERR, return -EXFULL);
260 if (streq_ptr(arg_trust, "all"))
261 easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0,
262 LOG_ERR, return -EUCLEAN);
263 else if (arg_trust || startswith(u->url, "https://"))
264 easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
265 LOG_ERR, return -EXFULL);
267 if (arg_key || arg_trust)
268 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
273 /* truncate the potential old error message */
280 /* upload to this place */
281 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
283 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
284 curl_easy_strerror(code));
293 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
299 assert(nmemb <= SSIZE_MAX / size);
304 r = read(u->input, buf, size * nmemb);
305 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
310 u->uploading = false;
312 log_debug("Reached EOF");
316 log_error("Aborting transfer after read error on input: %m.");
317 return CURL_READFUNC_ABORT;
321 static void close_fd_input(Uploader *u) {
325 close_nointr(u->input);
330 static int dispatch_fd_input(sd_event_source *event,
339 if (revents & EPOLLHUP) {
340 log_debug("Received HUP");
345 if (!(revents & EPOLLIN)) {
346 log_warning("Unexpected poll event %"PRIu32".", revents);
351 log_warning("dispatch_fd_input called when uploading, ignoring.");
355 return start_upload(u, fd_input_callback, u);
358 static int open_file_for_upload(Uploader *u, const char *filename) {
361 if (streq(filename, "-"))
364 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
366 log_error("Failed to open %s: %m", filename);
374 r = sd_event_add_io(u->events, &u->input_event,
375 fd, EPOLLIN, dispatch_fd_input, u);
377 if (r != -EPERM || arg_follow > 0) {
378 log_error("Failed to register input event: %s", strerror(-r));
382 /* Normal files should just be consumed without polling. */
383 r = start_upload(u, fd_input_callback, u);
390 static int dispatch_sigterm(sd_event_source *event,
391 const struct signalfd_siginfo *si,
393 Uploader *u = userdata;
397 log_received_signal(LOG_INFO, si);
400 close_journal_input(u);
402 sd_event_exit(u->events, 0);
406 static int setup_signals(Uploader *u) {
412 assert_se(sigemptyset(&mask) == 0);
413 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
414 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
416 r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
420 r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
427 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
429 const char *host, *proto = "";
434 memzero(u, sizeof(Uploader));
437 if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) {
442 if (strchr(host, ':'))
443 u->url = strjoin(proto, url, "/upload", NULL);
450 while (x > 0 && t[x - 1] == '/')
453 u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL);
458 u->state_file = state_file;
460 r = sd_event_default(&u->events);
462 log_error("sd_event_default failed: %s", strerror(-r));
466 r = setup_signals(u);
468 log_error("Failed to set up signals: %s", strerror(-r));
472 return load_cursor_state(u);
475 static void destroy_uploader(Uploader *u) {
478 curl_easy_cleanup(u->easy);
479 curl_slist_free_all(u->header);
482 free(u->last_cursor);
483 free(u->current_cursor);
487 u->input_event = sd_event_source_unref(u->input_event);
490 close_journal_input(u);
492 sd_event_source_unref(u->sigterm_event);
493 sd_event_source_unref(u->sigint_event);
494 sd_event_unref(u->events);
497 static int perform_upload(Uploader *u) {
503 code = curl_easy_perform(u->easy);
506 log_error("Upload to %s failed: %.*s",
507 u->url, (int) sizeof(u->error), u->error);
509 log_error("Upload to %s failed: %s",
510 u->url, curl_easy_strerror(code));
514 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
516 log_error("Failed to retrieve response code: %s",
517 curl_easy_strerror(code));
522 log_error("Upload to %s failed with code %lu: %s",
523 u->url, status, strna(u->answer));
525 } else if (status < 200) {
526 log_error("Upload to %s finished with unexpected code %lu: %s",
527 u->url, status, strna(u->answer));
530 log_debug("Upload finished successfully with code %lu: %s",
531 status, strna(u->answer));
533 free(u->last_cursor);
534 u->last_cursor = u->current_cursor;
535 u->current_cursor = NULL;
537 return update_cursor_state(u);
540 static int parse_config(void) {
541 const ConfigTableItem items[] = {
542 { "Upload", "URL", config_parse_string, 0, &arg_url },
543 { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key },
544 { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
545 { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
548 return config_parse(NULL, PKGSYSCONFDIR "/journal-upload.conf", NULL,
550 config_item_table_lookup, items,
551 false, false, true, NULL);
554 static void help(void) {
555 printf("%s -u URL {FILE|-}...\n\n"
556 "Upload journal events to a remote server.\n\n"
557 " -h --help Show this help\n"
558 " --version Show package version\n"
559 " -u --url=URL Upload to this address (default port "
560 STRINGIFY(DEFAULT_PORT) ")\n"
561 " --key=FILENAME Specify key in PEM format (default:\n"
562 " \"" PRIV_KEY_FILE "\")\n"
563 " --cert=FILENAME Specify certificate in PEM format (default:\n"
564 " \"" CERT_FILE "\")\n"
565 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
566 " \"" TRUST_FILE "\")\n"
567 " --system Use the system journal\n"
568 " --user Use the user journal for the current user\n"
569 " -m --merge Use all available journals\n"
570 " -M --machine=CONTAINER Operate on local container\n"
571 " -D --directory=PATH Use journal files from directory\n"
572 " --file=PATH Use this journal file\n"
573 " --cursor=CURSOR Start at the specified cursor\n"
574 " --after-cursor=CURSOR Start after the specified cursor\n"
575 " --follow[=BOOL] Do [not] wait for input\n"
576 " --save-state[=FILE] Save uploaded cursors (default \n"
578 " -h --help Show this help and exit\n"
579 " --version Print version string and exit\n"
580 , program_invocation_short_name);
583 static int parse_argv(int argc, char *argv[]) {
598 static const struct option options[] = {
599 { "help", no_argument, NULL, 'h' },
600 { "version", no_argument, NULL, ARG_VERSION },
601 { "url", required_argument, NULL, 'u' },
602 { "key", required_argument, NULL, ARG_KEY },
603 { "cert", required_argument, NULL, ARG_CERT },
604 { "trust", required_argument, NULL, ARG_TRUST },
605 { "system", no_argument, NULL, ARG_SYSTEM },
606 { "user", no_argument, NULL, ARG_USER },
607 { "merge", no_argument, NULL, 'm' },
608 { "machine", required_argument, NULL, 'M' },
609 { "directory", required_argument, NULL, 'D' },
610 { "file", required_argument, NULL, ARG_FILE },
611 { "cursor", required_argument, NULL, ARG_CURSOR },
612 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
613 { "follow", optional_argument, NULL, ARG_FOLLOW },
614 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
625 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
632 puts(PACKAGE_STRING);
633 puts(SYSTEMD_FEATURES);
638 log_error("cannot use more than one --url");
647 log_error("cannot use more than one --key");
656 log_error("cannot use more than one --cert");
665 log_error("cannot use more than one --trust");
673 arg_journal_type |= SD_JOURNAL_SYSTEM;
677 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
686 log_error("cannot use more than one --machine/-M");
690 arg_machine = optarg;
695 log_error("cannot use more than one --directory/-D");
699 arg_directory = optarg;
703 r = glob_extend(&arg_file, optarg);
705 log_error("Failed to add paths: %s", strerror(-r));
712 log_error("cannot use more than one --cursor/--after-cursor");
719 case ARG_AFTER_CURSOR:
721 log_error("cannot use more than one --cursor/--after-cursor");
726 arg_after_cursor = true;
731 r = parse_boolean(optarg);
733 log_error("Failed to parse --follow= parameter.");
744 arg_save_state = optarg ?: STATE_FILE;
748 log_error("Unknown option %s.", argv[optind-1]);
752 log_error("Missing argument to %s.", argv[optind-1]);
756 assert_not_reached("Unhandled option code.");
760 log_error("Required --url/-u option missing.");
764 if (!!arg_key != !!arg_cert) {
765 log_error("Options --key and --cert must be used together.");
769 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
770 log_error("Input arguments make no sense with journal input.");
777 static int open_journal(sd_journal **j) {
781 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
783 r = sd_journal_open_files(j, (const char**) arg_file, 0);
784 else if (arg_machine)
785 r = sd_journal_open_container(j, arg_machine, 0);
787 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
789 log_error("Failed to open %s: %s",
790 arg_directory ? arg_directory : arg_file ? "files" : "journal",
795 int main(int argc, char **argv) {
800 log_show_color(true);
801 log_parse_environment();
807 r = parse_argv(argc, argv);
811 r = setup_uploader(&u, arg_url, arg_save_state);
815 sd_event_set_watchdog(u.events, true);
817 r = check_cursor_updating(&u);
821 log_debug("%s running as pid "PID_FMT,
822 program_invocation_short_name, getpid());
824 use_journal = optind >= argc;
827 r = open_journal(&j);
830 r = open_journal_for_upload(&u, j,
831 arg_cursor ?: u.last_cursor,
832 arg_cursor ? arg_after_cursor : true,
840 "STATUS=Processing input...");
843 r = sd_event_get_state(u.events);
846 if (r == SD_EVENT_FINISHED)
853 r = check_journal_input(&u);
854 } else if (u.input < 0 && !use_journal) {
858 log_debug("Using %s as input.", argv[optind]);
859 r = open_file_for_upload(&u, argv[optind++]);
865 r = perform_upload(&u);
870 r = sd_event_run(u.events, u.timeout);
872 log_error("Failed to run event loop: %s", strerror(-r));
880 "STATUS=Shutting down...");
882 destroy_uploader(&u);
885 return r >= 0 ? EXIT_SUCCESS : EXIT_FAILURE;