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"
40 #define DEFAULT_PORT 19532
42 static const char* arg_url;
44 static void close_fd_input(Uploader *u);
46 static const char *arg_key = NULL;
47 static const char *arg_cert = NULL;
48 static const char *arg_trust = NULL;
50 static const char *arg_directory = NULL;
51 static char **arg_file = NULL;
52 static const char *arg_cursor = NULL;
53 static bool arg_after_cursor = false;
54 static int arg_journal_type = 0;
55 static const char *arg_machine = NULL;
56 static bool arg_merge = false;
57 static int arg_follow = -1;
58 static const char *arg_save_state = NULL;
60 #define SERVER_ANSWER_KEEP 2048
62 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
64 #define easy_setopt(curl, opt, value, level, cmd) \
66 code = curl_easy_setopt(curl, opt, value); \
69 "curl_easy_setopt " #opt " failed: %s", \
70 curl_easy_strerror(code)); \
75 static size_t output_callback(char *buf,
83 log_debug("The server answers (%zu bytes): %.*s",
84 size*nmemb, (int)(size*nmemb), buf);
86 if (nmemb && !u->answer) {
87 u->answer = strndup(buf, size*nmemb);
89 log_warning("Failed to store server answer (%zu bytes): %s",
90 size*nmemb, strerror(ENOMEM));
96 static int update_cursor_state(Uploader *u) {
97 _cleanup_free_ char *temp_path = NULL;
98 _cleanup_fclose_ FILE *f = NULL;
101 if (!u->state_file || !u->last_cursor)
104 r = fopen_temporary(u->state_file, &f, &temp_path);
109 "# This is private data. Do not parse.\n"
115 if (ferror(f) || rename(temp_path, u->state_file) < 0) {
117 unlink(u->state_file);
123 log_error("Failed to save state %s: %s", u->state_file, strerror(-r));
128 static int load_cursor_state(Uploader *u) {
134 r = parse_env_file(u->state_file, NEWLINE,
135 "LAST_CURSOR", &u->last_cursor,
138 if (r < 0 && r != -ENOENT) {
139 log_error("Failed to read state file %s: %s",
140 u->state_file, strerror(-r));
149 int start_upload(Uploader *u,
150 size_t (*input_callback)(void *ptr,
158 assert(input_callback);
161 struct curl_slist *h;
163 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
167 h = curl_slist_append(h, "Transfer-Encoding: chunked");
169 curl_slist_free_all(h);
173 h = curl_slist_append(h, "Accept: text/plain");
175 curl_slist_free_all(h);
185 curl = curl_easy_init();
187 log_error("Call to curl_easy_init failed.");
191 /* tell it to POST to the URL */
192 easy_setopt(curl, CURLOPT_POST, 1L,
193 LOG_ERR, return -EXFULL);
195 easy_setopt(curl, CURLOPT_ERRORBUFFER, u->error,
196 LOG_ERR, return -EXFULL);
198 /* set where to write to */
199 easy_setopt(curl, CURLOPT_WRITEFUNCTION, output_callback,
200 LOG_ERR, return -EXFULL);
202 easy_setopt(curl, CURLOPT_WRITEDATA, data,
203 LOG_ERR, return -EXFULL);
205 /* set where to read from */
206 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
207 LOG_ERR, return -EXFULL);
209 easy_setopt(curl, CURLOPT_READDATA, data,
210 LOG_ERR, return -EXFULL);
212 /* use our special own mime type and chunked transfer */
213 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
214 LOG_ERR, return -EXFULL);
216 /* enable verbose for easier tracing */
217 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
219 easy_setopt(curl, CURLOPT_USERAGENT,
220 "systemd-journal-upload " PACKAGE_STRING,
223 if (arg_key || startswith(u->url, "https://")) {
224 easy_setopt(curl, CURLOPT_SSLKEY, arg_key ?: PRIV_KEY_FILE,
225 LOG_ERR, return -EXFULL);
226 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert ?: CERT_FILE,
227 LOG_ERR, return -EXFULL);
230 if (arg_trust || startswith(u->url, "https://"))
231 easy_setopt(curl, CURLOPT_CAINFO, arg_trust ?: TRUST_FILE,
232 LOG_ERR, return -EXFULL);
234 if (arg_key || arg_trust)
235 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
240 /* truncate the potential old error message */
247 /* upload to this place */
248 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
250 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
251 curl_easy_strerror(code));
260 static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
266 assert(nmemb <= SSIZE_MAX / size);
271 r = read(u->input, buf, size * nmemb);
272 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
277 u->uploading = false;
279 log_debug("Reached EOF");
283 log_error("Aborting transfer after read error on input: %m.");
284 return CURL_READFUNC_ABORT;
288 static void close_fd_input(Uploader *u) {
292 close_nointr(u->input);
297 static int dispatch_fd_input(sd_event_source *event,
306 if (revents & EPOLLHUP) {
307 log_debug("Received HUP");
312 if (!(revents & EPOLLIN)) {
313 log_warning("Unexpected poll event %"PRIu32".", revents);
318 log_warning("dispatch_fd_input called when uploading, ignoring.");
322 return start_upload(u, fd_input_callback, u);
325 static int open_file_for_upload(Uploader *u, const char *filename) {
328 if (streq(filename, "-"))
331 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
333 log_error("Failed to open %s: %m", filename);
341 r = sd_event_add_io(u->events, &u->input_event,
342 fd, EPOLLIN, dispatch_fd_input, u);
344 if (r != -EPERM || arg_follow > 0) {
345 log_error("Failed to register input event: %s", strerror(-r));
349 /* Normal files should just be consumed without polling. */
350 r = start_upload(u, fd_input_callback, u);
357 static int dispatch_sigterm(sd_event_source *event,
358 const struct signalfd_siginfo *si,
360 Uploader *u = userdata;
364 log_received_signal(LOG_INFO, si);
367 close_journal_input(u);
369 sd_event_exit(u->events, 0);
373 static int setup_signals(Uploader *u) {
379 assert_se(sigemptyset(&mask) == 0);
380 sigset_add_many(&mask, SIGINT, SIGTERM, -1);
381 assert_se(sigprocmask(SIG_SETMASK, &mask, NULL) == 0);
383 r = sd_event_add_signal(u->events, &u->sigterm_event, SIGTERM, dispatch_sigterm, u);
387 r = sd_event_add_signal(u->events, &u->sigint_event, SIGINT, dispatch_sigterm, u);
394 static int setup_uploader(Uploader *u, const char *url, const char *state_file) {
396 const char *host, *proto = "";
401 memzero(u, sizeof(Uploader));
404 if (!(host = startswith(url, "http://")) && !(host = startswith(url, "https://"))) {
409 if (strchr(host, ':'))
410 u->url = strjoin(proto, url, "/upload", NULL);
417 while (x > 0 && t[x - 1] == '/')
420 u->url = strjoin(proto, t, ":" STRINGIFY(DEFAULT_PORT), "/upload", NULL);
425 u->state_file = state_file;
427 r = sd_event_default(&u->events);
429 log_error("sd_event_default failed: %s", strerror(-r));
433 r = setup_signals(u);
435 log_error("Failed to set up signals: %s", strerror(-r));
439 return load_cursor_state(u);
442 static void destroy_uploader(Uploader *u) {
445 curl_easy_cleanup(u->easy);
446 curl_slist_free_all(u->header);
449 free(u->last_cursor);
450 free(u->current_cursor);
454 u->input_event = sd_event_source_unref(u->input_event);
457 close_journal_input(u);
459 sd_event_source_unref(u->sigterm_event);
460 sd_event_source_unref(u->sigint_event);
461 sd_event_unref(u->events);
464 static int perform_upload(Uploader *u) {
470 code = curl_easy_perform(u->easy);
472 log_error("Upload to %s failed: %.*s",
474 u->error[0] ? (int) sizeof(u->error) : INT_MAX,
475 u->error[0] ? u->error : curl_easy_strerror(code));
479 code = curl_easy_getinfo(u->easy, CURLINFO_RESPONSE_CODE, &status);
481 log_error("Failed to retrieve response code: %s",
482 curl_easy_strerror(code));
487 log_error("Upload to %s failed with code %lu: %s",
488 u->url, status, strna(u->answer));
490 } else if (status < 200) {
491 log_error("Upload to %s finished with unexpected code %lu: %s",
492 u->url, status, strna(u->answer));
495 log_debug("Upload finished successfully with code %lu: %s",
496 status, strna(u->answer));
498 free(u->last_cursor);
499 u->last_cursor = u->current_cursor;
500 u->current_cursor = NULL;
502 return update_cursor_state(u);
505 static int parse_config(void) {
506 const ConfigTableItem items[] = {
507 { "Upload", "URL", config_parse_string, 0, &arg_url },
508 { "Upload", "ServerKeyFile", config_parse_path, 0, &arg_key },
509 { "Upload", "ServerCertificateFile", config_parse_path, 0, &arg_cert },
510 { "Upload", "TrustedCertificateFile", config_parse_path, 0, &arg_trust },
513 return config_parse(NULL, PKGSYSCONFDIR "/journal-upload.conf", NULL,
515 config_item_table_lookup, items,
516 false, false, true, NULL);
519 static void help(void) {
520 printf("%s -u URL {FILE|-}...\n\n"
521 "Upload journal events to a remote server.\n\n"
522 " -h --help Show this help\n"
523 " --version Show package version\n"
524 " -u --url=URL Upload to this address (default port "
525 STRINGIFY(DEFAULT_PORT) ")\n"
526 " --key=FILENAME Specify key in PEM format (default:\n"
527 " \"" PRIV_KEY_FILE "\")\n"
528 " --cert=FILENAME Specify certificate in PEM format (default:\n"
529 " \"" CERT_FILE "\")\n"
530 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
531 " \"" TRUST_FILE "\")\n"
532 " --system Use the system journal\n"
533 " --user Use the user journal for the current user\n"
534 " -m --merge Use all available journals\n"
535 " -M --machine=CONTAINER Operate on local container\n"
536 " -D --directory=PATH Use journal files from directory\n"
537 " --file=PATH Use this journal file\n"
538 " --cursor=CURSOR Start at the specified cursor\n"
539 " --after-cursor=CURSOR Start after the specified cursor\n"
540 " --follow[=BOOL] Do [not] wait for input\n"
541 " --save-state[=FILE] Save uploaded cursors (default \n"
543 " -h --help Show this help and exit\n"
544 " --version Print version string and exit\n"
545 , program_invocation_short_name);
548 static int parse_argv(int argc, char *argv[]) {
563 static const struct option options[] = {
564 { "help", no_argument, NULL, 'h' },
565 { "version", no_argument, NULL, ARG_VERSION },
566 { "url", required_argument, NULL, 'u' },
567 { "key", required_argument, NULL, ARG_KEY },
568 { "cert", required_argument, NULL, ARG_CERT },
569 { "trust", required_argument, NULL, ARG_TRUST },
570 { "system", no_argument, NULL, ARG_SYSTEM },
571 { "user", no_argument, NULL, ARG_USER },
572 { "merge", no_argument, NULL, 'm' },
573 { "machine", required_argument, NULL, 'M' },
574 { "directory", required_argument, NULL, 'D' },
575 { "file", required_argument, NULL, ARG_FILE },
576 { "cursor", required_argument, NULL, ARG_CURSOR },
577 { "after-cursor", required_argument, NULL, ARG_AFTER_CURSOR },
578 { "follow", optional_argument, NULL, ARG_FOLLOW },
579 { "save-state", optional_argument, NULL, ARG_SAVE_STATE },
590 while ((c = getopt_long(argc, argv, "hu:mM:D:", options, NULL)) >= 0)
597 puts(PACKAGE_STRING);
598 puts(SYSTEMD_FEATURES);
603 log_error("cannot use more than one --url");
612 log_error("cannot use more than one --key");
621 log_error("cannot use more than one --cert");
630 log_error("cannot use more than one --trust");
638 arg_journal_type |= SD_JOURNAL_SYSTEM;
642 arg_journal_type |= SD_JOURNAL_CURRENT_USER;
651 log_error("cannot use more than one --machine/-M");
655 arg_machine = optarg;
660 log_error("cannot use more than one --directory/-D");
664 arg_directory = optarg;
668 r = glob_extend(&arg_file, optarg);
670 log_error("Failed to add paths: %s", strerror(-r));
677 log_error("cannot use more than one --cursor/--after-cursor");
684 case ARG_AFTER_CURSOR:
686 log_error("cannot use more than one --cursor/--after-cursor");
691 arg_after_cursor = true;
696 r = parse_boolean(optarg);
698 log_error("Failed to parse --follow= parameter.");
709 arg_save_state = optarg ?: STATE_FILE;
713 log_error("Unknown option %s.", argv[optind-1]);
717 log_error("Missing argument to %s.", argv[optind-1]);
721 assert_not_reached("Unhandled option code.");
725 log_error("Required --url/-u option missing.");
729 if (!!arg_key != !!arg_cert) {
730 log_error("Options --key and --cert must be used together.");
734 if (optind < argc && (arg_directory || arg_file || arg_machine || arg_journal_type)) {
735 log_error("Input arguments make no sense with journal input.");
742 static int open_journal(sd_journal **j) {
746 r = sd_journal_open_directory(j, arg_directory, arg_journal_type);
748 r = sd_journal_open_files(j, (const char**) arg_file, 0);
749 else if (arg_machine)
750 r = sd_journal_open_container(j, arg_machine, 0);
752 r = sd_journal_open(j, !arg_merge*SD_JOURNAL_LOCAL_ONLY + arg_journal_type);
754 log_error("Failed to open %s: %s",
755 arg_directory ? arg_directory : arg_file ? "files" : "journal",
760 int main(int argc, char **argv) {
765 log_show_color(true);
766 log_parse_environment();
772 r = parse_argv(argc, argv);
776 r = setup_uploader(&u, arg_url, arg_save_state);
780 sd_event_set_watchdog(u.events, true);
782 log_debug("%s running as pid "PID_FMT,
783 program_invocation_short_name, getpid());
785 use_journal = optind >= argc;
788 r = open_journal(&j);
791 r = open_journal_for_upload(&u, j,
792 arg_cursor ?: u.last_cursor,
793 arg_cursor ? arg_after_cursor : true,
801 "STATUS=Processing input...");
808 r = check_journal_input(&u);
809 } else if (u.input < 0 && !use_journal) {
813 log_debug("Using %s as input.", argv[optind]);
814 r = open_file_for_upload(&u, argv[optind++]);
819 r = sd_event_get_state(u.events);
822 if (r == SD_EVENT_FINISHED)
826 r = perform_upload(&u);
831 r = sd_event_run(u.events, u.timeout);
833 log_error("Failed to run event loop: %s", strerror(-r));
841 "STATUS=Shutting down...");
843 destroy_uploader(&u);
846 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;