static int daemon_poll_err(bozohttpd_t *httpd, int fd, int idx) { if ((httpd->fds[idx].revents & (POLLNVAL|POLLERR|POLLHUP)) == 0) return 0; bozo_warn(httpd, "poll on fd %d pid %d revents %d: %s", httpd->fds[idx].fd, getpid(), httpd->fds[idx].revents, strerror(errno)); bozo_warn(httpd, "nsock = %d", httpd->nsock); close(httpd->sock[idx]); httpd->nsock--; bozo_warn(httpd, "nsock now = %d", httpd->nsock); /* no sockets left */ if (httpd->nsock == 0) exit(0); /* last socket closed is the easy case */ if (httpd->nsock != idx) { memmove(&httpd->fds[idx], &httpd->fds[idx+1], (httpd->nsock - idx) * sizeof(*httpd->fds)); memmove(&httpd->sock[idx], &httpd->sock[idx+1], (httpd->nsock - idx) * sizeof(*httpd->sock)); } return 1; }
/* * the parent never returns from this function, only children that * are ready to run... XXXMRG - still true in fork-lesser bozo? */ int bozo_daemon_fork(bozohttpd_t *httpd) { int i; debug((httpd, DEBUG_FAT, "%s: pid %u request_times %d", __func__, getpid(), httpd->request_times)); /* if we've handled 5 files, exit and let someone else work */ if (httpd->request_times > 5 || (httpd->background == 2 && httpd->request_times > 0)) _exit(0); #if 1 if (httpd->request_times > 0) _exit(0); #endif while (httpd->background) { struct sockaddr_storage ss; socklen_t slen; int fd; if (httpd->nsock == 0) exit(0); /* * wait for a connection, then fork() and return NULL in * the parent, who will come back here waiting for another * connection. read the request in in the child, and return * it, for processing. */ again: if (poll(httpd->fds, (unsigned)httpd->nsock, INFTIM) == -1) { /* fail on programmer errors */ if (errno == EFAULT || errno == EINVAL) bozo_err(httpd, 1, "poll: %s", strerror(errno)); /* sleep on some temporary kernel failures */ if (errno == ENOMEM || errno == EAGAIN) sleep(1); goto again; } for (i = 0; i < httpd->nsock; i++) { if (daemon_poll_err(httpd, fd, i)) break; if (httpd->fds[i].revents == 0) continue; slen = sizeof(ss); fd = accept(httpd->fds[i].fd, (struct sockaddr *)(void *)&ss, &slen); if (fd == -1) { if (errno == EFAULT || errno == EINVAL) bozo_err(httpd, 1, "accept: %s", strerror(errno)); if (errno == ENOMEM || errno == EAGAIN) sleep(1); continue; } #if 0 /* * This code doesn't work. It interacts very poorly * with ~user translation and needs to be fixed. */ if (httpd->request_times > 0) { daemon_runchild(httpd, fd); return 0; } #endif switch (fork()) { case -1: /* eep, failure */ bozo_warn(httpd, "fork() failed, sleeping for " "10 seconds: %s", strerror(errno)); close(fd); sleep(10); break; case 0: /* child */ daemon_runchild(httpd, fd); return 0; default: /* parent */ close(fd); break; } } } return 0; }
void bozo_daemon_init(bozohttpd_t *httpd) { struct addrinfo h, *r, *r0; const char *portnum; int e, i, on = 1; if (!httpd->background) return; portnum = (httpd->bindport) ? httpd->bindport : "http"; memset(&h, 0, sizeof(h)); h.ai_family = PF_UNSPEC; h.ai_socktype = SOCK_STREAM; h.ai_flags = AI_PASSIVE; e = getaddrinfo(httpd->bindaddress, portnum, &h, &r0); if (e) bozo_err(httpd, 1, "getaddrinfo([%s]:%s): %s", httpd->bindaddress ? httpd->bindaddress : "*", portnum, gai_strerror(e)); for (r = r0; r != NULL; r = r->ai_next) httpd->nsock++; httpd->sock = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->sock)); httpd->fds = bozomalloc(httpd, httpd->nsock * sizeof(*httpd->fds)); for (i = 0, r = r0; r != NULL; r = r->ai_next) { httpd->sock[i] = socket(r->ai_family, SOCK_STREAM, 0); if (httpd->sock[i] == -1) continue; if (setsockopt(httpd->sock[i], SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1) bozo_warn(httpd, "setsockopt SO_REUSEADDR: %s", strerror(errno)); if (bind(httpd->sock[i], r->ai_addr, r->ai_addrlen) == -1) continue; if (listen(httpd->sock[i], SOMAXCONN) == -1) continue; httpd->fds[i].events = POLLIN | POLLPRI | POLLRDNORM | POLLRDBAND | POLLERR; httpd->fds[i].fd = httpd->sock[i]; i++; } if (i == 0) bozo_err(httpd, 1, "could not find any addresses to bind"); httpd->nsock = i; freeaddrinfo(r0); if (httpd->foreground == 0) daemon(1, 0); create_pidfile(httpd); bozo_warn(httpd, "started in daemon mode as `%s' port `%s' root `%s'", httpd->virthostname, portnum, httpd->slashdir); signal(SIGHUP, controlled_exit); signal(SIGINT, controlled_exit); signal(SIGTERM, controlled_exit); signal(SIGCHLD, sigchild); }
/* * bozo_user_transform does this: * - chdir's /~user/public_html * - returns the rest of the file, index.html appended if required * - returned malloced file to serve in request->hr_file, * ala transform_request(). * * transform_request() is supposed to check that we have user support * enabled. */ int bozo_user_transform(bozo_httpreq_t *request, int *isindex) { bozohttpd_t *httpd = request->hr_httpd; char c, *s, *file = NULL; struct passwd *pw; *isindex = 0; if ((s = strchr(request->hr_file + 2, '/')) != NULL) { *s++ = '\0'; c = s[strlen(s)-1]; *isindex = (c == '/' || c == '\0'); } debug((httpd, DEBUG_OBESE, "looking for user %s", request->hr_file + 2)); pw = getpwnam(request->hr_file + 2); /* fix this up immediately */ if (s) s[-1] = '/'; if (pw == NULL) { (void)bozo_http_error(httpd, 404, request, "no such user"); return 0; } debug((httpd, DEBUG_OBESE, "user %s dir %s/%s uid %d gid %d", pw->pw_name, pw->pw_dir, httpd->public_html, pw->pw_uid, pw->pw_gid)); if (chdir(pw->pw_dir) < 0) { bozo_warn(httpd, "chdir1 error: %s: %s", pw->pw_dir, strerror(errno)); (void)bozo_http_error(httpd, 404, request, "can't chdir to homedir"); return 0; } if (chdir(httpd->public_html) < 0) { bozo_warn(httpd, "chdir2 error: %s: %s", httpd->public_html, strerror(errno)); (void)bozo_http_error(httpd, 404, request, "can't chdir to public_html"); return 0; } if (s == NULL || *s == '\0') { file = bozostrdup(httpd, httpd->index_html); } else { file = bozomalloc(httpd, strlen(s) + (*isindex ? strlen(httpd->index_html) + 1 : 1)); strcpy(file, s); if (*isindex) strcat(file, httpd->index_html); } /* see transform_request() */ if (*file == '/' || strcmp(file, "..") == 0 || strstr(file, "/..") || strstr(file, "../")) { (void)bozo_http_error(httpd, 403, request, "illegal request"); free(file); return 0; } if (bozo_auth_check(request, file)) { free(file); return 0; } free(request->hr_file); request->hr_file = file; debug((httpd, DEBUG_FAT, "transform_user returning %s under %s", file, pw->pw_dir)); return 1; }