1 /* SPDX-License-Identifier: LGPL-2.1+ */
3 This file is part of systemd.
5 Copyright 2014 Zbigniew Jędrzejewski-Szmek
7 systemd is free software; you can redistribute it and/or modify it
8 under the terms of the GNU Lesser General Public License as published by
9 the Free Software Foundation; either version 2.1 of the License, or
10 (at your option) any later version.
12 systemd is distributed in the hope that it will be useful, but
13 WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15 Lesser General Public License for more details.
17 You should have received a copy of the GNU Lesser General Public License
18 along with systemd; If not, see <http://www.gnu.org/licenses/>.
21 #include <curl/curl.h>
27 #include "sd-daemon.h"
29 #include "alloc-util.h"
30 #include "conf-parser.h"
34 #include "format-util.h"
35 #include "glob-util.h"
36 #include "journal-upload.h"
39 #include "parse-util.h"
41 #include "signal-util.h"
42 #include "string-util.h"
45 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
46 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
47 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
48 #define DEFAULT_PORT 19532
50 static const char* arg_url
= NULL
;
51 static const char *arg_key
= NULL
;
52 static const char *arg_cert
= NULL
;
53 static const char *arg_trust
= NULL
;
54 static const char *arg_directory
= NULL
;
55 static char **arg_file
= NULL
;
56 static const char *arg_cursor
= NULL
;
57 static bool arg_after_cursor
= false;
58 static int arg_journal_type
= 0;
59 static const char *arg_machine
= NULL
;
60 static bool arg_merge
= false;
61 static int arg_follow
= -1;
62 static const char *arg_save_state
= NULL
;
64 static void close_fd_input(Uploader
*u
);
66 #define SERVER_ANSWER_KEEP 2048
68 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
70 #define easy_setopt(curl, opt, value, level, cmd) \
72 code = curl_easy_setopt(curl, opt, value); \
75 "curl_easy_setopt " #opt " failed: %s", \
76 curl_easy_strerror(code)); \
81 static size_t output_callback(char *buf
,
89 log_debug("The server answers (%zu bytes): %.*s",
90 size
*nmemb
, (int)(size
*nmemb
), buf
);
92 if (nmemb
&& !u
->answer
) {
93 u
->answer
= strndup(buf
, size
*nmemb
);
95 log_warning_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
102 static int check_cursor_updating(Uploader
*u
) {
103 _cleanup_free_
char *temp_path
= NULL
;
104 _cleanup_fclose_
FILE *f
= NULL
;
110 r
= mkdir_parents(u
->state_file
, 0755);
112 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
115 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
117 return log_error_errno(r
, "Cannot save state to %s: %m",
124 static int update_cursor_state(Uploader
*u
) {
125 _cleanup_free_
char *temp_path
= NULL
;
126 _cleanup_fclose_
FILE *f
= NULL
;
129 if (!u
->state_file
|| !u
->last_cursor
)
132 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
137 "# This is private data. Do not parse.\n"
141 r
= fflush_and_check(f
);
145 if (rename(temp_path
, u
->state_file
) < 0) {
154 (void) unlink(temp_path
);
156 (void) unlink(u
->state_file
);
158 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
161 static int load_cursor_state(Uploader
*u
) {
167 r
= parse_env_file(u
->state_file
, NEWLINE
,
168 "LAST_CURSOR", &u
->last_cursor
,
172 log_debug("State file %s is not present.", u
->state_file
);
174 return log_error_errno(r
, "Failed to read state file %s: %m",
177 log_debug("Last cursor was %s", u
->last_cursor
);
184 int start_upload(Uploader
*u
,
185 size_t (*input_callback
)(void *ptr
,
193 assert(input_callback
);
196 struct curl_slist
*h
;
198 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
202 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
204 curl_slist_free_all(h
);
208 h
= curl_slist_append(h
, "Accept: text/plain");
210 curl_slist_free_all(h
);
220 curl
= curl_easy_init();
222 log_error("Call to curl_easy_init failed.");
226 /* tell it to POST to the URL */
227 easy_setopt(curl
, CURLOPT_POST
, 1L,
228 LOG_ERR
, return -EXFULL
);
230 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
231 LOG_ERR
, return -EXFULL
);
233 /* set where to write to */
234 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
235 LOG_ERR
, return -EXFULL
);
237 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
238 LOG_ERR
, return -EXFULL
);
240 /* set where to read from */
241 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
242 LOG_ERR
, return -EXFULL
);
244 easy_setopt(curl
, CURLOPT_READDATA
, data
,
245 LOG_ERR
, return -EXFULL
);
247 /* use our special own mime type and chunked transfer */
248 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
249 LOG_ERR
, return -EXFULL
);
251 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
252 /* enable verbose for easier tracing */
253 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
255 easy_setopt(curl
, CURLOPT_USERAGENT
,
256 "systemd-journal-upload " PACKAGE_STRING
,
259 if (arg_key
|| startswith(u
->url
, "https://")) {
260 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
261 LOG_ERR
, return -EXFULL
);
262 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
263 LOG_ERR
, return -EXFULL
);
266 if (streq_ptr(arg_trust
, "all"))
267 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
268 LOG_ERR
, return -EUCLEAN
);
269 else if (arg_trust
|| startswith(u
->url
, "https://"))
270 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
271 LOG_ERR
, return -EXFULL
);
273 if (arg_key
|| arg_trust
)
274 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
279 /* truncate the potential old error message */
286 /* upload to this place */
287 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
289 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
290 curl_easy_strerror(code
));
299 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
305 assert(nmemb
<= SSIZE_MAX
/ size
);
310 r
= read(u
->input
, buf
, size
* nmemb
);
311 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
316 u
->uploading
= false;
318 log_debug("Reached EOF");
322 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
323 return CURL_READFUNC_ABORT
;
327 static void close_fd_input(Uploader
*u
) {
331 close_nointr(u
->input
);
336 static int dispatch_fd_input(sd_event_source
*event
,
345 if (revents
& EPOLLHUP
) {
346 log_debug("Received HUP");
351 if (!(revents
& EPOLLIN
)) {
352 log_warning("Unexpected poll event %"PRIu32
".", revents
);
357 log_warning("dispatch_fd_input called when uploading, ignoring.");
361 return start_upload(u
, fd_input_callback
, u
);
364 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
367 if (streq(filename
, "-"))
370 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
372 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
378 r
= sd_event_add_io(u
->events
, &u
->input_event
,
379 fd
, EPOLLIN
, dispatch_fd_input
, u
);
381 if (r
!= -EPERM
|| arg_follow
> 0)
382 return log_error_errno(r
, "Failed to register input event: %m");
384 /* Normal files should just be consumed without polling. */
385 r
= start_upload(u
, fd_input_callback
, u
);
392 static int dispatch_sigterm(sd_event_source
*event
,
393 const struct signalfd_siginfo
*si
,
395 Uploader
*u
= userdata
;
399 log_received_signal(LOG_INFO
, si
);
402 close_journal_input(u
);
404 sd_event_exit(u
->events
, 0);
408 static int setup_signals(Uploader
*u
) {
413 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
415 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
419 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
426 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
428 const char *host
, *proto
= "";
433 memzero(u
, sizeof(Uploader
));
436 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
441 if (strchr(host
, ':'))
442 u
->url
= strjoin(proto
, url
, "/upload");
449 while (x
> 0 && t
[x
- 1] == '/')
452 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload");
457 u
->state_file
= state_file
;
459 r
= sd_event_default(&u
->events
);
461 return log_error_errno(r
, "sd_event_default failed: %m");
463 r
= setup_signals(u
);
465 return log_error_errno(r
, "Failed to set up signals: %m");
467 (void) sd_watchdog_enabled(false, &u
->watchdog_usec
);
469 return load_cursor_state(u
);
472 static void destroy_uploader(Uploader
*u
) {
475 curl_easy_cleanup(u
->easy
);
476 curl_slist_free_all(u
->header
);
479 free(u
->last_cursor
);
480 free(u
->current_cursor
);
484 u
->input_event
= sd_event_source_unref(u
->input_event
);
487 close_journal_input(u
);
489 sd_event_source_unref(u
->sigterm_event
);
490 sd_event_source_unref(u
->sigint_event
);
491 sd_event_unref(u
->events
);
494 static int perform_upload(Uploader
*u
) {
500 u
->watchdog_timestamp
= now(CLOCK_MONOTONIC
);
501 code
= curl_easy_perform(u
->easy
);
504 log_error("Upload to %s failed: %.*s",
505 u
->url
, (int) sizeof(u
->error
), u
->error
);
507 log_error("Upload to %s failed: %s",
508 u
->url
, curl_easy_strerror(code
));
512 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
514 log_error("Failed to retrieve response code: %s",
515 curl_easy_strerror(code
));
520 log_error("Upload to %s failed with code %ld: %s",
521 u
->url
, status
, strna(u
->answer
));
523 } else if (status
< 200) {
524 log_error("Upload to %s finished with unexpected code %ld: %s",
525 u
->url
, status
, strna(u
->answer
));
528 log_debug("Upload finished successfully with code %ld: %s",
529 status
, strna(u
->answer
));
531 free_and_replace(u
->last_cursor
, u
->current_cursor
);
533 return update_cursor_state(u
);
536 static int parse_config(void) {
537 const ConfigTableItem items
[] = {
538 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
539 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
540 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
541 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
544 return config_parse_many_nulstr(PKGSYSCONFDIR
"/journal-upload.conf",
545 CONF_PATHS_NULSTR("systemd/journal-upload.conf.d"),
546 "Upload\0", config_item_table_lookup
, items
,
547 CONFIG_PARSE_WARN
, NULL
);
550 static void help(void) {
551 printf("%s -u URL {FILE|-}...\n\n"
552 "Upload journal events to a remote server.\n\n"
553 " -h --help Show this help\n"
554 " --version Show package version\n"
555 " -u --url=URL Upload to this address (default port "
556 STRINGIFY(DEFAULT_PORT
) ")\n"
557 " --key=FILENAME Specify key in PEM format (default:\n"
558 " \"" PRIV_KEY_FILE
"\")\n"
559 " --cert=FILENAME Specify certificate in PEM format (default:\n"
560 " \"" CERT_FILE
"\")\n"
561 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
562 " \"" TRUST_FILE
"\")\n"
563 " --system Use the system journal\n"
564 " --user Use the user journal for the current user\n"
565 " -m --merge Use all available journals\n"
566 " -M --machine=CONTAINER Operate on local container\n"
567 " -D --directory=PATH Use journal files from directory\n"
568 " --file=PATH Use this journal file\n"
569 " --cursor=CURSOR Start at the specified cursor\n"
570 " --after-cursor=CURSOR Start after the specified cursor\n"
571 " --follow[=BOOL] Do [not] wait for input\n"
572 " --save-state[=FILE] Save uploaded cursors (default \n"
574 , program_invocation_short_name
);
577 static int parse_argv(int argc
, char *argv
[]) {
592 static const struct option options
[] = {
593 { "help", no_argument
, NULL
, 'h' },
594 { "version", no_argument
, NULL
, ARG_VERSION
},
595 { "url", required_argument
, NULL
, 'u' },
596 { "key", required_argument
, NULL
, ARG_KEY
},
597 { "cert", required_argument
, NULL
, ARG_CERT
},
598 { "trust", required_argument
, NULL
, ARG_TRUST
},
599 { "system", no_argument
, NULL
, ARG_SYSTEM
},
600 { "user", no_argument
, NULL
, ARG_USER
},
601 { "merge", no_argument
, NULL
, 'm' },
602 { "machine", required_argument
, NULL
, 'M' },
603 { "directory", required_argument
, NULL
, 'D' },
604 { "file", required_argument
, NULL
, ARG_FILE
},
605 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
606 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
607 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
608 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
619 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
630 log_error("cannot use more than one --url");
639 log_error("cannot use more than one --key");
648 log_error("cannot use more than one --cert");
657 log_error("cannot use more than one --trust");
665 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
669 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
678 log_error("cannot use more than one --machine/-M");
682 arg_machine
= optarg
;
687 log_error("cannot use more than one --directory/-D");
691 arg_directory
= optarg
;
695 r
= glob_extend(&arg_file
, optarg
);
697 return log_error_errno(r
, "Failed to add paths: %m");
702 log_error("cannot use more than one --cursor/--after-cursor");
709 case ARG_AFTER_CURSOR
:
711 log_error("cannot use more than one --cursor/--after-cursor");
716 arg_after_cursor
= true;
721 r
= parse_boolean(optarg
);
723 log_error("Failed to parse --follow= parameter.");
734 arg_save_state
= optarg
?: STATE_FILE
;
738 log_error("Unknown option %s.", argv
[optind
-1]);
742 log_error("Missing argument to %s.", argv
[optind
-1]);
746 assert_not_reached("Unhandled option code.");
750 log_error("Required --url/-u option missing.");
754 if (!!arg_key
!= !!arg_cert
) {
755 log_error("Options --key and --cert must be used together.");
759 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
760 log_error("Input arguments make no sense with journal input.");
767 static int open_journal(sd_journal
**j
) {
771 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
773 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
774 else if (arg_machine
)
775 r
= sd_journal_open_container(j
, arg_machine
, 0);
777 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
779 log_error_errno(r
, "Failed to open %s: %m",
780 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
784 int main(int argc
, char **argv
) {
789 log_show_color(true);
790 log_parse_environment();
796 r
= parse_argv(argc
, argv
);
802 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
806 sd_event_set_watchdog(u
.events
, true);
808 r
= check_cursor_updating(&u
);
812 log_debug("%s running as pid "PID_FMT
,
813 program_invocation_short_name
, getpid_cached());
815 use_journal
= optind
>= argc
;
818 r
= open_journal(&j
);
821 r
= open_journal_for_upload(&u
, j
,
822 arg_cursor
?: u
.last_cursor
,
823 arg_cursor
? arg_after_cursor
: true,
831 "STATUS=Processing input...");
834 r
= sd_event_get_state(u
.events
);
837 if (r
== SD_EVENT_FINISHED
)
844 r
= check_journal_input(&u
);
845 } else if (u
.input
< 0 && !use_journal
) {
849 log_debug("Using %s as input.", argv
[optind
]);
850 r
= open_file_for_upload(&u
, argv
[optind
++]);
856 r
= perform_upload(&u
);
861 r
= sd_event_run(u
.events
, u
.timeout
);
863 log_error_errno(r
, "Failed to run event loop: %m");
871 "STATUS=Shutting down...");
873 destroy_uploader(&u
);
876 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;