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