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