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