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/>.
22 #include <curl/curl.h>
28 #include "sd-daemon.h"
30 #include "alloc-util.h"
31 #include "conf-parser.h"
35 #include "formats-util.h"
36 #include "glob-util.h"
37 #include "journal-upload.h"
40 #include "parse-util.h"
42 #include "signal-util.h"
43 #include "string-util.h"
46 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
47 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
48 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
49 #define DEFAULT_PORT 19532
51 static const char* arg_url
= NULL
;
52 static const char *arg_key
= NULL
;
53 static const char *arg_cert
= NULL
;
54 static const char *arg_trust
= NULL
;
55 static const char *arg_directory
= NULL
;
56 static char **arg_file
= NULL
;
57 static const char *arg_cursor
= NULL
;
58 static bool arg_after_cursor
= false;
59 static int arg_journal_type
= 0;
60 static const char *arg_machine
= NULL
;
61 static bool arg_merge
= false;
62 static int arg_follow
= -1;
63 static const char *arg_save_state
= NULL
;
65 static void close_fd_input(Uploader
*u
);
67 #define SERVER_ANSWER_KEEP 2048
69 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
71 #define easy_setopt(curl, opt, value, level, cmd) \
73 code = curl_easy_setopt(curl, opt, value); \
76 "curl_easy_setopt " #opt " failed: %s", \
77 curl_easy_strerror(code)); \
82 static size_t output_callback(char *buf
,
90 log_debug("The server answers (%zu bytes): %.*s",
91 size
*nmemb
, (int)(size
*nmemb
), buf
);
93 if (nmemb
&& !u
->answer
) {
94 u
->answer
= strndup(buf
, size
*nmemb
);
96 log_warning_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
103 static int check_cursor_updating(Uploader
*u
) {
104 _cleanup_free_
char *temp_path
= NULL
;
105 _cleanup_fclose_
FILE *f
= NULL
;
111 r
= mkdir_parents(u
->state_file
, 0755);
113 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
116 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
118 return log_error_errno(r
, "Cannot save state to %s: %m",
125 static int update_cursor_state(Uploader
*u
) {
126 _cleanup_free_
char *temp_path
= NULL
;
127 _cleanup_fclose_
FILE *f
= NULL
;
130 if (!u
->state_file
|| !u
->last_cursor
)
133 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
138 "# This is private data. Do not parse.\n"
142 r
= fflush_and_check(f
);
146 if (rename(temp_path
, u
->state_file
) < 0) {
155 (void) unlink(temp_path
);
157 (void) unlink(u
->state_file
);
159 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
162 static int load_cursor_state(Uploader
*u
) {
168 r
= parse_env_file(u
->state_file
, NEWLINE
,
169 "LAST_CURSOR", &u
->last_cursor
,
173 log_debug("State file %s is not present.", u
->state_file
);
175 return log_error_errno(r
, "Failed to read state file %s: %m",
178 log_debug("Last cursor was %s", u
->last_cursor
);
185 int start_upload(Uploader
*u
,
186 size_t (*input_callback
)(void *ptr
,
194 assert(input_callback
);
197 struct curl_slist
*h
;
199 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
203 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
205 curl_slist_free_all(h
);
209 h
= curl_slist_append(h
, "Accept: text/plain");
211 curl_slist_free_all(h
);
221 curl
= curl_easy_init();
223 log_error("Call to curl_easy_init failed.");
227 /* tell it to POST to the URL */
228 easy_setopt(curl
, CURLOPT_POST
, 1L,
229 LOG_ERR
, return -EXFULL
);
231 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
232 LOG_ERR
, return -EXFULL
);
234 /* set where to write to */
235 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
236 LOG_ERR
, return -EXFULL
);
238 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
239 LOG_ERR
, return -EXFULL
);
241 /* set where to read from */
242 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
243 LOG_ERR
, return -EXFULL
);
245 easy_setopt(curl
, CURLOPT_READDATA
, data
,
246 LOG_ERR
, return -EXFULL
);
248 /* use our special own mime type and chunked transfer */
249 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
250 LOG_ERR
, return -EXFULL
);
252 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
253 /* enable verbose for easier tracing */
254 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
256 easy_setopt(curl
, CURLOPT_USERAGENT
,
257 "systemd-journal-upload " PACKAGE_STRING
,
260 if (arg_key
|| startswith(u
->url
, "https://")) {
261 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
262 LOG_ERR
, return -EXFULL
);
263 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
264 LOG_ERR
, return -EXFULL
);
267 if (streq_ptr(arg_trust
, "all"))
268 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
269 LOG_ERR
, return -EUCLEAN
);
270 else if (arg_trust
|| startswith(u
->url
, "https://"))
271 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
272 LOG_ERR
, return -EXFULL
);
274 if (arg_key
|| arg_trust
)
275 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
280 /* truncate the potential old error message */
287 /* upload to this place */
288 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
290 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
291 curl_easy_strerror(code
));
300 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
306 assert(nmemb
<= SSIZE_MAX
/ size
);
311 r
= read(u
->input
, buf
, size
* nmemb
);
312 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
317 u
->uploading
= false;
319 log_debug("Reached EOF");
323 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
324 return CURL_READFUNC_ABORT
;
328 static void close_fd_input(Uploader
*u
) {
332 close_nointr(u
->input
);
337 static int dispatch_fd_input(sd_event_source
*event
,
346 if (revents
& EPOLLHUP
) {
347 log_debug("Received HUP");
352 if (!(revents
& EPOLLIN
)) {
353 log_warning("Unexpected poll event %"PRIu32
".", revents
);
358 log_warning("dispatch_fd_input called when uploading, ignoring.");
362 return start_upload(u
, fd_input_callback
, u
);
365 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
368 if (streq(filename
, "-"))
371 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
373 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
379 r
= sd_event_add_io(u
->events
, &u
->input_event
,
380 fd
, EPOLLIN
, dispatch_fd_input
, u
);
382 if (r
!= -EPERM
|| arg_follow
> 0)
383 return log_error_errno(r
, "Failed to register input event: %m");
385 /* Normal files should just be consumed without polling. */
386 r
= start_upload(u
, fd_input_callback
, u
);
393 static int dispatch_sigterm(sd_event_source
*event
,
394 const struct signalfd_siginfo
*si
,
396 Uploader
*u
= userdata
;
400 log_received_signal(LOG_INFO
, si
);
403 close_journal_input(u
);
405 sd_event_exit(u
->events
, 0);
409 static int setup_signals(Uploader
*u
) {
414 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
416 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
420 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
427 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
429 const char *host
, *proto
= "";
434 memzero(u
, sizeof(Uploader
));
437 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
442 if (strchr(host
, ':'))
443 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
450 while (x
> 0 && t
[x
- 1] == '/')
453 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
458 u
->state_file
= state_file
;
460 r
= sd_event_default(&u
->events
);
462 return log_error_errno(r
, "sd_event_default failed: %m");
464 r
= setup_signals(u
);
466 return log_error_errno(r
, "Failed to set up signals: %m");
468 return load_cursor_state(u
);
471 static void destroy_uploader(Uploader
*u
) {
474 curl_easy_cleanup(u
->easy
);
475 curl_slist_free_all(u
->header
);
478 free(u
->last_cursor
);
479 free(u
->current_cursor
);
483 u
->input_event
= sd_event_source_unref(u
->input_event
);
486 close_journal_input(u
);
488 sd_event_source_unref(u
->sigterm_event
);
489 sd_event_source_unref(u
->sigint_event
);
490 sd_event_unref(u
->events
);
493 static int perform_upload(Uploader
*u
) {
499 code
= curl_easy_perform(u
->easy
);
502 log_error("Upload to %s failed: %.*s",
503 u
->url
, (int) sizeof(u
->error
), u
->error
);
505 log_error("Upload to %s failed: %s",
506 u
->url
, curl_easy_strerror(code
));
510 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
512 log_error("Failed to retrieve response code: %s",
513 curl_easy_strerror(code
));
518 log_error("Upload to %s failed with code %ld: %s",
519 u
->url
, status
, strna(u
->answer
));
521 } else if (status
< 200) {
522 log_error("Upload to %s finished with unexpected code %ld: %s",
523 u
->url
, status
, strna(u
->answer
));
526 log_debug("Upload finished successfully with code %ld: %s",
527 status
, strna(u
->answer
));
529 free(u
->last_cursor
);
530 u
->last_cursor
= u
->current_cursor
;
531 u
->current_cursor
= NULL
;
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(PKGSYSCONFDIR
"/journal-upload.conf",
545 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
546 "Upload\0", config_item_table_lookup
, items
,
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 " -h --help Show this help and exit\n"
575 " --version Print version string and exit\n"
576 , program_invocation_short_name
);
579 static int parse_argv(int argc
, char *argv
[]) {
594 static const struct option options
[] = {
595 { "help", no_argument
, NULL
, 'h' },
596 { "version", no_argument
, NULL
, ARG_VERSION
},
597 { "url", required_argument
, NULL
, 'u' },
598 { "key", required_argument
, NULL
, ARG_KEY
},
599 { "cert", required_argument
, NULL
, ARG_CERT
},
600 { "trust", required_argument
, NULL
, ARG_TRUST
},
601 { "system", no_argument
, NULL
, ARG_SYSTEM
},
602 { "user", no_argument
, NULL
, ARG_USER
},
603 { "merge", no_argument
, NULL
, 'm' },
604 { "machine", required_argument
, NULL
, 'M' },
605 { "directory", required_argument
, NULL
, 'D' },
606 { "file", required_argument
, NULL
, ARG_FILE
},
607 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
608 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
609 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
610 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
621 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
632 log_error("cannot use more than one --url");
641 log_error("cannot use more than one --key");
650 log_error("cannot use more than one --cert");
659 log_error("cannot use more than one --trust");
667 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
671 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
680 log_error("cannot use more than one --machine/-M");
684 arg_machine
= optarg
;
689 log_error("cannot use more than one --directory/-D");
693 arg_directory
= optarg
;
697 r
= glob_extend(&arg_file
, optarg
);
699 return log_error_errno(r
, "Failed to add paths: %m");
704 log_error("cannot use more than one --cursor/--after-cursor");
711 case ARG_AFTER_CURSOR
:
713 log_error("cannot use more than one --cursor/--after-cursor");
718 arg_after_cursor
= true;
723 r
= parse_boolean(optarg
);
725 log_error("Failed to parse --follow= parameter.");
736 arg_save_state
= optarg
?: STATE_FILE
;
740 log_error("Unknown option %s.", argv
[optind
-1]);
744 log_error("Missing argument to %s.", argv
[optind
-1]);
748 assert_not_reached("Unhandled option code.");
752 log_error("Required --url/-u option missing.");
756 if (!!arg_key
!= !!arg_cert
) {
757 log_error("Options --key and --cert must be used together.");
761 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
762 log_error("Input arguments make no sense with journal input.");
769 static int open_journal(sd_journal
**j
) {
773 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
775 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
776 else if (arg_machine
)
777 r
= sd_journal_open_container(j
, arg_machine
, 0);
779 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
781 log_error_errno(r
, "Failed to open %s: %m",
782 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
786 int main(int argc
, char **argv
) {
791 log_show_color(true);
792 log_parse_environment();
798 r
= parse_argv(argc
, argv
);
804 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
808 sd_event_set_watchdog(u
.events
, true);
810 r
= check_cursor_updating(&u
);
814 log_debug("%s running as pid "PID_FMT
,
815 program_invocation_short_name
, getpid());
817 use_journal
= optind
>= argc
;
820 r
= open_journal(&j
);
823 r
= open_journal_for_upload(&u
, j
,
824 arg_cursor
?: u
.last_cursor
,
825 arg_cursor
? arg_after_cursor
: true,
833 "STATUS=Processing input...");
836 r
= sd_event_get_state(u
.events
);
839 if (r
== SD_EVENT_FINISHED
)
846 r
= check_journal_input(&u
);
847 } else if (u
.input
< 0 && !use_journal
) {
851 log_debug("Using %s as input.", argv
[optind
]);
852 r
= open_file_for_upload(&u
, argv
[optind
++]);
858 r
= perform_upload(&u
);
863 r
= sd_event_run(u
.events
, u
.timeout
);
865 log_error_errno(r
, "Failed to run event loop: %m");
873 "STATUS=Shutting down...");
875 destroy_uploader(&u
);
878 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;