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