1 /*	$NetBSD: daemon-bozo.c,v 1.22 2020/10/15 04:21:53 mrg Exp $	*/
2 
3 /*	$eterna: daemon-bozo.c,v 1.24 2011/11/18 09:21:15 mrg Exp $	*/
4 
5 /*
6  * Copyright (c) 1997-2019 Matthew R. Green
7  * All rights reserved.
8  *
9  * Redistribution and use in source and binary forms, with or without
10  * modification, are permitted provided that the following conditions
11  * are met:
12  * 1. Redistributions of source code must retain the above copyright
13  *    notice, this list of conditions and the following disclaimer.
14  * 2. Redistributions in binary form must reproduce the above copyright
15  *    notice, this list of conditions and the following disclaimer and
16  *    dedication in the documentation and/or other materials provided
17  *    with the distribution.
18  *
19  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
20  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
21  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
22  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
23  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
24  * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
26  * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
28  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
29  * SUCH DAMAGE.
30  *
31  */
32 
33 /* this code implements daemon mode for bozohttpd */
34 
35 #ifndef NO_DAEMON_MODE
36 
37 #include <sys/param.h>
38 #include <sys/socket.h>
39 #include <sys/wait.h>
40 
41 #include <netinet/in.h>
42 
43 #include <assert.h>
44 #include <errno.h>
45 #include <netdb.h>
46 #include <poll.h>
47 #include <stdio.h>
48 #include <stdlib.h>
49 #include <string.h>
50 #include <unistd.h>
51 
52 #include "bozohttpd.h"
53 
54 static	void	sigchild(int);	/* SIGCHLD handler */
55 
56 #ifndef POLLRDNORM
57 #define POLLRDNORM 0
58 #endif
59 #ifndef POLLRDBAND
60 #define POLLRDBAND 0
61 #endif
62 #ifndef INFTIM
63 #define INFTIM -1
64 #endif
65 #ifndef USE_ARG
66 #define USE_ARG(x)	/*LINTED*/(void)&(x)
67 #endif
68 
69 static const char* pidfile_path = NULL;
70 static pid_t pidfile_pid = 0;
71 
72 static void
sigchild(int signo)73 sigchild(int signo)
74 {
75 	USE_ARG(signo);
76 	while (waitpid(-1, NULL, WNOHANG) > 0)
77 		/* nothing */;
78 }
79 
80 /* Signal handler to exit in a controlled manner.  This ensures that
81  * any atexit(3) handlers are properly executed. */
82 BOZO_DEAD static void
controlled_exit(int signo)83 controlled_exit(int signo)
84 {
85 	USE_ARG(signo);
86 	exit(EXIT_SUCCESS);
87 }
88 
89 static void
remove_pidfile(void)90 remove_pidfile(void)
91 {
92 
93 	if (pidfile_path != NULL && pidfile_pid == getpid()) {
94 		(void)unlink(pidfile_path);
95 		pidfile_path = NULL;
96 	}
97 }
98 
99 static void
create_pidfile(bozohttpd_t * httpd)100 create_pidfile(bozohttpd_t *httpd)
101 {
102 	FILE *file;
103 
104 	assert(pidfile_path == NULL);
105 
106 	if (httpd->pidfile == NULL)
107 		return;
108 
109 	if (atexit(remove_pidfile) == -1)
110 		bozoerr(httpd, 1, "Failed to install pidfile handler");
111 
112 	if ((file = fopen(httpd->pidfile, "w")) == NULL)
113 		bozoerr(httpd, 1, "Failed to create pidfile '%s'",
114 		    httpd->pidfile);
115 	(void)fprintf(file, "%d\n", getpid());
116 	(void)fclose(file);
117 
118 	pidfile_path = httpd->pidfile;
119 	pidfile_pid = getpid();
120 
121 	debug((httpd, DEBUG_FAT, "Created pid file '%s' for pid %d",
122 	    pidfile_path, pidfile_pid));
123 }
124 
125 void
bozo_daemon_init(bozohttpd_t * httpd)126 bozo_daemon_init(bozohttpd_t *httpd)
127 {
128 	struct addrinfo h, *r, *r0;
129 	const char	*portnum;
130 	int e, i, on = 1;
131 
132 	if (!httpd->background && !httpd->foreground)
133 		return;
134 
135 	if (!httpd->background)
136 		httpd->background = 1;
137 
138 	portnum = (httpd->bindport) ? httpd->bindport : "http";
139 
140 	memset(&h, 0, sizeof(h));
141 	h.ai_family = PF_UNSPEC;
142 	h.ai_socktype = SOCK_STREAM;
143 	h.ai_flags = AI_PASSIVE;
144 	e = getaddrinfo(httpd->bindaddress, portnum, &h, &r0);
145 	if (e)
146 		bozoerr(httpd, 1, "getaddrinfo([%s]:%s): %s",
147 		    httpd->bindaddress ? httpd->bindaddress : "*",
148 		    portnum, gai_strerror(e));
149 	for (r = r0; r != NULL; r = r->ai_next)
150 		httpd->nsock++;
151 	httpd->sock = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->sock));
152 	httpd->fds = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->fds));
153 	for (i = 0, r = r0; r != NULL; r = r->ai_next) {
154 		httpd->sock[i] = socket(r->ai_family, SOCK_STREAM, 0);
155 		if (httpd->sock[i] == -1)
156 			continue;
157 		if (setsockopt(httpd->sock[i], SOL_SOCKET, SO_REUSEADDR, &on,
158 		    sizeof(on)) == -1)
159 			bozowarn(httpd, "setsockopt SO_REUSEADDR: %s",
160 			    strerror(errno));
161 		if (bind(httpd->sock[i], r->ai_addr, r->ai_addrlen) == -1)
162 			continue;
163 		if (listen(httpd->sock[i], SOMAXCONN) == -1)
164 			continue;
165 		httpd->fds[i].events = POLLIN | POLLPRI | POLLRDNORM |
166 				POLLRDBAND | POLLERR;
167 		httpd->fds[i].fd = httpd->sock[i];
168 		i++;
169 	}
170 	if (i == 0)
171 		bozoerr(httpd, 1, "could not find any addresses to bind");
172 	httpd->nsock = i;
173 	freeaddrinfo(r0);
174 
175 	if (httpd->foreground == 0)
176 		daemon(1, 0);
177 
178 	create_pidfile(httpd);
179 
180 	bozowarn(httpd, "started in daemon mode as `%s' port `%s' root `%s'",
181 	    httpd->virthostname, portnum, httpd->slashdir);
182 
183 	signal(SIGHUP, controlled_exit);
184 	signal(SIGINT, controlled_exit);
185 	signal(SIGTERM, controlled_exit);
186 
187 	signal(SIGCHLD, sigchild);
188 }
189 
190 void
bozo_daemon_closefds(bozohttpd_t * httpd)191 bozo_daemon_closefds(bozohttpd_t *httpd)
192 {
193 	int i;
194 
195 	for (i = 0; i < httpd->nsock; i++)
196 		close(httpd->sock[i]);
197 }
198 
199 static void
daemon_runchild(bozohttpd_t * httpd,int fd)200 daemon_runchild(bozohttpd_t *httpd, int fd)
201 {
202 	httpd->request_times++;
203 
204 	/* setup stdin/stdout/stderr */
205 	dup2(fd, 0);
206 	dup2(fd, 1);
207 	/*dup2(fd, 2);*/
208 	close(fd);
209 }
210 
211 static int
daemon_poll_err(bozohttpd_t * httpd,int idx)212 daemon_poll_err(bozohttpd_t *httpd, int idx)
213 {
214 	if ((httpd->fds[idx].revents & (POLLNVAL|POLLERR|POLLHUP)) == 0)
215 		return 0;
216 
217 	bozowarn(httpd, "poll on fd %d pid %d revents %d: %s",
218 	    httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents,
219 	    strerror(errno));
220 	bozowarn(httpd, "nsock = %d", httpd->nsock);
221 	close(httpd->sock[idx]);
222 	httpd->nsock--;
223 	bozowarn(httpd, "nsock now = %d", httpd->nsock);
224 	/* no sockets left */
225 	if (httpd->nsock == 0)
226 		exit(0);
227 	/* last socket closed is the easy case */
228 	if (httpd->nsock != idx) {
229 		memmove(&httpd->fds[idx], &httpd->fds[idx+1],
230 			(httpd->nsock - idx) * sizeof(*httpd->fds));
231 		memmove(&httpd->sock[idx], &httpd->sock[idx+1],
232 			(httpd->nsock - idx) * sizeof(*httpd->sock));
233 	}
234 
235 	return 1;
236 }
237 
238 /*
239  * the parent never returns from this function, only children that
240  * are ready to run... XXXMRG - still true in fork-lesser bozo?
241  */
242 int
bozo_daemon_fork(bozohttpd_t * httpd)243 bozo_daemon_fork(bozohttpd_t *httpd)
244 {
245 	int i;
246 
247 	debug((httpd, DEBUG_FAT, "%s: pid %u request_times %d",
248 		__func__, getpid(),
249 		httpd->request_times));
250 	/* if we've handled 5 files, exit and let someone else work */
251 	if (httpd->request_times > 5 ||
252 	    (httpd->background == 2 && httpd->request_times > 0))
253 		_exit(0);
254 
255 #if 1
256 	if (httpd->request_times > 0)
257 		_exit(0);
258 #endif
259 
260 	while (httpd->background) {
261 		struct	sockaddr_storage ss;
262 		socklen_t slen;
263 		int fd;
264 
265 		if (httpd->nsock == 0)
266 			exit(0);
267 
268 		/*
269 		 * wait for a connection, then fork() and return NULL in
270 		 * the parent, who will come back here waiting for another
271 		 * connection.  read the request in in the child, and return
272 		 * it, for processing.
273 		 */
274 again:
275 		if (poll(httpd->fds, (unsigned)httpd->nsock, INFTIM) == -1) {
276 			/* fail on programmer errors */
277 			if (errno == EFAULT ||
278 			    errno == EINVAL)
279 				bozoerr(httpd, 1, "poll: %s",
280 					strerror(errno));
281 
282 			/* sleep on some temporary kernel failures */
283 			if (errno == ENOMEM ||
284 			    errno == EAGAIN)
285 				sleep(1);
286 
287 			goto again;
288 		}
289 
290 		for (i = 0; i < httpd->nsock; i++) {
291 			if (daemon_poll_err(httpd, i))
292 				break;
293 			if (httpd->fds[i].revents == 0)
294 				continue;
295 
296 			slen = sizeof(ss);
297 			fd = accept(httpd->fds[i].fd,
298 					(struct sockaddr *)(void *)&ss, &slen);
299 			if (fd == -1) {
300 				if (errno == EFAULT ||
301 				    errno == EINVAL)
302 					bozoerr(httpd, 1, "accept: %s",
303 						strerror(errno));
304 
305 				if (errno == ENOMEM ||
306 				    errno == EAGAIN)
307 					sleep(1);
308 
309 				continue;
310 			}
311 
312 #if 0
313 			/*
314 			 * This code doesn't work.  It interacts very poorly
315 			 * with ~user translation and needs to be fixed.
316 			 */
317 			if (httpd->request_times > 0) {
318 				daemon_runchild(httpd, fd);
319 				return 0;
320 			}
321 #endif
322 
323 			switch (fork()) {
324 			case -1: /* eep, failure */
325 				bozowarn(httpd, "fork() failed, sleeping for "
326 					"10 seconds: %s", strerror(errno));
327 				close(fd);
328 				sleep(10);
329 				break;
330 
331 			case 0: /* child */
332 				daemon_runchild(httpd, fd);
333 				return 0;
334 
335 			default: /* parent */
336 				close(fd);
337 				break;
338 			}
339 		}
340 	}
341 	return 0;
342 }
343 
344 #endif /* NO_DAEMON_MODE */
345