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"
136 r
= fflush_and_check(f
);
140 if (rename(temp_path
, u
->state_file
) < 0) {
149 (void) unlink(temp_path
);
151 (void) unlink(u
->state_file
);
153 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
156 static int load_cursor_state(Uploader
*u
) {
162 r
= parse_env_file(u
->state_file
, NEWLINE
,
163 "LAST_CURSOR", &u
->last_cursor
,
167 log_debug("State file %s is not present.", u
->state_file
);
169 return log_error_errno(r
, "Failed to read state file %s: %m",
172 log_debug("Last cursor was %s", u
->last_cursor
);
179 int start_upload(Uploader
*u
,
180 size_t (*input_callback
)(void *ptr
,
188 assert(input_callback
);
191 struct curl_slist
*h
;
193 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
197 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
199 curl_slist_free_all(h
);
203 h
= curl_slist_append(h
, "Accept: text/plain");
205 curl_slist_free_all(h
);
215 curl
= curl_easy_init();
217 log_error("Call to curl_easy_init failed.");
221 /* tell it to POST to the URL */
222 easy_setopt(curl
, CURLOPT_POST
, 1L,
223 LOG_ERR
, return -EXFULL
);
225 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
226 LOG_ERR
, return -EXFULL
);
228 /* set where to write to */
229 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
230 LOG_ERR
, return -EXFULL
);
232 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
233 LOG_ERR
, return -EXFULL
);
235 /* set where to read from */
236 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
237 LOG_ERR
, return -EXFULL
);
239 easy_setopt(curl
, CURLOPT_READDATA
, data
,
240 LOG_ERR
, return -EXFULL
);
242 /* use our special own mime type and chunked transfer */
243 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
244 LOG_ERR
, return -EXFULL
);
246 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
247 /* enable verbose for easier tracing */
248 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
250 easy_setopt(curl
, CURLOPT_USERAGENT
,
251 "systemd-journal-upload " PACKAGE_STRING
,
254 if (arg_key
|| startswith(u
->url
, "https://")) {
255 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
256 LOG_ERR
, return -EXFULL
);
257 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
258 LOG_ERR
, return -EXFULL
);
261 if (streq_ptr(arg_trust
, "all"))
262 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
263 LOG_ERR
, return -EUCLEAN
);
264 else if (arg_trust
|| startswith(u
->url
, "https://"))
265 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
266 LOG_ERR
, return -EXFULL
);
268 if (arg_key
|| arg_trust
)
269 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
274 /* truncate the potential old error message */
281 /* upload to this place */
282 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
284 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
285 curl_easy_strerror(code
));
294 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
300 assert(nmemb
<= SSIZE_MAX
/ size
);
305 r
= read(u
->input
, buf
, size
* nmemb
);
306 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
311 u
->uploading
= false;
313 log_debug("Reached EOF");
317 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
318 return CURL_READFUNC_ABORT
;
322 static void close_fd_input(Uploader
*u
) {
326 close_nointr(u
->input
);
331 static int dispatch_fd_input(sd_event_source
*event
,
340 if (revents
& EPOLLHUP
) {
341 log_debug("Received HUP");
346 if (!(revents
& EPOLLIN
)) {
347 log_warning("Unexpected poll event %"PRIu32
".", revents
);
352 log_warning("dispatch_fd_input called when uploading, ignoring.");
356 return start_upload(u
, fd_input_callback
, u
);
359 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
362 if (streq(filename
, "-"))
365 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
367 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
373 r
= sd_event_add_io(u
->events
, &u
->input_event
,
374 fd
, EPOLLIN
, dispatch_fd_input
, u
);
376 if (r
!= -EPERM
|| arg_follow
> 0)
377 return log_error_errno(r
, "Failed to register input event: %m");
379 /* Normal files should just be consumed without polling. */
380 r
= start_upload(u
, fd_input_callback
, u
);
387 static int dispatch_sigterm(sd_event_source
*event
,
388 const struct signalfd_siginfo
*si
,
390 Uploader
*u
= userdata
;
394 log_received_signal(LOG_INFO
, si
);
397 close_journal_input(u
);
399 sd_event_exit(u
->events
, 0);
403 static int setup_signals(Uploader
*u
) {
408 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
410 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
414 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
421 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
423 const char *host
, *proto
= "";
428 memzero(u
, sizeof(Uploader
));
431 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
436 if (strchr(host
, ':'))
437 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
444 while (x
> 0 && t
[x
- 1] == '/')
447 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
452 u
->state_file
= state_file
;
454 r
= sd_event_default(&u
->events
);
456 return log_error_errno(r
, "sd_event_default failed: %m");
458 r
= setup_signals(u
);
460 return log_error_errno(r
, "Failed to set up signals: %m");
462 return load_cursor_state(u
);
465 static void destroy_uploader(Uploader
*u
) {
468 curl_easy_cleanup(u
->easy
);
469 curl_slist_free_all(u
->header
);
472 free(u
->last_cursor
);
473 free(u
->current_cursor
);
477 u
->input_event
= sd_event_source_unref(u
->input_event
);
480 close_journal_input(u
);
482 sd_event_source_unref(u
->sigterm_event
);
483 sd_event_source_unref(u
->sigint_event
);
484 sd_event_unref(u
->events
);
487 static int perform_upload(Uploader
*u
) {
493 code
= curl_easy_perform(u
->easy
);
496 log_error("Upload to %s failed: %.*s",
497 u
->url
, (int) sizeof(u
->error
), u
->error
);
499 log_error("Upload to %s failed: %s",
500 u
->url
, curl_easy_strerror(code
));
504 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
506 log_error("Failed to retrieve response code: %s",
507 curl_easy_strerror(code
));
512 log_error("Upload to %s failed with code %ld: %s",
513 u
->url
, status
, strna(u
->answer
));
515 } else if (status
< 200) {
516 log_error("Upload to %s finished with unexpected code %ld: %s",
517 u
->url
, status
, strna(u
->answer
));
520 log_debug("Upload finished successfully with code %ld: %s",
521 status
, strna(u
->answer
));
523 free(u
->last_cursor
);
524 u
->last_cursor
= u
->current_cursor
;
525 u
->current_cursor
= NULL
;
527 return update_cursor_state(u
);
530 static int parse_config(void) {
531 const ConfigTableItem items
[] = {
532 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
533 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
534 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
535 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
538 return config_parse_many(PKGSYSCONFDIR
"/journal-upload.conf",
539 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
540 "Upload\0", config_item_table_lookup
, items
,
544 static void help(void) {
545 printf("%s -u URL {FILE|-}...\n\n"
546 "Upload journal events to a remote server.\n\n"
547 " -h --help Show this help\n"
548 " --version Show package version\n"
549 " -u --url=URL Upload to this address (default port "
550 STRINGIFY(DEFAULT_PORT
) ")\n"
551 " --key=FILENAME Specify key in PEM format (default:\n"
552 " \"" PRIV_KEY_FILE
"\")\n"
553 " --cert=FILENAME Specify certificate in PEM format (default:\n"
554 " \"" CERT_FILE
"\")\n"
555 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
556 " \"" TRUST_FILE
"\")\n"
557 " --system Use the system journal\n"
558 " --user Use the user journal for the current user\n"
559 " -m --merge Use all available journals\n"
560 " -M --machine=CONTAINER Operate on local container\n"
561 " -D --directory=PATH Use journal files from directory\n"
562 " --file=PATH Use this journal file\n"
563 " --cursor=CURSOR Start at the specified cursor\n"
564 " --after-cursor=CURSOR Start after the specified cursor\n"
565 " --follow[=BOOL] Do [not] wait for input\n"
566 " --save-state[=FILE] Save uploaded cursors (default \n"
568 " -h --help Show this help and exit\n"
569 " --version Print version string and exit\n"
570 , program_invocation_short_name
);
573 static int parse_argv(int argc
, char *argv
[]) {
588 static const struct option options
[] = {
589 { "help", no_argument
, NULL
, 'h' },
590 { "version", no_argument
, NULL
, ARG_VERSION
},
591 { "url", required_argument
, NULL
, 'u' },
592 { "key", required_argument
, NULL
, ARG_KEY
},
593 { "cert", required_argument
, NULL
, ARG_CERT
},
594 { "trust", required_argument
, NULL
, ARG_TRUST
},
595 { "system", no_argument
, NULL
, ARG_SYSTEM
},
596 { "user", no_argument
, NULL
, ARG_USER
},
597 { "merge", no_argument
, NULL
, 'm' },
598 { "machine", required_argument
, NULL
, 'M' },
599 { "directory", required_argument
, NULL
, 'D' },
600 { "file", required_argument
, NULL
, ARG_FILE
},
601 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
602 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
603 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
604 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
615 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
622 puts(PACKAGE_STRING
);
623 puts(SYSTEMD_FEATURES
);
628 log_error("cannot use more than one --url");
637 log_error("cannot use more than one --key");
646 log_error("cannot use more than one --cert");
655 log_error("cannot use more than one --trust");
663 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
667 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
676 log_error("cannot use more than one --machine/-M");
680 arg_machine
= optarg
;
685 log_error("cannot use more than one --directory/-D");
689 arg_directory
= optarg
;
693 r
= glob_extend(&arg_file
, optarg
);
695 return log_error_errno(r
, "Failed to add paths: %m");
700 log_error("cannot use more than one --cursor/--after-cursor");
707 case ARG_AFTER_CURSOR
:
709 log_error("cannot use more than one --cursor/--after-cursor");
714 arg_after_cursor
= true;
719 r
= parse_boolean(optarg
);
721 log_error("Failed to parse --follow= parameter.");
732 arg_save_state
= optarg
?: STATE_FILE
;
736 log_error("Unknown option %s.", argv
[optind
-1]);
740 log_error("Missing argument to %s.", argv
[optind
-1]);
744 assert_not_reached("Unhandled option code.");
748 log_error("Required --url/-u option missing.");
752 if (!!arg_key
!= !!arg_cert
) {
753 log_error("Options --key and --cert must be used together.");
757 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
758 log_error("Input arguments make no sense with journal input.");
765 static int open_journal(sd_journal
**j
) {
769 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
771 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
772 else if (arg_machine
)
773 r
= sd_journal_open_container(j
, arg_machine
, 0);
775 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
777 log_error_errno(r
, "Failed to open %s: %m",
778 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
782 int main(int argc
, char **argv
) {
787 log_show_color(true);
788 log_parse_environment();
794 r
= parse_argv(argc
, argv
);
800 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
804 sd_event_set_watchdog(u
.events
, true);
806 r
= check_cursor_updating(&u
);
810 log_debug("%s running as pid "PID_FMT
,
811 program_invocation_short_name
, getpid());
813 use_journal
= optind
>= argc
;
816 r
= open_journal(&j
);
819 r
= open_journal_for_upload(&u
, j
,
820 arg_cursor
?: u
.last_cursor
,
821 arg_cursor
? arg_after_cursor
: true,
829 "STATUS=Processing input...");
832 r
= sd_event_get_state(u
.events
);
835 if (r
== SD_EVENT_FINISHED
)
842 r
= check_journal_input(&u
);
843 } else if (u
.input
< 0 && !use_journal
) {
847 log_debug("Using %s as input.", argv
[optind
]);
848 r
= open_file_for_upload(&u
, argv
[optind
++]);
854 r
= perform_upload(&u
);
859 r
= sd_event_run(u
.events
, u
.timeout
);
861 log_error_errno(r
, "Failed to run event loop: %m");
869 "STATUS=Shutting down...");
871 destroy_uploader(&u
);
874 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;