1 /* $OpenBSD: logger.c,v 1.23 2020/12/31 14:15:40 tb Exp $ */ 2 3 /* 4 * Copyright (c) 2014 Reyk Floeter <reyk@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 19 #include <sys/types.h> 20 #include <sys/queue.h> 21 #include <sys/uio.h> 22 23 #include <limits.h> 24 #include <stdio.h> 25 #include <stdlib.h> 26 #include <string.h> 27 #include <unistd.h> 28 #include <fcntl.h> 29 #include <imsg.h> 30 31 #include "httpd.h" 32 33 int logger_dispatch_parent(int, struct privsep_proc *, 34 struct imsg *); 35 int logger_dispatch_server(int, struct privsep_proc *, 36 struct imsg *); 37 void logger_shutdown(void); 38 void logger_close(void); 39 struct log_file *logger_open_file(const char *); 40 int logger_open_fd(struct imsg *); 41 int logger_open(struct server *, struct server_config *, void *); 42 void logger_init(struct privsep *, struct privsep_proc *p, void *); 43 int logger_start(void); 44 int logger_log(struct imsg *); 45 46 static uint32_t last_log_id = 0; 47 48 static struct privsep_proc procs[] = { 49 { "parent", PROC_PARENT, logger_dispatch_parent }, 50 { "server", PROC_SERVER, logger_dispatch_server } 51 }; 52 53 void 54 logger(struct privsep *ps, struct privsep_proc *p) 55 { 56 proc_run(ps, p, procs, nitems(procs), logger_init, NULL); 57 } 58 59 void 60 logger_shutdown(void) 61 { 62 logger_close(); 63 config_purge(httpd_env, CONFIG_ALL); 64 } 65 66 void 67 logger_init(struct privsep *ps, struct privsep_proc *p, void *arg) 68 { 69 if (pledge("stdio recvfd", NULL) == -1) 70 fatal("pledge"); 71 72 if (config_init(ps->ps_env) == -1) 73 fatal("failed to initialize configuration"); 74 75 /* We use a custom shutdown callback */ 76 p->p_shutdown = logger_shutdown; 77 78 TAILQ_INIT(&log_files); 79 } 80 81 void 82 logger_close(void) 83 { 84 struct log_file *log, *next; 85 86 TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) { 87 if (log->log_fd != -1) { 88 close(log->log_fd); 89 log->log_fd = -1; 90 } 91 TAILQ_REMOVE(&log_files, log, log_entry); 92 free(log); 93 } 94 } 95 96 struct log_file * 97 logger_open_file(const char *name) 98 { 99 struct log_file *log; 100 struct iovec iov[2]; 101 102 if ((log = calloc(1, sizeof(*log))) == NULL) { 103 log_warn("failed to allocate log %s", name); 104 return (NULL); 105 } 106 107 log->log_id = ++last_log_id; 108 (void)strlcpy(log->log_name, name, sizeof(log->log_name)); 109 110 /* The file will be opened by the parent process */ 111 log->log_fd = -1; 112 113 iov[0].iov_base = &log->log_id; 114 iov[0].iov_len = sizeof(log->log_id); 115 iov[1].iov_base = log->log_name; 116 iov[1].iov_len = strlen(log->log_name) + 1; 117 118 if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN, 119 iov, 2) != 0) { 120 log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__); 121 goto err; 122 } 123 124 TAILQ_INSERT_TAIL(&log_files, log, log_entry); 125 126 return (log); 127 128 err: 129 free(log); 130 131 return (NULL); 132 } 133 134 int 135 logger_open_fd(struct imsg *imsg) 136 { 137 struct log_file *log; 138 uint32_t id; 139 140 IMSG_SIZE_CHECK(imsg, &id); 141 memcpy(&id, imsg->data, sizeof(id)); 142 143 TAILQ_FOREACH(log, &log_files, log_entry) { 144 if (log->log_id == id) { 145 DPRINTF("%s: received log fd %d, file %s", 146 __func__, imsg->fd, log->log_name); 147 log->log_fd = imsg->fd; 148 return (0); 149 } 150 } 151 152 return (-1); 153 } 154 155 int 156 logger_open_priv(struct imsg *imsg) 157 { 158 char path[PATH_MAX]; 159 char name[PATH_MAX], *p; 160 uint32_t id; 161 size_t len; 162 int fd; 163 164 /* called from the privileged process */ 165 IMSG_SIZE_CHECK(imsg, &id); 166 memcpy(&id, imsg->data, sizeof(id)); 167 p = (char *)imsg->data + sizeof(id); 168 169 if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name)) 170 return (-1); 171 if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path))) 172 >= sizeof(path)) 173 return (-1); 174 175 p = path + len; 176 len = sizeof(path) - len; 177 178 if (canonicalize_path(name, p, len) == NULL) { 179 log_warnx("invalid log name"); 180 return (-1); 181 } 182 183 if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) { 184 log_warn("failed to open %s", path); 185 return (-1); 186 } 187 188 proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1, 189 IMSG_LOG_OPEN, -1, fd, &id, sizeof(id)); 190 191 DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd); 192 193 return (0); 194 } 195 196 int 197 logger_open(struct server *srv, struct server_config *srv_conf, void *arg) 198 { 199 struct log_file *log, *logfile = NULL, *errfile = NULL; 200 201 if (srv_conf->flags & (SRVFLAG_SYSLOG | SRVFLAG_NO_LOG)) 202 return (0); 203 204 /* disassociate */ 205 srv_conf->logaccess = srv_conf->logerror = NULL; 206 207 TAILQ_FOREACH(log, &log_files, log_entry) { 208 if (strcmp(log->log_name, srv_conf->accesslog) == 0) 209 logfile = log; 210 if (strcmp(log->log_name, srv_conf->errorlog) == 0) 211 errfile = log; 212 } 213 214 if (logfile == NULL) { 215 if ((srv_conf->logaccess = 216 logger_open_file(srv_conf->accesslog)) == NULL) 217 return (-1); 218 } else 219 srv_conf->logaccess = logfile; 220 221 if (errfile == NULL) { 222 if ((srv_conf->logerror = 223 logger_open_file(srv_conf->errorlog)) == NULL) 224 return (-1); 225 } else 226 srv_conf->logerror = errfile; 227 228 return (0); 229 } 230 231 int 232 logger_start(void) 233 { 234 logger_close(); 235 if (server_foreach(logger_open, NULL) == -1) 236 fatalx("failed to open log files"); 237 return (0); 238 } 239 240 int 241 logger_log(struct imsg *imsg) 242 { 243 char *logline; 244 uint32_t id; 245 struct server_config *srv_conf; 246 struct log_file *log; 247 248 IMSG_SIZE_CHECK(imsg, &id); 249 memcpy(&id, imsg->data, sizeof(id)); 250 251 if ((srv_conf = serverconfig_byid(id)) == NULL) 252 fatalx("invalid logging requestr"); 253 254 if (imsg->hdr.type == IMSG_LOG_ACCESS) 255 log = srv_conf->logaccess; 256 else 257 log = srv_conf->logerror; 258 259 if (log == NULL || log->log_fd == -1) { 260 log_warnx("log file %s not opened", log ? log->log_name : ""); 261 return (0); 262 } 263 264 /* XXX get_string() would sanitize the string, but add a malloc */ 265 logline = (char *)imsg->data + sizeof(id); 266 267 /* For debug output */ 268 log_debug("%s", logline); 269 270 if (dprintf(log->log_fd, "%s\n", logline) == -1) { 271 if (logger_start() == -1) 272 return (-1); 273 } 274 275 return (0); 276 } 277 278 int 279 logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg) 280 { 281 switch (imsg->hdr.type) { 282 case IMSG_CFG_SERVER: 283 config_getserver(httpd_env, imsg); 284 break; 285 case IMSG_CFG_DONE: 286 config_getcfg(httpd_env, imsg); 287 break; 288 case IMSG_CTL_START: 289 case IMSG_CTL_REOPEN: 290 logger_start(); 291 break; 292 case IMSG_CTL_RESET: 293 config_getreset(httpd_env, imsg); 294 break; 295 case IMSG_LOG_OPEN: 296 return (logger_open_fd(imsg)); 297 default: 298 return (-1); 299 } 300 301 return (0); 302 } 303 304 int 305 logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg) 306 { 307 switch (imsg->hdr.type) { 308 case IMSG_LOG_ACCESS: 309 case IMSG_LOG_ERROR: 310 logger_log(imsg); 311 break; 312 default: 313 return (-1); 314 } 315 316 return (0); 317 } 318