]> git.ipfire.org Git - thirdparty/systemd.git/blame - src/journal-remote/journal-gatewayd.c
Merge pull request #1693 from ssahani/word
[thirdparty/systemd.git] / src / journal-remote / journal-gatewayd.c
CommitLineData
7b17a7d7
LP
1/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
2
3/***
4 This file is part of systemd.
5
6 Copyright 2012 Lennart Poettering
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
7b17a7d7 22#include <fcntl.h>
858634ff 23#include <getopt.h>
f12be7e8
ZJS
24#ifdef HAVE_GNUTLS
25#include <gnutls/gnutls.h>
26#endif
3ffd4af2
LP
27#include <microhttpd.h>
28#include <stdlib.h>
29#include <string.h>
30#include <unistd.h>
f12be7e8 31
a7edaadd 32#include "sd-bus.h"
3ffd4af2
LP
33#include "sd-daemon.h"
34#include "sd-journal.h"
3f6fd1ba 35
40ca29a1 36#include "bus-util.h"
3ffd4af2 37#include "fd-util.h"
3f6fd1ba
LP
38#include "fileio.h"
39#include "hostname-util.h"
40#include "log.h"
7b17a7d7 41#include "logs-show.h"
e64690a8 42#include "microhttpd-util.h"
2cf4172a 43#include "sigbus.h"
3f6fd1ba 44#include "util.h"
7b17a7d7 45
2cf4172a
LP
46static char *arg_key_pem = NULL;
47static char *arg_cert_pem = NULL;
48static char *arg_trust_pem = NULL;
f12be7e8 49
7b17a7d7
LP
50typedef struct RequestMeta {
51 sd_journal *journal;
52
53 OutputMode mode;
54
55 char *cursor;
56 int64_t n_skip;
57 uint64_t n_entries;
58 bool n_entries_set;
59
60 FILE *tmp;
61 uint64_t delta, size;
98206c93
LP
62
63 int argument_parse_error;
7a69007a
LP
64
65 bool follow;
c6511e85 66 bool discrete;
240a5fe8
LP
67
68 uint64_t n_fields;
69 bool n_fields_set;
7b17a7d7
LP
70} RequestMeta;
71
72static const char* const mime_types[_OUTPUT_MODE_MAX] = {
73 [OUTPUT_SHORT] = "text/plain",
74 [OUTPUT_JSON] = "application/json",
48383c25
LP
75 [OUTPUT_JSON_SSE] = "text/event-stream",
76 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
7b17a7d7
LP
77};
78
79static RequestMeta *request_meta(void **connection_cls) {
80 RequestMeta *m;
81
fdfccdbc 82 assert(connection_cls);
7b17a7d7
LP
83 if (*connection_cls)
84 return *connection_cls;
85
86 m = new0(RequestMeta, 1);
87 if (!m)
88 return NULL;
89
90 *connection_cls = m;
91 return m;
92}
93
94static void request_meta_free(
95 void *cls,
96 struct MHD_Connection *connection,
97 void **connection_cls,
98 enum MHD_RequestTerminationCode toe) {
99
100 RequestMeta *m = *connection_cls;
101
102 if (!m)
103 return;
104
3e044c49 105 sd_journal_close(m->journal);
7b17a7d7 106
74ca738f 107 safe_fclose(m->tmp);
7b17a7d7
LP
108
109 free(m->cursor);
110 free(m);
111}
112
113static int open_journal(RequestMeta *m) {
114 assert(m);
115
116 if (m->journal)
117 return 0;
118
a688baa8 119 return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
7b17a7d7
LP
120}
121
cc02a7b3
ZJS
122static int request_meta_ensure_tmp(RequestMeta *m) {
123 if (m->tmp)
124 rewind(m->tmp);
125 else {
126 int fd;
127
128 fd = open_tmpfile("/tmp", O_RDWR|O_CLOEXEC);
129 if (fd < 0)
130 return fd;
131
9e19c04f 132 m->tmp = fdopen(fd, "w+");
cc02a7b3
ZJS
133 if (!m->tmp) {
134 safe_close(fd);
135 return -errno;
136 }
137 }
138
139 return 0;
140}
141
7b17a7d7
LP
142static ssize_t request_reader_entries(
143 void *cls,
144 uint64_t pos,
145 char *buf,
146 size_t max) {
147
148 RequestMeta *m = cls;
149 int r;
150 size_t n, k;
151
152 assert(m);
153 assert(buf);
154 assert(max > 0);
155 assert(pos >= m->delta);
156
157 pos -= m->delta;
158
159 while (pos >= m->size) {
160 off_t sz;
161
162 /* End of this entry, so let's serialize the next
163 * one */
164
165 if (m->n_entries_set &&
166 m->n_entries <= 0)
167 return MHD_CONTENT_READER_END_OF_STREAM;
168
77ad3b93
LP
169 if (m->n_skip < 0)
170 r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
171 else if (m->n_skip > 0)
7b17a7d7
LP
172 r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
173 else
174 r = sd_journal_next(m->journal);
175
176 if (r < 0) {
da927ba9 177 log_error_errno(r, "Failed to advance journal pointer: %m");
7b17a7d7 178 return MHD_CONTENT_READER_END_WITH_ERROR;
7a69007a
LP
179 } else if (r == 0) {
180
181 if (m->follow) {
182 r = sd_journal_wait(m->journal, (uint64_t) -1);
183 if (r < 0) {
da927ba9 184 log_error_errno(r, "Couldn't wait for journal event: %m");
7a69007a
LP
185 return MHD_CONTENT_READER_END_WITH_ERROR;
186 }
187
188 continue;
189 }
190
7b17a7d7 191 return MHD_CONTENT_READER_END_OF_STREAM;
7a69007a 192 }
7b17a7d7 193
c6511e85
LP
194 if (m->discrete) {
195 assert(m->cursor);
196
197 r = sd_journal_test_cursor(m->journal, m->cursor);
198 if (r < 0) {
da927ba9 199 log_error_errno(r, "Failed to test cursor: %m");
c6511e85
LP
200 return MHD_CONTENT_READER_END_WITH_ERROR;
201 }
202
203 if (r == 0)
204 return MHD_CONTENT_READER_END_OF_STREAM;
205 }
206
7b17a7d7
LP
207 pos -= m->size;
208 m->delta += m->size;
209
210 if (m->n_entries_set)
211 m->n_entries -= 1;
212
213 m->n_skip = 0;
214
cc02a7b3
ZJS
215 r = request_meta_ensure_tmp(m);
216 if (r < 0) {
217 log_error_errno(r, "Failed to create temporary file: %m");
218 return MHD_CONTENT_READER_END_WITH_ERROR;
7b17a7d7
LP
219 }
220
94e0bd7d 221 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
7b17a7d7 222 if (r < 0) {
da927ba9 223 log_error_errno(r, "Failed to serialize item: %m");
7b17a7d7
LP
224 return MHD_CONTENT_READER_END_WITH_ERROR;
225 }
226
227 sz = ftello(m->tmp);
228 if (sz == (off_t) -1) {
56f64d95 229 log_error_errno(errno, "Failed to retrieve file position: %m");
7b17a7d7
LP
230 return MHD_CONTENT_READER_END_WITH_ERROR;
231 }
232
233 m->size = (uint64_t) sz;
234 }
235
236 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
56f64d95 237 log_error_errno(errno, "Failed to seek to position: %m");
7b17a7d7
LP
238 return MHD_CONTENT_READER_END_WITH_ERROR;
239 }
240
241 n = m->size - pos;
242 if (n > max)
243 n = max;
244
245 errno = 0;
246 k = fread(buf, 1, n, m->tmp);
247 if (k != n) {
248 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
249 return MHD_CONTENT_READER_END_WITH_ERROR;
250 }
251
252 return (ssize_t) k;
253}
254
255static int request_parse_accept(
256 RequestMeta *m,
257 struct MHD_Connection *connection) {
258
6374a73b 259 const char *header;
7b17a7d7
LP
260
261 assert(m);
262 assert(connection);
263
6374a73b
ZJS
264 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
265 if (!header)
7b17a7d7
LP
266 return 0;
267
6374a73b 268 if (streq(header, mime_types[OUTPUT_JSON]))
7b17a7d7 269 m->mode = OUTPUT_JSON;
6374a73b 270 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
48383c25 271 m->mode = OUTPUT_JSON_SSE;
6374a73b 272 else if (streq(header, mime_types[OUTPUT_EXPORT]))
7b17a7d7
LP
273 m->mode = OUTPUT_EXPORT;
274 else
275 m->mode = OUTPUT_SHORT;
276
277 return 0;
278}
279
280static int request_parse_range(
281 RequestMeta *m,
282 struct MHD_Connection *connection) {
283
284 const char *range, *colon, *colon2;
285 int r;
286
287 assert(m);
288 assert(connection);
289
290 range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
291 if (!range)
292 return 0;
293
294 if (!startswith(range, "entries="))
295 return 0;
296
297 range += 8;
298 range += strspn(range, WHITESPACE);
299
300 colon = strchr(range, ':');
301 if (!colon)
302 m->cursor = strdup(range);
303 else {
304 const char *p;
305
306 colon2 = strchr(colon + 1, ':');
307 if (colon2) {
7fd1b19b 308 _cleanup_free_ char *t;
7b17a7d7
LP
309
310 t = strndup(colon + 1, colon2 - colon - 1);
311 if (!t)
312 return -ENOMEM;
313
314 r = safe_atoi64(t, &m->n_skip);
7b17a7d7
LP
315 if (r < 0)
316 return r;
317 }
318
319 p = (colon2 ? colon2 : colon) + 1;
320 if (*p) {
321 r = safe_atou64(p, &m->n_entries);
322 if (r < 0)
323 return r;
324
325 if (m->n_entries <= 0)
326 return -EINVAL;
327
328 m->n_entries_set = true;
329 }
330
331 m->cursor = strndup(range, colon - range);
332 }
333
334 if (!m->cursor)
335 return -ENOMEM;
336
337 m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
ece174c5 338 if (isempty(m->cursor))
a1e58e8e 339 m->cursor = mfree(m->cursor);
7b17a7d7
LP
340
341 return 0;
342}
343
98206c93
LP
344static int request_parse_arguments_iterator(
345 void *cls,
346 enum MHD_ValueKind kind,
347 const char *key,
348 const char *value) {
349
350 RequestMeta *m = cls;
351 _cleanup_free_ char *p = NULL;
352 int r;
353
354 assert(m);
355
356 if (isempty(key)) {
357 m->argument_parse_error = -EINVAL;
358 return MHD_NO;
359 }
360
7a69007a
LP
361 if (streq(key, "follow")) {
362 if (isempty(value)) {
363 m->follow = true;
364 return MHD_YES;
365 }
366
367 r = parse_boolean(value);
368 if (r < 0) {
369 m->argument_parse_error = r;
370 return MHD_NO;
371 }
372
373 m->follow = r;
374 return MHD_YES;
375 }
376
c6511e85
LP
377 if (streq(key, "discrete")) {
378 if (isempty(value)) {
379 m->discrete = true;
380 return MHD_YES;
381 }
382
383 r = parse_boolean(value);
384 if (r < 0) {
385 m->argument_parse_error = r;
386 return MHD_NO;
387 }
388
389 m->discrete = r;
390 return MHD_YES;
391 }
392
082d0180
LP
393 if (streq(key, "boot")) {
394 if (isempty(value))
395 r = true;
396 else {
397 r = parse_boolean(value);
398 if (r < 0) {
399 m->argument_parse_error = r;
400 return MHD_NO;
401 }
402 }
403
404 if (r) {
405 char match[9 + 32 + 1] = "_BOOT_ID=";
406 sd_id128_t bid;
407
408 r = sd_id128_get_boot(&bid);
409 if (r < 0) {
da927ba9 410 log_error_errno(r, "Failed to get boot ID: %m");
082d0180
LP
411 return MHD_NO;
412 }
413
414 sd_id128_to_string(bid, match + 9);
415 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
416 if (r < 0) {
417 m->argument_parse_error = r;
418 return MHD_NO;
419 }
420 }
421
422 return MHD_YES;
423 }
424
98206c93
LP
425 p = strjoin(key, "=", strempty(value), NULL);
426 if (!p) {
427 m->argument_parse_error = log_oom();
428 return MHD_NO;
429 }
430
431 r = sd_journal_add_match(m->journal, p, 0);
432 if (r < 0) {
433 m->argument_parse_error = r;
434 return MHD_NO;
435 }
436
437 return MHD_YES;
438}
439
440static int request_parse_arguments(
441 RequestMeta *m,
442 struct MHD_Connection *connection) {
443
444 assert(m);
445 assert(connection);
446
447 m->argument_parse_error = 0;
448 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
449
450 return m->argument_parse_error;
451}
452
7b17a7d7
LP
453static int request_handler_entries(
454 struct MHD_Connection *connection,
8530a143 455 void *connection_cls) {
7b17a7d7
LP
456
457 struct MHD_Response *response;
8530a143 458 RequestMeta *m = connection_cls;
7b17a7d7
LP
459 int r;
460
461 assert(connection);
8530a143 462 assert(m);
7b17a7d7
LP
463
464 r = open_journal(m);
465 if (r < 0)
e7216d11 466 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
7b17a7d7
LP
467
468 if (request_parse_accept(m, connection) < 0)
e7216d11 469 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
7b17a7d7
LP
470
471 if (request_parse_range(m, connection) < 0)
e7216d11 472 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
7b17a7d7 473
98206c93 474 if (request_parse_arguments(m, connection) < 0)
e7216d11 475 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
98206c93 476
c6511e85
LP
477 if (m->discrete) {
478 if (!m->cursor)
e7216d11 479 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
c6511e85
LP
480
481 m->n_entries = 1;
482 m->n_entries_set = true;
483 }
484
7b17a7d7
LP
485 if (m->cursor)
486 r = sd_journal_seek_cursor(m->journal, m->cursor);
487 else if (m->n_skip >= 0)
488 r = sd_journal_seek_head(m->journal);
489 else if (m->n_skip < 0)
490 r = sd_journal_seek_tail(m->journal);
491 if (r < 0)
e7216d11 492 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
7b17a7d7
LP
493
494 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
495 if (!response)
496 return respond_oom(connection);
497
498 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
499
500 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
501 MHD_destroy_response(response);
502
503 return r;
504}
505
240a5fe8
LP
506static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
507 const char *eq;
508 size_t j;
509
510 eq = memchr(d, '=', l);
511 if (!eq)
512 return -EINVAL;
513
514 j = l - (eq - d + 1);
515
516 if (m == OUTPUT_JSON) {
517 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
518 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
519 fputs(" }\n", f);
520 } else {
521 fwrite(eq+1, 1, j, f);
522 fputc('\n', f);
523 }
524
525 return 0;
526}
527
528static ssize_t request_reader_fields(
529 void *cls,
530 uint64_t pos,
531 char *buf,
532 size_t max) {
533
534 RequestMeta *m = cls;
535 int r;
536 size_t n, k;
537
538 assert(m);
539 assert(buf);
540 assert(max > 0);
541 assert(pos >= m->delta);
542
543 pos -= m->delta;
544
545 while (pos >= m->size) {
546 off_t sz;
547 const void *d;
548 size_t l;
549
550 /* End of this field, so let's serialize the next
551 * one */
552
553 if (m->n_fields_set &&
554 m->n_fields <= 0)
555 return MHD_CONTENT_READER_END_OF_STREAM;
556
557 r = sd_journal_enumerate_unique(m->journal, &d, &l);
558 if (r < 0) {
da927ba9 559 log_error_errno(r, "Failed to advance field index: %m");
240a5fe8
LP
560 return MHD_CONTENT_READER_END_WITH_ERROR;
561 } else if (r == 0)
562 return MHD_CONTENT_READER_END_OF_STREAM;
563
564 pos -= m->size;
565 m->delta += m->size;
566
567 if (m->n_fields_set)
568 m->n_fields -= 1;
569
cc02a7b3
ZJS
570 r = request_meta_ensure_tmp(m);
571 if (r < 0) {
572 log_error_errno(r, "Failed to create temporary file: %m");
573 return MHD_CONTENT_READER_END_WITH_ERROR;
240a5fe8
LP
574 }
575
576 r = output_field(m->tmp, m->mode, d, l);
577 if (r < 0) {
da927ba9 578 log_error_errno(r, "Failed to serialize item: %m");
240a5fe8
LP
579 return MHD_CONTENT_READER_END_WITH_ERROR;
580 }
581
582 sz = ftello(m->tmp);
583 if (sz == (off_t) -1) {
56f64d95 584 log_error_errno(errno, "Failed to retrieve file position: %m");
240a5fe8
LP
585 return MHD_CONTENT_READER_END_WITH_ERROR;
586 }
587
588 m->size = (uint64_t) sz;
589 }
590
591 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
56f64d95 592 log_error_errno(errno, "Failed to seek to position: %m");
240a5fe8
LP
593 return MHD_CONTENT_READER_END_WITH_ERROR;
594 }
595
596 n = m->size - pos;
597 if (n > max)
598 n = max;
599
600 errno = 0;
601 k = fread(buf, 1, n, m->tmp);
602 if (k != n) {
603 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
604 return MHD_CONTENT_READER_END_WITH_ERROR;
605 }
606
607 return (ssize_t) k;
608}
609
610static int request_handler_fields(
611 struct MHD_Connection *connection,
612 const char *field,
613 void *connection_cls) {
614
615 struct MHD_Response *response;
8530a143 616 RequestMeta *m = connection_cls;
240a5fe8
LP
617 int r;
618
619 assert(connection);
8530a143 620 assert(m);
240a5fe8
LP
621
622 r = open_journal(m);
623 if (r < 0)
e7216d11 624 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
240a5fe8
LP
625
626 if (request_parse_accept(m, connection) < 0)
e7216d11 627 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
240a5fe8
LP
628
629 r = sd_journal_query_unique(m->journal, field);
630 if (r < 0)
e7216d11 631 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
240a5fe8
LP
632
633 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
634 if (!response)
635 return respond_oom(connection);
636
637 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
638
639 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
640 MHD_destroy_response(response);
641
642 return r;
643}
644
7b17a7d7
LP
645static int request_handler_redirect(
646 struct MHD_Connection *connection,
647 const char *target) {
648
649 char *page;
650 struct MHD_Response *response;
651 int ret;
652
653 assert(connection);
fadd79d2 654 assert(target);
7b17a7d7
LP
655
656 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
657 return respond_oom(connection);
658
659 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
660 if (!response) {
661 free(page);
662 return respond_oom(connection);
663 }
664
665 MHD_add_response_header(response, "Content-Type", "text/html");
666 MHD_add_response_header(response, "Location", target);
667
668 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
669 MHD_destroy_response(response);
670
671 return ret;
672}
673
674static int request_handler_file(
675 struct MHD_Connection *connection,
676 const char *path,
677 const char *mime_type) {
678
679 struct MHD_Response *response;
680 int ret;
681 _cleanup_close_ int fd = -1;
682 struct stat st;
683
684 assert(connection);
685 assert(path);
686 assert(mime_type);
687
688 fd = open(path, O_RDONLY|O_CLOEXEC);
689 if (fd < 0)
e7216d11 690 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
7b17a7d7
LP
691
692 if (fstat(fd, &st) < 0)
e7216d11 693 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
7b17a7d7
LP
694
695 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
696 if (!response)
697 return respond_oom(connection);
698
699 fd = -1;
700
701 MHD_add_response_header(response, "Content-Type", mime_type);
702
703 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
704 MHD_destroy_response(response);
705
706 return ret;
707}
708
a7edaadd 709static int get_virtualization(char **v) {
a7edaadd 710 _cleanup_bus_unref_ sd_bus *bus = NULL;
39883f62 711 char *b = NULL;
a7edaadd
LP
712 int r;
713
76b54375 714 r = sd_bus_default_system(&bus);
a7edaadd
LP
715 if (r < 0)
716 return r;
717
47c649b5 718 r = sd_bus_get_property_string(
a7edaadd
LP
719 bus,
720 "org.freedesktop.systemd1",
721 "/org/freedesktop/systemd1",
917b5dc7 722 "org.freedesktop.systemd1.Manager",
47c649b5
LP
723 "Virtualization",
724 NULL,
725 &b);
a7edaadd
LP
726 if (r < 0)
727 return r;
728
47c649b5
LP
729 if (isempty(b)) {
730 free(b);
a7edaadd
LP
731 *v = NULL;
732 return 0;
733 }
734
a7edaadd
LP
735 *v = b;
736 return 1;
737}
738
7b17a7d7
LP
739static int request_handler_machine(
740 struct MHD_Connection *connection,
8530a143 741 void *connection_cls) {
7b17a7d7
LP
742
743 struct MHD_Response *response;
8530a143 744 RequestMeta *m = connection_cls;
7b17a7d7
LP
745 int r;
746 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
a7f7d1bd 747 uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
7b17a7d7
LP
748 char *json;
749 sd_id128_t mid, bid;
a7edaadd 750 _cleanup_free_ char *v = NULL;
7b17a7d7
LP
751
752 assert(connection);
8530a143 753 assert(m);
7b17a7d7
LP
754
755 r = open_journal(m);
756 if (r < 0)
e7216d11 757 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
7b17a7d7
LP
758
759 r = sd_id128_get_machine(&mid);
760 if (r < 0)
e7216d11 761 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
7b17a7d7
LP
762
763 r = sd_id128_get_boot(&bid);
764 if (r < 0)
e7216d11 765 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
7b17a7d7
LP
766
767 hostname = gethostname_malloc();
768 if (!hostname)
769 return respond_oom(connection);
770
771 r = sd_journal_get_usage(m->journal, &usage);
772 if (r < 0)
e7216d11 773 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
7b17a7d7
LP
774
775 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
776 if (r < 0)
e7216d11 777 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
7b17a7d7 778
5ae4d543 779 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
e51728b0 780 (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
7b17a7d7 781
a7edaadd 782 get_virtualization(&v);
7b17a7d7
LP
783
784 r = asprintf(&json,
785 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
786 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
787 "\"hostname\" : \"%s\","
788 "\"os_pretty_name\" : \"%s\","
789 "\"virtualization\" : \"%s\","
507f22bd
ZJS
790 "\"usage\" : \"%"PRIu64"\","
791 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
792 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
7b17a7d7
LP
793 SD_ID128_FORMAT_VAL(mid),
794 SD_ID128_FORMAT_VAL(bid),
ae691c1d 795 hostname_cleanup(hostname),
7b17a7d7 796 os_name ? os_name : "Linux",
a7edaadd 797 v ? v : "bare",
507f22bd
ZJS
798 usage,
799 cutoff_from,
800 cutoff_to);
7b17a7d7
LP
801
802 if (r < 0)
803 return respond_oom(connection);
804
805 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
806 if (!response) {
807 free(json);
808 return respond_oom(connection);
809 }
810
811 MHD_add_response_header(response, "Content-Type", "application/json");
812 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
813 MHD_destroy_response(response);
814
815 return r;
816}
817
818static int request_handler(
819 void *cls,
820 struct MHD_Connection *connection,
821 const char *url,
822 const char *method,
823 const char *version,
824 const char *upload_data,
825 size_t *upload_data_size,
826 void **connection_cls) {
f12be7e8 827 int r, code;
7b17a7d7
LP
828
829 assert(connection);
8530a143 830 assert(connection_cls);
7b17a7d7
LP
831 assert(url);
832 assert(method);
833
834 if (!streq(method, "GET"))
e7216d11
ZJS
835 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
836 "Unsupported method.\n");
a93035ce 837
7b17a7d7 838
8530a143
ZJS
839 if (!*connection_cls) {
840 if (!request_meta(connection_cls))
841 return respond_oom(connection);
842 return MHD_YES;
843 }
844
2cf4172a 845 if (arg_trust_pem) {
8201af08 846 r = check_permissions(connection, &code, NULL);
f12be7e8
ZJS
847 if (r < 0)
848 return code;
849 }
850
7b17a7d7
LP
851 if (streq(url, "/"))
852 return request_handler_redirect(connection, "/browse");
853
854 if (streq(url, "/entries"))
8530a143 855 return request_handler_entries(connection, *connection_cls);
7b17a7d7 856
240a5fe8 857 if (startswith(url, "/fields/"))
8530a143 858 return request_handler_fields(connection, url + 8, *connection_cls);
240a5fe8 859
7b17a7d7
LP
860 if (streq(url, "/browse"))
861 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
862
863 if (streq(url, "/machine"))
8530a143 864 return request_handler_machine(connection, *connection_cls);
7b17a7d7 865
e7216d11 866 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
7b17a7d7
LP
867}
868
601185b4 869static void help(void) {
c3a7cfb7
ZJS
870 printf("%s [OPTIONS...] ...\n\n"
871 "HTTP server for journal events.\n\n"
872 " -h --help Show this help\n"
873 " --version Show package version\n"
e5ebe12b
ZJS
874 " --cert=CERT.PEM Server certificate in PEM format\n"
875 " --key=KEY.PEM Server key in PEM format\n"
876 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
c3a7cfb7 877 program_invocation_short_name);
c3a7cfb7
ZJS
878}
879
858634ff
ZJS
880static int parse_argv(int argc, char *argv[]) {
881 enum {
882 ARG_VERSION = 0x100,
883 ARG_KEY,
884 ARG_CERT,
e5ebe12b 885 ARG_TRUST,
858634ff
ZJS
886 };
887
888 int r, c;
889
890 static const struct option options[] = {
c3a7cfb7 891 { "help", no_argument, NULL, 'h' },
858634ff
ZJS
892 { "version", no_argument, NULL, ARG_VERSION },
893 { "key", required_argument, NULL, ARG_KEY },
894 { "cert", required_argument, NULL, ARG_CERT },
e5ebe12b 895 { "trust", required_argument, NULL, ARG_TRUST },
eb9da376 896 {}
858634ff
ZJS
897 };
898
899 assert(argc >= 0);
900 assert(argv);
901
c3a7cfb7 902 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
eb9da376 903
858634ff 904 switch(c) {
eb9da376
LP
905
906 case 'h':
601185b4
ZJS
907 help();
908 return 0;
eb9da376 909
858634ff 910 case ARG_VERSION:
3f6fd1ba 911 return version();
858634ff
ZJS
912
913 case ARG_KEY:
2cf4172a 914 if (arg_key_pem) {
858634ff
ZJS
915 log_error("Key file specified twice");
916 return -EINVAL;
917 }
2cf4172a 918 r = read_full_file(optarg, &arg_key_pem, NULL);
23bbb0de
MS
919 if (r < 0)
920 return log_error_errno(r, "Failed to read key file: %m");
2cf4172a 921 assert(arg_key_pem);
858634ff 922 break;
7b17a7d7 923
858634ff 924 case ARG_CERT:
2cf4172a 925 if (arg_cert_pem) {
858634ff
ZJS
926 log_error("Certificate file specified twice");
927 return -EINVAL;
928 }
2cf4172a 929 r = read_full_file(optarg, &arg_cert_pem, NULL);
23bbb0de
MS
930 if (r < 0)
931 return log_error_errno(r, "Failed to read certificate file: %m");
2cf4172a 932 assert(arg_cert_pem);
858634ff
ZJS
933 break;
934
e5ebe12b 935 case ARG_TRUST:
f12be7e8 936#ifdef HAVE_GNUTLS
2cf4172a 937 if (arg_trust_pem) {
e5ebe12b
ZJS
938 log_error("CA certificate file specified twice");
939 return -EINVAL;
940 }
2cf4172a 941 r = read_full_file(optarg, &arg_trust_pem, NULL);
23bbb0de
MS
942 if (r < 0)
943 return log_error_errno(r, "Failed to read CA certificate file: %m");
2cf4172a 944 assert(arg_trust_pem);
e5ebe12b 945 break;
f12be7e8
ZJS
946#else
947 log_error("Option --trust is not available.");
948#endif
e5ebe12b 949
858634ff
ZJS
950 case '?':
951 return -EINVAL;
952
953 default:
eb9da376 954 assert_not_reached("Unhandled option");
858634ff
ZJS
955 }
956
957 if (optind < argc) {
7b17a7d7 958 log_error("This program does not take arguments.");
858634ff
ZJS
959 return -EINVAL;
960 }
961
2cf4172a 962 if (!!arg_key_pem != !!arg_cert_pem) {
858634ff
ZJS
963 log_error("Certificate and key files must be specified together");
964 return -EINVAL;
7b17a7d7
LP
965 }
966
2cf4172a 967 if (arg_trust_pem && !arg_key_pem) {
e5ebe12b
ZJS
968 log_error("CA certificate can only be used with certificate file");
969 return -EINVAL;
970 }
971
858634ff
ZJS
972 return 1;
973}
974
975int main(int argc, char *argv[]) {
976 struct MHD_Daemon *d = NULL;
977 int r, n;
978
77ad3b93 979 log_set_target(LOG_TARGET_AUTO);
7b17a7d7
LP
980 log_parse_environment();
981 log_open();
982
858634ff
ZJS
983 r = parse_argv(argc, argv);
984 if (r < 0)
985 return EXIT_FAILURE;
986 if (r == 0)
987 return EXIT_SUCCESS;
988
2cf4172a
LP
989 sigbus_install();
990
d357562c
ZJS
991 r = setup_gnutls_logger(NULL);
992 if (r < 0)
993 return EXIT_FAILURE;
cafc7f91 994
7b17a7d7
LP
995 n = sd_listen_fds(1);
996 if (n < 0) {
da927ba9 997 log_error_errno(n, "Failed to determine passed sockets: %m");
7b17a7d7
LP
998 goto finish;
999 } else if (n > 1) {
1000 log_error("Can't listen on more than one socket.");
1001 goto finish;
7b17a7d7 1002 } else {
c54ff8e3
ZJS
1003 struct MHD_OptionItem opts[] = {
1004 { MHD_OPTION_NOTIFY_COMPLETED,
1005 (intptr_t) request_meta_free, NULL },
e64690a8
ZJS
1006 { MHD_OPTION_EXTERNAL_LOGGER,
1007 (intptr_t) microhttpd_logger, NULL },
c54ff8e3 1008 { MHD_OPTION_END, 0, NULL },
858634ff
ZJS
1009 { MHD_OPTION_END, 0, NULL },
1010 { MHD_OPTION_END, 0, NULL },
e5ebe12b 1011 { MHD_OPTION_END, 0, NULL },
c54ff8e3 1012 { MHD_OPTION_END, 0, NULL }};
e64690a8 1013 int opts_pos = 2;
ef08ced6
LP
1014
1015 /* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order
1016 * to make sure libmicrohttpd doesn't use shutdown()
1017 * on our listening socket, which would break socket
1018 * re-activation. See
1019 *
1020 * https://lists.gnu.org/archive/html/libmicrohttpd/2015-09/msg00014.html
1021 * https://github.com/systemd/systemd/pull/1286
1022 */
1023
1024 int flags =
1025 MHD_USE_DEBUG |
1026 MHD_USE_DUAL_STACK |
1027 MHD_USE_PIPE_FOR_SHUTDOWN |
1028 MHD_USE_POLL |
1029 MHD_USE_THREAD_PER_CONNECTION;
858634ff 1030
c54ff8e3 1031 if (n > 0)
858634ff
ZJS
1032 opts[opts_pos++] = (struct MHD_OptionItem)
1033 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
2cf4172a
LP
1034 if (arg_key_pem) {
1035 assert(arg_cert_pem);
858634ff 1036 opts[opts_pos++] = (struct MHD_OptionItem)
2cf4172a 1037 {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
858634ff 1038 opts[opts_pos++] = (struct MHD_OptionItem)
2cf4172a 1039 {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
858634ff
ZJS
1040 flags |= MHD_USE_SSL;
1041 }
2cf4172a 1042 if (arg_trust_pem) {
e5ebe12b
ZJS
1043 assert(flags & MHD_USE_SSL);
1044 opts[opts_pos++] = (struct MHD_OptionItem)
2cf4172a 1045 {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
e5ebe12b 1046 }
858634ff
ZJS
1047
1048 d = MHD_start_daemon(flags, 19531,
1049 NULL, NULL,
1050 request_handler, NULL,
1051 MHD_OPTION_ARRAY, opts,
1052 MHD_OPTION_END);
7b17a7d7
LP
1053 }
1054
6374a73b 1055 if (!d) {
7b17a7d7
LP
1056 log_error("Failed to start daemon!");
1057 goto finish;
1058 }
1059
1060 pause();
1061
1062 r = EXIT_SUCCESS;
1063
1064finish:
6374a73b
ZJS
1065 if (d)
1066 MHD_stop_daemon(d);
7b17a7d7
LP
1067
1068 return r;
1069}