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