chiark / gitweb /
db81fe3ca38edfd9a6c0d6e228f92161bca0a080
[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 void help(void) {
861         printf("%s [OPTIONS...] ...\n\n"
862                "HTTP server for journal events.\n\n"
863                "  -h --help           Show this help\n"
864                "     --version        Show package version\n"
865                "     --cert=CERT.PEM  Server certificate in PEM format\n"
866                "     --key=KEY.PEM    Server key in PEM format\n"
867                "     --trust=CERT.PEM Certificat authority certificate in PEM format\n",
868                program_invocation_short_name);
869 }
870
871 static int parse_argv(int argc, char *argv[]) {
872         enum {
873                 ARG_VERSION = 0x100,
874                 ARG_KEY,
875                 ARG_CERT,
876                 ARG_TRUST,
877         };
878
879         int r, c;
880
881         static const struct option options[] = {
882                 { "help",    no_argument,       NULL, 'h'         },
883                 { "version", no_argument,       NULL, ARG_VERSION },
884                 { "key",     required_argument, NULL, ARG_KEY     },
885                 { "cert",    required_argument, NULL, ARG_CERT    },
886                 { "trust",   required_argument, NULL, ARG_TRUST   },
887                 {}
888         };
889
890         assert(argc >= 0);
891         assert(argv);
892
893         while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
894
895                 switch(c) {
896
897                 case 'h':
898                         help();
899                         return 0;
900
901                 case ARG_VERSION:
902                         puts(PACKAGE_STRING);
903                         puts(SYSTEMD_FEATURES);
904                         return 0;
905
906                 case ARG_KEY:
907                         if (key_pem) {
908                                 log_error("Key file specified twice");
909                                 return -EINVAL;
910                         }
911                         r = read_full_file(optarg, &key_pem, NULL);
912                         if (r < 0) {
913                                 log_error("Failed to read key file: %s", strerror(-r));
914                                 return r;
915                         }
916                         assert(key_pem);
917                         break;
918
919                 case ARG_CERT:
920                         if (cert_pem) {
921                                 log_error("Certificate file specified twice");
922                                 return -EINVAL;
923                         }
924                         r = read_full_file(optarg, &cert_pem, NULL);
925                         if (r < 0) {
926                                 log_error("Failed to read certificate file: %s", strerror(-r));
927                                 return r;
928                         }
929                         assert(cert_pem);
930                         break;
931
932                 case ARG_TRUST:
933 #ifdef HAVE_GNUTLS
934                         if (trust_pem) {
935                                 log_error("CA certificate file specified twice");
936                                 return -EINVAL;
937                         }
938                         r = read_full_file(optarg, &trust_pem, NULL);
939                         if (r < 0) {
940                                 log_error("Failed to read CA certificate file: %s", strerror(-r));
941                                 return r;
942                         }
943                         assert(trust_pem);
944                         break;
945 #else
946                         log_error("Option --trust is not available.");
947 #endif
948
949                 case '?':
950                         return -EINVAL;
951
952                 default:
953                         assert_not_reached("Unhandled option");
954                 }
955
956         if (optind < argc) {
957                 log_error("This program does not take arguments.");
958                 return -EINVAL;
959         }
960
961         if (!!key_pem != !!cert_pem) {
962                 log_error("Certificate and key files must be specified together");
963                 return -EINVAL;
964         }
965
966         if (trust_pem && !key_pem) {
967                 log_error("CA certificate can only be used with certificate file");
968                 return -EINVAL;
969         }
970
971         return 1;
972 }
973
974 int main(int argc, char *argv[]) {
975         struct MHD_Daemon *d = NULL;
976         int r, n;
977
978         log_set_target(LOG_TARGET_AUTO);
979         log_parse_environment();
980         log_open();
981
982         r = parse_argv(argc, argv);
983         if (r < 0)
984                 return EXIT_FAILURE;
985         if (r == 0)
986                 return EXIT_SUCCESS;
987
988 #ifdef HAVE_GNUTLS
989         gnutls_global_set_log_function(log_func_gnutls);
990         log_reset_gnutls_level();
991 #endif
992
993         n = sd_listen_fds(1);
994         if (n < 0) {
995                 log_error("Failed to determine passed sockets: %s", strerror(-n));
996                 goto finish;
997         } else if (n > 1) {
998                 log_error("Can't listen on more than one socket.");
999                 goto finish;
1000         } else {
1001                 struct MHD_OptionItem opts[] = {
1002                         { MHD_OPTION_NOTIFY_COMPLETED,
1003                           (intptr_t) request_meta_free, NULL },
1004                         { MHD_OPTION_EXTERNAL_LOGGER,
1005                           (intptr_t) microhttpd_logger, NULL },
1006                         { MHD_OPTION_END, 0, NULL },
1007                         { MHD_OPTION_END, 0, NULL },
1008                         { MHD_OPTION_END, 0, NULL },
1009                         { MHD_OPTION_END, 0, NULL },
1010                         { MHD_OPTION_END, 0, NULL }};
1011                 int opts_pos = 2;
1012                 int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
1013
1014                 if (n > 0)
1015                         opts[opts_pos++] = (struct MHD_OptionItem)
1016                                 {MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
1017                 if (key_pem) {
1018                         assert(cert_pem);
1019                         opts[opts_pos++] = (struct MHD_OptionItem)
1020                                 {MHD_OPTION_HTTPS_MEM_KEY, 0, key_pem};
1021                         opts[opts_pos++] = (struct MHD_OptionItem)
1022                                 {MHD_OPTION_HTTPS_MEM_CERT, 0, cert_pem};
1023                         flags |= MHD_USE_SSL;
1024                 }
1025                 if (trust_pem) {
1026                         assert(flags & MHD_USE_SSL);
1027                         opts[opts_pos++] = (struct MHD_OptionItem)
1028                                 {MHD_OPTION_HTTPS_MEM_TRUST, 0, trust_pem};
1029                 }
1030
1031                 d = MHD_start_daemon(flags, 19531,
1032                                      NULL, NULL,
1033                                      request_handler, NULL,
1034                                      MHD_OPTION_ARRAY, opts,
1035                                      MHD_OPTION_END);
1036         }
1037
1038         if (!d) {
1039                 log_error("Failed to start daemon!");
1040                 goto finish;
1041         }
1042
1043         pause();
1044
1045         r = EXIT_SUCCESS;
1046
1047 finish:
1048         if (d)
1049                 MHD_stop_daemon(d);
1050
1051         return r;
1052 }