chiark / gitweb /
journal-gatewayd: ask clients to provide certificates
[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 "sd-bus.h"
35 #include "bus-util.h"
36 #include "logs-show.h"
37 #include "microhttpd-util.h"
38 #include "build.h"
39 #include "fileio.h"
40
41 typedef struct RequestMeta {
42         sd_journal *journal;
43
44         OutputMode mode;
45
46         char *cursor;
47         int64_t n_skip;
48         uint64_t n_entries;
49         bool n_entries_set;
50
51         FILE *tmp;
52         uint64_t delta, size;
53
54         int argument_parse_error;
55
56         bool follow;
57         bool discrete;
58
59         uint64_t n_fields;
60         bool n_fields_set;
61 } RequestMeta;
62
63 static const char* const mime_types[_OUTPUT_MODE_MAX] = {
64         [OUTPUT_SHORT] = "text/plain",
65         [OUTPUT_JSON] = "application/json",
66         [OUTPUT_JSON_SSE] = "text/event-stream",
67         [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
68 };
69
70 static RequestMeta *request_meta(void **connection_cls) {
71         RequestMeta *m;
72
73         if (*connection_cls)
74                 return *connection_cls;
75
76         m = new0(RequestMeta, 1);
77         if (!m)
78                 return NULL;
79
80         *connection_cls = m;
81         return m;
82 }
83
84 static void request_meta_free(
85                 void *cls,
86                 struct MHD_Connection *connection,
87                 void **connection_cls,
88                 enum MHD_RequestTerminationCode toe) {
89
90         RequestMeta *m = *connection_cls;
91
92         if (!m)
93                 return;
94
95         if (m->journal)
96                 sd_journal_close(m->journal);
97
98         if (m->tmp)
99                 fclose(m->tmp);
100
101         free(m->cursor);
102         free(m);
103 }
104
105 static int open_journal(RequestMeta *m) {
106         assert(m);
107
108         if (m->journal)
109                 return 0;
110
111         return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
112 }
113
114 static int respond_oom_internal(struct MHD_Connection *connection) {
115         struct MHD_Response *response;
116         const char m[] = "Out of memory.\n";
117         int ret;
118
119         assert(connection);
120
121         response = MHD_create_response_from_buffer(sizeof(m)-1, (char*) m, MHD_RESPMEM_PERSISTENT);
122         if (!response)
123                 return MHD_NO;
124
125         MHD_add_response_header(response, "Content-Type", "text/plain");
126         ret = MHD_queue_response(connection, MHD_HTTP_SERVICE_UNAVAILABLE, response);
127         MHD_destroy_response(response);
128
129         return ret;
130 }
131
132 #define respond_oom(connection) log_oom(), respond_oom_internal(connection)
133
134 _printf_(3,4)
135 static int respond_error(
136                 struct MHD_Connection *connection,
137                 unsigned code,
138                 const char *format, ...) {
139
140         struct MHD_Response *response;
141         char *m;
142         int r;
143         va_list ap;
144
145         assert(connection);
146         assert(format);
147
148         va_start(ap, format);
149         r = vasprintf(&m, format, ap);
150         va_end(ap);
151
152         if (r < 0)
153                 return respond_oom(connection);
154
155         response = MHD_create_response_from_buffer(strlen(m), m, MHD_RESPMEM_MUST_FREE);
156         if (!response) {
157                 free(m);
158                 return respond_oom(connection);
159         }
160
161         MHD_add_response_header(response, "Content-Type", "text/plain");
162         r = MHD_queue_response(connection, code, response);
163         MHD_destroy_response(response);
164
165         return r;
166 }
167
168 static ssize_t request_reader_entries(
169                 void *cls,
170                 uint64_t pos,
171                 char *buf,
172                 size_t max) {
173
174         RequestMeta *m = cls;
175         int r;
176         size_t n, k;
177
178         assert(m);
179         assert(buf);
180         assert(max > 0);
181         assert(pos >= m->delta);
182
183         pos -= m->delta;
184
185         while (pos >= m->size) {
186                 off_t sz;
187
188                 /* End of this entry, so let's serialize the next
189                  * one */
190
191                 if (m->n_entries_set &&
192                     m->n_entries <= 0)
193                         return MHD_CONTENT_READER_END_OF_STREAM;
194
195                 if (m->n_skip < 0)
196                         r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
197                 else if (m->n_skip > 0)
198                         r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
199                 else
200                         r = sd_journal_next(m->journal);
201
202                 if (r < 0) {
203                         log_error("Failed to advance journal pointer: %s", strerror(-r));
204                         return MHD_CONTENT_READER_END_WITH_ERROR;
205                 } else if (r == 0) {
206
207                         if (m->follow) {
208                                 r = sd_journal_wait(m->journal, (uint64_t) -1);
209                                 if (r < 0) {
210                                         log_error("Couldn't wait for journal event: %s", strerror(-r));
211                                         return MHD_CONTENT_READER_END_WITH_ERROR;
212                                 }
213
214                                 continue;
215                         }
216
217                         return MHD_CONTENT_READER_END_OF_STREAM;
218                 }
219
220                 if (m->discrete) {
221                         assert(m->cursor);
222
223                         r = sd_journal_test_cursor(m->journal, m->cursor);
224                         if (r < 0) {
225                                 log_error("Failed to test cursor: %s", strerror(-r));
226                                 return MHD_CONTENT_READER_END_WITH_ERROR;
227                         }
228
229                         if (r == 0)
230                                 return MHD_CONTENT_READER_END_OF_STREAM;
231                 }
232
233                 pos -= m->size;
234                 m->delta += m->size;
235
236                 if (m->n_entries_set)
237                         m->n_entries -= 1;
238
239                 m->n_skip = 0;
240
241                 if (m->tmp)
242                         rewind(m->tmp);
243                 else {
244                         m->tmp = tmpfile();
245                         if (!m->tmp) {
246                                 log_error("Failed to create temporary file: %m");
247                                 return MHD_CONTENT_READER_END_WITH_ERROR;
248                         }
249                 }
250
251                 r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
252                 if (r < 0) {
253                         log_error("Failed to serialize item: %s", strerror(-r));
254                         return MHD_CONTENT_READER_END_WITH_ERROR;
255                 }
256
257                 sz = ftello(m->tmp);
258                 if (sz == (off_t) -1) {
259                         log_error("Failed to retrieve file position: %m");
260                         return MHD_CONTENT_READER_END_WITH_ERROR;
261                 }
262
263                 m->size = (uint64_t) sz;
264         }
265
266         if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
267                 log_error("Failed to seek to position: %m");
268                 return MHD_CONTENT_READER_END_WITH_ERROR;
269         }
270
271         n = m->size - pos;
272         if (n > max)
273                 n = max;
274
275         errno = 0;
276         k = fread(buf, 1, n, m->tmp);
277         if (k != n) {
278                 log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
279                 return MHD_CONTENT_READER_END_WITH_ERROR;
280         }
281
282         return (ssize_t) k;
283 }
284
285 static int request_parse_accept(
286                 RequestMeta *m,
287                 struct MHD_Connection *connection) {
288
289         const char *header;
290
291         assert(m);
292         assert(connection);
293
294         header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
295         if (!header)
296                 return 0;
297
298         if (streq(header, mime_types[OUTPUT_JSON]))
299                 m->mode = OUTPUT_JSON;
300         else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
301                 m->mode = OUTPUT_JSON_SSE;
302         else if (streq(header, mime_types[OUTPUT_EXPORT]))
303                 m->mode = OUTPUT_EXPORT;
304         else
305                 m->mode = OUTPUT_SHORT;
306
307         return 0;
308 }
309
310 static int request_parse_range(
311                 RequestMeta *m,
312                 struct MHD_Connection *connection) {
313
314         const char *range, *colon, *colon2;
315         int r;
316
317         assert(m);
318         assert(connection);
319
320         range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
321         if (!range)
322                 return 0;
323
324         if (!startswith(range, "entries="))
325                 return 0;
326
327         range += 8;
328         range += strspn(range, WHITESPACE);
329
330         colon = strchr(range, ':');
331         if (!colon)
332                 m->cursor = strdup(range);
333         else {
334                 const char *p;
335
336                 colon2 = strchr(colon + 1, ':');
337                 if (colon2) {
338                         _cleanup_free_ char *t;
339
340                         t = strndup(colon + 1, colon2 - colon - 1);
341                         if (!t)
342                                 return -ENOMEM;
343
344                         r = safe_atoi64(t, &m->n_skip);
345                         if (r < 0)
346                                 return r;
347                 }
348
349                 p = (colon2 ? colon2 : colon) + 1;
350                 if (*p) {
351                         r = safe_atou64(p, &m->n_entries);
352                         if (r < 0)
353                                 return r;
354
355                         if (m->n_entries <= 0)
356                                 return -EINVAL;
357
358                         m->n_entries_set = true;
359                 }
360
361                 m->cursor = strndup(range, colon - range);
362         }
363
364         if (!m->cursor)
365                 return -ENOMEM;
366
367         m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
368         if (isempty(m->cursor)) {
369                 free(m->cursor);
370                 m->cursor = NULL;
371         }
372
373         return 0;
374 }
375
376 static int request_parse_arguments_iterator(
377                 void *cls,
378                 enum MHD_ValueKind kind,
379                 const char *key,
380                 const char *value) {
381
382         RequestMeta *m = cls;
383         _cleanup_free_ char *p = NULL;
384         int r;
385
386         assert(m);
387
388         if (isempty(key)) {
389                 m->argument_parse_error = -EINVAL;
390                 return MHD_NO;
391         }
392
393         if (streq(key, "follow")) {
394                 if (isempty(value)) {
395                         m->follow = true;
396                         return MHD_YES;
397                 }
398
399                 r = parse_boolean(value);
400                 if (r < 0) {
401                         m->argument_parse_error = r;
402                         return MHD_NO;
403                 }
404
405                 m->follow = r;
406                 return MHD_YES;
407         }
408
409         if (streq(key, "discrete")) {
410                 if (isempty(value)) {
411                         m->discrete = true;
412                         return MHD_YES;
413                 }
414
415                 r = parse_boolean(value);
416                 if (r < 0) {
417                         m->argument_parse_error = r;
418                         return MHD_NO;
419                 }
420
421                 m->discrete = r;
422                 return MHD_YES;
423         }
424
425         if (streq(key, "boot")) {
426                 if (isempty(value))
427                         r = true;
428                 else {
429                         r = parse_boolean(value);
430                         if (r < 0) {
431                                 m->argument_parse_error = r;
432                                 return MHD_NO;
433                         }
434                 }
435
436                 if (r) {
437                         char match[9 + 32 + 1] = "_BOOT_ID=";
438                         sd_id128_t bid;
439
440                         r = sd_id128_get_boot(&bid);
441                         if (r < 0) {
442                                 log_error("Failed to get boot ID: %s", strerror(-r));
443                                 return MHD_NO;
444                         }
445
446                         sd_id128_to_string(bid, match + 9);
447                         r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
448                         if (r < 0) {
449                                 m->argument_parse_error = r;
450                                 return MHD_NO;
451                         }
452                 }
453
454                 return MHD_YES;
455         }
456
457         p = strjoin(key, "=", strempty(value), NULL);
458         if (!p) {
459                 m->argument_parse_error = log_oom();
460                 return MHD_NO;
461         }
462
463         r = sd_journal_add_match(m->journal, p, 0);
464         if (r < 0) {
465                 m->argument_parse_error = r;
466                 return MHD_NO;
467         }
468
469         return MHD_YES;
470 }
471
472 static int request_parse_arguments(
473                 RequestMeta *m,
474                 struct MHD_Connection *connection) {
475
476         assert(m);
477         assert(connection);
478
479         m->argument_parse_error = 0;
480         MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
481
482         return m->argument_parse_error;
483 }
484
485 static int request_handler_entries(
486                 struct MHD_Connection *connection,
487                 void *connection_cls) {
488
489         struct MHD_Response *response;
490         RequestMeta *m = connection_cls;
491         int r;
492
493         assert(connection);
494         assert(m);
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 = connection_cls;
653         int r;
654
655         assert(connection);
656         assert(m);
657
658         r = open_journal(m);
659         if (r < 0)
660                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
661
662         if (request_parse_accept(m, connection) < 0)
663                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
664
665         r = sd_journal_query_unique(m->journal, field);
666         if (r < 0)
667                 return respond_error(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
668
669         response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
670         if (!response)
671                 return respond_oom(connection);
672
673         MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
674
675         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
676         MHD_destroy_response(response);
677
678         return r;
679 }
680
681 static int request_handler_redirect(
682                 struct MHD_Connection *connection,
683                 const char *target) {
684
685         char *page;
686         struct MHD_Response *response;
687         int ret;
688
689         assert(connection);
690         assert(target);
691
692         if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
693                 return respond_oom(connection);
694
695         response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
696         if (!response) {
697                 free(page);
698                 return respond_oom(connection);
699         }
700
701         MHD_add_response_header(response, "Content-Type", "text/html");
702         MHD_add_response_header(response, "Location", target);
703
704         ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
705         MHD_destroy_response(response);
706
707         return ret;
708 }
709
710 static int request_handler_file(
711                 struct MHD_Connection *connection,
712                 const char *path,
713                 const char *mime_type) {
714
715         struct MHD_Response *response;
716         int ret;
717         _cleanup_close_ int fd = -1;
718         struct stat st;
719
720         assert(connection);
721         assert(path);
722         assert(mime_type);
723
724         fd = open(path, O_RDONLY|O_CLOEXEC);
725         if (fd < 0)
726                 return respond_error(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
727
728         if (fstat(fd, &st) < 0)
729                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
730
731         response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
732         if (!response)
733                 return respond_oom(connection);
734
735         fd = -1;
736
737         MHD_add_response_header(response, "Content-Type", mime_type);
738
739         ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
740         MHD_destroy_response(response);
741
742         return ret;
743 }
744
745 static int get_virtualization(char **v) {
746         _cleanup_bus_unref_ sd_bus *bus = NULL;
747         char *b = NULL;
748         int r;
749
750         r = sd_bus_default_system(&bus);
751         if (r < 0)
752                 return r;
753
754         r = sd_bus_get_property_string(
755                         bus,
756                         "org.freedesktop.systemd1",
757                         "/org/freedesktop/systemd1",
758                         "org.freedesktop.systemd1.Manager",
759                         "Virtualization",
760                         NULL,
761                         &b);
762         if (r < 0)
763                 return r;
764
765         if (isempty(b)) {
766                 free(b);
767                 *v = NULL;
768                 return 0;
769         }
770
771         *v = b;
772         return 1;
773 }
774
775 static int request_handler_machine(
776                 struct MHD_Connection *connection,
777                 void *connection_cls) {
778
779         struct MHD_Response *response;
780         RequestMeta *m = connection_cls;
781         int r;
782         _cleanup_free_ char* hostname = NULL, *os_name = NULL;
783         uint64_t cutoff_from = 0, cutoff_to = 0, usage;
784         char *json;
785         sd_id128_t mid, bid;
786         _cleanup_free_ char *v = NULL;
787
788         assert(connection);
789         assert(m);
790
791         r = open_journal(m);
792         if (r < 0)
793                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
794
795         r = sd_id128_get_machine(&mid);
796         if (r < 0)
797                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
798
799         r = sd_id128_get_boot(&bid);
800         if (r < 0)
801                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
802
803         hostname = gethostname_malloc();
804         if (!hostname)
805                 return respond_oom(connection);
806
807         r = sd_journal_get_usage(m->journal, &usage);
808         if (r < 0)
809                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
810
811         r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
812         if (r < 0)
813                 return respond_error(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
814
815         parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
816
817         get_virtualization(&v);
818
819         r = asprintf(&json,
820                      "{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
821                      "\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
822                      "\"hostname\" : \"%s\","
823                      "\"os_pretty_name\" : \"%s\","
824                      "\"virtualization\" : \"%s\","
825                      "\"usage\" : \"%"PRIu64"\","
826                      "\"cutoff_from_realtime\" : \"%"PRIu64"\","
827                      "\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
828                      SD_ID128_FORMAT_VAL(mid),
829                      SD_ID128_FORMAT_VAL(bid),
830                      hostname_cleanup(hostname, false),
831                      os_name ? os_name : "Linux",
832                      v ? v : "bare",
833                      usage,
834                      cutoff_from,
835                      cutoff_to);
836
837         if (r < 0)
838                 return respond_oom(connection);
839
840         response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
841         if (!response) {
842                 free(json);
843                 return respond_oom(connection);
844         }
845
846         MHD_add_response_header(response, "Content-Type", "application/json");
847         r = MHD_queue_response(connection, MHD_HTTP_OK, response);
848         MHD_destroy_response(response);
849
850         return r;
851 }
852
853 static int request_handler(
854                 void *cls,
855                 struct MHD_Connection *connection,
856                 const char *url,
857                 const char *method,
858                 const char *version,
859                 const char *upload_data,
860                 size_t *upload_data_size,
861                 void **connection_cls) {
862
863         assert(connection);
864         assert(connection_cls);
865         assert(url);
866         assert(method);
867
868         if (!streq(method, "GET"))
869                 return respond_error(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
870                                      "Unsupported method.\n");
871
872
873         if (!*connection_cls) {
874                 if (!request_meta(connection_cls))
875                         return respond_oom(connection);
876                 return MHD_YES;
877         }
878
879         if (streq(url, "/"))
880                 return request_handler_redirect(connection, "/browse");
881
882         if (streq(url, "/entries"))
883                 return request_handler_entries(connection, *connection_cls);
884
885         if (startswith(url, "/fields/"))
886                 return request_handler_fields(connection, url + 8, *connection_cls);
887
888         if (streq(url, "/browse"))
889                 return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
890
891         if (streq(url, "/machine"))
892                 return request_handler_machine(connection, *connection_cls);
893
894         return respond_error(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
895 }
896
897 static int help(void) {
898
899         printf("%s [OPTIONS...] ...\n\n"
900                "HTTP server for journal events.\n\n"
901                "  -h --help           Show this help\n"
902                "     --version        Show package version\n"
903                "     --cert=CERT.PEM  Server certificate in PEM format\n"
904                "     --key=KEY.PEM    Server key in PEM format\n"
905                "     --trust=CERT.PEM Certificat authority certificate in PEM format\n",
906                program_invocation_short_name);
907
908         return 0;
909 }
910
911 static char *key_pem = NULL;
912 static char *cert_pem = NULL;
913 static char *trust_pem = NULL;
914
915 static int parse_argv(int argc, char *argv[]) {
916         enum {
917                 ARG_VERSION = 0x100,
918                 ARG_KEY,
919                 ARG_CERT,
920                 ARG_TRUST,
921         };
922
923         int r, c;
924
925         static const struct option options[] = {
926                 { "help",    no_argument,       NULL, 'h'         },
927                 { "version", no_argument,       NULL, ARG_VERSION },
928                 { "key",     required_argument, NULL, ARG_KEY     },
929                 { "cert",    required_argument, NULL, ARG_CERT    },
930                 { "trust",   required_argument, NULL, ARG_TRUST   },
931                 {}
932         };
933
934         assert(argc >= 0);
935         assert(argv);
936
937         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
938
939                 switch(c) {
940
941                 case 'h':
942                         return help();
943
944                 case ARG_VERSION:
945                         puts(PACKAGE_STRING);
946                         puts(SYSTEMD_FEATURES);
947                         return 0;
948
949                 case ARG_KEY:
950                         if (key_pem) {
951                                 log_error("Key file specified twice");
952                                 return -EINVAL;
953                         }
954                         r = read_full_file(optarg, &key_pem, NULL);
955                         if (r < 0) {
956                                 log_error("Failed to read key file: %s", strerror(-r));
957                                 return r;
958                         }
959                         assert(key_pem);
960                         break;
961
962                 case ARG_CERT:
963                         if (cert_pem) {
964                                 log_error("Certificate file specified twice");
965                                 return -EINVAL;
966                         }
967                         r = read_full_file(optarg, &cert_pem, NULL);
968                         if (r < 0) {
969                                 log_error("Failed to read certificate file: %s", strerror(-r));
970                                 return r;
971                         }
972                         assert(cert_pem);
973                         break;
974
975                 case ARG_TRUST:
976                         if (trust_pem) {
977                                 log_error("CA certificate file specified twice");
978                                 return -EINVAL;
979                         }
980                         r = read_full_file(optarg, &trust_pem, NULL);
981                         if (r < 0) {
982                                 log_error("Failed to read CA certificate file: %s", strerror(-r));
983                                 return r;
984                         }
985                         assert(trust_pem);
986                         break;
987
988                 case '?':
989                         return -EINVAL;
990
991                 default:
992                         assert_not_reached("Unhandled option");
993                 }
994
995         if (optind < argc) {
996                 log_error("This program does not take arguments.");
997                 return -EINVAL;
998         }
999
1000         if (!!key_pem != !!cert_pem) {
1001                 log_error("Certificate and key files must be specified together");
1002                 return -EINVAL;
1003         }
1004
1005         if (trust_pem && !key_pem) {
1006                 log_error("CA certificate can only be used with certificate file");
1007                 return -EINVAL;
1008         }
1009
1010         return 1;
1011 }
1012
1013 int main(int argc, char *argv[]) {
1014         struct MHD_Daemon *d = NULL;
1015         int r, n;
1016
1017         log_set_target(LOG_TARGET_AUTO);
1018         log_parse_environment();
1019         log_open();
1020
1021         r = parse_argv(argc, argv);
1022         if (r < 0)
1023                 return EXIT_FAILURE;
1024         if (r == 0)
1025                 return EXIT_SUCCESS;
1026
1027         n = sd_listen_fds(1);
1028         if (n < 0) {
1029                 log_error("Failed to determine passed sockets: %s", strerror(-n));
1030                 goto finish;
1031         } else if (n > 1) {
1032                 log_error("Can't listen on more than one socket.");
1033                 goto finish;
1034         } else {
1035                 struct MHD_OptionItem opts[] = {
1036                         { MHD_OPTION_NOTIFY_COMPLETED,
1037                           (intptr_t) request_meta_free, NULL },
1038                         { MHD_OPTION_EXTERNAL_LOGGER,
1039                           (intptr_t) microhttpd_logger, NULL },
1040                         { MHD_OPTION_END, 0, NULL },
1041                         { MHD_OPTION_END, 0, NULL },
1042                         { MHD_OPTION_END, 0, NULL },
1043                         { MHD_OPTION_END, 0, NULL },
1044                         { MHD_OPTION_END, 0, NULL }};
1045                 int opts_pos = 2;
1046                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1047
1048                 if (n > 0)
1049                         opts[opts_pos++] = (struct MHD_OptionItem)
1050                                 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1051                 if (key_pem) {
1052                         assert(cert_pem);
1053                         opts[opts_pos++] = (struct MHD_OptionItem)
1054                                 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1055                         opts[opts_pos++] = (struct MHD_OptionItem)
1056                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1057                         flags |= MHD_USE_SSL;
1058                 }
1059                 if (trust_pem) {
1060                         assert(flags & MHD_USE_SSL);
1061                         opts[opts_pos++] = (struct MHD_OptionItem)
1062                                 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1063                 }
1064
1065                 d = MHD_start_daemon(flags, 19531,
1066                                      NULL, NULL,
1067                                      request_handler, NULL,
1068                                      MHD_OPTION_ARRAY, opts,
1069                                      MHD_OPTION_END);
1070         }
1071
1072         if (!d) {
1073                 log_error("Failed to start daemon!");
1074                 goto finish;
1075         }
1076
1077         pause();
1078
1079         r = EXIT_SUCCESS;
1080
1081 finish:
1082         if (d)
1083                 MHD_stop_daemon(d);
1084
1085         return r;
1086 }