Home | History | Annotate | Line # | Download | only in httpd
      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
     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
     83 controlled_exit(int signo)
     84 {
     85 	USE_ARG(signo);
     86 	exit(EXIT_SUCCESS);
     87 }
     88 
     89 static 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
    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
    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
    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
    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
    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
    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