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