journal-gatewayd.c revision e51728b0475f68d0f84c1dca8a02c17c9f5a6a29
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen/***
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen This file is part of systemd.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen Copyright 2012 Lennart Poettering
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen systemd is free software; you can redistribute it and/or modify it
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen under the terms of the GNU Lesser General Public License as published by
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen the Free Software Foundation; either version 2.1 of the License, or
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen (at your option) any later version.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen systemd is distributed in the hope that it will be useful, but
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen WITHOUT ANY WARRANTY; without even the implied warranty of
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen Lesser General Public License for more details.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen You should have received a copy of the GNU Lesser General Public License
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen along with systemd; If not, see <http://www.gnu.org/licenses/>.
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen***/
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <stdlib.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <string.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <unistd.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <fcntl.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <getopt.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <microhttpd.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#ifdef HAVE_GNUTLS
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include <gnutls/gnutls.h>
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#endif
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "sd-journal.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "sd-daemon.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "sd-bus.h"
920b52e4909d9dc812817fd8b82f83ca23a11c91Thomas Hindoe Paaboel Andersen#include "log.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "util.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "bus-util.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "logs-show.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "microhttpd-util.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "build.h"
79008bddf679a5e0900369950eb346c9fa687107Lennart Poettering#include "fileio.h"
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen#include "sigbus.h"
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic char *arg_key_pem = NULL;
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poetteringstatic char *arg_cert_pem = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic char *arg_trust_pem = NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersentypedef struct RequestMeta {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen sd_journal *journal;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen OutputMode mode;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen char *cursor;
91b5f997316ddc77d26f9a7a5e24c335484586bdTom Gundersen int64_t n_skip;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen uint64_t n_entries;
ed9e361a8a798f9fee353b5c7e0e23308e0d329fTom Gundersen bool n_entries_set;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering FILE *tmp;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen uint64_t delta, size;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int argument_parse_error;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen bool follow;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen bool discrete;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
91b5f997316ddc77d26f9a7a5e24c335484586bdTom Gundersen uint64_t n_fields;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen bool n_fields_set;
8012cd391932d58b44332df106d426a360faf0a6Tom Gundersen} RequestMeta;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic const char* const mime_types[_OUTPUT_MODE_MAX] = {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen [OUTPUT_SHORT] = "text/plain",
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen [OUTPUT_JSON] = "application/json",
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen [OUTPUT_JSON_SSE] = "text/event-stream",
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen [OUTPUT_EXPORT] = "application/vnd.fdo.journal",
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen};
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic RequestMeta *request_meta(void **connection_cls) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen RequestMeta *m;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen assert(connection_cls);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (*connection_cls)
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering return *connection_cls;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m = new0(RequestMeta, 1);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!m)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return NULL;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen *connection_cls = m;
8012cd391932d58b44332df106d426a360faf0a6Tom Gundersen return m;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic void request_meta_free(
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen void *cls,
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen struct MHD_Connection *connection,
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen void **connection_cls,
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen enum MHD_RequestTerminationCode toe) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen RequestMeta *m = *connection_cls;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!m)
1c4baffc1895809bae9ac36b670af90a4cb9cd7dTom Gundersen return;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
e53fc357a9bb9d0a5362ccc4246d598cb0febd5eLennart Poettering if (m->journal)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen sd_journal_close(m->journal);
45af44d47da6933b260c734ad9ff721f63f80a4dTom Gundersen
200a0868fcdf7b95f3d8d1fda3aa2aef48d84fddTom Gundersen if (m->tmp)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen fclose(m->tmp);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(m->cursor);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen free(m);
8012cd391932d58b44332df106d426a360faf0a6Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic int open_journal(RequestMeta *m) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(m);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->journal)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return sd_journal_open(&m->journal, SD_JOURNAL_LOCAL_ONLY|SD_JOURNAL_SYSTEM);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic int request_meta_ensure_tmp(RequestMeta *m) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->tmp)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen rewind(m->tmp);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen else {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int fd;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen fd = open_tmpfile("/tmp", O_RDWR|O_CLOEXEC);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (fd < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return fd;
79008bddf679a5e0900369950eb346c9fa687107Lennart Poettering
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->tmp = fdopen(fd, "rw");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (!m->tmp) {
f0213e3796b4dd66e546e2de4d677db319f9171bTom Gundersen safe_close(fd);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return -errno;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen}
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersenstatic ssize_t request_reader_entries(
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen void *cls,
6666907869fb3bc7fe6a6025540db5b887c7a78bTom Gundersen uint64_t pos,
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen char *buf,
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen size_t max) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen RequestMeta *m = cls;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen int r;
ed9e361a8a798f9fee353b5c7e0e23308e0d329fTom Gundersen size_t n, k;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(m);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(buf);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(max > 0);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(pos >= m->delta);
ed9e361a8a798f9fee353b5c7e0e23308e0d329fTom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen pos -= m->delta;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen while (pos >= m->size) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen off_t sz;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen /* End of this entry, so let's serialize the next
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen * one */
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->n_entries_set &&
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->n_entries <= 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_OF_STREAM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->n_skip < 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = sd_journal_previous_skip(m->journal, (uint64_t) -m->n_skip + 1);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen else if (m->n_skip > 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = sd_journal_next_skip(m->journal, (uint64_t) m->n_skip + 1);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen else
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = sd_journal_next(m->journal);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(r, "Failed to advance journal pointer: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen } else if (r == 0) {
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann if (m->follow) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = sd_journal_wait(m->journal, (uint64_t) -1);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(r, "Couldn't wait for journal event: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
be19c5b5e0c0f78b8429b126936fa15856550a23David Herrmann continue;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_OF_STREAM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->discrete) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen assert(m->cursor);
b45e4eb679ad0c9a77c4fe6e404c8842d4097fdbTom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = sd_journal_test_cursor(m->journal, m->cursor);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(r, "Failed to test cursor: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r == 0)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_OF_STREAM;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
e0ee46f29028e291eb67f435aff1b6202d75d9d6Lennart Poettering pos -= m->size;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->delta += m->size;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (m->n_entries_set)
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->n_entries -= 1;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->n_skip = 0;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = request_meta_ensure_tmp(m);
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen if (r < 0) {
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen log_error_errno(r, "Failed to create temporary file: %m");
e3dca0089b7b50e2ec21409d1292727921d06102Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen r = output_journal(m->tmp, m->journal, m->mode, 0, OUTPUT_FULL_WIDTH, NULL);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (r < 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(r, "Failed to serialize item: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen sz = ftello(m->tmp);
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (sz == (off_t) -1) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(errno, "Failed to retrieve file position: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen m->size = (uint64_t) sz;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen log_error_errno(errno, "Failed to seek to position: %m");
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen return MHD_CONTENT_READER_END_WITH_ERROR;
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen }
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen
b22d8a00f48f3c5fc4510b4acd3e1a43e731e592Tom Gundersen n = m->size - pos;
if (n > max)
n = max;
errno = 0;
k = fread(buf, 1, n, m->tmp);
if (k != n) {
log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
return (ssize_t) k;
}
static int request_parse_accept(
RequestMeta *m,
struct MHD_Connection *connection) {
const char *header;
assert(m);
assert(connection);
header = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Accept");
if (!header)
return 0;
if (streq(header, mime_types[OUTPUT_JSON]))
m->mode = OUTPUT_JSON;
else if (streq(header, mime_types[OUTPUT_JSON_SSE]))
m->mode = OUTPUT_JSON_SSE;
else if (streq(header, mime_types[OUTPUT_EXPORT]))
m->mode = OUTPUT_EXPORT;
else
m->mode = OUTPUT_SHORT;
return 0;
}
static int request_parse_range(
RequestMeta *m,
struct MHD_Connection *connection) {
const char *range, *colon, *colon2;
int r;
assert(m);
assert(connection);
range = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Range");
if (!range)
return 0;
if (!startswith(range, "entries="))
return 0;
range += 8;
range += strspn(range, WHITESPACE);
colon = strchr(range, ':');
if (!colon)
m->cursor = strdup(range);
else {
const char *p;
colon2 = strchr(colon + 1, ':');
if (colon2) {
_cleanup_free_ char *t;
t = strndup(colon + 1, colon2 - colon - 1);
if (!t)
return -ENOMEM;
r = safe_atoi64(t, &m->n_skip);
if (r < 0)
return r;
}
p = (colon2 ? colon2 : colon) + 1;
if (*p) {
r = safe_atou64(p, &m->n_entries);
if (r < 0)
return r;
if (m->n_entries <= 0)
return -EINVAL;
m->n_entries_set = true;
}
m->cursor = strndup(range, colon - range);
}
if (!m->cursor)
return -ENOMEM;
m->cursor[strcspn(m->cursor, WHITESPACE)] = 0;
if (isempty(m->cursor)) {
free(m->cursor);
m->cursor = NULL;
}
return 0;
}
static int request_parse_arguments_iterator(
void *cls,
enum MHD_ValueKind kind,
const char *key,
const char *value) {
RequestMeta *m = cls;
_cleanup_free_ char *p = NULL;
int r;
assert(m);
if (isempty(key)) {
m->argument_parse_error = -EINVAL;
return MHD_NO;
}
if (streq(key, "follow")) {
if (isempty(value)) {
m->follow = true;
return MHD_YES;
}
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
m->follow = r;
return MHD_YES;
}
if (streq(key, "discrete")) {
if (isempty(value)) {
m->discrete = true;
return MHD_YES;
}
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
m->discrete = r;
return MHD_YES;
}
if (streq(key, "boot")) {
if (isempty(value))
r = true;
else {
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
if (r) {
char match[9 + 32 + 1] = "_BOOT_ID=";
sd_id128_t bid;
r = sd_id128_get_boot(&bid);
if (r < 0) {
log_error_errno(r, "Failed to get boot ID: %m");
return MHD_NO;
}
sd_id128_to_string(bid, match + 9);
r = sd_journal_add_match(m->journal, match, sizeof(match)-1);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
return MHD_YES;
}
p = strjoin(key, "=", strempty(value), NULL);
if (!p) {
m->argument_parse_error = log_oom();
return MHD_NO;
}
r = sd_journal_add_match(m->journal, p, 0);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
return MHD_YES;
}
static int request_parse_arguments(
RequestMeta *m,
struct MHD_Connection *connection) {
assert(m);
assert(connection);
m->argument_parse_error = 0;
MHD_get_connection_values(connection, MHD_GET_ARGUMENT_KIND, request_parse_arguments_iterator, m);
return m->argument_parse_error;
}
static int request_handler_entries(
struct MHD_Connection *connection,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
assert(connection);
assert(m);
r = open_journal(m);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
if (request_parse_accept(m, connection) < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
if (request_parse_range(m, connection) < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Range header.\n");
if (request_parse_arguments(m, connection) < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse URL arguments.\n");
if (m->discrete) {
if (!m->cursor)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Discrete seeks require a cursor specification.\n");
m->n_entries = 1;
m->n_entries_set = true;
}
if (m->cursor)
r = sd_journal_seek_cursor(m->journal, m->cursor);
else if (m->n_skip >= 0)
r = sd_journal_seek_head(m->journal);
else if (m->n_skip < 0)
r = sd_journal_seek_tail(m->journal);
if (r < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to seek in journal.\n");
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
if (!response)
return respond_oom(connection);
MHD_add_response_header(response, "Content-Type", mime_types[m->mode]);
r = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return r;
}
static int output_field(FILE *f, OutputMode m, const char *d, size_t l) {
const char *eq;
size_t j;
eq = memchr(d, '=', l);
if (!eq)
return -EINVAL;
j = l - (eq - d + 1);
if (m == OUTPUT_JSON) {
fprintf(f, "{ \"%.*s\" : ", (int) (eq - d), d);
json_escape(f, eq+1, j, OUTPUT_FULL_WIDTH);
fputs(" }\n", f);
} else {
fwrite(eq+1, 1, j, f);
fputc('\n', f);
}
return 0;
}
static ssize_t request_reader_fields(
void *cls,
uint64_t pos,
char *buf,
size_t max) {
RequestMeta *m = cls;
int r;
size_t n, k;
assert(m);
assert(buf);
assert(max > 0);
assert(pos >= m->delta);
pos -= m->delta;
while (pos >= m->size) {
off_t sz;
const void *d;
size_t l;
/* End of this field, so let's serialize the next
* one */
if (m->n_fields_set &&
m->n_fields <= 0)
return MHD_CONTENT_READER_END_OF_STREAM;
r = sd_journal_enumerate_unique(m->journal, &d, &l);
if (r < 0) {
log_error_errno(r, "Failed to advance field index: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
} else if (r == 0)
return MHD_CONTENT_READER_END_OF_STREAM;
pos -= m->size;
m->delta += m->size;
if (m->n_fields_set)
m->n_fields -= 1;
r = request_meta_ensure_tmp(m);
if (r < 0) {
log_error_errno(r, "Failed to create temporary file: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
r = output_field(m->tmp, m->mode, d, l);
if (r < 0) {
log_error_errno(r, "Failed to serialize item: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
sz = ftello(m->tmp);
if (sz == (off_t) -1) {
log_error_errno(errno, "Failed to retrieve file position: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
m->size = (uint64_t) sz;
}
if (fseeko(m->tmp, pos, SEEK_SET) < 0) {
log_error_errno(errno, "Failed to seek to position: %m");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
n = m->size - pos;
if (n > max)
n = max;
errno = 0;
k = fread(buf, 1, n, m->tmp);
if (k != n) {
log_error("Failed to read from file: %s", errno ? strerror(errno) : "Premature EOF");
return MHD_CONTENT_READER_END_WITH_ERROR;
}
return (ssize_t) k;
}
static int request_handler_fields(
struct MHD_Connection *connection,
const char *field,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
assert(connection);
assert(m);
r = open_journal(m);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
if (request_parse_accept(m, connection) < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to parse Accept header.\n");
r = sd_journal_query_unique(m->journal, field);
if (r < 0)
return mhd_respond(connection, MHD_HTTP_BAD_REQUEST, "Failed to query unique fields.\n");
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_fields, m, NULL);
if (!response)
return respond_oom(connection);
MHD_add_response_header(response, "Content-Type", mime_types[m->mode == OUTPUT_JSON ? OUTPUT_JSON : OUTPUT_SHORT]);
r = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return r;
}
static int request_handler_redirect(
struct MHD_Connection *connection,
const char *target) {
char *page;
struct MHD_Response *response;
int ret;
assert(connection);
assert(target);
if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
return respond_oom(connection);
response = MHD_create_response_from_buffer(strlen(page), page, MHD_RESPMEM_MUST_FREE);
if (!response) {
free(page);
return respond_oom(connection);
}
MHD_add_response_header(response, "Content-Type", "text/html");
MHD_add_response_header(response, "Location", target);
ret = MHD_queue_response(connection, MHD_HTTP_MOVED_PERMANENTLY, response);
MHD_destroy_response(response);
return ret;
}
static int request_handler_file(
struct MHD_Connection *connection,
const char *path,
const char *mime_type) {
struct MHD_Response *response;
int ret;
_cleanup_close_ int fd = -1;
struct stat st;
assert(connection);
assert(path);
assert(mime_type);
fd = open(path, O_RDONLY|O_CLOEXEC);
if (fd < 0)
return mhd_respondf(connection, MHD_HTTP_NOT_FOUND, "Failed to open file %s: %m\n", path);
if (fstat(fd, &st) < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to stat file: %m\n");
response = MHD_create_response_from_fd_at_offset(st.st_size, fd, 0);
if (!response)
return respond_oom(connection);
fd = -1;
MHD_add_response_header(response, "Content-Type", mime_type);
ret = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return ret;
}
static int get_virtualization(char **v) {
_cleanup_bus_unref_ sd_bus *bus = NULL;
char *b = NULL;
int r;
r = sd_bus_default_system(&bus);
if (r < 0)
return r;
r = sd_bus_get_property_string(
bus,
"org.freedesktop.systemd1",
"/org/freedesktop/systemd1",
"org.freedesktop.systemd1.Manager",
"Virtualization",
NULL,
&b);
if (r < 0)
return r;
if (isempty(b)) {
free(b);
*v = NULL;
return 0;
}
*v = b;
return 1;
}
static int request_handler_machine(
struct MHD_Connection *connection,
void *connection_cls) {
struct MHD_Response *response;
RequestMeta *m = connection_cls;
int r;
_cleanup_free_ char* hostname = NULL, *os_name = NULL;
uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0;
char *json;
sd_id128_t mid, bid;
_cleanup_free_ char *v = NULL;
assert(connection);
assert(m);
r = open_journal(m);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to open journal: %s\n", strerror(-r));
r = sd_id128_get_machine(&mid);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine machine ID: %s\n", strerror(-r));
r = sd_id128_get_boot(&bid);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine boot ID: %s\n", strerror(-r));
hostname = gethostname_malloc();
if (!hostname)
return respond_oom(connection);
r = sd_journal_get_usage(m->journal, &usage);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
r = sd_journal_get_cutoff_realtime_usec(m->journal, &cutoff_from, &cutoff_to);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
if (parse_env_file("/etc/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL) == -ENOENT)
(void) parse_env_file("/usr/lib/os-release", NEWLINE, "PRETTY_NAME", &os_name, NULL);
get_virtualization(&v);
r = asprintf(&json,
"{ \"machine_id\" : \"" SD_ID128_FORMAT_STR "\","
"\"boot_id\" : \"" SD_ID128_FORMAT_STR "\","
"\"hostname\" : \"%s\","
"\"os_pretty_name\" : \"%s\","
"\"virtualization\" : \"%s\","
"\"usage\" : \"%"PRIu64"\","
"\"cutoff_from_realtime\" : \"%"PRIu64"\","
"\"cutoff_to_realtime\" : \"%"PRIu64"\" }\n",
SD_ID128_FORMAT_VAL(mid),
SD_ID128_FORMAT_VAL(bid),
hostname_cleanup(hostname, false),
os_name ? os_name : "Linux",
v ? v : "bare",
usage,
cutoff_from,
cutoff_to);
if (r < 0)
return respond_oom(connection);
response = MHD_create_response_from_buffer(strlen(json), json, MHD_RESPMEM_MUST_FREE);
if (!response) {
free(json);
return respond_oom(connection);
}
MHD_add_response_header(response, "Content-Type", "application/json");
r = MHD_queue_response(connection, MHD_HTTP_OK, response);
MHD_destroy_response(response);
return r;
}
static int 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) {
int r, code;
assert(connection);
assert(connection_cls);
assert(url);
assert(method);
if (!streq(method, "GET"))
return mhd_respond(connection, MHD_HTTP_METHOD_NOT_ACCEPTABLE,
"Unsupported method.\n");
if (!*connection_cls) {
if (!request_meta(connection_cls))
return respond_oom(connection);
return MHD_YES;
}
if (arg_trust_pem) {
r = check_permissions(connection, &code, NULL);
if (r < 0)
return code;
}
if (streq(url, "/"))
return request_handler_redirect(connection, "/browse");
if (streq(url, "/entries"))
return request_handler_entries(connection, *connection_cls);
if (startswith(url, "/fields/"))
return request_handler_fields(connection, url + 8, *connection_cls);
if (streq(url, "/browse"))
return request_handler_file(connection, DOCUMENT_ROOT "/browse.html", "text/html");
if (streq(url, "/machine"))
return request_handler_machine(connection, *connection_cls);
return mhd_respond(connection, MHD_HTTP_NOT_FOUND, "Not found.\n");
}
static void help(void) {
printf("%s [OPTIONS...] ...\n\n"
"HTTP server for journal events.\n\n"
" -h --help Show this help\n"
" --version Show package version\n"
" --cert=CERT.PEM Server certificate in PEM format\n"
" --key=KEY.PEM Server key in PEM format\n"
" --trust=CERT.PEM Certificat authority certificate in PEM format\n",
program_invocation_short_name);
}
static int parse_argv(int argc, char *argv[]) {
enum {
ARG_VERSION = 0x100,
ARG_KEY,
ARG_CERT,
ARG_TRUST,
};
int r, c;
static const struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "version", no_argument, NULL, ARG_VERSION },
{ "key", required_argument, NULL, ARG_KEY },
{ "cert", required_argument, NULL, ARG_CERT },
{ "trust", required_argument, NULL, ARG_TRUST },
{}
};
assert(argc >= 0);
assert(argv);
while ((c = getopt_long(argc, argv, "h", options, NULL)) >= 0)
switch(c) {
case 'h':
help();
return 0;
case ARG_VERSION:
puts(PACKAGE_STRING);
puts(SYSTEMD_FEATURES);
return 0;
case ARG_KEY:
if (arg_key_pem) {
log_error("Key file specified twice");
return -EINVAL;
}
r = read_full_file(optarg, &arg_key_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read key file: %m");
assert(arg_key_pem);
break;
case ARG_CERT:
if (arg_cert_pem) {
log_error("Certificate file specified twice");
return -EINVAL;
}
r = read_full_file(optarg, &arg_cert_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read certificate file: %m");
assert(arg_cert_pem);
break;
case ARG_TRUST:
#ifdef HAVE_GNUTLS
if (arg_trust_pem) {
log_error("CA certificate file specified twice");
return -EINVAL;
}
r = read_full_file(optarg, &arg_trust_pem, NULL);
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file: %m");
assert(arg_trust_pem);
break;
#else
log_error("Option --trust is not available.");
#endif
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
if (optind < argc) {
log_error("This program does not take arguments.");
return -EINVAL;
}
if (!!arg_key_pem != !!arg_cert_pem) {
log_error("Certificate and key files must be specified together");
return -EINVAL;
}
if (arg_trust_pem && !arg_key_pem) {
log_error("CA certificate can only be used with certificate file");
return -EINVAL;
}
return 1;
}
int main(int argc, char *argv[]) {
struct MHD_Daemon *d = NULL;
int r, n;
log_set_target(LOG_TARGET_AUTO);
log_parse_environment();
log_open();
r = parse_argv(argc, argv);
if (r < 0)
return EXIT_FAILURE;
if (r == 0)
return EXIT_SUCCESS;
sigbus_install();
r = setup_gnutls_logger(NULL);
if (r < 0)
return EXIT_FAILURE;
n = sd_listen_fds(1);
if (n < 0) {
log_error_errno(n, "Failed to determine passed sockets: %m");
goto finish;
} else if (n > 1) {
log_error("Can't listen on more than one socket.");
goto finish;
} else {
struct MHD_OptionItem opts[] = {
{ MHD_OPTION_NOTIFY_COMPLETED,
(intptr_t) request_meta_free, NULL },
{ MHD_OPTION_EXTERNAL_LOGGER,
(intptr_t) microhttpd_logger, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL },
{ MHD_OPTION_END, 0, NULL }};
int opts_pos = 2;
int flags = MHD_USE_THREAD_PER_CONNECTION|MHD_USE_POLL|MHD_USE_DEBUG;
if (n > 0)
opts[opts_pos++] = (struct MHD_OptionItem)
{MHD_OPTION_LISTEN_SOCKET, SD_LISTEN_FDS_START};
if (arg_key_pem) {
assert(arg_cert_pem);
opts[opts_pos++] = (struct MHD_OptionItem)
{MHD_OPTION_HTTPS_MEM_KEY, 0, arg_key_pem};
opts[opts_pos++] = (struct MHD_OptionItem)
{MHD_OPTION_HTTPS_MEM_CERT, 0, arg_cert_pem};
flags |= MHD_USE_SSL;
}
if (arg_trust_pem) {
assert(flags & MHD_USE_SSL);
opts[opts_pos++] = (struct MHD_OptionItem)
{MHD_OPTION_HTTPS_MEM_TRUST, 0, arg_trust_pem};
}
d = MHD_start_daemon(flags, 19531,
NULL, NULL,
request_handler, NULL,
MHD_OPTION_ARRAY, opts,
MHD_OPTION_END);
}
if (!d) {
log_error("Failed to start daemon!");
goto finish;
}
pause();
r = EXIT_SUCCESS;
finish:
if (d)
MHD_stop_daemon(d);
return r;
}