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