diff options
| author | Sam Varshavchik | 2013-08-19 16:39:41 -0400 |
|---|---|---|
| committer | Sam Varshavchik | 2013-08-25 14:43:51 -0400 |
| commit | 9c45d9ad13fdf439d44d7443ae75da15ea0223ed (patch) | |
| tree | 7a81a04cb51efb078ee350859a64be2ebc6b8813 /cgi/cgidaemond.c | |
| parent | a9520698b770168d1f33d6301463bb70a19655ec (diff) | |
| download | courier-libs-9c45d9ad13fdf439d44d7443ae75da15ea0223ed.tar.bz2 | |
Initial checkin
Imported from subversion report, converted to git. Updated all paths in
scripts and makefiles, reflecting the new directory hierarchy.
Diffstat (limited to 'cgi/cgidaemond.c')
| -rw-r--r-- | cgi/cgidaemond.c | 484 |
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; +} |
