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