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