summaryrefslogtreecommitdiffstats
path: root/cgi/cgidaemond.c
diff options
context:
space:
mode:
Diffstat (limited to 'cgi/cgidaemond.c')
-rw-r--r--cgi/cgidaemond.c484
1 files changed, 484 insertions, 0 deletions
diff --git a/cgi/cgidaemond.c b/cgi/cgidaemond.c
new file mode 100644
index 0000000..fd928fb
--- /dev/null
+++ b/cgi/cgidaemond.c
@@ -0,0 +1,484 @@
+/*
+** Copyright 2007 Double Precision, Inc.
+** See COPYING for distribution information.
+*/
+
+/*
+*/
+#include "cgi.h"
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#if HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#if TIME_WITH_SYS_TIME
+#include <sys/time.h>
+#include <time.h>
+#else
+#if HAVE_SYS_TIME_H
+#include <sys/time.h>
+#else
+#include <time.h>
+#endif
+#endif
+#if HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#endif
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/socket.h>
+#include <sys/uio.h>
+#include <sys/un.h>
+
+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<ndaemons; i++)
+ cpipes[i]= -1;
+
+ for (i=0; i<ndaemons; i++)
+ cpipes[i]=prefork(fd, cpipes, ndaemons, termpipe, handler,
+ dummy);
+
+
+ for (;;)
+ {
+ fd_set fdr;
+ int maxfd=0;
+
+ FD_ZERO(&fdr);
+
+ for (i=0; i<ndaemons; i++)
+ {
+ if (cpipes[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<ndaemons; i++)
+ {
+ if (FD_ISSET(cpipes[i], &fdr))
+ {
+ close(cpipes[i]);
+ cpipes[i]= -1;
+ cpipes[i]=prefork(fd, cpipes,
+ ndaemons, termpipe, handler,
+ dummy);
+ }
+ }
+ }
+}
+
+/* Start a preforked process */
+
+static int prefork(int listenfd, int *allpipes, size_t ndaemons,
+ int *termpipe, void (*handler)(void *), void *dummy)
+{
+ int newpipe[2];
+ pid_t p;
+ int waitstat;
+ size_t i;
+
+ if (pipe(newpipe) < 0)
+ {
+ fprintf(stderr,
+ "CRIT: pipe failed: %s\n", strerror(errno));
+ exit(1);
+ }
+
+ while ((p=fork()) < 0)
+ {
+ fprintf(stderr,
+ "CRIT: fork failed: %s\n", strerror(errno));
+ sleep(5);
+ }
+
+ if (p) /* parent */
+ {
+ close(newpipe[1]);
+
+ /* Wait for first child process to go away */
+
+ while (wait(&waitstat) != p)
+ ;
+
+ return (newpipe[0]);
+ }
+
+ close(newpipe[0]);
+ close(termpipe[1]);
+
+ /* New child doesn't need pipes from other children */
+
+ for (i=0; i<ndaemons; i++)
+ if (allpipes[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;
+}