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