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