/* $OpenBSD: logger.c,v 1.22 2019/05/02 22:32:34 kn Exp $ */ /* * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include <sys/types.h> #include <sys/queue.h> #include <sys/uio.h> #include <limits.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <fcntl.h> #include <imsg.h> #include "httpd.h" int logger_dispatch_parent(int, struct privsep_proc *, struct imsg *); int logger_dispatch_server(int, struct privsep_proc *, struct imsg *); void logger_shutdown(void); void logger_close(void); struct log_file *logger_open_file(const char *); int logger_open_fd(struct imsg *); int logger_open(struct server *, struct server_config *, void *); void logger_init(struct privsep *, struct privsep_proc *p, void *); int logger_start(void); int logger_log(struct imsg *); static uint32_t last_log_id = 0; static struct privsep_proc procs[] = { { "parent", PROC_PARENT, logger_dispatch_parent }, { "server", PROC_SERVER, logger_dispatch_server } }; void logger(struct privsep *ps, struct privsep_proc *p) { proc_run(ps, p, procs, nitems(procs), logger_init, NULL); } void logger_shutdown(void) { logger_close(); config_purge(httpd_env, CONFIG_ALL); } void logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) { if (pledge("stdio recvfd", NULL) == -1) fatal("pledge"); if (config_init(ps->ps_env) == -1) fatal("failed to initialize configuration"); /* We use a custom shutdown callback */ p->p_shutdown = logger_shutdown; TAILQ_INIT(&log_files); } void logger_close(void) { struct log_file *log, *next; TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { if (log->log_fd != -1) { close(log->log_fd); log->log_fd = -1; } TAILQ_REMOVE(&log_files, log, log_entry); } } struct log_file * logger_open_file(const char *name) { struct log_file *log; struct iovec iov[2]; if ((log = calloc(1, sizeof(*log))) == NULL) { log_warn("failed to allocate log %s", name); return (NULL); } log->log_id = ++last_log_id; (void)strlcpy(log->log_name, name, sizeof(log->log_name)); /* The file will be opened by the parent process */ log->log_fd = -1; iov[0].iov_base = &log->log_id; iov[0].iov_len = sizeof(log->log_id); iov[1].iov_base = log->log_name; iov[1].iov_len = strlen(log->log_name) + 1; if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, iov, 2) != 0) { log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); goto err; } TAILQ_INSERT_TAIL(&log_files, log, log_entry); return (log); err: free(log); return (NULL); } int logger_open_fd(struct imsg *imsg) { struct log_file *log; uint32_t id; IMSG_SIZE_CHECK(imsg, &id); memcpy(&id, imsg->data, sizeof(id)); TAILQ_FOREACH(log, &log_files, log_entry) { if (log->log_id == id) { DPRINTF("%s: received log fd %d, file %s", __func__, imsg->fd, log->log_name); log->log_fd = imsg->fd; return (0); } } return (-1); } int logger_open_priv(struct imsg *imsg) { char path[PATH_MAX]; char name[PATH_MAX], *p; uint32_t id; size_t len; int fd; /* called from the privileged process */ IMSG_SIZE_CHECK(imsg, &id); memcpy(&id, imsg->data, sizeof(id)); p = (char *)imsg->data + sizeof(id); if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) return (-1); if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path))) >= sizeof(path)) return (-1); p = path + len; len = sizeof(path) - len; if (canonicalize_path(name, p, len) == NULL) { log_warnx("invalid log name"); return (-1); } if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { log_warn("failed to open %s", path); return (-1); } proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1, IMSG_LOG_OPEN, -1, fd, &id, sizeof(id)); DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); return (0); } int logger_open(struct server *srv, struct server_config *srv_conf, void *arg) { struct log_file *log, *logfile = NULL, *errfile = NULL; if (srv_conf->flags & (SRVFLAG_SYSLOG | SRVFLAG_NO_LOG)) return (0); /* disassociate */ srv_conf->logaccess = srv_conf->logerror = NULL; TAILQ_FOREACH(log, &log_files, log_entry) { if (strcmp(log->log_name, srv_conf->accesslog) == 0) logfile = log; if (strcmp(log->log_name, srv_conf->errorlog) == 0) errfile = log; } if (logfile == NULL) { if ((srv_conf->logaccess = logger_open_file(srv_conf->accesslog)) == NULL) return (-1); } else srv_conf->logaccess = logfile; if (errfile == NULL) { if ((srv_conf->logerror = logger_open_file(srv_conf->errorlog)) == NULL) return (-1); } else srv_conf->logerror = errfile; return (0); } int logger_start(void) { logger_close(); if (server_foreach(logger_open, NULL) == -1) fatalx("failed to open log files"); return (0); } int logger_log(struct imsg *imsg) { char *logline; uint32_t id; struct server_config *srv_conf; struct log_file *log; IMSG_SIZE_CHECK(imsg, &id); memcpy(&id, imsg->data, sizeof(id)); if ((srv_conf = serverconfig_byid(id)) == NULL) fatalx("invalid logging requestr"); if (imsg->hdr.type == IMSG_LOG_ACCESS) log = srv_conf->logaccess; else log = srv_conf->logerror; if (log == NULL || log->log_fd == -1) { log_warnx("log file %s not opened", log ? log->log_name : ""); return (0); } /* XXX get_string() would sanitize the string, but add a malloc */ logline = (char *)imsg->data + sizeof(id); /* For debug output */ log_debug("%s", logline); if (dprintf(log->log_fd, "%s\n", logline) == -1) { if (logger_start() == -1) return (-1); } return (0); } int logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) { switch (imsg->hdr.type) { case IMSG_CFG_SERVER: config_getserver(httpd_env, imsg); break; case IMSG_CFG_DONE: config_getcfg(httpd_env, imsg); break; case IMSG_CTL_START: case IMSG_CTL_REOPEN: logger_start(); break; case IMSG_CTL_RESET: config_getreset(httpd_env, imsg); break; case IMSG_LOG_OPEN: return (logger_open_fd(imsg)); default: return (-1); } return (0); } int logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) { switch (imsg->hdr.type) { case IMSG_LOG_ACCESS: case IMSG_LOG_ERROR: logger_log(imsg); break; default: return (-1); } return (0); }