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