xref: /openbsd/usr.sbin/httpd/logger.c (revision 097a140d)
1 /*	$OpenBSD: logger.c,v 1.24 2021/01/27 07:21:53 deraadt 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 struct log_files log_files;
49 
50 static struct privsep_proc procs[] = {
51 	{ "parent",	PROC_PARENT,	logger_dispatch_parent },
52 	{ "server",	PROC_SERVER,	logger_dispatch_server }
53 };
54 
55 void
56 logger(struct privsep *ps, struct privsep_proc *p)
57 {
58 	proc_run(ps, p, procs, nitems(procs), logger_init, NULL);
59 }
60 
61 void
62 logger_shutdown(void)
63 {
64 	logger_close();
65 	config_purge(httpd_env, CONFIG_ALL);
66 }
67 
68 void
69 logger_init(struct privsep *ps, struct privsep_proc *p, void *arg)
70 {
71 	if (pledge("stdio recvfd", NULL) == -1)
72 		fatal("pledge");
73 
74 	if (config_init(ps->ps_env) == -1)
75 		fatal("failed to initialize configuration");
76 
77 	/* We use a custom shutdown callback */
78 	p->p_shutdown = logger_shutdown;
79 
80 	TAILQ_INIT(&log_files);
81 }
82 
83 void
84 logger_close(void)
85 {
86 	struct log_file	*log, *next;
87 
88 	TAILQ_FOREACH_SAFE(log, &log_files, log_entry, next) {
89 		if (log->log_fd != -1) {
90 			close(log->log_fd);
91 			log->log_fd = -1;
92 		}
93 		TAILQ_REMOVE(&log_files, log, log_entry);
94 		free(log);
95 	}
96 }
97 
98 struct log_file *
99 logger_open_file(const char *name)
100 {
101 	struct log_file	*log;
102 	struct iovec	 iov[2];
103 
104 	if ((log = calloc(1, sizeof(*log))) == NULL) {
105 		log_warn("failed to allocate log %s", name);
106 		return (NULL);
107 	}
108 
109 	log->log_id = ++last_log_id;
110 	(void)strlcpy(log->log_name, name, sizeof(log->log_name));
111 
112 	/* The file will be opened by the parent process */
113 	log->log_fd = -1;
114 
115 	iov[0].iov_base = &log->log_id;
116 	iov[0].iov_len = sizeof(log->log_id);
117 	iov[1].iov_base = log->log_name;
118 	iov[1].iov_len = strlen(log->log_name) + 1;
119 
120 	if (proc_composev(httpd_env->sc_ps, PROC_PARENT, IMSG_LOG_OPEN,
121 	    iov, 2) != 0) {
122 		log_warn("%s: failed to compose IMSG_LOG_OPEN imsg", __func__);
123 		goto err;
124 	}
125 
126 	TAILQ_INSERT_TAIL(&log_files, log, log_entry);
127 
128 	return (log);
129 
130 err:
131 	free(log);
132 
133 	return (NULL);
134 }
135 
136 int
137 logger_open_fd(struct imsg *imsg)
138 {
139 	struct log_file		*log;
140 	uint32_t		 id;
141 
142 	IMSG_SIZE_CHECK(imsg, &id);
143 	memcpy(&id, imsg->data, sizeof(id));
144 
145 	TAILQ_FOREACH(log, &log_files, log_entry) {
146 		if (log->log_id == id) {
147 			DPRINTF("%s: received log fd %d, file %s",
148 			    __func__, imsg->fd, log->log_name);
149 			log->log_fd = imsg->fd;
150 			return (0);
151 		}
152 	}
153 
154 	return (-1);
155 }
156 
157 int
158 logger_open_priv(struct imsg *imsg)
159 {
160 	char			 path[PATH_MAX];
161 	char			 name[PATH_MAX], *p;
162 	uint32_t		 id;
163 	size_t			 len;
164 	int			 fd;
165 
166 	/* called from the privileged process */
167 	IMSG_SIZE_CHECK(imsg, &id);
168 	memcpy(&id, imsg->data, sizeof(id));
169 	p = (char *)imsg->data + sizeof(id);
170 
171 	if ((size_t)snprintf(name, sizeof(name), "/%s", p) >= sizeof(name))
172 		return (-1);
173 	if ((len = strlcpy(path, httpd_env->sc_logdir, sizeof(path)))
174 	    >= sizeof(path))
175 		return (-1);
176 
177 	p = path + len;
178 	len = sizeof(path) - len;
179 
180 	if (canonicalize_path(name, p, len) == NULL) {
181 		log_warnx("invalid log name");
182 		return (-1);
183 	}
184 
185 	if ((fd = open(path, O_WRONLY|O_APPEND|O_CREAT, 0644)) == -1) {
186 		log_warn("failed to open %s", path);
187 		return (-1);
188 	}
189 
190 	proc_compose_imsg(httpd_env->sc_ps, PROC_LOGGER, -1,
191 	    IMSG_LOG_OPEN, -1, fd, &id, sizeof(id));
192 
193 	DPRINTF("%s: opened log file %s, fd %d", __func__, path, fd);
194 
195 	return (0);
196 }
197 
198 int
199 logger_open(struct server *srv, struct server_config *srv_conf, void *arg)
200 {
201 	struct log_file	*log, *logfile = NULL, *errfile = NULL;
202 
203 	if (srv_conf->flags & (SRVFLAG_SYSLOG | SRVFLAG_NO_LOG))
204 		return (0);
205 
206 	/* disassociate */
207 	srv_conf->logaccess = srv_conf->logerror = NULL;
208 
209 	TAILQ_FOREACH(log, &log_files, log_entry) {
210 		if (strcmp(log->log_name, srv_conf->accesslog) == 0)
211 			logfile = log;
212 		if (strcmp(log->log_name, srv_conf->errorlog) == 0)
213 			errfile = log;
214 	}
215 
216 	if (logfile == NULL) {
217 		if ((srv_conf->logaccess =
218 		    logger_open_file(srv_conf->accesslog)) == NULL)
219 			return (-1);
220 	} else
221 		srv_conf->logaccess = logfile;
222 
223 	if (errfile == NULL) {
224 		if ((srv_conf->logerror =
225 		    logger_open_file(srv_conf->errorlog)) == NULL)
226 			return (-1);
227 	} else
228 		srv_conf->logerror = errfile;
229 
230 	return (0);
231 }
232 
233 int
234 logger_start(void)
235 {
236 	logger_close();
237 	if (server_foreach(logger_open, NULL) == -1)
238 		fatalx("failed to open log files");
239 	return (0);
240 }
241 
242 int
243 logger_log(struct imsg *imsg)
244 {
245 	char			*logline;
246 	uint32_t		 id;
247 	struct server_config	*srv_conf;
248 	struct log_file		*log;
249 
250 	IMSG_SIZE_CHECK(imsg, &id);
251 	memcpy(&id, imsg->data, sizeof(id));
252 
253 	if ((srv_conf = serverconfig_byid(id)) == NULL)
254 		fatalx("invalid logging requestr");
255 
256 	if (imsg->hdr.type == IMSG_LOG_ACCESS)
257 		log = srv_conf->logaccess;
258 	else
259 		log = srv_conf->logerror;
260 
261 	if (log == NULL || log->log_fd == -1) {
262 		log_warnx("log file %s not opened", log ? log->log_name : "");
263 		return (0);
264 	}
265 
266 	/* XXX get_string() would sanitize the string, but add a malloc */
267 	logline = (char *)imsg->data + sizeof(id);
268 
269 	/* For debug output */
270 	log_debug("%s", logline);
271 
272 	if (dprintf(log->log_fd, "%s\n", logline) == -1) {
273 		if (logger_start() == -1)
274 			return (-1);
275 	}
276 
277 	return (0);
278 }
279 
280 int
281 logger_dispatch_parent(int fd, struct privsep_proc *p, struct imsg *imsg)
282 {
283 	switch (imsg->hdr.type) {
284 	case IMSG_CFG_SERVER:
285 		config_getserver(httpd_env, imsg);
286 		break;
287 	case IMSG_CFG_DONE:
288 		config_getcfg(httpd_env, imsg);
289 		break;
290 	case IMSG_CTL_START:
291 	case IMSG_CTL_REOPEN:
292 		logger_start();
293 		break;
294 	case IMSG_CTL_RESET:
295 		config_getreset(httpd_env, imsg);
296 		break;
297 	case IMSG_LOG_OPEN:
298 		return (logger_open_fd(imsg));
299 	default:
300 		return (-1);
301 	}
302 
303 	return (0);
304 }
305 
306 int
307 logger_dispatch_server(int fd, struct privsep_proc *p, struct imsg *imsg)
308 {
309 	switch (imsg->hdr.type) {
310 	case IMSG_LOG_ACCESS:
311 	case IMSG_LOG_ERROR:
312 		logger_log(imsg);
313 		break;
314 	default:
315 		return (-1);
316 	}
317 
318 	return (0);
319 }
320