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"
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
= NULL
;
44 static const char *arg_key
= NULL
;
45 static const char *arg_cert
= NULL
;
46 static const char *arg_trust
= NULL
;
47 static const char *arg_directory
= NULL
;
48 static char **arg_file
= NULL
;
49 static const char *arg_cursor
= NULL
;
50 static bool arg_after_cursor
= false;
51 static int arg_journal_type
= 0;
52 static const char *arg_machine
= NULL
;
53 static bool arg_merge
= false;
54 static int arg_follow
= -1;
55 static const char *arg_save_state
= NULL
;
57 static void close_fd_input(Uploader
*u
);
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_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
95 static int check_cursor_updating(Uploader
*u
) {
96 _cleanup_free_
char *temp_path
= NULL
;
97 _cleanup_fclose_
FILE *f
= NULL
;
103 r
= mkdir_parents(u
->state_file
, 0755);
105 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
108 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
110 return log_error_errno(r
, "Cannot save state to %s: %m",
117 static int update_cursor_state(Uploader
*u
) {
118 _cleanup_free_
char *temp_path
= NULL
;
119 _cleanup_fclose_
FILE *f
= NULL
;
122 if (!u
->state_file
|| !u
->last_cursor
)
125 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
130 "# This is private data. Do not parse.\n"
136 if (ferror(f
) || rename(temp_path
, u
->state_file
) < 0) {
138 unlink(u
->state_file
);
144 log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
149 static int load_cursor_state(Uploader
*u
) {
155 r
= parse_env_file(u
->state_file
, NEWLINE
,
156 "LAST_CURSOR", &u
->last_cursor
,
160 log_debug("State file %s is not present.", u
->state_file
);
162 return log_error_errno(r
, "Failed to read state file %s: %m",
165 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 struct curl_slist
*h
;
186 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
190 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
192 curl_slist_free_all(h
);
196 h
= curl_slist_append(h
, "Accept: text/plain");
198 curl_slist_free_all(h
);
208 curl
= curl_easy_init();
210 log_error("Call to curl_easy_init failed.");
214 /* tell it to POST to the URL */
215 easy_setopt(curl
, CURLOPT_POST
, 1L,
216 LOG_ERR
, return -EXFULL
);
218 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
219 LOG_ERR
, return -EXFULL
);
221 /* set where to write to */
222 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
223 LOG_ERR
, return -EXFULL
);
225 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
226 LOG_ERR
, return -EXFULL
);
228 /* set where to read from */
229 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
230 LOG_ERR
, return -EXFULL
);
232 easy_setopt(curl
, CURLOPT_READDATA
, data
,
233 LOG_ERR
, return -EXFULL
);
235 /* use our special own mime type and chunked transfer */
236 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
237 LOG_ERR
, return -EXFULL
);
239 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
240 /* enable verbose for easier tracing */
241 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
243 easy_setopt(curl
, CURLOPT_USERAGENT
,
244 "systemd-journal-upload " PACKAGE_STRING
,
247 if (arg_key
|| startswith(u
->url
, "https://")) {
248 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
249 LOG_ERR
, return -EXFULL
);
250 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
251 LOG_ERR
, return -EXFULL
);
254 if (streq_ptr(arg_trust
, "all"))
255 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
256 LOG_ERR
, return -EUCLEAN
);
257 else if (arg_trust
|| startswith(u
->url
, "https://"))
258 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
259 LOG_ERR
, return -EXFULL
);
261 if (arg_key
|| arg_trust
)
262 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
267 /* truncate the potential old error message */
274 /* upload to this place */
275 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
277 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
278 curl_easy_strerror(code
));
287 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
293 assert(nmemb
<= SSIZE_MAX
/ size
);
298 r
= read(u
->input
, buf
, size
* nmemb
);
299 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
304 u
->uploading
= false;
306 log_debug("Reached EOF");
310 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
311 return CURL_READFUNC_ABORT
;
315 static void close_fd_input(Uploader
*u
) {
319 close_nointr(u
->input
);
324 static int dispatch_fd_input(sd_event_source
*event
,
333 if (revents
& EPOLLHUP
) {
334 log_debug("Received HUP");
339 if (!(revents
& EPOLLIN
)) {
340 log_warning("Unexpected poll event %"PRIu32
".", revents
);
345 log_warning("dispatch_fd_input called when uploading, ignoring.");
349 return start_upload(u
, fd_input_callback
, u
);
352 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
355 if (streq(filename
, "-"))
358 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
360 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
366 r
= sd_event_add_io(u
->events
, &u
->input_event
,
367 fd
, EPOLLIN
, dispatch_fd_input
, u
);
369 if (r
!= -EPERM
|| arg_follow
> 0)
370 return log_error_errno(r
, "Failed to register input event: %m");
372 /* Normal files should just be consumed without polling. */
373 r
= start_upload(u
, fd_input_callback
, u
);
380 static int dispatch_sigterm(sd_event_source
*event
,
381 const struct signalfd_siginfo
*si
,
383 Uploader
*u
= userdata
;
387 log_received_signal(LOG_INFO
, si
);
390 close_journal_input(u
);
392 sd_event_exit(u
->events
, 0);
396 static int setup_signals(Uploader
*u
) {
402 assert_se(sigemptyset(&mask
) == 0);
403 sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1);
404 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) == 0);
406 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
410 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
417 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
419 const char *host
, *proto
= "";
424 memzero(u
, sizeof(Uploader
));
427 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
432 if (strchr(host
, ':'))
433 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
440 while (x
> 0 && t
[x
- 1] == '/')
443 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
448 u
->state_file
= state_file
;
450 r
= sd_event_default(&u
->events
);
452 return log_error_errno(r
, "sd_event_default failed: %m");
454 r
= setup_signals(u
);
456 return log_error_errno(r
, "Failed to set up signals: %m");
458 return load_cursor_state(u
);
461 static void destroy_uploader(Uploader
*u
) {
464 curl_easy_cleanup(u
->easy
);
465 curl_slist_free_all(u
->header
);
468 free(u
->last_cursor
);
469 free(u
->current_cursor
);
473 u
->input_event
= sd_event_source_unref(u
->input_event
);
476 close_journal_input(u
);
478 sd_event_source_unref(u
->sigterm_event
);
479 sd_event_source_unref(u
->sigint_event
);
480 sd_event_unref(u
->events
);
483 static int perform_upload(Uploader
*u
) {
489 code
= curl_easy_perform(u
->easy
);
492 log_error("Upload to %s failed: %.*s",
493 u
->url
, (int) sizeof(u
->error
), u
->error
);
495 log_error("Upload to %s failed: %s",
496 u
->url
, curl_easy_strerror(code
));
500 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
502 log_error("Failed to retrieve response code: %s",
503 curl_easy_strerror(code
));
508 log_error("Upload to %s failed with code %ld: %s",
509 u
->url
, status
, strna(u
->answer
));
511 } else if (status
< 200) {
512 log_error("Upload to %s finished with unexpected code %ld: %s",
513 u
->url
, status
, strna(u
->answer
));
516 log_debug("Upload finished successfully with code %ld: %s",
517 status
, strna(u
->answer
));
519 free(u
->last_cursor
);
520 u
->last_cursor
= u
->current_cursor
;
521 u
->current_cursor
= NULL
;
523 return update_cursor_state(u
);
526 static int parse_config(void) {
527 const ConfigTableItem items
[] = {
528 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
529 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
530 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
531 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
534 return config_parse_many(PKGSYSCONFDIR
"/journal-upload.conf",
535 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
536 "Upload\0", config_item_table_lookup
, items
,
540 static void help(void) {
541 printf("%s -u URL {FILE|-}...\n\n"
542 "Upload journal events to a remote server.\n\n"
543 " -h --help Show this help\n"
544 " --version Show package version\n"
545 " -u --url=URL Upload to this address (default port "
546 STRINGIFY(DEFAULT_PORT
) ")\n"
547 " --key=FILENAME Specify key in PEM format (default:\n"
548 " \"" PRIV_KEY_FILE
"\")\n"
549 " --cert=FILENAME Specify certificate in PEM format (default:\n"
550 " \"" CERT_FILE
"\")\n"
551 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
552 " \"" TRUST_FILE
"\")\n"
553 " --system Use the system journal\n"
554 " --user Use the user journal for the current user\n"
555 " -m --merge Use all available journals\n"
556 " -M --machine=CONTAINER Operate on local container\n"
557 " -D --directory=PATH Use journal files from directory\n"
558 " --file=PATH Use this journal file\n"
559 " --cursor=CURSOR Start at the specified cursor\n"
560 " --after-cursor=CURSOR Start after the specified cursor\n"
561 " --follow[=BOOL] Do [not] wait for input\n"
562 " --save-state[=FILE] Save uploaded cursors (default \n"
564 " -h --help Show this help and exit\n"
565 " --version Print version string and exit\n"
566 , program_invocation_short_name
);
569 static int parse_argv(int argc
, char *argv
[]) {
584 static const struct option options
[] = {
585 { "help", no_argument
, NULL
, 'h' },
586 { "version", no_argument
, NULL
, ARG_VERSION
},
587 { "url", required_argument
, NULL
, 'u' },
588 { "key", required_argument
, NULL
, ARG_KEY
},
589 { "cert", required_argument
, NULL
, ARG_CERT
},
590 { "trust", required_argument
, NULL
, ARG_TRUST
},
591 { "system", no_argument
, NULL
, ARG_SYSTEM
},
592 { "user", no_argument
, NULL
, ARG_USER
},
593 { "merge", no_argument
, NULL
, 'm' },
594 { "machine", required_argument
, NULL
, 'M' },
595 { "directory", required_argument
, NULL
, 'D' },
596 { "file", required_argument
, NULL
, ARG_FILE
},
597 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
598 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
599 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
600 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
611 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
618 puts(PACKAGE_STRING
);
619 puts(SYSTEMD_FEATURES
);
624 log_error("cannot use more than one --url");
633 log_error("cannot use more than one --key");
642 log_error("cannot use more than one --cert");
651 log_error("cannot use more than one --trust");
659 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
663 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
672 log_error("cannot use more than one --machine/-M");
676 arg_machine
= optarg
;
681 log_error("cannot use more than one --directory/-D");
685 arg_directory
= optarg
;
689 r
= glob_extend(&arg_file
, optarg
);
691 return log_error_errno(r
, "Failed to add paths: %m");
696 log_error("cannot use more than one --cursor/--after-cursor");
703 case ARG_AFTER_CURSOR
:
705 log_error("cannot use more than one --cursor/--after-cursor");
710 arg_after_cursor
= true;
715 r
= parse_boolean(optarg
);
717 log_error("Failed to parse --follow= parameter.");
728 arg_save_state
= optarg
?: STATE_FILE
;
732 log_error("Unknown option %s.", argv
[optind
-1]);
736 log_error("Missing argument to %s.", argv
[optind
-1]);
740 assert_not_reached("Unhandled option code.");
744 log_error("Required --url/-u option missing.");
748 if (!!arg_key
!= !!arg_cert
) {
749 log_error("Options --key and --cert must be used together.");
753 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
754 log_error("Input arguments make no sense with journal input.");
761 static int open_journal(sd_journal
**j
) {
765 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
767 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
768 else if (arg_machine
)
769 r
= sd_journal_open_container(j
, arg_machine
, 0);
771 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
773 log_error_errno(r
, "Failed to open %s: %m",
774 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
778 int main(int argc
, char **argv
) {
783 log_show_color(true);
784 log_parse_environment();
790 r
= parse_argv(argc
, argv
);
796 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
800 sd_event_set_watchdog(u
.events
, true);
802 r
= check_cursor_updating(&u
);
806 log_debug("%s running as pid "PID_FMT
,
807 program_invocation_short_name
, getpid());
809 use_journal
= optind
>= argc
;
812 r
= open_journal(&j
);
815 r
= open_journal_for_upload(&u
, j
,
816 arg_cursor
?: u
.last_cursor
,
817 arg_cursor
? arg_after_cursor
: true,
825 "STATUS=Processing input...");
828 r
= sd_event_get_state(u
.events
);
831 if (r
== SD_EVENT_FINISHED
)
838 r
= check_journal_input(&u
);
839 } else if (u
.input
< 0 && !use_journal
) {
843 log_debug("Using %s as input.", argv
[optind
]);
844 r
= open_file_for_upload(&u
, argv
[optind
++]);
850 r
= perform_upload(&u
);
855 r
= sd_event_run(u
.events
, u
.timeout
);
857 log_error_errno(r
, "Failed to run event loop: %m");
865 "STATUS=Shutting down...");
867 destroy_uploader(&u
);
870 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;