1 /* SPDX-License-Identifier: LGPL-2.1-or-later */
10 #include "sd-daemon.h"
12 #include "alloc-util.h"
14 #include "conf-parser.h"
15 #include "constants.h"
16 #include "daemon-util.h"
20 #include "format-util.h"
22 #include "glob-util.h"
23 #include "journal-upload.h"
25 #include "main-func.h"
27 #include "parse-argument.h"
28 #include "parse-helpers.h"
29 #include "pretty-print.h"
30 #include "process-util.h"
31 #include "rlimit-util.h"
33 #include "signal-util.h"
34 #include "string-util.h"
36 #include "tmpfile-util.h"
39 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
40 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
41 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
42 #define DEFAULT_PORT 19532
44 static const char* arg_url
= NULL
;
45 static const char *arg_key
= NULL
;
46 static const char *arg_cert
= NULL
;
47 static const char *arg_trust
= NULL
;
48 static const char *arg_directory
= NULL
;
49 static char **arg_file
= NULL
;
50 static const char *arg_cursor
= NULL
;
51 static bool arg_after_cursor
= false;
52 static int arg_journal_type
= 0;
53 static const char *arg_machine
= NULL
;
54 static bool arg_merge
= false;
55 static int arg_follow
= -1;
56 static const char *arg_save_state
= NULL
;
57 static usec_t arg_network_timeout_usec
= USEC_INFINITY
;
59 static void close_fd_input(Uploader
*u
);
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 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(CURL
*, curl_easy_cleanup
, NULL
);
77 DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(struct curl_slist
*, curl_slist_free_all
, NULL
);
79 static size_t output_callback(char *buf
,
83 Uploader
*u
= ASSERT_PTR(userp
);
85 log_debug("The server answers (%zu bytes): %.*s",
86 size
*nmemb
, (int)(size
*nmemb
), buf
);
88 if (nmemb
&& !u
->answer
) {
89 u
->answer
= strndup(buf
, size
*nmemb
);
91 log_warning("Failed to store server answer (%zu bytes): out of memory", size
*nmemb
);
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 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
110 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
112 return log_error_errno(r
, "Cannot save state to %s: %m",
114 (void) unlink(temp_path
);
119 static int update_cursor_state(Uploader
*u
) {
120 _cleanup_(unlink_and_freep
) char *temp_path
= NULL
;
121 _cleanup_fclose_
FILE *f
= NULL
;
124 if (!u
->state_file
|| !u
->last_cursor
)
127 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
132 "# This is private data. Do not parse.\n"
136 r
= fflush_and_check(f
);
140 if (rename(temp_path
, u
->state_file
) < 0) {
145 temp_path
= mfree(temp_path
);
149 (void) unlink(u
->state_file
);
151 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
154 static int load_cursor_state(Uploader
*u
) {
160 r
= parse_env_file(NULL
, u
->state_file
, "LAST_CURSOR", &u
->last_cursor
);
162 log_debug("State file %s is not present.", u
->state_file
);
164 return log_error_errno(r
, "Failed to read state file %s: %m",
167 log_debug("Last cursor was %s", u
->last_cursor
);
172 int start_upload(Uploader
*u
,
173 size_t (*input_callback
)(void *ptr
,
181 assert(input_callback
);
184 _cleanup_(curl_slist_free_allp
) struct curl_slist
*h
= NULL
;
185 struct curl_slist
*l
;
187 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
191 l
= curl_slist_append(h
, "Transfer-Encoding: chunked");
196 l
= curl_slist_append(h
, "Accept: text/plain");
201 u
->header
= TAKE_PTR(h
);
205 _cleanup_(curl_easy_cleanupp
) CURL
*curl
= NULL
;
207 curl
= curl_easy_init();
209 return log_error_errno(SYNTHETIC_ERRNO(ENOSR
),
210 "Call to curl_easy_init failed.");
212 /* If configured, set a timeout for the curl operation. */
213 if (arg_network_timeout_usec
!= USEC_INFINITY
)
214 easy_setopt(curl
, CURLOPT_TIMEOUT
,
215 (long) DIV_ROUND_UP(arg_network_timeout_usec
, USEC_PER_SEC
),
216 LOG_ERR
, return -EXFULL
);
218 /* tell it to POST to the URL */
219 easy_setopt(curl
, CURLOPT_POST
, 1L,
220 LOG_ERR
, return -EXFULL
);
222 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
223 LOG_ERR
, return -EXFULL
);
225 /* set where to write to */
226 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
227 LOG_ERR
, return -EXFULL
);
229 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
230 LOG_ERR
, return -EXFULL
);
232 /* set where to read from */
233 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
234 LOG_ERR
, return -EXFULL
);
236 easy_setopt(curl
, CURLOPT_READDATA
, data
,
237 LOG_ERR
, return -EXFULL
);
239 /* use our special own mime type and chunked transfer */
240 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
241 LOG_ERR
, return -EXFULL
);
244 /* enable verbose for easier tracing */
245 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
247 easy_setopt(curl
, CURLOPT_USERAGENT
,
248 "systemd-journal-upload " GIT_VERSION
,
251 if (!streq_ptr(arg_key
, "-") && (arg_key
|| startswith(u
->url
, "https://"))) {
252 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
253 LOG_ERR
, return -EXFULL
);
254 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
255 LOG_ERR
, return -EXFULL
);
258 if (STRPTR_IN_SET(arg_trust
, "-", "all"))
259 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
260 LOG_ERR
, return -EUCLEAN
);
261 else if (arg_trust
|| startswith(u
->url
, "https://"))
262 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
263 LOG_ERR
, return -EXFULL
);
265 if (arg_key
|| arg_trust
)
266 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
269 u
->easy
= TAKE_PTR(curl
);
271 /* truncate the potential old error message */
278 /* upload to this place */
279 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
281 return log_error_errno(SYNTHETIC_ERRNO(EXFULL
),
282 "curl_easy_setopt CURLOPT_URL failed: %s",
283 curl_easy_strerror(code
));
290 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
291 Uploader
*u
= ASSERT_PTR(userp
);
294 assert(nmemb
< SSIZE_MAX
/ size
);
299 assert(!size_multiply_overflow(size
, nmemb
));
301 n
= read(u
->input
, buf
, size
* nmemb
);
302 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, n
);
306 u
->uploading
= false;
308 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
309 return CURL_READFUNC_ABORT
;
312 log_debug("Reached EOF");
317 static void close_fd_input(Uploader
*u
) {
320 u
->input
= safe_close(u
->input
);
324 static int dispatch_fd_input(sd_event_source
*event
,
328 Uploader
*u
= ASSERT_PTR(userp
);
332 if (revents
& EPOLLHUP
) {
333 log_debug("Received HUP");
338 if (!(revents
& EPOLLIN
)) {
339 log_warning("Unexpected poll event %"PRIu32
".", revents
);
344 log_warning("dispatch_fd_input called when uploading, ignoring.");
348 return start_upload(u
, fd_input_callback
, u
);
351 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
354 if (streq(filename
, "-"))
357 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
359 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
364 if (arg_follow
!= 0) {
365 r
= sd_event_add_io(u
->events
, &u
->input_event
,
366 fd
, EPOLLIN
, dispatch_fd_input
, u
);
368 if (r
!= -EPERM
|| arg_follow
> 0)
369 return log_error_errno(r
, "Failed to register input event: %m");
371 /* Normal files should just be consumed without polling. */
372 r
= start_upload(u
, fd_input_callback
, u
);
379 static int dispatch_sigterm(sd_event_source
*event
,
380 const struct signalfd_siginfo
*si
,
382 Uploader
*u
= ASSERT_PTR(userdata
);
384 log_received_signal(LOG_INFO
, si
);
387 close_journal_input(u
);
389 sd_event_exit(u
->events
, 0);
393 static int setup_signals(Uploader
*u
) {
398 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
400 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
404 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
411 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
413 const char *host
, *proto
= "";
422 host
= STARTSWITH_SET(url
, "http://", "https://");
428 if (strchr(host
, ':'))
429 u
->url
= strjoin(proto
, url
, "/upload");
434 t
= strdupa_safe(url
);
436 while (x
> 0 && t
[x
- 1] == '/')
439 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload");
444 u
->state_file
= state_file
;
446 r
= sd_event_default(&u
->events
);
448 return log_error_errno(r
, "sd_event_default failed: %m");
450 r
= setup_signals(u
);
452 return log_error_errno(r
, "Failed to set up signals: %m");
454 (void) sd_watchdog_enabled(false, &u
->watchdog_usec
);
456 return load_cursor_state(u
);
459 static void destroy_uploader(Uploader
*u
) {
462 curl_easy_cleanup(u
->easy
);
463 curl_slist_free_all(u
->header
);
466 free(u
->last_cursor
);
467 free(u
->current_cursor
);
471 u
->input_event
= sd_event_source_unref(u
->input_event
);
474 close_journal_input(u
);
476 sd_event_source_unref(u
->sigterm_event
);
477 sd_event_source_unref(u
->sigint_event
);
478 sd_event_unref(u
->events
);
481 static int perform_upload(Uploader
*u
) {
487 u
->watchdog_timestamp
= now(CLOCK_MONOTONIC
);
488 code
= curl_easy_perform(u
->easy
);
491 log_error("Upload to %s failed: %.*s",
492 u
->url
, (int) sizeof(u
->error
), u
->error
);
494 log_error("Upload to %s failed: %s",
495 u
->url
, curl_easy_strerror(code
));
499 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
501 return log_error_errno(SYNTHETIC_ERRNO(EUCLEAN
),
502 "Failed to retrieve response code: %s",
503 curl_easy_strerror(code
));
506 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
507 "Upload to %s failed with code %ld: %s",
508 u
->url
, status
, strna(u
->answer
));
509 else if (status
< 200)
510 return log_error_errno(SYNTHETIC_ERRNO(EIO
),
511 "Upload to %s finished with unexpected code %ld: %s",
512 u
->url
, status
, strna(u
->answer
));
514 log_debug("Upload finished successfully with code %ld: %s",
515 status
, strna(u
->answer
));
517 free_and_replace(u
->last_cursor
, u
->current_cursor
);
519 return update_cursor_state(u
);
522 static int config_parse_path_or_ignore(
524 const char *filename
,
527 unsigned section_line
,
534 _cleanup_free_
char *n
= NULL
;
536 char **s
= ASSERT_PTR(data
);
553 r
= path_simplify_and_warn(n
, PATH_CHECK_ABSOLUTE
| (fatal
? PATH_CHECK_FATAL
: 0), unit
, filename
, line
, lvalue
);
555 return fatal
? -ENOEXEC
: 0;
558 return free_and_replace(*s
, n
);
561 static int parse_config(void) {
562 const ConfigTableItem items
[] = {
563 { "Upload", "URL", config_parse_string
, CONFIG_PARSE_STRING_SAFE
, &arg_url
},
564 { "Upload", "ServerKeyFile", config_parse_path_or_ignore
, 0, &arg_key
},
565 { "Upload", "ServerCertificateFile", config_parse_path_or_ignore
, 0, &arg_cert
},
566 { "Upload", "TrustedCertificateFile", config_parse_path_or_ignore
, 0, &arg_trust
},
567 { "Upload", "NetworkTimeoutSec", config_parse_sec
, 0, &arg_network_timeout_usec
},
571 return config_parse_config_file("journal-upload.conf", "Upload\0",
572 config_item_table_lookup
, items
,
573 CONFIG_PARSE_WARN
, NULL
);
576 static int help(void) {
577 _cleanup_free_
char *link
= NULL
;
580 r
= terminal_urlify_man("systemd-journal-upload.service", "8", &link
);
584 printf("%s -u URL {FILE|-}...\n\n"
585 "Upload journal events to a remote server.\n\n"
586 " -h --help Show this help\n"
587 " --version Show package version\n"
588 " -u --url=URL Upload to this address (default port "
589 STRINGIFY(DEFAULT_PORT
) ")\n"
590 " --key=FILENAME Specify key in PEM format (default:\n"
591 " \"" PRIV_KEY_FILE
"\")\n"
592 " --cert=FILENAME Specify certificate in PEM format (default:\n"
593 " \"" CERT_FILE
"\")\n"
594 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
595 " \"" TRUST_FILE
"\")\n"
596 " --system Use the system journal\n"
597 " --user Use the user journal for the current user\n"
598 " -m --merge Use all available journals\n"
599 " -M --machine=CONTAINER Operate on local container\n"
600 " -D --directory=PATH Use journal files from directory\n"
601 " --file=PATH Use this journal file\n"
602 " --cursor=CURSOR Start at the specified cursor\n"
603 " --after-cursor=CURSOR Start after the specified cursor\n"
604 " --follow[=BOOL] Do [not] wait for input\n"
605 " --save-state[=FILE] Save uploaded cursors (default \n"
607 "\nSee the %s for details.\n",
608 program_invocation_short_name
,
614 static int parse_argv(int argc
, char *argv
[]) {
629 static const struct option options
[] = {
630 { "help", no_argument
, NULL
, 'h' },
631 { "version", no_argument
, NULL
, ARG_VERSION
},
632 { "url", required_argument
, NULL
, 'u' },
633 { "key", required_argument
, NULL
, ARG_KEY
},
634 { "cert", required_argument
, NULL
, ARG_CERT
},
635 { "trust", required_argument
, NULL
, ARG_TRUST
},
636 { "system", no_argument
, NULL
, ARG_SYSTEM
},
637 { "user", no_argument
, NULL
, ARG_USER
},
638 { "merge", no_argument
, NULL
, 'm' },
639 { "machine", required_argument
, NULL
, 'M' },
640 { "directory", required_argument
, NULL
, 'D' },
641 { "file", required_argument
, NULL
, ARG_FILE
},
642 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
643 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
644 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
645 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
656 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
666 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
667 "cannot use more than one --url");
674 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
675 "cannot use more than one --key");
682 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
683 "cannot use more than one --cert");
690 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
691 "cannot use more than one --trust");
697 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
701 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
710 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
711 "cannot use more than one --machine/-M");
713 arg_machine
= optarg
;
718 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
719 "cannot use more than one --directory/-D");
721 arg_directory
= optarg
;
725 r
= glob_extend(&arg_file
, optarg
, GLOB_NOCHECK
);
727 return log_error_errno(r
, "Failed to add paths: %m");
732 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
733 "cannot use more than one --cursor/--after-cursor");
738 case ARG_AFTER_CURSOR
:
740 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
741 "cannot use more than one --cursor/--after-cursor");
744 arg_after_cursor
= true;
748 r
= parse_boolean_argument("--follow", optarg
, NULL
);
755 arg_save_state
= optarg
?: STATE_FILE
;
759 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
760 "Unknown option %s.",
764 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
765 "Missing argument to %s.",
769 assert_not_reached();
773 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
774 "Required --url/-u option missing.");
776 if (!!arg_key
!= !!arg_cert
)
777 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
778 "Options --key and --cert must be used together.");
780 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
))
781 return log_error_errno(SYNTHETIC_ERRNO(EINVAL
),
782 "Input arguments make no sense with journal input.");
787 static int open_journal(sd_journal
**j
) {
791 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
793 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
794 else if (arg_machine
) {
795 #pragma GCC diagnostic push
796 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
797 /* FIXME: replace with D-Bus call OpenMachineRootDirectory() so that things also work with raw disk images */
798 r
= sd_journal_open_container(j
, arg_machine
, 0);
799 #pragma GCC diagnostic pop
801 r
= sd_journal_open(j
, (arg_merge
? 0 : SD_JOURNAL_LOCAL_ONLY
) | arg_journal_type
);
803 log_error_errno(r
, "Failed to open %s: %m",
804 arg_directory
?: (arg_file
? "files" : "journal"));
808 static int run(int argc
, char **argv
) {
809 _cleanup_(destroy_uploader
) Uploader u
= {};
810 _unused_
_cleanup_(notify_on_cleanup
) const char *notify_message
= NULL
;
814 log_show_color(true);
815 log_parse_environment();
817 /* The journal merging logic potentially needs a lot of fds. */
818 (void) rlimit_nofile_bump(HIGH_RLIMIT_NOFILE
);
824 r
= parse_argv(argc
, argv
);
830 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
834 sd_event_set_watchdog(u
.events
, true);
836 r
= check_cursor_updating(&u
);
840 log_debug("%s running as pid "PID_FMT
,
841 program_invocation_short_name
, getpid_cached());
843 use_journal
= optind
>= argc
;
846 r
= open_journal(&j
);
849 r
= open_journal_for_upload(&u
, j
,
850 arg_cursor
?: u
.last_cursor
,
851 arg_cursor
? arg_after_cursor
: true,
857 notify_message
= notify_start("READY=1\n"
858 "STATUS=Processing input...",
862 r
= sd_event_get_state(u
.events
);
865 if (r
== SD_EVENT_FINISHED
)
872 r
= check_journal_input(&u
);
873 } else if (u
.input
< 0 && !use_journal
) {
877 log_debug("Using %s as input.", argv
[optind
]);
878 r
= open_file_for_upload(&u
, argv
[optind
++]);
884 r
= perform_upload(&u
);
889 r
= sd_event_run(u
.events
, u
.timeout
);
891 return log_error_errno(r
, "Failed to run event loop: %m");
895 DEFINE_MAIN_FUNCTION(run
);