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