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 "conf-parser.h"
33 #include "formats-util.h"
34 #include "journal-upload.h"
37 #include "parse-util.h"
39 #include "signal-util.h"
40 #include "string-util.h"
43 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
44 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
45 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
46 #define DEFAULT_PORT 19532
48 static const char* arg_url
= NULL
;
49 static const char *arg_key
= NULL
;
50 static const char *arg_cert
= NULL
;
51 static const char *arg_trust
= NULL
;
52 static const char *arg_directory
= NULL
;
53 static char **arg_file
= NULL
;
54 static const char *arg_cursor
= NULL
;
55 static bool arg_after_cursor
= false;
56 static int arg_journal_type
= 0;
57 static const char *arg_machine
= NULL
;
58 static bool arg_merge
= false;
59 static int arg_follow
= -1;
60 static const char *arg_save_state
= NULL
;
62 static void close_fd_input(Uploader
*u
);
64 #define SERVER_ANSWER_KEEP 2048
66 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
68 #define easy_setopt(curl, opt, value, level, cmd) \
70 code = curl_easy_setopt(curl, opt, value); \
73 "curl_easy_setopt " #opt " failed: %s", \
74 curl_easy_strerror(code)); \
79 static size_t output_callback(char *buf
,
87 log_debug("The server answers (%zu bytes): %.*s",
88 size
*nmemb
, (int)(size
*nmemb
), buf
);
90 if (nmemb
&& !u
->answer
) {
91 u
->answer
= strndup(buf
, size
*nmemb
);
93 log_warning_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
100 static int check_cursor_updating(Uploader
*u
) {
101 _cleanup_free_
char *temp_path
= NULL
;
102 _cleanup_fclose_
FILE *f
= NULL
;
108 r
= mkdir_parents(u
->state_file
, 0755);
110 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
113 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
115 return log_error_errno(r
, "Cannot save state to %s: %m",
122 static int update_cursor_state(Uploader
*u
) {
123 _cleanup_free_
char *temp_path
= NULL
;
124 _cleanup_fclose_
FILE *f
= NULL
;
127 if (!u
->state_file
|| !u
->last_cursor
)
130 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
135 "# This is private data. Do not parse.\n"
139 r
= fflush_and_check(f
);
143 if (rename(temp_path
, u
->state_file
) < 0) {
152 (void) unlink(temp_path
);
154 (void) unlink(u
->state_file
);
156 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
159 static int load_cursor_state(Uploader
*u
) {
165 r
= parse_env_file(u
->state_file
, NEWLINE
,
166 "LAST_CURSOR", &u
->last_cursor
,
170 log_debug("State file %s is not present.", u
->state_file
);
172 return log_error_errno(r
, "Failed to read state file %s: %m",
175 log_debug("Last cursor was %s", u
->last_cursor
);
182 int start_upload(Uploader
*u
,
183 size_t (*input_callback
)(void *ptr
,
191 assert(input_callback
);
194 struct curl_slist
*h
;
196 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
200 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
202 curl_slist_free_all(h
);
206 h
= curl_slist_append(h
, "Accept: text/plain");
208 curl_slist_free_all(h
);
218 curl
= curl_easy_init();
220 log_error("Call to curl_easy_init failed.");
224 /* tell it to POST to the URL */
225 easy_setopt(curl
, CURLOPT_POST
, 1L,
226 LOG_ERR
, return -EXFULL
);
228 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
229 LOG_ERR
, return -EXFULL
);
231 /* set where to write to */
232 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
233 LOG_ERR
, return -EXFULL
);
235 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
236 LOG_ERR
, return -EXFULL
);
238 /* set where to read from */
239 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
240 LOG_ERR
, return -EXFULL
);
242 easy_setopt(curl
, CURLOPT_READDATA
, data
,
243 LOG_ERR
, return -EXFULL
);
245 /* use our special own mime type and chunked transfer */
246 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
247 LOG_ERR
, return -EXFULL
);
249 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
250 /* enable verbose for easier tracing */
251 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
253 easy_setopt(curl
, CURLOPT_USERAGENT
,
254 "systemd-journal-upload " PACKAGE_STRING
,
257 if (arg_key
|| startswith(u
->url
, "https://")) {
258 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
259 LOG_ERR
, return -EXFULL
);
260 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
261 LOG_ERR
, return -EXFULL
);
264 if (streq_ptr(arg_trust
, "all"))
265 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
266 LOG_ERR
, return -EUCLEAN
);
267 else if (arg_trust
|| startswith(u
->url
, "https://"))
268 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
269 LOG_ERR
, return -EXFULL
);
271 if (arg_key
|| arg_trust
)
272 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
277 /* truncate the potential old error message */
284 /* upload to this place */
285 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
287 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
288 curl_easy_strerror(code
));
297 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
303 assert(nmemb
<= SSIZE_MAX
/ size
);
308 r
= read(u
->input
, buf
, size
* nmemb
);
309 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
314 u
->uploading
= false;
316 log_debug("Reached EOF");
320 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
321 return CURL_READFUNC_ABORT
;
325 static void close_fd_input(Uploader
*u
) {
329 close_nointr(u
->input
);
334 static int dispatch_fd_input(sd_event_source
*event
,
343 if (revents
& EPOLLHUP
) {
344 log_debug("Received HUP");
349 if (!(revents
& EPOLLIN
)) {
350 log_warning("Unexpected poll event %"PRIu32
".", revents
);
355 log_warning("dispatch_fd_input called when uploading, ignoring.");
359 return start_upload(u
, fd_input_callback
, u
);
362 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
365 if (streq(filename
, "-"))
368 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
370 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
376 r
= sd_event_add_io(u
->events
, &u
->input_event
,
377 fd
, EPOLLIN
, dispatch_fd_input
, u
);
379 if (r
!= -EPERM
|| arg_follow
> 0)
380 return log_error_errno(r
, "Failed to register input event: %m");
382 /* Normal files should just be consumed without polling. */
383 r
= start_upload(u
, fd_input_callback
, u
);
390 static int dispatch_sigterm(sd_event_source
*event
,
391 const struct signalfd_siginfo
*si
,
393 Uploader
*u
= userdata
;
397 log_received_signal(LOG_INFO
, si
);
400 close_journal_input(u
);
402 sd_event_exit(u
->events
, 0);
406 static int setup_signals(Uploader
*u
) {
411 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
413 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
417 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
424 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
426 const char *host
, *proto
= "";
431 memzero(u
, sizeof(Uploader
));
434 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
439 if (strchr(host
, ':'))
440 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
447 while (x
> 0 && t
[x
- 1] == '/')
450 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
455 u
->state_file
= state_file
;
457 r
= sd_event_default(&u
->events
);
459 return log_error_errno(r
, "sd_event_default failed: %m");
461 r
= setup_signals(u
);
463 return log_error_errno(r
, "Failed to set up signals: %m");
465 return load_cursor_state(u
);
468 static void destroy_uploader(Uploader
*u
) {
471 curl_easy_cleanup(u
->easy
);
472 curl_slist_free_all(u
->header
);
475 free(u
->last_cursor
);
476 free(u
->current_cursor
);
480 u
->input_event
= sd_event_source_unref(u
->input_event
);
483 close_journal_input(u
);
485 sd_event_source_unref(u
->sigterm_event
);
486 sd_event_source_unref(u
->sigint_event
);
487 sd_event_unref(u
->events
);
490 static int perform_upload(Uploader
*u
) {
496 code
= curl_easy_perform(u
->easy
);
499 log_error("Upload to %s failed: %.*s",
500 u
->url
, (int) sizeof(u
->error
), u
->error
);
502 log_error("Upload to %s failed: %s",
503 u
->url
, curl_easy_strerror(code
));
507 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
509 log_error("Failed to retrieve response code: %s",
510 curl_easy_strerror(code
));
515 log_error("Upload to %s failed with code %ld: %s",
516 u
->url
, status
, strna(u
->answer
));
518 } else if (status
< 200) {
519 log_error("Upload to %s finished with unexpected code %ld: %s",
520 u
->url
, status
, strna(u
->answer
));
523 log_debug("Upload finished successfully with code %ld: %s",
524 status
, strna(u
->answer
));
526 free(u
->last_cursor
);
527 u
->last_cursor
= u
->current_cursor
;
528 u
->current_cursor
= NULL
;
530 return update_cursor_state(u
);
533 static int parse_config(void) {
534 const ConfigTableItem items
[] = {
535 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
536 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
537 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
538 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
541 return config_parse_many(PKGSYSCONFDIR
"/journal-upload.conf",
542 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
543 "Upload\0", config_item_table_lookup
, items
,
547 static void help(void) {
548 printf("%s -u URL {FILE|-}...\n\n"
549 "Upload journal events to a remote server.\n\n"
550 " -h --help Show this help\n"
551 " --version Show package version\n"
552 " -u --url=URL Upload to this address (default port "
553 STRINGIFY(DEFAULT_PORT
) ")\n"
554 " --key=FILENAME Specify key in PEM format (default:\n"
555 " \"" PRIV_KEY_FILE
"\")\n"
556 " --cert=FILENAME Specify certificate in PEM format (default:\n"
557 " \"" CERT_FILE
"\")\n"
558 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
559 " \"" TRUST_FILE
"\")\n"
560 " --system Use the system journal\n"
561 " --user Use the user journal for the current user\n"
562 " -m --merge Use all available journals\n"
563 " -M --machine=CONTAINER Operate on local container\n"
564 " -D --directory=PATH Use journal files from directory\n"
565 " --file=PATH Use this journal file\n"
566 " --cursor=CURSOR Start at the specified cursor\n"
567 " --after-cursor=CURSOR Start after the specified cursor\n"
568 " --follow[=BOOL] Do [not] wait for input\n"
569 " --save-state[=FILE] Save uploaded cursors (default \n"
571 " -h --help Show this help and exit\n"
572 " --version Print version string and exit\n"
573 , program_invocation_short_name
);
576 static int parse_argv(int argc
, char *argv
[]) {
591 static const struct option options
[] = {
592 { "help", no_argument
, NULL
, 'h' },
593 { "version", no_argument
, NULL
, ARG_VERSION
},
594 { "url", required_argument
, NULL
, 'u' },
595 { "key", required_argument
, NULL
, ARG_KEY
},
596 { "cert", required_argument
, NULL
, ARG_CERT
},
597 { "trust", required_argument
, NULL
, ARG_TRUST
},
598 { "system", no_argument
, NULL
, ARG_SYSTEM
},
599 { "user", no_argument
, NULL
, ARG_USER
},
600 { "merge", no_argument
, NULL
, 'm' },
601 { "machine", required_argument
, NULL
, 'M' },
602 { "directory", required_argument
, NULL
, 'D' },
603 { "file", required_argument
, NULL
, ARG_FILE
},
604 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
605 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
606 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
607 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
618 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
629 log_error("cannot use more than one --url");
638 log_error("cannot use more than one --key");
647 log_error("cannot use more than one --cert");
656 log_error("cannot use more than one --trust");
664 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
668 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
677 log_error("cannot use more than one --machine/-M");
681 arg_machine
= optarg
;
686 log_error("cannot use more than one --directory/-D");
690 arg_directory
= optarg
;
694 r
= glob_extend(&arg_file
, optarg
);
696 return log_error_errno(r
, "Failed to add paths: %m");
701 log_error("cannot use more than one --cursor/--after-cursor");
708 case ARG_AFTER_CURSOR
:
710 log_error("cannot use more than one --cursor/--after-cursor");
715 arg_after_cursor
= true;
720 r
= parse_boolean(optarg
);
722 log_error("Failed to parse --follow= parameter.");
733 arg_save_state
= optarg
?: STATE_FILE
;
737 log_error("Unknown option %s.", argv
[optind
-1]);
741 log_error("Missing argument to %s.", argv
[optind
-1]);
745 assert_not_reached("Unhandled option code.");
749 log_error("Required --url/-u option missing.");
753 if (!!arg_key
!= !!arg_cert
) {
754 log_error("Options --key and --cert must be used together.");
758 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
759 log_error("Input arguments make no sense with journal input.");
766 static int open_journal(sd_journal
**j
) {
770 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
772 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
773 else if (arg_machine
)
774 r
= sd_journal_open_container(j
, arg_machine
, 0);
776 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
778 log_error_errno(r
, "Failed to open %s: %m",
779 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
783 int main(int argc
, char **argv
) {
788 log_show_color(true);
789 log_parse_environment();
795 r
= parse_argv(argc
, argv
);
801 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
805 sd_event_set_watchdog(u
.events
, true);
807 r
= check_cursor_updating(&u
);
811 log_debug("%s running as pid "PID_FMT
,
812 program_invocation_short_name
, getpid());
814 use_journal
= optind
>= argc
;
817 r
= open_journal(&j
);
820 r
= open_journal_for_upload(&u
, j
,
821 arg_cursor
?: u
.last_cursor
,
822 arg_cursor
? arg_after_cursor
: true,
830 "STATUS=Processing input...");
833 r
= sd_event_get_state(u
.events
);
836 if (r
== SD_EVENT_FINISHED
)
843 r
= check_journal_input(&u
);
844 } else if (u
.input
< 0 && !use_journal
) {
848 log_debug("Using %s as input.", argv
[optind
]);
849 r
= open_file_for_upload(&u
, argv
[optind
++]);
855 r
= perform_upload(&u
);
860 r
= sd_event_run(u
.events
, u
.timeout
);
862 log_error_errno(r
, "Failed to run event loop: %m");
870 "STATUS=Shutting down...");
872 destroy_uploader(&u
);
875 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;