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