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