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"
35 #include "conf-parser.h"
36 #include "journal-upload.h"
38 #define PRIV_KEY_FILE CERTIFICATE_ROOT "/private/journal-upload.pem"
39 #define CERT_FILE CERTIFICATE_ROOT "/certs/journal-upload.pem"
40 #define TRUST_FILE CERTIFICATE_ROOT "/ca/trusted.pem"
41 #define DEFAULT_PORT 19532
43 static const char* arg_url
;
45 static void close_fd_input(Uploader
*u
);
47 static const char *arg_key
= NULL
;
48 static const char *arg_cert
= NULL
;
49 static const char *arg_trust
= NULL
;
51 static const char *arg_directory
= NULL
;
52 static char **arg_file
= NULL
;
53 static const char *arg_cursor
= NULL
;
54 static bool arg_after_cursor
= false;
55 static int arg_journal_type
= 0;
56 static const char *arg_machine
= NULL
;
57 static bool arg_merge
= false;
58 static int arg_follow
= -1;
59 static const char *arg_save_state
= NULL
;
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("Failed to store server answer (%zu bytes): %s",
91 size
*nmemb
, strerror(ENOMEM
));
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 log_error("Cannot create parent directory of state file %s: %s",
108 u
->state_file
, strerror(-r
));
112 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
114 log_error("Cannot save state to %s: %s",
115 u
->state_file
, strerror(-r
));
123 static int update_cursor_state(Uploader
*u
) {
124 _cleanup_free_
char *temp_path
= NULL
;
125 _cleanup_fclose_
FILE *f
= NULL
;
128 if (!u
->state_file
|| !u
->last_cursor
)
131 r
= fopen_temporary(u
->state_file
, &f
, &temp_path
);
136 "# This is private data. Do not parse.\n"
142 if (ferror(f
) || rename(temp_path
, u
->state_file
) < 0) {
144 unlink(u
->state_file
);
150 log_error("Failed to save state %s: %s", u
->state_file
, strerror(-r
));
155 static int load_cursor_state(Uploader
*u
) {
161 r
= parse_env_file(u
->state_file
, NEWLINE
,
162 "LAST_CURSOR", &u
->last_cursor
,
165 if (r
< 0 && r
!= -ENOENT
) {
166 log_error("Failed to read state file %s: %s",
167 u
->state_file
, strerror(-r
));
176 int start_upload(Uploader
*u
,
177 size_t (*input_callback
)(void *ptr
,
185 assert(input_callback
);
188 struct curl_slist
*h
;
190 h
= curl_slist_append(NULL
, "Content-Type: application/vnd.fdo.journal");
194 h
= curl_slist_append(h
, "Transfer-Encoding: chunked");
196 curl_slist_free_all(h
);
200 h
= curl_slist_append(h
, "Accept: text/plain");
202 curl_slist_free_all(h
);
212 curl
= curl_easy_init();
214 log_error("Call to curl_easy_init failed.");
218 /* tell it to POST to the URL */
219 easy_setopt(curl
, CURLOPT_POST
, 1L,
220 LOG_ERR
, return -EXFULL
);
222 easy_setopt(curl
, CURLOPT_ERRORBUFFER
, u
->error
,
223 LOG_ERR
, return -EXFULL
);
225 /* set where to write to */
226 easy_setopt(curl
, CURLOPT_WRITEFUNCTION
, output_callback
,
227 LOG_ERR
, return -EXFULL
);
229 easy_setopt(curl
, CURLOPT_WRITEDATA
, data
,
230 LOG_ERR
, return -EXFULL
);
232 /* set where to read from */
233 easy_setopt(curl
, CURLOPT_READFUNCTION
, input_callback
,
234 LOG_ERR
, return -EXFULL
);
236 easy_setopt(curl
, CURLOPT_READDATA
, data
,
237 LOG_ERR
, return -EXFULL
);
239 /* use our special own mime type and chunked transfer */
240 easy_setopt(curl
, CURLOPT_HTTPHEADER
, u
->header
,
241 LOG_ERR
, return -EXFULL
);
243 /* enable verbose for easier tracing */
244 easy_setopt(curl
, CURLOPT_VERBOSE
, 1L, LOG_WARNING
, );
246 easy_setopt(curl
, CURLOPT_USERAGENT
,
247 "systemd-journal-upload " PACKAGE_STRING
,
250 if (arg_key
|| startswith(u
->url
, "https://")) {
251 easy_setopt(curl
, CURLOPT_SSLKEY
, arg_key
?: PRIV_KEY_FILE
,
252 LOG_ERR
, return -EXFULL
);
253 easy_setopt(curl
, CURLOPT_SSLCERT
, arg_cert
?: CERT_FILE
,
254 LOG_ERR
, return -EXFULL
);
257 if (arg_trust
|| startswith(u
->url
, "https://"))
258 easy_setopt(curl
, CURLOPT_CAINFO
, arg_trust
?: TRUST_FILE
,
259 LOG_ERR
, return -EXFULL
);
261 if (arg_key
|| arg_trust
)
262 easy_setopt(curl
, CURLOPT_SSLVERSION
, CURL_SSLVERSION_TLSv1
,
267 /* truncate the potential old error message */
274 /* upload to this place */
275 code
= curl_easy_setopt(u
->easy
, CURLOPT_URL
, u
->url
);
277 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
278 curl_easy_strerror(code
));
287 static size_t fd_input_callback(void *buf
, size_t size
, size_t nmemb
, void *userp
) {
293 assert(nmemb
<= SSIZE_MAX
/ size
);
298 r
= read(u
->input
, buf
, size
* nmemb
);
299 log_debug("%s: allowed %zu, read %zu", __func__
, size
*nmemb
, r
);
304 u
->uploading
= false;
306 log_debug("Reached EOF");
310 log_error("Aborting transfer after read error on input: %m.");
311 return CURL_READFUNC_ABORT
;
315 static void close_fd_input(Uploader
*u
) {
319 close_nointr(u
->input
);
324 static int dispatch_fd_input(sd_event_source
*event
,
333 if (revents
& EPOLLHUP
) {
334 log_debug("Received HUP");
339 if (!(revents
& EPOLLIN
)) {
340 log_warning("Unexpected poll event %"PRIu32
".", revents
);
345 log_warning("dispatch_fd_input called when uploading, ignoring.");
349 return start_upload(u
, fd_input_callback
, u
);
352 static int open_file_for_upload(Uploader
*u
, const char *filename
) {
355 if (streq(filename
, "-"))
358 fd
= open(filename
, O_RDONLY
|O_CLOEXEC
|O_NOCTTY
);
360 log_error("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 log_error("Failed to register input event: %s", strerror(-r
));
376 /* Normal files should just be consumed without polling. */
377 r
= start_upload(u
, fd_input_callback
, u
);
384 static int dispatch_sigterm(sd_event_source
*event
,
385 const struct signalfd_siginfo
*si
,
387 Uploader
*u
= userdata
;
391 log_received_signal(LOG_INFO
, si
);
394 close_journal_input(u
);
396 sd_event_exit(u
->events
, 0);
400 static int setup_signals(Uploader
*u
) {
406 assert_se(sigemptyset(&mask
) == 0);
407 sigset_add_many(&mask
, SIGINT
, SIGTERM
, -1);
408 assert_se(sigprocmask(SIG_SETMASK
, &mask
, NULL
) == 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 log_error("sd_event_default failed: %s", strerror(-r
));
460 r
= setup_signals(u
);
462 log_error("Failed to set up signals: %s", strerror(-r
));
466 return load_cursor_state(u
);
469 static void destroy_uploader(Uploader
*u
) {
472 curl_easy_cleanup(u
->easy
);
473 curl_slist_free_all(u
->header
);
476 free(u
->last_cursor
);
477 free(u
->current_cursor
);
481 u
->input_event
= sd_event_source_unref(u
->input_event
);
484 close_journal_input(u
);
486 sd_event_source_unref(u
->sigterm_event
);
487 sd_event_source_unref(u
->sigint_event
);
488 sd_event_unref(u
->events
);
491 static int perform_upload(Uploader
*u
) {
497 code
= curl_easy_perform(u
->easy
);
499 log_error("Upload to %s failed: %.*s",
501 u
->error
[0] ? (int) sizeof(u
->error
) : INT_MAX
,
502 u
->error
[0] ? u
->error
: curl_easy_strerror(code
));
506 code
= curl_easy_getinfo(u
->easy
, CURLINFO_RESPONSE_CODE
, &status
);
508 log_error("Failed to retrieve response code: %s",
509 curl_easy_strerror(code
));
514 log_error("Upload to %s failed with code %lu: %s",
515 u
->url
, status
, strna(u
->answer
));
517 } else if (status
< 200) {
518 log_error("Upload to %s finished with unexpected code %lu: %s",
519 u
->url
, status
, strna(u
->answer
));
522 log_debug("Upload finished successfully with code %lu: %s",
523 status
, strna(u
->answer
));
525 free(u
->last_cursor
);
526 u
->last_cursor
= u
->current_cursor
;
527 u
->current_cursor
= NULL
;
529 return update_cursor_state(u
);
532 static int parse_config(void) {
533 const ConfigTableItem items
[] = {
534 { "Upload", "URL", config_parse_string
, 0, &arg_url
},
535 { "Upload", "ServerKeyFile", config_parse_path
, 0, &arg_key
},
536 { "Upload", "ServerCertificateFile", config_parse_path
, 0, &arg_cert
},
537 { "Upload", "TrustedCertificateFile", config_parse_path
, 0, &arg_trust
},
540 return config_parse(NULL
, PKGSYSCONFDIR
"/journal-upload.conf", NULL
,
542 config_item_table_lookup
, items
,
543 false, false, true, NULL
);
546 static void help(void) {
547 printf("%s -u URL {FILE|-}...\n\n"
548 "Upload journal events to a remote server.\n\n"
549 " -h --help Show this help\n"
550 " --version Show package version\n"
551 " -u --url=URL Upload to this address (default port "
552 STRINGIFY(DEFAULT_PORT
) ")\n"
553 " --key=FILENAME Specify key in PEM format (default:\n"
554 " \"" PRIV_KEY_FILE
"\")\n"
555 " --cert=FILENAME Specify certificate in PEM format (default:\n"
556 " \"" CERT_FILE
"\")\n"
557 " --trust=FILENAME|all Specify CA certificate or disable checking (default:\n"
558 " \"" TRUST_FILE
"\")\n"
559 " --system Use the system journal\n"
560 " --user Use the user journal for the current user\n"
561 " -m --merge Use all available journals\n"
562 " -M --machine=CONTAINER Operate on local container\n"
563 " -D --directory=PATH Use journal files from directory\n"
564 " --file=PATH Use this journal file\n"
565 " --cursor=CURSOR Start at the specified cursor\n"
566 " --after-cursor=CURSOR Start after the specified cursor\n"
567 " --follow[=BOOL] Do [not] wait for input\n"
568 " --save-state[=FILE] Save uploaded cursors (default \n"
570 " -h --help Show this help and exit\n"
571 " --version Print version string and exit\n"
572 , program_invocation_short_name
);
575 static int parse_argv(int argc
, char *argv
[]) {
590 static const struct option options
[] = {
591 { "help", no_argument
, NULL
, 'h' },
592 { "version", no_argument
, NULL
, ARG_VERSION
},
593 { "url", required_argument
, NULL
, 'u' },
594 { "key", required_argument
, NULL
, ARG_KEY
},
595 { "cert", required_argument
, NULL
, ARG_CERT
},
596 { "trust", required_argument
, NULL
, ARG_TRUST
},
597 { "system", no_argument
, NULL
, ARG_SYSTEM
},
598 { "user", no_argument
, NULL
, ARG_USER
},
599 { "merge", no_argument
, NULL
, 'm' },
600 { "machine", required_argument
, NULL
, 'M' },
601 { "directory", required_argument
, NULL
, 'D' },
602 { "file", required_argument
, NULL
, ARG_FILE
},
603 { "cursor", required_argument
, NULL
, ARG_CURSOR
},
604 { "after-cursor", required_argument
, NULL
, ARG_AFTER_CURSOR
},
605 { "follow", optional_argument
, NULL
, ARG_FOLLOW
},
606 { "save-state", optional_argument
, NULL
, ARG_SAVE_STATE
},
617 while ((c
= getopt_long(argc
, argv
, "hu:mM:D:", options
, NULL
)) >= 0)
624 puts(PACKAGE_STRING
);
625 puts(SYSTEMD_FEATURES
);
630 log_error("cannot use more than one --url");
639 log_error("cannot use more than one --key");
648 log_error("cannot use more than one --cert");
657 log_error("cannot use more than one --trust");
665 arg_journal_type
|= SD_JOURNAL_SYSTEM
;
669 arg_journal_type
|= SD_JOURNAL_CURRENT_USER
;
678 log_error("cannot use more than one --machine/-M");
682 arg_machine
= optarg
;
687 log_error("cannot use more than one --directory/-D");
691 arg_directory
= optarg
;
695 r
= glob_extend(&arg_file
, optarg
);
697 log_error("Failed to add paths: %s", strerror(-r
));
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("Failed to open %s: %s",
782 arg_directory
? arg_directory
: arg_file
? "files" : "journal",
787 int main(int argc
, char **argv
) {
792 log_show_color(true);
793 log_parse_environment();
799 r
= parse_argv(argc
, argv
);
803 r
= setup_uploader(&u
, arg_url
, arg_save_state
);
807 sd_event_set_watchdog(u
.events
, true);
809 r
= check_cursor_updating(&u
);
813 log_debug("%s running as pid "PID_FMT
,
814 program_invocation_short_name
, getpid());
816 use_journal
= optind
>= argc
;
819 r
= open_journal(&j
);
822 r
= open_journal_for_upload(&u
, j
,
823 arg_cursor
?: u
.last_cursor
,
824 arg_cursor
? arg_after_cursor
: true,
832 "STATUS=Processing input...");
839 r
= check_journal_input(&u
);
840 } else if (u
.input
< 0 && !use_journal
) {
844 log_debug("Using %s as input.", argv
[optind
]);
845 r
= open_file_for_upload(&u
, argv
[optind
++]);
850 r
= sd_event_get_state(u
.events
);
853 if (r
== SD_EVENT_FINISHED
)
857 r
= perform_upload(&u
);
862 r
= sd_event_run(u
.events
, u
.timeout
);
864 log_error("Failed to run event loop: %s", strerror(-r
));
872 "STATUS=Shutting down...");
874 destroy_uploader(&u
);
877 return r
== 0 ? EXIT_SUCCESS
: EXIT_FAILURE
;