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"
32 #include "formats-util.h"
36 #include "signal-util.h"
37 #include "string-util.h"
39 #include "journal-upload.h"
41 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
42 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
43 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
44 #define DEFAULT_PORT 19532
46 static const char* arg_url
= NULL
;
47 static const char *arg_key
= NULL
;
48 static const char *arg_cert
= NULL
;
49 static const char *arg_trust
= NULL
;
50 static const char *arg_directory
= NULL
;
51 static char **arg_file
= NULL
;
52 static const char *arg_cursor
= NULL
;
53 static bool arg_after_cursor
= false;
54 static int arg_journal_type
= 0;
55 static const char *arg_machine
= NULL
;
56 static bool arg_merge
= false;
57 static int arg_follow
= -1;
58 static const char *arg_save_state
= NULL
;
60 static void close_fd_input(Uploader
*u
);
62 #define SERVER_ANSWER_KEEP 2048
64 #define STATE_FILE "/var/lib/systemd/journal-upload/state"
66 #define easy_setopt(curl, opt, value, level, cmd) \
68 code = curl_easy_setopt(curl, opt, value); \
71 "curl_easy_setopt " #opt " failed: %s", \
72 curl_easy_strerror(code)); \
77 static size_t output_callback(char *buf
,
85 log_debug("The server answers (%zu bytes): %.*s",
86 size
*nmemb
, (int)(size
*nmemb
), buf
);
88 if (nmemb
&& !u
->answer
) {
89 u
->answer
= strndup(buf
, size
*nmemb
);
91 log_warning_errno(ENOMEM
, "Failed to store server answer (%zu bytes): %m",
98 static int check_cursor_updating(Uploader
*u
) {
99 _cleanup_free_
char *temp_path
= NULL
;
100 _cleanup_fclose_
FILE *f
= NULL
;
106 r
= mkdir_parents(u
->state_file
, 0755);
108 return log_error_errno(r
, "Cannot create parent directory of state file %s: %m",
111 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
113 return log_error_errno(r
, "Cannot save state to %s: %m",
120 static int update_cursor_state(Uploader
*u
) {
121 _cleanup_free_
char *temp_path
= NULL
;
122 _cleanup_fclose_
FILE *f
= NULL
;
125 if (!u
->state_file
|| !u
->last_cursor
)
128 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
133 "# This is private data. Do not parse.\n"
137 r
= fflush_and_check(f
);
141 if (rename(temp_path
, u
->state_file
) < 0) {
150 (void) unlink(temp_path
);
152 (void) unlink(u
->state_file
);
154 return log_error_errno(r
, "Failed to save state %s: %m", u
->state_file
);
157 static int load_cursor_state(Uploader
*u
) {
163 r
= parse_env_file(u
->state_file
, NEWLINE
,
164 "LAST_CURSOR", &u
->last_cursor
,
168 log_debug("State file %s is not present.", u
->state_file
);
170 return log_error_errno(r
, "Failed to read state file %s: %m",
173 log_debug("Last cursor was %s", u
->last_cursor
);
180 int start_upload(Uploader
*u
,
181 size_t (*input_callback
)(void *ptr
,
189 assert(input_callback
);
192 struct curl_slist
*h
;
194 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
198 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
200 curl_slist_free_all(h
);
204 h
= curl_slist_append(h
, "Accept: text/plain");
206 curl_slist_free_all(h
);
216 curl
= curl_easy_init();
218 log_error("Call to curl_easy_init failed.");
222 /* tell it to POST to the URL */
223 easy_setopt(curl
, CURLOPT_POST
, 1L,
224 LOG_ERR
, return -EXFULL
);
226 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
227 LOG_ERR
, return -EXFULL
);
229 /* set where to write to */
230 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
231 LOG_ERR
, return -EXFULL
);
233 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
234 LOG_ERR
, return -EXFULL
);
236 /* set where to read from */
237 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
238 LOG_ERR
, return -EXFULL
);
240 easy_setopt(curl
, CURLOPT_READDATA
, data
,
241 LOG_ERR
, return -EXFULL
);
243 /* use our special own mime type and chunked transfer */
244 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
245 LOG_ERR
, return -EXFULL
);
247 if (_unlikely_(log_get_max_level() >= LOG_DEBUG
))
248 /* enable verbose for easier tracing */
249 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
251 easy_setopt(curl
, CURLOPT_USERAGENT
,
252 "systemd-journal-upload " PACKAGE_STRING
,
255 if (arg_key
|| startswith(u
->url
, "https://")) {
256 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
257 LOG_ERR
, return -EXFULL
);
258 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
259 LOG_ERR
, return -EXFULL
);
262 if (streq_ptr(arg_trust
, "all"))
263 easy_setopt(curl
, CURLOPT_SSL_VERIFYPEER
, 0,
264 LOG_ERR
, return -EUCLEAN
);
265 else if (arg_trust
|| startswith(u
->url
, "https://"))
266 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
267 LOG_ERR
, return -EXFULL
);
269 if (arg_key
|| arg_trust
)
270 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
275 /* truncate the potential old error message */
282 /* upload to this place */
283 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
285 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
286 curl_easy_strerror(code
));
295 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
301 assert(nmemb
<= SSIZE_MAX
/ size
);
306 r
= read(u
->input
, buf
, size
* nmemb
);
307 log_debug("%s: allowed %zu, read %zd", __func__
, size
*nmemb
, r
);
312 u
->uploading
= false;
314 log_debug("Reached EOF");
318 log_error_errno(errno
, "Aborting transfer after read error on input: %m.");
319 return CURL_READFUNC_ABORT
;
323 static void close_fd_input(Uploader
*u
) {
327 close_nointr(u
->input
);
332 static int dispatch_fd_input(sd_event_source
*event
,
341 if (revents
& EPOLLHUP
) {
342 log_debug("Received HUP");
347 if (!(revents
& EPOLLIN
)) {
348 log_warning("Unexpected poll event %"PRIu32
".", revents
);
353 log_warning("dispatch_fd_input called when uploading, ignoring.");
357 return start_upload(u
, fd_input_callback
, u
);
360 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
363 if (streq(filename
, "-"))
366 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
368 return log_error_errno(errno
, "Failed to open %s: %m", filename
);
374 r
= sd_event_add_io(u
->events
, &u
->input_event
,
375 fd
, EPOLLIN
, dispatch_fd_input
, u
);
377 if (r
!= -EPERM
|| arg_follow
> 0)
378 return log_error_errno(r
, "Failed to register input event: %m");
380 /* Normal files should just be consumed without polling. */
381 r
= start_upload(u
, fd_input_callback
, u
);
388 static int dispatch_sigterm(sd_event_source
*event
,
389 const struct signalfd_siginfo
*si
,
391 Uploader
*u
= userdata
;
395 log_received_signal(LOG_INFO
, si
);
398 close_journal_input(u
);
400 sd_event_exit(u
->events
, 0);
404 static int setup_signals(Uploader
*u
) {
409 assert_se(sigprocmask_many(SIG_SETMASK
, NULL
, SIGINT
, SIGTERM
, -1) >= 0);
411 r
= sd_event_add_signal(u
->events
, &u
->sigterm_event
, SIGTERM
, dispatch_sigterm
, u
);
415 r
= sd_event_add_signal(u
->events
, &u
->sigint_event
, SIGINT
, dispatch_sigterm
, u
);
422 static int setup_uploader(Uploader
*u
, const char *url
, const char *state_file
) {
424 const char *host
, *proto
= "";
429 memzero(u
, sizeof(Uploader
));
432 if (!(host
= startswith(url
, "http://")) && !(host
= startswith(url
, "https://"))) {
437 if (strchr(host
, ':'))
438 u
->url
= strjoin(proto
, url
, "/upload", NULL
);
445 while (x
> 0 && t
[x
- 1] == '/')
448 u
->url
= strjoin(proto
, t
, ":" STRINGIFY(DEFAULT_PORT
), "/upload", NULL
);
453 u
->state_file
= state_file
;
455 r
= sd_event_default(&u
->events
);
457 return log_error_errno(r
, "sd_event_default failed: %m");
459 r
= setup_signals(u
);
461 return log_error_errno(r
, "Failed to set up signals: %m");
463 return load_cursor_state(u
);
466 static void destroy_uploader(Uploader
*u
) {
469 curl_easy_cleanup(u
->easy
);
470 curl_slist_free_all(u
->header
);
473 free(u
->last_cursor
);
474 free(u
->current_cursor
);
478 u
->input_event
= sd_event_source_unref(u
->input_event
);
481 close_journal_input(u
);
483 sd_event_source_unref(u
->sigterm_event
);
484 sd_event_source_unref(u
->sigint_event
);
485 sd_event_unref(u
->events
);
488 static int perform_upload(Uploader
*u
) {
494 code
= curl_easy_perform(u
->easy
);
497 log_error("Upload to %s failed: %.*s",
498 u
->url
, (int) sizeof(u
->error
), u
->error
);
500 log_error("Upload to %s failed: %s",
501 u
->url
, curl_easy_strerror(code
));
505 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
507 log_error("Failed to retrieve response code: %s",
508 curl_easy_strerror(code
));
513 log_error("Upload to %s failed with code %ld: %s",
514 u
->url
, status
, strna(u
->answer
));
516 } else if (status
< 200) {
517 log_error("Upload to %s finished with unexpected code %ld: %s",
518 u
->url
, status
, strna(u
->answer
));
521 log_debug("Upload finished successfully with code %ld: %s",
522 status
, strna(u
->answer
));
524 free(u
->last_cursor
);
525 u
->last_cursor
= u
->current_cursor
;
526 u
->current_cursor
= NULL
;
528 return update_cursor_state(u
);
531 static int parse_config(void) {
532 const ConfigTableItem items
[] = {
533 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
534 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
535 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
536 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
539 return config_parse_many(PKGSYSCONFDIR
"/journal-upload.conf",
540 CONF_DIRS_NULSTR("systemd/journal-upload.conf"),
541 "Upload\0", config_item_table_lookup
, items
,
545 static void help(void) {
546 printf("%s -u URL {FILE|-}...\n\n"
547 "Upload journal events to a remote server.\n\n"
548 " -h --help Show this help\n"
549 " --version Show package version\n"
550 " -u --url=URL Upload to this address (default port "
551 STRINGIFY(DEFAULT_PORT
) ")\n"
552 " --key=FILENAME Specify key in PEM format (default:\n"
553 " \"" PRIV_KEY_FILE
"\")\n"
554 " --cert=FILENAME Specify certificate in PEM format (default:\n"
555 " \"" CERT_FILE
"\")\n"
556 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
557 " \"" TRUST_FILE
"\")\n"
558 " --system Use the system journal\n"
559 " --user Use the user journal for the current user\n"
560 " -m --merge Use all available journals\n"
561 " -M --machine=CONTAINER Operate on local container\n"
562 " -D --directory=PATH Use journal files from directory\n"
563 " --file=PATH Use this journal file\n"
564 " --cursor=CURSOR Start at the specified cursor\n"
565 " --after-cursor=CURSOR Start after the specified cursor\n"
566 " --follow[=BOOL] Do [not] wait for input\n"
567 " --save-state[=FILE] Save uploaded cursors (default \n"
569 " -h --help Show this help and exit\n"
570 " --version Print version string and exit\n"
571 , program_invocation_short_name
);
574 static int parse_argv(int argc
, char *argv
[]) {
589 static const struct option options
[] = {
590 { "help", no_argument
, NULL
, 'h' },
591 { "version", no_argument
, NULL
, ARG_VERSION
},
592 { "url", required_argument
, NULL
, 'u' },
593 { "key", required_argument
, NULL
, ARG_KEY
},
594 { "cert", required_argument
, NULL
, ARG_CERT
},
595 { "trust", required_argument
, NULL
, ARG_TRUST
},
596 { "system", no_argument
, NULL
, ARG_SYSTEM
},
597 { "user", no_argument
, NULL
, ARG_USER
},
598 { "merge", no_argument
, NULL
, 'm' },
599 { "machine", required_argument
, NULL
, 'M' },
600 { "directory", required_argument
, NULL
, 'D' },
601 { "file", required_argument
, NULL
, ARG_FILE
},
602 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
603 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
604 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
605 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
616 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
627 log_error("cannot use more than one --url");
636 log_error("cannot use more than one --key");
645 log_error("cannot use more than one --cert");
654 log_error("cannot use more than one --trust");
662 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
666 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
675 log_error("cannot use more than one --machine/-M");
679 arg_machine
= optarg
;
684 log_error("cannot use more than one --directory/-D");
688 arg_directory
= optarg
;
692 r
= glob_extend(&arg_file
, optarg
);
694 return log_error_errno(r
, "Failed to add paths: %m");
699 log_error("cannot use more than one --cursor/--after-cursor");
706 case ARG_AFTER_CURSOR
:
708 log_error("cannot use more than one --cursor/--after-cursor");
713 arg_after_cursor
= true;
718 r
= parse_boolean(optarg
);
720 log_error("Failed to parse --follow= parameter.");
731 arg_save_state
= optarg
?: STATE_FILE
;
735 log_error("Unknown option %s.", argv
[optind
-1]);
739 log_error("Missing argument to %s.", argv
[optind
-1]);
743 assert_not_reached("Unhandled option code.");
747 log_error("Required --url/-u option missing.");
751 if (!!arg_key
!= !!arg_cert
) {
752 log_error("Options --key and --cert must be used together.");
756 if (optind
< argc
&& (arg_directory
|| arg_file
|| arg_machine
|| arg_journal_type
)) {
757 log_error("Input arguments make no sense with journal input.");
764 static int open_journal(sd_journal
**j
) {
768 r
= sd_journal_open_directory(j
, arg_directory
, arg_journal_type
);
770 r
= sd_journal_open_files(j
, (const char**) arg_file
, 0);
771 else if (arg_machine
)
772 r
= sd_journal_open_container(j
, arg_machine
, 0);
774 r
= sd_journal_open(j
, !arg_merge
*SD_JOURNAL_LOCAL_ONLY
+ arg_journal_type
);
776 log_error_errno(r
, "Failed to open %s: %m",
777 arg_directory
? arg_directory
: arg_file
? "files" : "journal");
781 int main(int argc
, char **argv
) {
786 log_show_color(true);
787 log_parse_environment();
793 r
= parse_argv(argc
, argv
);
799 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
803 sd_event_set_watchdog(u
.events
, true);
805 r
= check_cursor_updating(&u
);
809 log_debug("%s running as pid "PID_FMT
,
810 program_invocation_short_name
, getpid());
812 use_journal
= optind
>= argc
;
815 r
= open_journal(&j
);
818 r
= open_journal_for_upload(&u
, j
,
819 arg_cursor
?: u
.last_cursor
,
820 arg_cursor
? arg_after_cursor
: true,
828 "STATUS=Processing input...");
831 r
= sd_event_get_state(u
.events
);
834 if (r
== SD_EVENT_FINISHED
)
841 r
= check_journal_input(&u
);
842 } else if (u
.input
< 0 && !use_journal
) {
846 log_debug("Using %s as input.", argv
[optind
]);
847 r
= open_file_for_upload(&u
, argv
[optind
++]);
853 r
= perform_upload(&u
);
858 r
= sd_event_run(u
.events
, u
.timeout
);
860 log_error_errno(r
, "Failed to run event loop: %m");
868 "STATUS=Shutting down...");
870 destroy_uploader(&u
);
873 return r
>= 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;