]> git.ipfire.org Git - thirdparty/systemd.git/blob - src/journal-remote/journal-gatewayd.c
tree-wide: use coccinelle to patch a lot of code to use mfree()
[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
344 return 0;
345 }
346
347 static int request_parse_arguments_iterator(
348 void *cls,
349 enum MHD_ValueKind kind,
350 const char *key,
351 const char *value) {
352
353 RequestMeta *m = cls;
354 _cleanup_free_ char *p = NULL;
355 int r;
356
357 assert(m);
358
359 if (isempty(key)) {
360 m->argument_parse_error = -EINVAL;
361 return MHD_NO;
362 }
363
364 if (streq(key, "follow")) {
365 if (isempty(value)) {
366 m->follow = 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->follow = r;
377 return MHD_YES;
378 }
379
380 if (streq(key, "discrete")) {
381 if (isempty(value)) {
382 m->discrete = true;
383 return MHD_YES;
384 }
385
386 r = parse_boolean(value);
387 if (r < 0) {
388 m->argument_parse_error = r;
389 return MHD_NO;
390 }
391
392 m->discrete = r;
393 return MHD_YES;
394 }
395
396 if (streq(key, "boot")) {
397 if (isempty(value))
398 r = true;
399 else {
400 r = parse_boolean(value);
401 if (r < 0) {
402 m->argument_parse_error = r;
403 return MHD_NO;
404 }
405 }
406
407 if (r) {
408 char match[9 + 32 + 1] = "_BOOT_ID=";
409 sd_id128_t bid;
410
411 r = sd_id128_get_boot(&bid);
412 if (r < 0) {
413 log_error_errno(r, "Failed to get boot ID: %m");
414 return MHD_NO;
415 }
416
417 sd_id128_to_string(bid, match + 9);
418 r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
419 if (r < 0) {
420 m->argument_parse_error = r;
421 return MHD_NO;
422 }
423 }
424
425 return MHD_YES;
426 }
427
428 p = strjoin(key, "=", strempty(value), NULL);
429 if (!p) {
430 m->argument_parse_error = log_oom();
431 return MHD_NO;
432 }
433
434 r = sd_journal_add_match(m->journal, p, 0);
435 if (r < 0) {
436 m->argument_parse_error = r;
437 return MHD_NO;
438 }
439
440 return MHD_YES;
441 }
442
443 static int request_parse_arguments(
444 RequestMeta *m,
445 struct MHD_Connection *connection) {
446
447 assert(m);
448 assert(connection);
449
450 m->argument_parse_error = 0;
451 MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
452
453 return m->argument_parse_error;
454 }
455
456 static int request_handler_entries(
457 struct MHD_Connection *connection,
458 void *connection_cls) {
459
460 struct MHD_Response *response;
461 RequestMeta *m = connection_cls;
462 int r;
463
464 assert(connection);
465 assert(m);
466
467 r = open_journal(m);
468 if (r < 0)
469 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
470
471 if (request_parse_accept(m, connection) < 0)
472 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
473
474 if (request_parse_range(m, connection) < 0)
475 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
476
477 if (request_parse_arguments(m, connection) < 0)
478 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
479
480 if (m->discrete) {
481 if (!m->cursor)
482 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
483
484 m->n_entries = 1;
485 m->n_entries_set = true;
486 }
487
488 if (m->cursor)
489 r = sd_journal_seek_cursor(m->journal, m->cursor);
490 else if (m->n_skip >= 0)
491 r = sd_journal_seek_head(m->journal);
492 else if (m->n_skip < 0)
493 r = sd_journal_seek_tail(m->journal);
494 if (r < 0)
495 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
496
497 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
498 if (!response)
499 return respond_oom(connection);
500
501 MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
502
503 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
504 MHD_destroy_response(response);
505
506 return r;
507 }
508
509 static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
510 const char *eq;
511 size_t j;
512
513 eq = memchr(d, '=', l);
514 if (!eq)
515 return -EINVAL;
516
517 j = l - (eq - d + 1);
518
519 if (m == OUTPUT_JSON) {
520 fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
521 json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
522 fputs(" }\n", f);
523 } else {
524 fwrite(eq+1, 1, j, f);
525 fputc('\n', f);
526 }
527
528 return 0;
529 }
530
531 static ssize_t request_reader_fields(
532 void *cls,
533 uint64_t pos,
534 char *buf,
535 size_t max) {
536
537 RequestMeta *m = cls;
538 int r;
539 size_t n, k;
540
541 assert(m);
542 assert(buf);
543 assert(max > 0);
544 assert(pos >= m->delta);
545
546 pos -= m->delta;
547
548 while (pos >= m->size) {
549 off_t sz;
550 const void *d;
551 size_t l;
552
553 /* End of this field, so let's serialize the next
554 * one */
555
556 if (m->n_fields_set &&
557 m->n_fields <= 0)
558 return MHD_CONTENT_READER_END_OF_STREAM;
559
560 r = sd_journal_enumerate_unique(m->journal, &d, &l);
561 if (r < 0) {
562 log_error_errno(r, "Failed to advance field index: %m");
563 return MHD_CONTENT_READER_END_WITH_ERROR;
564 } else if (r == 0)
565 return MHD_CONTENT_READER_END_OF_STREAM;
566
567 pos -= m->size;
568 m->delta += m->size;
569
570 if (m->n_fields_set)
571 m->n_fields -= 1;
572
573 r = request_meta_ensure_tmp(m);
574 if (r < 0) {
575 log_error_errno(r, "Failed to create temporary file: %m");
576 return MHD_CONTENT_READER_END_WITH_ERROR;
577 }
578
579 r = output_field(m->tmp, m->mode, d, l);
580 if (r < 0) {
581 log_error_errno(r, "Failed to serialize item: %m");
582 return MHD_CONTENT_READER_END_WITH_ERROR;
583 }
584
585 sz = ftello(m->tmp);
586 if (sz == (off_t) -1) {
587 log_error_errno(errno, "Failed to retrieve file position: %m");
588 return MHD_CONTENT_READER_END_WITH_ERROR;
589 }
590
591 m->size = (uint64_t) sz;
592 }
593
594 if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
595 log_error_errno(errno, "Failed to seek to position: %m");
596 return MHD_CONTENT_READER_END_WITH_ERROR;
597 }
598
599 n = m->size - pos;
600 if (n > max)
601 n = max;
602
603 errno = 0;
604 k = fread(buf, 1, n, m->tmp);
605 if (k != n) {
606 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
607 return MHD_CONTENT_READER_END_WITH_ERROR;
608 }
609
610 return (ssize_t) k;
611 }
612
613 static int request_handler_fields(
614 struct MHD_Connection *connection,
615 const char *field,
616 void *connection_cls) {
617
618 struct MHD_Response *response;
619 RequestMeta *m = connection_cls;
620 int r;
621
622 assert(connection);
623 assert(m);
624
625 r = open_journal(m);
626 if (r < 0)
627 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
628
629 if (request_parse_accept(m, connection) < 0)
630 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
631
632 r = sd_journal_query_unique(m->journal, field);
633 if (r < 0)
634 return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
635
636 response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
637 if (!response)
638 return respond_oom(connection);
639
640 MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
641
642 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
643 MHD_destroy_response(response);
644
645 return r;
646 }
647
648 static int request_handler_redirect(
649 struct MHD_Connection *connection,
650 const char *target) {
651
652 char *page;
653 struct MHD_Response *response;
654 int ret;
655
656 assert(connection);
657 assert(target);
658
659 if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
660 return respond_oom(connection);
661
662 response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
663 if (!response) {
664 free(page);
665 return respond_oom(connection);
666 }
667
668 MHD_add_response_header(response, "Content-Type", "text/html");
669 MHD_add_response_header(response, "Location", target);
670
671 ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
672 MHD_destroy_response(response);
673
674 return ret;
675 }
676
677 static int request_handler_file(
678 struct MHD_Connection *connection,
679 const char *path,
680 const char *mime_type) {
681
682 struct MHD_Response *response;
683 int ret;
684 _cleanup_close_ int fd = -1;
685 struct stat st;
686
687 assert(connection);
688 assert(path);
689 assert(mime_type);
690
691 fd = open(path, O_RDONLY|O_CLOEXEC);
692 if (fd < 0)
693 return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
694
695 if (fstat(fd, &st) < 0)
696 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
697
698 response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
699 if (!response)
700 return respond_oom(connection);
701
702 fd = -1;
703
704 MHD_add_response_header(response, "Content-Type", mime_type);
705
706 ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
707 MHD_destroy_response(response);
708
709 return ret;
710 }
711
712 static int get_virtualization(char **v) {
713 _cleanup_bus_unref_ sd_bus *bus = NULL;
714 char *b = NULL;
715 int r;
716
717 r = sd_bus_default_system(&bus);
718 if (r < 0)
719 return r;
720
721 r = sd_bus_get_property_string(
722 bus,
723 "org.freedesktop.systemd1",
724 "/org/freedesktop/systemd1",
725 "org.freedesktop.systemd1.Manager",
726 "Virtualization",
727 NULL,
728 &b);
729 if (r < 0)
730 return r;
731
732 if (isempty(b)) {
733 free(b);
734 *v = NULL;
735 return 0;
736 }
737
738 *v = b;
739 return 1;
740 }
741
742 static int request_handler_machine(
743 struct MHD_Connection *connection,
744 void *connection_cls) {
745
746 struct MHD_Response *response;
747 RequestMeta *m = connection_cls;
748 int r;
749 _cleanup_free_ char* hostname = NULL, *os_name = NULL;
750 uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
751 char *json;
752 sd_id128_t mid, bid;
753 _cleanup_free_ char *v = NULL;
754
755 assert(connection);
756 assert(m);
757
758 r = open_journal(m);
759 if (r < 0)
760 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
761
762 r = sd_id128_get_machine(&mid);
763 if (r < 0)
764 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
765
766 r = sd_id128_get_boot(&bid);
767 if (r < 0)
768 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
769
770 hostname = gethostname_malloc();
771 if (!hostname)
772 return respond_oom(connection);
773
774 r = sd_journal_get_usage(m->journal, &usage);
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 r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
779 if (r < 0)
780 return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
781
782 if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
783 (void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
784
785 get_virtualization(&v);
786
787 r = asprintf(&json,
788 "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
789 "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
790 "\"hostname\" : \"%s\","
791 "\"os_pretty_name\" : \"%s\","
792 "\"virtualization\" : \"%s\","
793 "\"usage\" : \"%"PRIu64"\","
794 "\"cutoff_from_realtime\" : \"%"PRIu64"\","
795 "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
796 SD_ID128_FORMAT_VAL(mid),
797 SD_ID128_FORMAT_VAL(bid),
798 hostname_cleanup(hostname),
799 os_name ? os_name : "Linux",
800 v ? v : "bare",
801 usage,
802 cutoff_from,
803 cutoff_to);
804
805 if (r < 0)
806 return respond_oom(connection);
807
808 response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
809 if (!response) {
810 free(json);
811 return respond_oom(connection);
812 }
813
814 MHD_add_response_header(response, "Content-Type", "application/json");
815 r = MHD_queue_response(connection, MHD_HTTP_OK, response);
816 MHD_destroy_response(response);
817
818 return r;
819 }
820
821 static int request_handler(
822 void *cls,
823 struct MHD_Connection *connection,
824 const char *url,
825 const char *method,
826 const char *version,
827 const char *upload_data,
828 size_t *upload_data_size,
829 void **connection_cls) {
830 int r, code;
831
832 assert(connection);
833 assert(connection_cls);
834 assert(url);
835 assert(method);
836
837 if (!streq(method, "GET"))
838 return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
839 "Unsupported method.\n");
840
841
842 if (!*connection_cls) {
843 if (!request_meta(connection_cls))
844 return respond_oom(connection);
845 return MHD_YES;
846 }
847
848 if (arg_trust_pem) {
849 r = check_permissions(connection, &code, NULL);
850 if (r < 0)
851 return code;
852 }
853
854 if (streq(url, "/"))
855 return request_handler_redirect(connection, "/browse");
856
857 if (streq(url, "/entries"))
858 return request_handler_entries(connection, *connection_cls);
859
860 if (startswith(url, "/fields/"))
861 return request_handler_fields(connection, url + 8, *connection_cls);
862
863 if (streq(url, "/browse"))
864 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
865
866 if (streq(url, "/machine"))
867 return request_handler_machine(connection, *connection_cls);
868
869 return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
870 }
871
872 static void help(void) {
873 printf("%s [OPTIONS...] ...\n\n"
874 "HTTP server for journal events.\n\n"
875 " -h --help Show this help\n"
876 " --version Show package version\n"
877 " --cert=CERT.PEM Server certificate in PEM format\n"
878 " --key=KEY.PEM Server key in PEM format\n"
879 " --trust=CERT.PEM Certificat authority certificate in PEM format\n",
880 program_invocation_short_name);
881 }
882
883 static int parse_argv(int argc, char *argv[]) {
884 enum {
885 ARG_VERSION = 0x100,
886 ARG_KEY,
887 ARG_CERT,
888 ARG_TRUST,
889 };
890
891 int r, c;
892
893 static const struct option options[] = {
894 { "help", no_argument, NULL, 'h' },
895 { "version", no_argument, NULL, ARG_VERSION },
896 { "key", required_argument, NULL, ARG_KEY },
897 { "cert", required_argument, NULL, ARG_CERT },
898 { "trust", required_argument, NULL, ARG_TRUST },
899 {}
900 };
901
902 assert(argc >= 0);
903 assert(argv);
904
905 while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
906
907 switch(c) {
908
909 case 'h':
910 help();
911 return 0;
912
913 case ARG_VERSION:
914 puts(PACKAGE_STRING);
915 puts(SYSTEMD_FEATURES);
916 return 0;
917
918 case ARG_KEY:
919 if (arg_key_pem) {
920 log_error("Key file specified twice");
921 return -EINVAL;
922 }
923 r = read_full_file(optarg, &arg_key_pem, NULL);
924 if (r < 0)
925 return log_error_errno(r, "Failed to read key file: %m");
926 assert(arg_key_pem);
927 break;
928
929 case ARG_CERT:
930 if (arg_cert_pem) {
931 log_error("Certificate file specified twice");
932 return -EINVAL;
933 }
934 r = read_full_file(optarg, &arg_cert_pem, NULL);
935 if (r < 0)
936 return log_error_errno(r, "Failed to read certificate file: %m");
937 assert(arg_cert_pem);
938 break;
939
940 case ARG_TRUST:
941 #ifdef HAVE_GNUTLS
942 if (arg_trust_pem) {
943 log_error("CA certificate file specified twice");
944 return -EINVAL;
945 }
946 r = read_full_file(optarg, &arg_trust_pem, NULL);
947 if (r < 0)
948 return log_error_errno(r, "Failed to read CA certificate file: %m");
949 assert(arg_trust_pem);
950 break;
951 #else
952 log_error("Option --trust is not available.");
953 #endif
954
955 case '?':
956 return -EINVAL;
957
958 default:
959 assert_not_reached("Unhandled option");
960 }
961
962 if (optind < argc) {
963 log_error("This program does not take arguments.");
964 return -EINVAL;
965 }
966
967 if (!!arg_key_pem != !!arg_cert_pem) {
968 log_error("Certificate and key files must be specified together");
969 return -EINVAL;
970 }
971
972 if (arg_trust_pem && !arg_key_pem) {
973 log_error("CA certificate can only be used with certificate file");
974 return -EINVAL;
975 }
976
977 return 1;
978 }
979
980 int main(int argc, char *argv[]) {
981 struct MHD_Daemon *d = NULL;
982 int r, n;
983
984 log_set_target(LOG_TARGET_AUTO);
985 log_parse_environment();
986 log_open();
987
988 r = parse_argv(argc, argv);
989 if (r < 0)
990 return EXIT_FAILURE;
991 if (r == 0)
992 return EXIT_SUCCESS;
993
994 sigbus_install();
995
996 r = setup_gnutls_logger(NULL);
997 if (r < 0)
998 return EXIT_FAILURE;
999
1000 n = sd_listen_fds(1);
1001 if (n < 0) {
1002 log_error_errno(n, "Failed to determine passed sockets: %m");
1003 goto finish;
1004 } else if (n > 1) {
1005 log_error("Can't listen on more than one socket.");
1006 goto finish;
1007 } else {
1008 struct MHD_OptionItem opts[] = {
1009 { MHD_OPTION_NOTIFY_COMPLETED,
1010 (intptr_t) request_meta_free, NULL },
1011 { MHD_OPTION_EXTERNAL_LOGGER,
1012 (intptr_t) microhttpd_logger, 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 { MHD_OPTION_END, 0, NULL }};
1018 int opts_pos = 2;
1019 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1020
1021 if (n > 0)
1022 opts[opts_pos++] = (struct MHD_OptionItem)
1023 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1024 if (arg_key_pem) {
1025 assert(arg_cert_pem);
1026 opts[opts_pos++] = (struct MHD_OptionItem)
1027 {MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
1028 opts[opts_pos++] = (struct MHD_OptionItem)
1029 {MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
1030 flags |= MHD_USE_SSL;
1031 }
1032 if (arg_trust_pem) {
1033 assert(flags & MHD_USE_SSL);
1034 opts[opts_pos++] = (struct MHD_OptionItem)
1035 {MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
1036 }
1037
1038 d = MHD_start_daemon(flags, 19531,
1039 NULL, NULL,
1040 request_handler, NULL,
1041 MHD_OPTION_ARRAY, opts,
1042 MHD_OPTION_END);
1043 }
1044
1045 if (!d) {
1046 log_error("Failed to start daemon!");
1047 goto finish;
1048 }
1049
1050 pause();
1051
1052 r = EXIT_SUCCESS;
1053
1054 finish:
1055 if (d)
1056 MHD_stop_daemon(d);
1057
1058 return r;
1059 }