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 "formats-util.h"
37 #include "signal-util.h"
38 #include "journal-upload.h"
40 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
41 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
42 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
43 #define DEFAULT_PORT 19532
45 static const char* arg_url
= NULL
;
46 static const char *arg_key
= NULL
;
47 static const char *arg_cert
= NULL
;
48 static const char *arg_trust
= NULL
;
49 static const char *arg_directory
= NULL
;
50 static char **arg_file
= NULL
;
51 static const char *arg_cursor
= NULL
;
52 static bool arg_after_cursor
= false;
53 static int arg_journal_type
= 0;
54 static const char *arg_machine
= NULL
;
55 static bool arg_merge
= false;
56 static int arg_follow
= -1;
57 static const char *arg_save_state
= NULL
;
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 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_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
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",
119 static int update_cursor_state(Uploader
*u
) {
120 _cleanup_free_
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"
138 if (ferror(f
) || rename(temp_path
, u
->state_file
) < 0) {
140 unlink(u
->state_file
);
146 log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
151 static int load_cursor_state(Uploader
*u
) {
157 r
= parse_env_file(u
->state_file
, NEWLINE
,
158 "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
);
174 int start_upload(Uploader
*u
,
175 size_t (*input_callback
)(void *ptr
,
183 assert(input_callback
);
186 struct curl_slist
*h
;
188 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
192 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
194 curl_slist_free_all(h
);
198 h
= curl_slist_append(h
, "Accept: text/plain");
200 curl_slist_free_all(h
);
210 curl
= curl_easy_init();
212 log_error("Call to curl_easy_init failed.");
216 /* tell it to POST to the URL */
217 easy_setopt(curl
, CURLOPT_POST
, 1L,
218 LOG_ERR
, return -EXFULL
);
220 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
221 LOG_ERR
, return -EXFULL
);
223 /* set where to write to */
224 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
225 LOG_ERR
, return -EXFULL
);
227 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
228 LOG_ERR
, return -EXFULL
);
230 /* set where to read from */
231 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
232 LOG_ERR
, return -EXFULL
);
234 easy_setopt(curl
, CURLOPT_READDATA
, data
,
235 LOG_ERR
, return -EXFULL
);
237 /* use our special own mime type and chunked transfer */
238 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
239 LOG_ERR
, return -EXFULL
);
241 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
242 /* enable verbose for easier tracing */
243 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
245 easy_setopt(curl
, CURLOPT_USERAGENT
,
246 "systemd-journal-upload " PACKAGE_STRING
,
249 if (arg_key
|| startswith(u
->url
, "https://")) {
250 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
251 LOG_ERR
, return -EXFULL
);
252 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
253 LOG_ERR
, return -EXFULL
);
256 if (streq_ptr(arg_trust
, "all"))
257 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
258 LOG_ERR
, return -EUCLEAN
);
259 else if (arg_trust
|| startswith(u
->url
, "https://"))
260 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
261 LOG_ERR
, return -EXFULL
);
263 if (arg_key
|| arg_trust
)
264 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
269 /* truncate the potential old error message */
276 /* upload to this place */
277 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
279 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
280 curl_easy_strerror(code
));
289 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
295 assert(nmemb
<= SSIZE_MAX
/ size
);
300 r
= read(u
->input
, buf
, size
* nmemb
);
301 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
306 u
->uploading
= false;
308 log_debug("Reached EOF");
312 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
313 return CURL_READFUNC_ABORT
;
317 static void close_fd_input(Uploader
*u
) {
321 close_nointr(u
->input
);
326 static int dispatch_fd_input(sd_event_source
*event
,
335 if (revents
& EPOLLHUP
) {
336 log_debug("Received HUP");
341 if (!(revents
& EPOLLIN
)) {
342 log_warning("Unexpected poll event %"PRIu32
".", revents
);
347 log_warning("dispatch_fd_input called when uploading, ignoring.");
351 return start_upload(u
, fd_input_callback
, u
);
354 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
357 if (streq(filename
, "-"))
360 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
362 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
368 r
= sd_event_add_io(u
->events
, &u
->input_event
,
369 fd
, EPOLLIN
, dispatch_fd_input
, u
);
371 if (r
!= -EPERM
|| arg_follow
> 0)
372 return log_error_errno(r
, "Failed to register input event: %m");
374 /* Normal files should just be consumed without polling. */
375 r
= start_upload(u
, fd_input_callback
, u
);
382 static int dispatch_sigterm(sd_event_source
*event
,
383 const struct signalfd_siginfo
*si
,
385 Uploader
*u
= userdata
;
389 log_received_signal(LOG_INFO
, si
);
392 close_journal_input(u
);
394 sd_event_exit(u
->events
, 0);
398 static int setup_signals(Uploader
*u
) {
404 assert_se(sigemptyset(&mask
) == 0);
405 sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1);
406 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) == 0);
408 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
412 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
419 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
421 const char *host
, *proto
= "";
426 memzero(u
, sizeof(Uploader
));
429 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
434 if (strchr(host
, ':'))
435 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
442 while (x
> 0 && t
[x
- 1] == '/')
445 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
450 u
->state_file
= state_file
;
452 r
= sd_event_default(&u
->events
);
454 return log_error_errno(r
, "sd_event_default failed: %m");
456 r
= setup_signals(u
);
458 return log_error_errno(r
, "Failed to set up signals: %m");
460 return load_cursor_state(u
);
463 static void destroy_uploader(Uploader
*u
) {
466 curl_easy_cleanup(u
->easy
);
467 curl_slist_free_all(u
->header
);
470 free(u
->last_cursor
);
471 free(u
->current_cursor
);
475 u
->input_event
= sd_event_source_unref(u
->input_event
);
478 close_journal_input(u
);
480 sd_event_source_unref(u
->sigterm_event
);
481 sd_event_source_unref(u
->sigint_event
);
482 sd_event_unref(u
->events
);
485 static int perform_upload(Uploader
*u
) {
491 code
= curl_easy_perform(u
->easy
);
494 log_error("Upload to %s failed: %.*s",
495 u
->url
, (int) sizeof(u
->error
), u
->error
);
497 log_error("Upload to %s failed: %s",
498 u
->url
, curl_easy_strerror(code
));
502 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
504 log_error("Failed to retrieve response code: %s",
505 curl_easy_strerror(code
));
510 log_error("Upload to %s failed with code %ld: %s",
511 u
->url
, status
, strna(u
->answer
));
513 } else if (status
< 200) {
514 log_error("Upload to %s finished with unexpected code %ld: %s",
515 u
->url
, status
, strna(u
->answer
));
518 log_debug("Upload finished successfully with code %ld: %s",
519 status
, strna(u
->answer
));
521 free(u
->last_cursor
);
522 u
->last_cursor
= u
->current_cursor
;
523 u
->current_cursor
= NULL
;
525 return update_cursor_state(u
);
528 static int parse_config(void) {
529 const ConfigTableItem items
[] = {
530 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
531 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
532 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
533 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
536 return config_parse_many(PKGSYSCONFDIR
"/journal-upload.conf",
537 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
538 "Upload\0", config_item_table_lookup
, items
,
542 static void help(void) {
543 printf("%s -u URL {FILE|-}...\n\n"
544 "Upload journal events to a remote server.\n\n"
545 " -h --help Show this help\n"
546 " --version Show package version\n"
547 " -u --url=URL Upload to this address (default port "
548 STRINGIFY(DEFAULT_PORT
) ")\n"
549 " --key=FILENAME Specify key in PEM format (default:\n"
550 " \"" PRIV_KEY_FILE
"\")\n"
551 " --cert=FILENAME Specify certificate in PEM format (default:\n"
552 " \"" CERT_FILE
"\")\n"
553 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
554 " \"" TRUST_FILE
"\")\n"
555 " --system Use the system journal\n"
556 " --user Use the user journal for the current user\n"
557 " -m --merge Use all available journals\n"
558 " -M --machine=CONTAINER Operate on local container\n"
559 " -D --directory=PATH Use journal files from directory\n"
560 " --file=PATH Use this journal file\n"
561 " --cursor=CURSOR Start at the specified cursor\n"
562 " --after-cursor=CURSOR Start after the specified cursor\n"
563 " --follow[=BOOL] Do [not] wait for input\n"
564 " --save-state[=FILE] Save uploaded cursors (default \n"
566 " -h --help Show this help and exit\n"
567 " --version Print version string and exit\n"
568 , program_invocation_short_name
);
571 static int parse_argv(int argc
, char *argv
[]) {
586 static const struct option options
[] = {
587 { "help", no_argument
, NULL
, 'h' },
588 { "version", no_argument
, NULL
, ARG_VERSION
},
589 { "url", required_argument
, NULL
, 'u' },
590 { "key", required_argument
, NULL
, ARG_KEY
},
591 { "cert", required_argument
, NULL
, ARG_CERT
},
592 { "trust", required_argument
, NULL
, ARG_TRUST
},
593 { "system", no_argument
, NULL
, ARG_SYSTEM
},
594 { "user", no_argument
, NULL
, ARG_USER
},
595 { "merge", no_argument
, NULL
, 'm' },
596 { "machine", required_argument
, NULL
, 'M' },
597 { "directory", required_argument
, NULL
, 'D' },
598 { "file", required_argument
, NULL
, ARG_FILE
},
599 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
600 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
601 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
602 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
613 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
620 puts(PACKAGE_STRING
);
621 puts(SYSTEMD_FEATURES
);
626 log_error("cannot use more than one --url");
635 log_error("cannot use more than one --key");
644 log_error("cannot use more than one --cert");
653 log_error("cannot use more than one --trust");
661 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
665 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
674 log_error("cannot use more than one --machine/-M");
678 arg_machine
= optarg
;
683 log_error("cannot use more than one --directory/-D");
687 arg_directory
= optarg
;
691 r
= glob_extend(&arg_file
, optarg
);
693 return log_error_errno(r
, "Failed to add paths: %m");
698 log_error("cannot use more than one --cursor/--after-cursor");
705 case ARG_AFTER_CURSOR
:
707 log_error("cannot use more than one --cursor/--after-cursor");
712 arg_after_cursor
= true;
717 r
= parse_boolean(optarg
);
719 log_error("Failed to parse --follow= parameter.");
730 arg_save_state
= optarg
?: STATE_FILE
;
734 log_error("Unknown option %s.", argv
[optind
-1]);
738 log_error("Missing argument to %s.", argv
[optind
-1]);
742 assert_not_reached("Unhandled option code.");
746 log_error("Required --url/-u option missing.");
750 if (!!arg_key
!= !!arg_cert
) {
751 log_error("Options --key and --cert must be used together.");
755 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
756 log_error("Input arguments make no sense with journal input.");
763 static int open_journal(sd_journal
**j
) {
767 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
769 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
770 else if (arg_machine
)
771 r
= sd_journal_open_container(j
, arg_machine
, 0);
773 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
775 log_error_errno(r
, "Failed to open %s: %m",
776 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
780 int main(int argc
, char **argv
) {
785 log_show_color(true);
786 log_parse_environment();
792 r
= parse_argv(argc
, argv
);
798 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
802 sd_event_set_watchdog(u
.events
, true);
804 r
= check_cursor_updating(&u
);
808 log_debug("%s running as pid "PID_FMT
,
809 program_invocation_short_name
, getpid());
811 use_journal
= optind
>= argc
;
814 r
= open_journal(&j
);
817 r
= open_journal_for_upload(&u
, j
,
818 arg_cursor
?: u
.last_cursor
,
819 arg_cursor
? arg_after_cursor
: true,
827 "STATUS=Processing input...");
830 r
= sd_event_get_state(u
.events
);
833 if (r
== SD_EVENT_FINISHED
)
840 r
= check_journal_input(&u
);
841 } else if (u
.input
< 0 && !use_journal
) {
845 log_debug("Using %s as input.", argv
[optind
]);
846 r
= open_file_for_upload(&u
, argv
[optind
++]);
852 r
= perform_upload(&u
);
857 r
= sd_event_run(u
.events
, u
.timeout
);
859 log_error_errno(r
, "Failed to run event loop: %m");
867 "STATUS=Shutting down...");
869 destroy_uploader(&u
);
872 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;