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