/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <fcntl.h>
#include <getopt.h>
#ifdef HAVE_GNUTLS
#endif
#include <microhttpd.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sd-bus.h"
#include "sd-daemon.h"
#include "sd-journal.h"
#include "alloc-util.h"
#include "bus-util.h"
#include "fd-util.h"
#include "fileio.h"
#include "hostname-util.h"
#include "log.h"
#include "logs-show.h"
#include "microhttpd-util.h"
#include "parse-util.h"
#include "sigbus.h"
#include "util.h"
typedef struct RequestMeta {
char *cursor;
bool n_entries_set;
int argument_parse_error;
bool follow;
bool discrete;
bool n_fields_set;
} RequestMeta;
[OUTPUT_SHORT] = "text/plain",
[OUTPUT_JSON] = "application/json",
[OUTPUT_JSON_SSE] = "text/event-stream",
};
RequestMeta *m;
if (*connection_cls)
return *connection_cls;
if (!m)
return NULL;
*connection_cls = m;
return m;
}
static void request_meta_free(
void *cls,
struct MHD_Connection *connection,
void **connection_cls,
enum MHD_RequestTerminationCode toe) {
RequestMeta *m = *connection_cls;
if (!m)
return;
sd_journal_close(m->journal);
safe_fclose(m->tmp);
free(m);
}
assert(m);
if (m->journal)
return 0;
}
if (m->tmp)
else {
int fd;
if (fd < 0)
return fd;
if (!m->tmp) {
safe_close(fd);
return -errno;
}
}
return 0;
}
void *cls,
char *buf,
RequestMeta *m = cls;
int r;
size_t n, k;
assert(m);
/* End of this entry, so let's serialize the next
* one */
if (m->n_entries_set &&
m->n_entries <= 0)
return MHD_CONTENT_READER_END_OF_STREAM;
if (m->n_skip < 0)
else if (m->n_skip > 0)
else
r = sd_journal_next(m->journal);
if (r < 0) {
log_error_errno(r, "Failed to advance journal pointer: %m");
} else if (r == 0) {
if (m->follow) {
if (r < 0) {
log_error_errno(r, "Couldn't wait for journal event: %m");
}
if (r == SD_JOURNAL_NOP)
break;
continue;
}
return MHD_CONTENT_READER_END_OF_STREAM;
}
if (m->discrete) {
if (r < 0) {
log_error_errno(r, "Failed to test cursor: %m");
}
if (r == 0)
return MHD_CONTENT_READER_END_OF_STREAM;
}
if (m->n_entries_set)
m->n_entries -= 1;
m->n_skip = 0;
r = request_meta_ensure_tmp(m);
if (r < 0) {
log_error_errno(r, "Failed to create temporary file: %m");
}
if (r < 0) {
log_error_errno(r, "Failed to serialize item: %m");
}
}
}
}
if (n < 1)
return 0;
if (n > max)
n = max;
errno = 0;
if (k != n) {
}
return (ssize_t) k;
}
static int request_parse_accept(
RequestMeta *m,
struct MHD_Connection *connection) {
const char *header;
assert(m);
if (!header)
return 0;
m->mode = OUTPUT_JSON;
m->mode = OUTPUT_JSON_SSE;
m->mode = OUTPUT_EXPORT;
else
m->mode = OUTPUT_SHORT;
return 0;
}
static int request_parse_range(
RequestMeta *m,
struct MHD_Connection *connection) {
int r;
assert(m);
if (!range)
return 0;
return 0;
range += 8;
if (!colon)
else {
const char *p;
if (colon2) {
_cleanup_free_ char *t;
if (!t)
return -ENOMEM;
r = safe_atoi64(t, &m->n_skip);
if (r < 0)
return r;
}
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;
}
}
if (!m->cursor)
return -ENOMEM;
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);
m->argument_parse_error = -EINVAL;
return MHD_NO;
}
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;
}
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;
}
r = true;
else {
r = parse_boolean(value);
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
if (r) {
r = sd_id128_get_boot(&bid);
if (r < 0) {
log_error_errno(r, "Failed to get boot ID: %m");
return MHD_NO;
}
if (r < 0) {
m->argument_parse_error = r;
return MHD_NO;
}
}
return MHD_YES;
}
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);
m->argument_parse_error = 0;
return m->argument_parse_error;
}
static int request_handler_entries(
struct MHD_Connection *connection,
void *connection_cls) {
RequestMeta *m = connection_cls;
int r;
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)
if (request_parse_range(m, connection) < 0)
if (request_parse_arguments(m, connection) < 0)
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)
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)
response = MHD_create_response_from_callback(MHD_SIZE_UNKNOWN, 4*1024, request_reader_entries, m, NULL);
if (!response)
return respond_oom(connection);
return r;
}
const char *eq;
size_t j;
if (!eq)
return -EINVAL;
j = l - (eq - d + 1);
if (m == OUTPUT_JSON) {
fputs(" }\n", f);
} else {
fputc('\n', f);
}
return 0;
}
void *cls,
char *buf,
RequestMeta *m = cls;
int r;
size_t n, k;
assert(m);
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");
} else if (r == 0)
return MHD_CONTENT_READER_END_OF_STREAM;
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");
}
if (r < 0) {
log_error_errno(r, "Failed to serialize item: %m");
}
}
}
}
if (n > max)
n = max;
errno = 0;
if (k != n) {
}
return (ssize_t) k;
}
static int request_handler_fields(
struct MHD_Connection *connection,
const char *field,
void *connection_cls) {
RequestMeta *m = connection_cls;
int r;
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)
if (r < 0)
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]);
return r;
}
static int request_handler_redirect(
struct MHD_Connection *connection,
const char *target) {
char *page;
int ret;
if (asprintf(&page, "<html><body>Please continue to the <a href=\"%s\">journal browser</a>.</body></html>", target) < 0)
return respond_oom(connection);
if (!response) {
return respond_oom(connection);
}
return ret;
}
static int request_handler_file(
struct MHD_Connection *connection,
const char *path,
const char *mime_type) {
int ret;
if (fd < 0)
if (!response)
return respond_oom(connection);
fd = -1;
return ret;
}
static int get_virtualization(char **v) {
char *b = NULL;
int r;
r = sd_bus_default_system(&bus);
if (r < 0)
return r;
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) {
RequestMeta *m = connection_cls;
int r;
char *json;
_cleanup_free_ char *v = NULL;
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));
if (!hostname)
return respond_oom(connection);
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
if (r < 0)
return mhd_respondf(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %s\n", strerror(-r));
get_virtualization(&v);
"\"hostname\" : \"%s\","
"\"os_pretty_name\" : \"%s\","
"\"virtualization\" : \"%s\","
v ? v : "bare",
if (r < 0)
return respond_oom(connection);
if (!response) {
return respond_oom(connection);
}
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,
void **connection_cls) {
int r, code;
"Unsupported method.\n");
if (!*connection_cls) {
if (!request_meta(connection_cls))
return respond_oom(connection);
return MHD_YES;
}
if (arg_trust_pem) {
if (r < 0)
return code;
}
}
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",
}
enum {
};
int r, c;
{}
};
switch(c) {
case 'h':
help();
return 0;
case ARG_VERSION:
return version();
case ARG_KEY:
if (arg_key_pem) {
log_error("Key file specified twice");
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to read key file: %m");
break;
case ARG_CERT:
if (arg_cert_pem) {
log_error("Certificate file specified twice");
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to read certificate file: %m");
break;
case ARG_TRUST:
#ifdef HAVE_GNUTLS
if (arg_trust_pem) {
log_error("CA certificate file specified twice");
return -EINVAL;
}
if (r < 0)
return log_error_errno(r, "Failed to read CA certificate file: %m");
break;
#else
log_error("Option --trust is not available.");
#endif
case '?':
return -EINVAL;
default:
assert_not_reached("Unhandled option");
}
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;
}
struct MHD_Daemon *d = NULL;
int r, n;
log_open();
if (r < 0)
return EXIT_FAILURE;
if (r == 0)
return EXIT_SUCCESS;
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 {
{ 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 }};
/* We force MHD_USE_PIPE_FOR_SHUTDOWN here, in order
* to make sure libmicrohttpd doesn't use shutdown()
* on our listening socket, which would break socket
* re-activation. See
*
*/
int flags =
if (n > 0)
if (arg_key_pem) {
flags |= MHD_USE_SSL;
}
if (arg_trust_pem) {
}
}
if (!d) {
log_error("Failed to start daemon!");
goto finish;
}
pause();
r = EXIT_SUCCESS;
if (d)
MHD_stop_daemon(d);
return r;
}