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