]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-upload.c
journal-upload: HTTPS support
[thirdparty/systemd.git] / src / journal-remote / journal-upload.c
CommitLineData
3d090cc6
ZJS
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2014 Zbigniew Jędrzejewski-Szmek
7
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.
12
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.
17
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/>.
20***/
21
22#include <stdio.h>
23#include <curl/curl.h>
24#include <sys/stat.h>
25#include <fcntl.h>
26#include <getopt.h>
27
28#include "sd-daemon.h"
29
30#include "log.h"
31#include "util.h"
32#include "build.h"
33#include "journal-upload.h"
34
35static const char* arg_url;
36
37static void close_fd_input(Uploader *u);
38
7449bc1f
ZJS
39static const char *arg_key = NULL;
40static const char *arg_cert = NULL;
41static const char *arg_trust = NULL;
42
3d090cc6
ZJS
43#define easy_setopt(curl, opt, value, level, cmd) \
44 { \
45 code = curl_easy_setopt(curl, opt, value); \
46 if (code) { \
47 log_full(level, \
48 "curl_easy_setopt " #opt " failed: %s", \
49 curl_easy_strerror(code)); \
50 cmd; \
51 } \
52 }
53
54int start_upload(Uploader *u,
55 size_t (*input_callback)(void *ptr,
56 size_t size,
57 size_t nmemb,
58 void *userdata),
59 void *data) {
60 CURLcode code;
61
62 assert(u);
63 assert(input_callback);
64
65 if (!u->header) {
66 struct curl_slist *h;
67
68 h = curl_slist_append(NULL, "Content-Type: application/vnd.fdo.journal");
69 if (!h)
70 return log_oom();
71
72 h = curl_slist_append(h, "Transfer-Encoding: chunked");
73 if (!h) {
74 curl_slist_free_all(h);
75 return log_oom();
76 }
77
78 h = curl_slist_append(h, "Accept: text/plain");
79 if (!h) {
80 curl_slist_free_all(h);
81 return log_oom();
82 }
83
84 u->header = h;
85 }
86
87 if (!u->easy) {
88 CURL *curl;
89
90 curl = curl_easy_init();
91 if (!curl) {
92 log_error("Call to curl_easy_init failed.");
93 return -ENOSR;
94 }
95
96 /* tell it to POST to the URL */
97 easy_setopt(curl, CURLOPT_POST, 1L,
98 LOG_ERR, return -EXFULL);
99
100 /* set where to read from */
101 easy_setopt(curl, CURLOPT_READFUNCTION, input_callback,
102 LOG_ERR, return -EXFULL);
103
104 easy_setopt(curl, CURLOPT_READDATA, data,
105 LOG_ERR, return -EXFULL);
106
107 /* use our special own mime type and chunked transfer */
108 easy_setopt(curl, CURLOPT_HTTPHEADER, u->header,
109 LOG_ERR, return -EXFULL);
110
111 /* enable verbose for easier tracing */
112 easy_setopt(curl, CURLOPT_VERBOSE, 1L, LOG_WARNING, );
113
114 easy_setopt(curl, CURLOPT_USERAGENT,
115 "systemd-journal-upload " PACKAGE_STRING,
116 LOG_WARNING, );
117
7449bc1f
ZJS
118 if (arg_key) {
119 assert(arg_cert);
120
121 easy_setopt(curl, CURLOPT_SSLKEY, arg_key,
122 LOG_ERR, return -EXFULL);
123 easy_setopt(curl, CURLOPT_SSLCERT, arg_cert,
124 LOG_ERR, return -EXFULL);
125 }
126
127 if (arg_trust)
128 easy_setopt(curl, CURLOPT_CAINFO, arg_trust,
129 LOG_ERR, return -EXFULL);
130
131 if (arg_key || arg_trust)
132 easy_setopt(curl, CURLOPT_SSLVERSION, CURL_SSLVERSION_TLSv1,
133 LOG_WARNING, );
134
3d090cc6
ZJS
135 u->easy = curl;
136 }
137
138 /* upload to this place */
139 code = curl_easy_setopt(u->easy, CURLOPT_URL, u->url);
140 if (code) {
141 log_error("curl_easy_setopt CURLOPT_URL failed: %s",
142 curl_easy_strerror(code));
143 return -EXFULL;
144 }
145
146 u->uploading = true;
147
148 return 0;
149}
150
151static size_t fd_input_callback(void *buf, size_t size, size_t nmemb, void *userp) {
152 Uploader *u = userp;
153
154 ssize_t r;
155
156 assert(u);
157 assert(nmemb <= SSIZE_MAX / size);
158
159 if (u->input < 0)
160 return 0;
161
162 r = read(u->input, buf, size * nmemb);
163 log_debug("%s: allowed %zu, read %zu", __func__, size*nmemb, r);
164
165 if (r > 0)
166 return r;
167
168 u->uploading = false;
169 if (r == 0) {
170 log_debug("Reached EOF");
171 close_fd_input(u);
172 return 0;
173 } else {
174 log_error("Aborting transfer after read error on input: %m.");
175 return CURL_READFUNC_ABORT;
176 }
177}
178
179static void close_fd_input(Uploader *u) {
180 assert(u);
181
182 if (u->input >= 0)
183 close_nointr(u->input);
184 u->input = -1;
185}
186
187static int dispatch_fd_input(sd_event_source *event,
188 int fd,
189 uint32_t revents,
190 void *userp) {
191 Uploader *u = userp;
192
193 assert(u);
194 assert(revents & EPOLLIN);
195 assert(fd >= 0);
196
197 if (u->uploading) {
198 log_warning("dispatch_fd_input called when uploading, ignoring.");
199 return 0;
200 }
201
202 return start_upload(u, fd_input_callback, u);
203}
204
205static int open_file_for_upload(Uploader *u, const char *filename) {
206 int fd, r;
207
208 if (streq(filename, "-"))
209 fd = STDIN_FILENO;
210 else {
211 fd = open(filename, O_RDONLY|O_CLOEXEC|O_NOCTTY);
212 if (fd < 0) {
213 log_error("Failed to open %s: %m", filename);
214 return -errno;
215 }
216 }
217
218 u->input = fd;
219
220 r = sd_event_add_io(u->events, &u->input_event,
221 fd, EPOLLIN, dispatch_fd_input, u);
222 if (r < 0) {
223 if (r != -EPERM) {
224 log_error("Failed to register input event: %s", strerror(-r));
225 return r;
226 }
227
228 /* Normal files should just be consumed without polling. */
229 r = start_upload(u, fd_input_callback, u);
230 }
231 return r;
232}
233
234static int setup_uploader(Uploader *u, const char *url) {
235 int r;
236
237 assert(u);
238 assert(url);
239
240 memzero(u, sizeof(Uploader));
241 u->input = -1;
242
243 u->url = url;
244
245 r = sd_event_default(&u->events);
246 if (r < 0) {
247 log_error("sd_event_default failed: %s", strerror(-r));
248 return r;
249 }
250
251 return 0;
252}
253
254static void destroy_uploader(Uploader *u) {
255 assert(u);
256
257 curl_easy_cleanup(u->easy);
258 curl_slist_free_all(u->header);
259
260 u->input_event = sd_event_source_unref(u->input_event);
261
262 close_fd_input(u);
263
264 sd_event_unref(u->events);
265}
266
267static void help(void) {
268 printf("%s -u URL {FILE|-}...\n\n"
269 "Upload journal events to a remote server.\n\n"
270 "Options:\n"
271 " --url=URL Upload to this address\n"
7449bc1f
ZJS
272 " --key=FILENAME Specify key in PEM format\n"
273 " --cert=FILENAME Specify certificate in PEM format\n"
274 " --trust=FILENAME Specify CA certificate in PEM format\n"
3d090cc6
ZJS
275 " -h --help Show this help and exit\n"
276 " --version Print version string and exit\n"
277 , program_invocation_short_name);
278}
279
280static int parse_argv(int argc, char *argv[]) {
281 enum {
282 ARG_VERSION = 0x100,
7449bc1f
ZJS
283 ARG_KEY,
284 ARG_CERT,
285 ARG_TRUST,
3d090cc6
ZJS
286 };
287
288 static const struct option options[] = {
289 { "help", no_argument, NULL, 'h' },
290 { "version", no_argument, NULL, ARG_VERSION },
291 { "url", required_argument, NULL, 'u' },
7449bc1f
ZJS
292 { "key", required_argument, NULL, ARG_KEY },
293 { "cert", required_argument, NULL, ARG_CERT },
294 { "trust", required_argument, NULL, ARG_TRUST },
3d090cc6
ZJS
295 {}
296 };
297
298 int c;
299
300 assert(argc >= 0);
301 assert(argv);
302
303 opterr = 0;
304
305 while ((c = getopt_long(argc, argv, "hu:", options, NULL)) >= 0)
306 switch(c) {
307 case 'h':
308 help();
309 return 0 /* done */;
310
311 case ARG_VERSION:
312 puts(PACKAGE_STRING);
313 puts(SYSTEMD_FEATURES);
314 return 0 /* done */;
315
316 case 'u':
317 if (arg_url) {
318 log_error("cannot use more than one --url");
319 return -EINVAL;
320 }
321
322 arg_url = optarg;
323 break;
324
7449bc1f
ZJS
325 case ARG_KEY:
326 if (arg_key) {
327 log_error("cannot use more than one --key");
328 return -EINVAL;
329 }
330
331 arg_key = optarg;
332 break;
333
334 case ARG_CERT:
335 if (arg_cert) {
336 log_error("cannot use more than one --cert");
337 return -EINVAL;
338 }
339
340 arg_cert = optarg;
341 break;
342
343 case ARG_TRUST:
344 if (arg_trust) {
345 log_error("cannot use more than one --trust");
346 return -EINVAL;
347 }
348
349 arg_trust = optarg;
350 break;
351
3d090cc6
ZJS
352 case '?':
353 log_error("Unknown option %s.", argv[optind-1]);
354 return -EINVAL;
355
356 case ':':
357 log_error("Missing argument to %s.", argv[optind-1]);
358 return -EINVAL;
359
360 default:
361 assert_not_reached("Unhandled option code.");
362 }
363
364 if (!arg_url) {
365 log_error("Required --url/-u option missing.");
366 return -EINVAL;
367 }
368
7449bc1f
ZJS
369 if (!!arg_key != !!arg_cert) {
370 log_error("Options --key and --cert must be used together.");
371 return -EINVAL;
372 }
373
3d090cc6
ZJS
374 if (optind >= argc) {
375 log_error("Input argument missing.");
376 return -EINVAL;
377 }
378
379 return 1;
380}
381
382
383int main(int argc, char **argv) {
384 Uploader u;
385 int r;
386
387 log_show_color(true);
388 log_parse_environment();
389
390 r = parse_argv(argc, argv);
391 if (r <= 0)
392 goto finish;
393
394 r = setup_uploader(&u, arg_url);
395 if (r < 0)
396 goto cleanup;
397
398 log_debug("%s running as pid "PID_FMT,
399 program_invocation_short_name, getpid());
400 sd_notify(false,
401 "READY=1\n"
402 "STATUS=Processing input...");
403
404 while (true) {
405 if (u.input < 0) {
406 if (optind >= argc)
407 break;
408
409 log_debug("Using %s as input.", argv[optind]);
410
411 r = open_file_for_upload(&u, argv[optind++]);
412 if (r < 0)
413 goto cleanup;
414
415 }
416
417 r = sd_event_get_state(u.events);
418 if (r < 0)
419 break;
420 if (r == SD_EVENT_FINISHED)
421 break;
422
423 if (u.uploading) {
424 CURLcode code;
425
426 assert(u.easy);
427
428 code = curl_easy_perform(u.easy);
429 if (code) {
430 log_error("Upload to %s failed: %s",
431 u.url, curl_easy_strerror(code));
432 r = -EIO;
433 break;
434 } else
435 log_debug("Upload finished successfully.");
436 }
437
438 r = sd_event_run(u.events, u.input >= 0 ? -1 : 0);
439 if (r < 0) {
440 log_error("Failed to run event loop: %s", strerror(-r));
441 break;
442 }
443 }
444
445cleanup:
446 sd_notify(false, "STATUS=Shutting down...");
447 destroy_uploader(&u);
448
449finish:
450 return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
451}