/* ** Copyright 2007 Double Precision, Inc. ** See COPYING for distribution information. */ /* */ #include "cgi.h" #include #include #include #if HAVE_UNISTD_H #include #endif #if TIME_WITH_SYS_TIME #include #include #else #if HAVE_SYS_TIME_H #include #else #include #endif #endif #if HAVE_SYS_WAIT_H #include #endif #include #include #include #include #include #include #include static int read_environ(int); static int start_daemon(const char *lockfilename); static void run_daemon(int fd, int termfd, int connfd, void (*handler)(void *), void *dummy); static void run_prefork(int fd, size_t ndaemons, void (*handler)(void *), void *dummy); void cgi_daemon(int nprocs, const char *lockfile, void (*postinit)(void *), void (*handler)(void *), void *dummy) { int fd=start_daemon(lockfile); if (postinit) (*postinit)(dummy); if (nprocs > 0) run_prefork(fd, nprocs, handler, dummy); else run_daemon(fd, -1, -1, handler, dummy); } /* Start in daemon mode. Return listening socket file descriptor */ static int start_daemon(const char *lockfile) { int fd; struct sockaddr_un skun; unlink(lockfile); fd=socket(PF_UNIX, SOCK_STREAM, 0); if (fd < 0) { perror("socket"); return (-1); } skun.sun_family=AF_UNIX; strcpy(skun.sun_path, lockfile); if (bind(fd, (const struct sockaddr *)&skun, sizeof(skun)) || listen(fd, SOMAXCONN) || chmod(skun.sun_path, 0777) || fcntl(fd, F_SETFL, O_NONBLOCK) < 0) { perror(lockfile); close(fd); return (-1); } return fd; } static int prefork(int listenfd, int *allpipes, size_t ndaemos, int *termpipe, void (*handler)(void *), void *dummy); static void run_prefork(int fd, size_t ndaemons, void (*handler)(void *), void *dummy) { int *cpipes; /* Completion pipes from preforked processes */ int termpipe[2]; /* Termination pipe to preforked processes */ size_t i; if ((cpipes=malloc(sizeof(int)*ndaemons)) == NULL) { fprintf(stderr, "CRIT: malloc failed: %s\n", strerror(errno)); exit(1); } if (pipe(termpipe) < 0) { fprintf(stderr, "CRIT: pipe failed: %s\n", strerror(errno)); exit(1); } /* Start the initial set of preforked daemons */ for (i=0; i= maxfd) maxfd=cpipes[i]+1; FD_SET(cpipes[i], &fdr); } if (select(maxfd, &fdr, NULL, NULL, NULL) <= 0) continue; /* ** When child process gets a connection, it closes its ** completion pipe, which makes the pipe selectable for ** read. */ for (i=0; i= 0) close(allpipes[i]); /* Fork once more, so that the parent process can continue */ if (fork()) exit(0); run_daemon(listenfd, termpipe[0], newpipe[1], handler, dummy); return (-1); } static void run_daemon(int fd, int termfd, int acceptedfd, void (*handler)(void *), void *dummy) { int cfd; for (;;) { fd_set fdr; pid_t p; int maxfd; FD_ZERO(&fdr); FD_SET(fd, &fdr); maxfd=fd; if (termfd >= 0) { if (termfd > maxfd) maxfd=termfd; FD_SET(termfd, &fdr); } if (select(maxfd+1, &fdr, NULL, NULL, NULL) <= 0) continue; if (termfd >= 0 && FD_ISSET(termfd, &fdr)) /* Terminate all child procs */ exit(0); if (!FD_ISSET(fd, &fdr)) continue; cfd=accept(fd, NULL, 0); if (cfd < 0) continue; if (acceptedfd >= 0) /* preforked daemon */ { if (termfd >= 0) close(termfd); close(acceptedfd); break; } p=fork(); if (p < 0) { fprintf(stderr, "CRIT: fork() failed: %s\n", strerror(errno)); continue; } if (p) { int dummy; close(cfd); while (wait(&dummy) != p) continue; continue; } /* Child forks once more, parent exits */ if (fork()) exit(0); break; } /* child */ if (fcntl(cfd, F_SETFL, 0) < 0) { fprintf(stderr, "CRIT: fcntl(): %s\n", strerror(errno)); exit(0); } close(fd); if (read_environ(cfd)) { close(0); close(1); if (dup(cfd) != 0 || dup(cfd) != 1) { fprintf(stderr, "CRIT: dup() did not work as expected\n"); exit(0); } close(cfd); } if (fcntl(0, F_SETFL, 0) < 0 || fcntl(1, F_SETFL, 0) < 0) { fprintf(stderr, "CRIT: fcntl() failed: %s\n", strerror(errno)); exit(0); } (*handler)(dummy); exit(0); } /* Read environment from the sqwebmail stub */ static void force_read(int cfd, char *p, size_t l) { int m; while (l) { m=read(cfd, p, l); if (m <= 0) { fprintf(stderr, "WARN: socket closed while reading" " environment.\n"); exit(0); } p += m; l -= m; } } /* Receive CGI environment */ static int read_environ(int cfd) { static char buf[SOCKENVIRONLEN]; size_t l; char *p; int passfd; force_read(cfd, buf, 1+sizeof(l)); memcpy(&l, buf, sizeof(l)); passfd=buf[sizeof(l)]; if (l >= sizeof(buf)) { fprintf(stderr, "WARN: invalid environment received via socket.\n"); exit(0); } alarm(10); /* Just in case - punt */ force_read(cfd, buf, l); buf[l]=0; alarm(0); /* Vet environment strings for only known good strings */ p=buf; while (p < buf+l) { if (strchr(p, '=') == NULL || !VALIDCGIVAR(p)) { fprintf(stderr, "WARN: invalid environment received" " via socket: %s\n" , p); exit(0); } putenv(p); while (*p++) ; } /* Receive file descriptors, if supported by the platform */ #if CGI_PASSFD if (passfd) { struct iovec iov; char dummy; #if CGI_PASSFD_MSGACCRIGHTS int fdbuf[2]; struct msghdr msg; #endif #if CGI_PASSFD_MSGCONTROL int fdbuf[2]; struct msghdr msg; struct cmsghdr *cmsg; char buf[CMSG_SPACE(sizeof(fdbuf))]; #endif #if CGI_PASSFD_MSGACCRIGHTS memset(&iov, 0, sizeof(iov)); msg.msg_accrights=(caddr_t)fdbuf; msg.msg_accrightslen=sizeof(fdbuf); #endif #if CGI_PASSFD_MSGCONTROL memset(&msg, 0, sizeof(msg)); msg.msg_control=buf; msg.msg_controllen=sizeof(buf); #endif msg.msg_iov=&iov; msg.msg_iovlen=1; iov.iov_base=&dummy; iov.iov_len=1; if (recvmsg(cfd, &msg, 0) <= 0) { perror("Internal error - recvmsg() failed"); exit(0); } #if CGI_PASSFD_MSGACCRIGHTS if (msg.msg_accrightslen < sizeof(fdbuf)) { perror("Internal error - malformed recvmsg()"); exit(0); } #endif #if CGI_PASSFD_MSGCONTROL if (msg.msg_controllen < sizeof(buf) || (cmsg = CMSG_FIRSTHDR(&msg))->cmsg_level != SOL_SOCKET || cmsg->cmsg_type != SCM_RIGHTS || cmsg->cmsg_len != CMSG_LEN(sizeof(fdbuf))) { perror("Internal error - malformed recvmsg()"); exit(0); } memcpy(fdbuf, CMSG_DATA(cmsg), sizeof(fdbuf)); #endif close(0); close(1); if (dup(fdbuf[0]) != 0 || dup(fdbuf[1]) != 1) fprintf(stderr, "CRIT: dup() did not work as expected in" " read_environ()\n"); close(fdbuf[0]); close(fdbuf[1]); return 0; } #endif return 1; }