chiark / gitweb /
538ba8b6502deb726c3f37632f54f39ea475d77b
[elogind.git] / src / journal-remote / journal-upload.c
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
35 static const char* arg_url;
36
37 static void close_fd_input(Uploader *u);
38
39 static const char *arg_key = NULL;
40 static const char *arg_cert = NULL;
41 static const char *arg_trust = NULL;
42
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
54 int 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
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
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
151 static 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
179 static 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
187 static 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
205 static 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
234 static 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
254 static 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
267 static 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"
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"
275                "  -h --help                Show this help and exit\n"
276                "  --version                Print version string and exit\n"
277                , program_invocation_short_name);
278 }
279
280 static int parse_argv(int argc, char *argv[]) {
281         enum {
282                 ARG_VERSION = 0x100,
283                 ARG_KEY,
284                 ARG_CERT,
285                 ARG_TRUST,
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'                },
292                 { "key",          required_argument, NULL, ARG_KEY            },
293                 { "cert",         required_argument, NULL, ARG_CERT           },
294                 { "trust",        required_argument, NULL, ARG_TRUST          },
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
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
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
369         if (!!arg_key != !!arg_cert) {
370                 log_error("Options --key and --cert must be used together.");
371                 return -EINVAL;
372         }
373
374         if (optind >= argc) {
375                 log_error("Input argument missing.");
376                 return -EINVAL;
377         }
378
379         return 1;
380 }
381
382
383 int 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
445 cleanup:
446         sd_notify(false, "STATUS=Shutting down...");
447         destroy_uploader(&u);
448
449 finish:
450         return r == 0 ? EXIT_SUCCESS : EXIT_FAILURE;
451 }