diff options
Diffstat (limited to 'src/spawn-fcgi.c')
-rw-r--r-- | src/spawn-fcgi.c | 431 |
1 files changed, 431 insertions, 0 deletions
diff --git a/src/spawn-fcgi.c b/src/spawn-fcgi.c new file mode 100644 index 0000000..99fc31a --- /dev/null +++ b/src/spawn-fcgi.c @@ -0,0 +1,431 @@ +#include <sys/types.h> +#include <sys/time.h> +#include <sys/stat.h> + +#include <stdlib.h> +#include <string.h> +#include <errno.h> +#include <stdio.h> +#include <unistd.h> +#include <fcntl.h> + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + + +#ifdef HAVE_PWD_H +#include <grp.h> +#include <pwd.h> +#endif + +#ifdef HAVE_GETOPT_H +#include <getopt.h> +#endif + +#define FCGI_LISTENSOCK_FILENO 0 + +#ifndef UNIX_PATH_MAX +# define UNIX_PATH_MAX 108 +#endif + +#include "sys-socket.h" + +#ifdef HAVE_SYS_WAIT_H +#include <sys/wait.h> +#endif + +/* for solaris 2.5 and netbsd 1.3.x */ +#ifndef HAVE_SOCKLEN_T +typedef int socklen_t; +#endif + +#ifdef HAVE_SYS_UN_H +int fcgi_spawn_connection(char *appPath, unsigned short port, const char *unixsocket, int child_count, int pid_fd, int nofork) { + int fcgi_fd; + int socket_type, status; + struct timeval tv = { 0, 100 * 1000 }; + + struct sockaddr_un fcgi_addr_un; + struct sockaddr_in fcgi_addr_in; + struct sockaddr *fcgi_addr; + + socklen_t servlen; + + if (child_count < 2) { + child_count = 5; + } + + if (child_count > 256) { + child_count = 256; + } + + + if (unixsocket) { + memset(&fcgi_addr, 0, sizeof(fcgi_addr)); + + fcgi_addr_un.sun_family = AF_UNIX; + strcpy(fcgi_addr_un.sun_path, unixsocket); + +#ifdef SUN_LEN + servlen = SUN_LEN(&fcgi_addr_un); +#else + /* stevens says: */ + servlen = strlen(fcgi_addr_un.sun_path) + sizeof(fcgi_addr_un.sun_family); +#endif + socket_type = AF_UNIX; + fcgi_addr = (struct sockaddr *) &fcgi_addr_un; + } else { + fcgi_addr_in.sin_family = AF_INET; + fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); + fcgi_addr_in.sin_port = htons(port); + servlen = sizeof(fcgi_addr_in); + + socket_type = AF_INET; + fcgi_addr = (struct sockaddr *) &fcgi_addr_in; + } + + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + fprintf(stderr, "%s.%d\n", + __FILE__, __LINE__); + return -1; + } + + if (-1 == connect(fcgi_fd, fcgi_addr, servlen)) { + /* server is not up, spawn in */ + pid_t child; + int val; + + if (unixsocket) unlink(unixsocket); + + close(fcgi_fd); + + /* reopen socket */ + if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + fprintf(stderr, "%s.%d\n", + __FILE__, __LINE__); + return -1; + } + + val = 1; + if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { + fprintf(stderr, "%s.%d\n", + __FILE__, __LINE__); + return -1; + } + + /* create socket */ + if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) { + fprintf(stderr, "%s.%d: bind failed: %s\n", + __FILE__, __LINE__, + strerror(errno)); + return -1; + } + + if (-1 == listen(fcgi_fd, 1024)) { + fprintf(stderr, "%s.%d: fd = -1\n", + __FILE__, __LINE__); + return -1; + } + + if (!nofork) { + child = fork(); + } else { + child = 0; + } + + switch (child) { + case 0: { + char cgi_childs[64]; + char *b; + + int i = 0; + + /* is save as we limit to 256 childs */ + sprintf(cgi_childs, "PHP_FCGI_CHILDREN=%d", child_count); + + if(fcgi_fd != FCGI_LISTENSOCK_FILENO) { + close(FCGI_LISTENSOCK_FILENO); + dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO); + close(fcgi_fd); + } + + /* we don't need the client socket */ + for (i = 3; i < 256; i++) { + close(i); + } + + /* create environment */ + + putenv(cgi_childs); + + /* fork and replace shell */ + b = malloc(strlen("exec ") + strlen(appPath) + 1); + strcpy(b, "exec "); + strcat(b, appPath); + + /* exec the cgi */ + execl("/bin/sh", "sh", "-c", b, NULL); + + exit(errno); + + break; + } + case -1: + /* error */ + break; + default: + /* father */ + + /* wait */ + select(0, NULL, NULL, NULL, &tv); + + switch (waitpid(child, &status, WNOHANG)) { + case 0: + fprintf(stderr, "%s.%d: child spawned successfully: PID: %d\n", + __FILE__, __LINE__, + child); + + /* write pid file */ + if (pid_fd != -1) { + /* assume a 32bit pid_t */ + char pidbuf[12]; + + snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child); + + write(pid_fd, pidbuf, strlen(pidbuf)); + close(pid_fd); + pid_fd = -1; + } + + break; + case -1: + break; + default: + if (WIFEXITED(status)) { + fprintf(stderr, "%s.%d: child exited with: %d, %s\n", + __FILE__, __LINE__, + WEXITSTATUS(status), strerror(WEXITSTATUS(status))); + } else if (WIFSIGNALED(status)) { + fprintf(stderr, "%s.%d: child signaled: %d\n", + __FILE__, __LINE__, + WTERMSIG(status)); + } else { + fprintf(stderr, "%s.%d: child died somehow: %d\n", + __FILE__, __LINE__, + status); + } + } + + break; + } + } else { + fprintf(stderr, "%s.%d: socket is already used, can't spawn\n", + __FILE__, __LINE__); + return -1; + } + + close(fcgi_fd); + + return 0; +} + + +void show_version () { + char *b = "spawn-fcgi" "-" PACKAGE_VERSION \ +" - spawns fastcgi processes\n" +; + write(1, b, strlen(b)); +} + +void show_help () { + char *b = "spawn-fcgi" "-" PACKAGE_VERSION \ +" - spawns fastcgi processes\n" \ +"usage:\n" \ +" -f <fcgiapp> filename of the fcgi-application\n" \ +" -p <port> bind to tcp-port\n" \ +" -s <path> bind to unix-domain socket\n" \ +" -C <childs> (PHP only) numbers of childs to spawn (default 5)\n" \ +" -P <path> name of PID-file for spawed process\n" \ +" -n no fork (for daemontools)\n" \ +" -v show version\n" \ +" -h show this help\n" \ +"(root only)\n" \ +" -c <dir> chroot to directory\n" \ +" -u <user> change to user-id\n" \ +" -g <group> change to group-id\n" \ +; + write(1, b, strlen(b)); +} + + +int main(int argc, char **argv) { + char *fcgi_app = NULL, *changeroot = NULL, *username = NULL, + *groupname = NULL, *unixsocket = NULL, *pid_file = NULL; + unsigned short port = 0; + int child_count = 5; + int i_am_root, o; + int pid_fd = -1; + int nofork = 0; + + i_am_root = (getuid() == 0); + + while(-1 != (o = getopt(argc, argv, "c:f:g:hnp:u:vC:s:P:"))) { + switch(o) { + case 'f': fcgi_app = optarg; break; + case 'p': port = strtol(optarg, NULL, 10);/* port */ break; + case 'C': child_count = strtol(optarg, NULL, 10);/* */ break; + case 's': unixsocket = optarg; /* unix-domain socket */ break; + case 'c': if (i_am_root) { changeroot = optarg; }/* chroot() */ break; + case 'u': if (i_am_root) { username = optarg; } /* set user */ break; + case 'g': if (i_am_root) { groupname = optarg; } /* set group */ break; + case 'n': nofork = 1; break; + case 'P': pid_file = optarg; /* PID file */ break; + case 'v': show_version(); return 0; + case 'h': show_help(); return 0; + default: + show_help(); + return -1; + } + } + + if (fcgi_app == NULL || (port == 0 && unixsocket == NULL)) { + show_help(); + return -1; + } + + if (unixsocket && port) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "either a unix domain socket or a tcp-port, but not both\n"); + + return -1; + } + + if (unixsocket && strlen(unixsocket) > UNIX_PATH_MAX - 1) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "path of the unix socket is too long\n"); + + return -1; + } + + /* UID handling */ + if (!i_am_root && (geteuid() == 0 || getegid() == 0)) { + /* we are setuid-root */ + + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "Are you nuts ? Don't apply a SUID bit to this binary\n"); + + return -1; + } + + if (pid_file && + (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_EXCL | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)))) { + struct stat st; + if (errno != EEXIST) { + fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + return -1; + } + + /* ok, file exists */ + + if (0 != stat(pid_file, &st)) { + fprintf(stderr, "%s.%d: stating pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + return -1; + } + + /* is it a regular file ? */ + + if (!S_ISREG(st.st_mode)) { + fprintf(stderr, "%s.%d: pid-file exists and isn't regular file: '%s'\n", + __FILE__, __LINE__, + pid_file); + + return -1; + } + + if (-1 == (pid_fd = open(pid_file, O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH))) { + fprintf(stderr, "%s.%d: opening pid-file '%s' failed: %s\n", + __FILE__, __LINE__, + pid_file, strerror(errno)); + + return -1; + } + } + + if (i_am_root) { + struct group *grp = NULL; + struct passwd *pwd = NULL; + + /* set user and group */ + + if (username) { + if (NULL == (pwd = getpwnam(username))) { + fprintf(stderr, "%s.%d: %s, %s\n", + __FILE__, __LINE__, + "can't find username", username); + return -1; + } + + if (pwd->pw_uid == 0) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "I will not set uid to 0\n"); + return -1; + } + } + + if (groupname) { + if (NULL == (grp = getgrnam(groupname))) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "can't find groupname", + groupname); + return -1; + } + if (grp->gr_gid == 0) { + fprintf(stderr, "%s.%d: %s\n", + __FILE__, __LINE__, + "I will not set gid to 0\n"); + return -1; + } + } + + if (changeroot) { + if (-1 == chroot(changeroot)) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "chroot failed: ", strerror(errno)); + return -1; + } + if (-1 == chdir("/")) { + fprintf(stderr, "%s.%d: %s %s\n", + __FILE__, __LINE__, + "chdir failed: ", strerror(errno)); + return -1; + } + } + + /* drop root privs */ + if (groupname) { + setgid(grp->gr_gid); + setgroups(0, NULL); + } + if (username) setuid(pwd->pw_uid); + } + + return fcgi_spawn_connection(fcgi_app, port, unixsocket, child_count, pid_fd, nofork); +} +#else +int main() { + return -1; +} +#endif |