]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-gatewayd.c
Move network-related journal programs to src/journal-remote/
[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 "log.h"
35 #include "util.h"
36 #include "sd-journal.h"
37 #include "sd-daemon.h"
38 #include "sd-bus.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
45 static char *key_pem = NULL;
46 static char *cert_pem = NULL;
47 static char *trust_pem = NULL;
48
49 typedef 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;
61
62 int argument_parse_error;
63
64 bool follow;
65 bool discrete;
66
67 uint64_t n_fields;
68 bool n_fields_set;
69 } RequestMeta;
70
71 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
72 [OUTPUT_SHORT] = "text/plain",
73 [OUTPUT_JSON] = "application/json",
74 [OUTPUT_JSON_SSE] = "text/event-stream",
75 [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
76 };
77
78 static RequestMeta *request_meta(void **connection_cls) {
79 RequestMeta *m;
80
81 assert(connection_cls);
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
93 static 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
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 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
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)
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;
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
172 return MHD_CONTENT_READER_END_OF_STREAM;
173 }
174
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
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");
202 return MHD_CONTENT_READER_END_WITH_ERROR;
203 }
204 }
205
206 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
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
240 static int request_parse_accept(
241 RequestMeta *m,
242 struct MHD_Connection *connection) {
243
244 const char *header;
245
246 assert(m);
247 assert(connection);
248
249 header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
250 if (!header)
251 return 0;
252
253 if (streq(header, mime_types[OUTPUT_JSON]))
254 m->mode = OUTPUT_JSON;
255 else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
256 m->mode = OUTPUT_JSON_SSE;
257 else if (streq(header, mime_types[OUTPUT_EXPORT]))
258 m->mode = OUTPUT_EXPORT;
259 else
260 m->mode = OUTPUT_SHORT;
261
262 return 0;
263 }
264
265 static 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) {
293 _cleanup_free_ char *t;
294
295 t = strndup(colon + 1, colon2 - colon - 1);
296 if (!t)
297 return -ENOMEM;
298
299 r = safe_atoi64(t, &m->n_skip);
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
331 static 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
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
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
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
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
427 static 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
440 static int request_handler_entries(
441 struct MHD_Connection *connection,
442 void *connection_cls) {
443
444 struct MHD_Response *response;
445 RequestMeta *m = connection_cls;
446 int r;
447
448 assert(connection);
449 assert(m);
450
451 r = open_journal(m);
452 if (r < 0)
453 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
454
455 if (request_parse_accept(m, connection) < 0)
456 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
457
458 if (request_parse_range(m, connection) < 0)
459 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
460
461 if (request_parse_arguments(m, connection) < 0)
462 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
463
464 if (m->discrete) {
465 if (!m->cursor)
466 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
467
468 m->n_entries = 1;
469 m->n_entries_set = true;
470 }
471
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)
479 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
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
493 static 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
515 static 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");
563 return MHD_CONTENT_READER_END_WITH_ERROR;
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
601 static int request_handler_fields(
602 struct MHD_Connection *connection,
603 const char *field,
604 void *connection_cls) {
605
606 struct MHD_Response *response;
607 RequestMeta *m = connection_cls;
608 int r;
609
610 assert(connection);
611 assert(m);
612
613 r = open_journal(m);
614 if (r < 0)
615 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
616
617 if (request_parse_accept(m, connection) < 0)
618 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
619
620 r = sd_journal_query_unique(m->journal, field);
621 if (r < 0)
622 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
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
636 static 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);
645 assert(target);
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
665 static 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)
681 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
682
683 if (fstat(fd, &st) < 0)
684 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
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
700 static int get_virtualization(char **v) {
701 _cleanup_bus_unref_ sd_bus *bus = NULL;
702 char *b = NULL;
703 int r;
704
705 r = sd_bus_default_system(&bus);
706 if (r < 0)
707 return r;
708
709 r = sd_bus_get_property_string(
710 bus,
711 "org.freedesktop.systemd1",
712 "/org/freedesktop/systemd1",
713 "org.freedesktop.systemd1.Manager",
714 "Virtualization",
715 NULL,
716 &b);
717 if (r < 0)
718 return r;
719
720 if (isempty(b)) {
721 free(b);
722 *v = NULL;
723 return 0;
724 }
725
726 *v = b;
727 return 1;
728 }
729
730 static int request_handler_machine(
731 struct MHD_Connection *connection,
732 void *connection_cls) {
733
734 struct MHD_Response *response;
735 RequestMeta *m = connection_cls;
736 int r;
737 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
738 uint64_t cutoff_from = 0, cutoff_to = 0, usage;
739 char *json;
740 sd_id128_t mid, bid;
741 _cleanup_free_ char *v = NULL;
742
743 assert(connection);
744 assert(m);
745
746 r = open_journal(m);
747 if (r < 0)
748 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
749
750 r = sd_id128_get_machine(&mid);
751 if (r < 0)
752 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
753
754 r = sd_id128_get_boot(&bid);
755 if (r < 0)
756 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
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)
764 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
765
766 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
767 if (r < 0)
768 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
769
770 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
771 parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
772
773 get_virtualization(&v);
774
775 r = asprintf(&json,
776 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
777 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
778 "\"hostname\" : \"%s\","
779 "\"os_pretty_name\" : \"%s\","
780 "\"virtualization\" : \"%s\","
781 "\"usage\" : \"%"PRIu64"\","
782 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
783 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
784 SD_ID128_FORMAT_VAL(mid),
785 SD_ID128_FORMAT_VAL(bid),
786 hostname_cleanup(hostname, false),
787 os_name ? os_name : "Linux",
788 v ? v : "bare",
789 usage,
790 cutoff_from,
791 cutoff_to);
792
793 if (r < 0)
794 return respond_oom(connection);
795
796 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
797 if (!response) {
798 free(json);
799 return respond_oom(connection);
800 }
801
802 MHD_add_response_header(response, "Content-Type", "application/json");
803 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
804 MHD_destroy_response(response);
805
806 return r;
807 }
808
809 static int request_handler(
810 void *cls,
811 struct MHD_Connection *connection,
812 const char *url,
813 const char *method,
814 const char *version,
815 const char *upload_data,
816 size_t *upload_data_size,
817 void **connection_cls) {
818 int r, code;
819
820 assert(connection);
821 assert(connection_cls);
822 assert(url);
823 assert(method);
824
825 if (!streq(method, "GET"))
826 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
827 "Unsupported method.\n");
828
829
830 if (!*connection_cls) {
831 if (!request_meta(connection_cls))
832 return respond_oom(connection);
833 return MHD_YES;
834 }
835
836 if (trust_pem) {
837 r = check_permissions(connection, &code);
838 if (r < 0)
839 return code;
840 }
841
842 if (streq(url, "/"))
843 return request_handler_redirect(connection, "/browse");
844
845 if (streq(url, "/entries"))
846 return request_handler_entries(connection, *connection_cls);
847
848 if (startswith(url, "/fields/"))
849 return request_handler_fields(connection, url + 8, *connection_cls);
850
851 if (streq(url, "/browse"))
852 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
853
854 if (streq(url, "/machine"))
855 return request_handler_machine(connection, *connection_cls);
856
857 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
858 }
859
860 static int help(void) {
861
862 printf("%s [OPTIONS...] ...\n\n"
863 "HTTP server for journal events.\n\n"
864 " -h --help Show this help\n"
865 " --version Show package version\n"
866 " --cert=CERT.PEM Server certificate in PEM format\n"
867 " --key=KEY.PEM Server key in PEM format\n"
868 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
869 program_invocation_short_name);
870
871 return 0;
872 }
873
874 static int parse_argv(int argc, char *argv[]) {
875 enum {
876 ARG_VERSION = 0x100,
877 ARG_KEY,
878 ARG_CERT,
879 ARG_TRUST,
880 };
881
882 int r, c;
883
884 static const struct option options[] = {
885 { "help", no_argument, NULL, 'h' },
886 { "version", no_argument, NULL, ARG_VERSION },
887 { "key", required_argument, NULL, ARG_KEY },
888 { "cert", required_argument, NULL, ARG_CERT },
889 { "trust", required_argument, NULL, ARG_TRUST },
890 {}
891 };
892
893 assert(argc >= 0);
894 assert(argv);
895
896 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
897
898 switch(c) {
899
900 case 'h':
901 return help();
902
903 case ARG_VERSION:
904 puts(PACKAGE_STRING);
905 puts(SYSTEMD_FEATURES);
906 return 0;
907
908 case ARG_KEY:
909 if (key_pem) {
910 log_error("Key file specified twice");
911 return -EINVAL;
912 }
913 r = read_full_file(optarg, &key_pem, NULL);
914 if (r < 0) {
915 log_error("Failed to read key file: %s", strerror(-r));
916 return r;
917 }
918 assert(key_pem);
919 break;
920
921 case ARG_CERT:
922 if (cert_pem) {
923 log_error("Certificate file specified twice");
924 return -EINVAL;
925 }
926 r = read_full_file(optarg, &cert_pem, NULL);
927 if (r < 0) {
928 log_error("Failed to read certificate file: %s", strerror(-r));
929 return r;
930 }
931 assert(cert_pem);
932 break;
933
934 case ARG_TRUST:
935 #ifdef HAVE_GNUTLS
936 if (trust_pem) {
937 log_error("CA certificate file specified twice");
938 return -EINVAL;
939 }
940 r = read_full_file(optarg, &trust_pem, NULL);
941 if (r < 0) {
942 log_error("Failed to read CA certificate file: %s", strerror(-r));
943 return r;
944 }
945 assert(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 (!!key_pem != !!cert_pem) {
964 log_error("Certificate and key files must be specified together");
965 return -EINVAL;
966 }
967
968 if (trust_pem && !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 #ifdef HAVE_GNUTLS
991 gnutls_global_set_log_function(log_func_gnutls);
992 log_reset_gnutls_level();
993 #endif
994
995 n = sd_listen_fds(1);
996 if (n < 0) {
997 log_error("Failed to determine passed sockets: %s", strerror(-n));
998 goto finish;
999 } else if (n > 1) {
1000 log_error("Can't listen on more than one socket.");
1001 goto finish;
1002 } else {
1003 struct MHD_OptionItem opts[] = {
1004 { MHD_OPTION_NOTIFY_COMPLETED,
1005 (intptr_t) request_meta_free, NULL },
1006 { MHD_OPTION_EXTERNAL_LOGGER,
1007 (intptr_t) microhttpd_logger, NULL },
1008 { MHD_OPTION_END, 0, 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 int opts_pos = 2;
1014 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1015
1016 if (n > 0)
1017 opts[opts_pos++] = (struct MHD_OptionItem)
1018 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1019 if (key_pem) {
1020 assert(cert_pem);
1021 opts[opts_pos++] = (struct MHD_OptionItem)
1022 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1023 opts[opts_pos++] = (struct MHD_OptionItem)
1024 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1025 flags |= MHD_USE_SSL;
1026 }
1027 if (trust_pem) {
1028 assert(flags & MHD_USE_SSL);
1029 opts[opts_pos++] = (struct MHD_OptionItem)
1030 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1031 }
1032
1033 d = MHD_start_daemon(flags, 19531,
1034 NULL, NULL,
1035 request_handler, NULL,
1036 MHD_OPTION_ARRAY, opts,
1037 MHD_OPTION_END);
1038 }
1039
1040 if (!d) {
1041 log_error("Failed to start daemon!");
1042 goto finish;
1043 }
1044
1045 pause();
1046
1047 r = EXIT_SUCCESS;
1048
1049 finish:
1050 if (d)
1051 MHD_stop_daemon(d);
1052
1053 return r;
1054 }