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