pax_global_header00006660000000000000000000000064147606614730014530gustar00rootroot0000000000000052 comment=c14c87f9f6fd3bf3d170ad1b02bc5acd781350ef spawn-fcgi-spawn-fcgi-1.6.6/000077500000000000000000000000001476066147300156345ustar00rootroot00000000000000spawn-fcgi-spawn-fcgi-1.6.6/AUTHORS000066400000000000000000000001041476066147300166770ustar00rootroot00000000000000jan kneschke stefan bühler spawn-fcgi-spawn-fcgi-1.6.6/COPYING000066400000000000000000000027371476066147300167000ustar00rootroot00000000000000 Copyright (c) 2004, Jan Kneschke, incremental All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - Neither the name of the 'incremental' nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. spawn-fcgi-spawn-fcgi-1.6.6/NEWS000066400000000000000000000046761476066147300163500ustar00rootroot00000000000000 ==== NEWS ==== - 1.6.6 - 2025-03-01 * Use meson instead of autotools and cmake * Simplify/reduce configure checks and #ifdefs - 1.6.5 - 2022-12-21 * Configure script cleanups (fixing detection of IPv6) * Update links and email addresses - 1.6.4 - 2014-06-05 * Use octal mode for -M (patch by dfjoerg) * Add -b backlog option (fixes #2422, patch by aschmitz) * Restrict Unix socket file ownership by default to ug=rw * Add example apparmor spawn-fcgi abstraction * Use autoreconf instead of calling tools manually * Add more flags to extra-warning flags * Check return values of setuid, setgid, setgroups, initgroups, write * Check whether compiler supports wanted CFLAGS (fixes #2235) * Fix resource leaks in failure cases (found with coverity) - 1.6.3 - 2009-09-23 * Fix unix socket mode change to work without specifying user/group for socket * Add some ./run script examples for use with daemontools or runit * Fix Invalid Argument in chmod if mode=-1 (fixes #2033) * Add deprecated and /bin/sh info for -f option; wrap syntax output (fixes #2044) * Add run script examples in automake dist build - 1.6.2 - 2009-04-18 * Add homepage to README * Add IPv6 support * Fix problems with usernames starting with a digit and non-existent uids; add warning if only user privs are dropped. (fixes #1959) * Add check to link against socket/nsl if needed (fixes #1960) * List IPv6 as feature after the version if it is supported - 1.6.1 - 2009-03-29 * Add build date to show-version * Added options to chown/chmod the socket and to create the socket before chroot() (fixes #1906) * Updated man page * Add proper SUID bit detection * Added option to change the directory before spawning (fixes #1847) - 1.6.0 - 2009-02-28 * Separated spawn-fcgi from lighttpd * Remove limits for php children; per default the PHP_FCGI_CHILDREN var is not changed (php defaults to no children, one worker) * Modified the log messages format (more details on errors, no source line) * Only try to connect to unix socket (not tcp) before spawning (fixes again #1575) * Only disconnect from terminal in fork mode (keep stderr/stdout open in nofork mode) * Allow numerical user and group ids for -u/-g (fixes #1141) * Ignore pid-file option in no-fork mode (instead of producing empty file) * Fix error handling for unix-socket-connect test * Man page update * Use header include order from 1.4.x * Fix segfault due to uninitialized var spawn-fcgi-spawn-fcgi-1.6.6/README.md000066400000000000000000000013171476066147300171150ustar00rootroot00000000000000 # spawn-fcgi **authors:** Jan Kneschke, Stefan Bühler **homepage:** https://redmine.lighttpd.net/projects/spawn-fcgi **abstract:** spawn-fcgi is used to spawn FastCGI applications ## Features - binds to IPv4/IPv6 and Unix domain sockets - supports privilege separation: chmod/chown socket, drop to uid/gid - supports chroot - supports daemontools supervise ## Build [meson](https://mesonbuild.com/) is required to build. Setup a build directory `build`: meson setup build --prefix /usr/local Compile it: meson compile -C build Install: meson install -C build ### Usage See man page, e.g. [rendered](https://manpages.debian.org/unstable/spawn-fcgi/spawn-fcgi.1.en.html) for debian unstable. spawn-fcgi-spawn-fcgi-1.6.6/doc/000077500000000000000000000000001476066147300164015ustar00rootroot00000000000000spawn-fcgi-spawn-fcgi-1.6.6/doc/apparmor.d-abstractions-spawn-fcgi000066400000000000000000000024621476066147300251210ustar00rootroot00000000000000# /etc/apparmor.d/abstractions/spawn-fcgi # # a (nested) spawn-fcgi profile should include this abstraction # and a rule to execute the FastCGI application itself # # Example for runit (or daemontools) service "foo" starting php: # # #include # /etc/sv/foo/run { # #include # /bin/dash ix, # /etc/sv/foo/run r, # # # spawn-fcgi + alternatives handling in debian # /usr/bin/spawn-fcgi* px -> /etc/sv/foo/run//spawn-fcgi, # # profile spawn-fcgi { # #include # /usr/bin/php5-cgi px -> /etc/sv/foo/run//php, # } # # profile php { # #include # #include # /var/www/** r, # } # } #include #include capability net_bind_service, capability setgid, capability setuid, capability chown, capability dac_override, network inet stream, network inet6 stream, network inet dgram, network inet6 dgram, # if the binary is compiled with hardening options it might try to make a # previously writable mmapped area readonly (RELRO, mprotect PROT_READ), which # requires additional permissions in AppArmor. # more permissions -> more secure, obviously. # again match standard location + debian alternatives: /usr/bin/spawn-fcgi* r, /{,var/}run/*.sock rw, spawn-fcgi-spawn-fcgi-1.6.6/doc/run-generic000066400000000000000000000020651476066147300205450ustar00rootroot00000000000000#!/bin/bash # Use this as a run script with daemontools or runit ## ABSOLUTE path to the spawn-fcgi binary SPAWNFCGI="/usr/bin/spawn-fcgi" ## ABSOLUTE path to the FastCGI application (php-cgi, dispatch.fcgi, ...) FCGIPROGRAM="/usr/bin/php5-cgi" ## bind to unix socket FCGISOCKET="/var/run/lighttpd/your-fcgi-app.sock" # allowed environment variables separated by spaces ALLOWED_ENV="PATH USER" ## if this script is run as root switch to the following user USERID=xxx SOCKUSERID=www-data #CHROOT=/home/www/ #RAILS_ENV="production" #export RAILS_ENV ################## no config below this line exec 2>&1 if test x$PHP_FCGI_CHILDREN = x; then PHP_FCGI_CHILDREN=4 fi ALLOWED_ENV="$ALLOWED_ENV RAILS_ENV" if test x$UID = x0; then EX="$SPAWNFCGI -n -s $FCGISOCKET -u $USERID -U $SOCKUSERID -C $PHP_FCGI_CHILDREN -- $FCGIPROGRAM" else EX="$SPAWNFCGI -n -s $FCGISOCKET -C $PHP_FCGI_CHILDREN -- $FCGIPROGRAM" fi # copy the allowed environment variables E= for i in $ALLOWED_ENV; do E="$E $i=${!i}" done # clean environment and set up a new one exec env - $E $EX spawn-fcgi-spawn-fcgi-1.6.6/doc/run-php000066400000000000000000000005221476066147300177140ustar00rootroot00000000000000#!/bin/sh # Use this as a ./run script with daemontools or runit # You should replace xxx with the user you want php to run as (and www-data with the user lighty runs as) exec 2>&1 PHP_FCGI_CHILDREN=2 \ PHP_FCGI_MAX_REQUESTS=1000 \ exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/php-xxx.sock -n -u xxx -U www-data -- /usr/bin/php5-cgi spawn-fcgi-spawn-fcgi-1.6.6/doc/run-rails000066400000000000000000000006141476066147300202410ustar00rootroot00000000000000#!/bin/sh # Use this as a ./run script with daemontools or runit # You should replace xxx with the user you want rails to run as (and www-data with the user lighty runs as) # /path-to-rails should be replaced with the correct path too :) exec 2>&1 RAILS_ENV="production" \ exec /usr/bin/spawn-fcgi -n -s /var/run/lighttpd/rails-xxx.sock -u xxx -U www-data -- /path-to-rails/public/dispatch.fcgi spawn-fcgi-spawn-fcgi-1.6.6/meson.build000066400000000000000000000044371476066147300200060ustar00rootroot00000000000000project( 'spawn-fcgi', 'c', default_options: [ 'buildtype=debugoptimized', 'warning_level=3', 'c_std=gnu99', ], version: '1.6.6', license: 'BSD-3-Clause', ) compiler = meson.get_compiler('c') conf_data = configuration_data() conf_data.set_quoted('PACKAGE_VERSION', meson.project_version()) conf_data.set_quoted('PACKAGE_NAME', meson.project_name()) warn_c_args = [ '-Wshadow', '-W', '-pedantic', ] if get_option('extra-warnings') warn_c_args += [ '-Wmissing-declarations', '-Wdeclaration-after-statement', '-Wcast-align', '-Wsign-compare', '-Wnested-externs', '-Wpointer-arith', '-Wmissing-prototypes', '-Wno-pointer-sign', '-Wformat', '-Wformat-security', '-D_FORTIFY_SOURCE=2', '-fstack-protector', '--param=ssp-buffer-size=4', ] endif check_libc_functions = [ 'issetugid', ] # run compiler/env checks foreach libc_function: check_libc_functions if compiler.has_function(libc_function) conf_data.set10('HAVE_' + libc_function.underscorify().to_upper(), true) endif endforeach add_project_arguments( compiler.get_supported_arguments(warn_c_args), language: 'c' ) if compiler.has_type( 'socklen_t', prefix: '\n'.join([ '#include ', ]), ) conf_data.set10('HAVE_SOCKLEN_T', true) endif # IPv6 support is mandatory by default if get_option('ipv6') has_sockaddr_in6 = compiler.has_type( 'struct sockaddr_in6', prefix: '\n'.join([ '#include ', '#include ', '#include ', ]), ) has_inet_pton = compiler.has_function('inet_pton') if has_sockaddr_in6 and has_inet_pton conf_data.set10('USE_IPV6', true) else error('Missing struct sockaddr_in6 or inet_pton, required for IPv6 support') endif endif # solaris needs -lsocket -lnsl test_socket_inet_addr_link = ''' #include #include #include int main() { int s = socket(0, 0, 0); in_addr_t a = inet_addr(""); return 0; } ''' if compiler.links(test_socket_inet_addr_link) socket_deps = [] else socket_deps = [ compiler.find_library('socket'), compiler.find_library('nsl'), ] endif subdir('src') install_man( 'spawn-fcgi.1', ) summary( { 'ipv6': get_option('ipv6'), }, section: 'Features', ) spawn-fcgi-spawn-fcgi-1.6.6/meson_options.txt000066400000000000000000000003041476066147300212660ustar00rootroot00000000000000option('ipv6', type : 'boolean', value : true, description : 'Build with IPv6 support') option('extra-warnings', type : 'boolean', value : true, description : 'Build with extra warnings enabled') spawn-fcgi-spawn-fcgi-1.6.6/packdist.sh000077500000000000000000000056561476066147300200110ustar00rootroot00000000000000#!/bin/bash # may take one argument for prereleases like # ./packdist.sh [--nopack] -rc1-r10 builddir=distbuild tmpdir=$(mktemp --tmpdir -d packdist-XXXXXXX) trap 'rm -rf "${tmpdir}"' EXIT append="$1" force() { "$@" || { echo "Command failed: $*" exit 1 } } # summarize all changes since last release genchanges() { ( echo "h1. Changes" echo cat "${self}/NEWS" | sed "/^- ${version}/,/^-/p;d" | sed "/^- /d;/^$/d" | sed -e 's/^ \*/\*/' ) > CHANGES return 0 } self=$(dirname "$(readlink -f "$0")") force cd "${self}" if [ -d "${builddir}" ]; then # make distcheck may leave readonly files chmod u+w -R "${builddir}" rm -rf "${builddir}" fi force meson setup "${builddir}" # meson dist ensures tree isn't dirty, and also compiles/tests force meson dist -C "${builddir}" --formats gztar package=$(meson introspect "${builddir}" --projectinfo | jq -r '.descriptive_name') version=$(meson introspect "${builddir}" --projectinfo | jq -r '.version') name="${package}-${version}" if [ -z "${package}" -o -z "${version}" ]; then echo >&2 "Failed extracting package name and/or version" exit 1 fi # meson dist isn't reproducable (meson offers dist hooks and probably uses current time; # git archive uses timestamps from git commit). # so use git archive instead of meson dist, but show tardiff (should be empty; timestamps not shown). # (still use meson dist for compile/tests run) force git archive --format tar -o "${builddir}/${name}.tar" --prefix "${name}/" HEAD force gzip -n --keep "${builddir}/${name}.tar" force xz --keep "${builddir}/${name}.tar" force rm "${builddir}/${name}.tar" echo "Diff git -> meson dist tar.gz" force tardiff --modified --autoskip "${builddir}/${name}.tar.gz" "${builddir}/meson-dist/${name}.tar.gz" force cd "${builddir}" downloadbaseurl="https://download.lighttpd.net/spawn-fcgi/releases-1.6.x" if [ -n "${append}" ]; then cp "${name}.tar.xz" "${name}${append}.tar.xz" cp "${name}.tar.gz" "${name}${append}.tar.gz" name="${name}${append}" downloadbaseurl="https://download.lighttpd.net/spawn-fcgi/snapshots-1.6.x" fi force sha256sum "${name}.tar."{xz,gz} > "${name}.sha256sum" force gpg -a --output "${name}.tar.xz.asc" --detach-sig "${name}.tar.xz" force gpg -a --output "${name}.tar.gz.asc" --detach-sig "${name}.tar.gz" ( echo "h1. Downloads" echo echo "* ${downloadbaseurl}/${name}.tar.xz" echo "** GPG signature: ${downloadbaseurl}/${name}.tar.xz.asc" echo "** SHA256: @$(sha256sum ${name}.tar.xz | cut -d' ' -f1)@" echo "* SHA256 checksums: ${downloadbaseurl}/${name}.sha256sum" echo "* ${downloadbaseurl}/${name}.tar.gz" echo "** GPG signature: ${downloadbaseurl}/${name}.tar.gz.asc" echo "** SHA256: @$(sha256sum ${name}.tar.gz | cut -d' ' -f1)@" ) > DOWNLOADS force genchanges echo ------- cat CHANGES echo cat DOWNLOADS echo ------- echo "scp ${builddir}/${name}.{tar*,sha256sum} lighttpd.net:..." echo wget "${downloadbaseurl}/${name}".'{tar.xz,tar.gz,sha256sum}; sha256sum -c '${name}'.sha256sum' spawn-fcgi-spawn-fcgi-1.6.6/spawn-fcgi.1000066400000000000000000000073441476066147300177640ustar00rootroot00000000000000.TH spawn-fcgi 1 "21 November 2012" . .SH NAME . spawn-fcgi \- Spawns FastCGI processes . .SH SYNOPSIS . .B spawn-fcgi [options] [ -- [fcgi app arguments]] .P .B spawn-fcgi \-v .P .B spawn-fcgi \-h . .SH DESCRIPTION . \fIspawn-fcgi\fP is used to spawn remote and local FastCGI processes. .P While it is obviously needed to spawn remote FastCGI backends (the web server can only spawn local ones), it is recommended to spawn local backends with spawn-fcgi, too. .P Reasons why you may want to use spawn-fcgi instead of something else: .IP * 3 Privilege separation without needing a suid-binary or running a server as root. .IP * 3 You can restart your web server and the FastCGI applications without restarting the others. .IP * 3 You can run them in different chroot()s. .IP * 3 Running your FastCGI applications doesn't depend on the web server you are running, which allows for easier testing of other web servers. . .SH OPTIONS . \fIspawn-fcgi\fP accepts the following options: .TP 8 .B \-f Filename of the FastCGI application to spawn. This option is deprecated and it is recommend to always specify the application (absolute path) and its parameters after "--"; the fcgiapp parameter is directly used for the exec() call, while for starting the binary given with \-f /bin/sh is needed (which may not be available in a chroot). .IP This option is ignored if fcgiapp is given. .TP 8 .B \-d Change the current directory before spawning the application. .TP 8 .B \-a
IPv4/IPv6 address to bind to; only used if \-p is given too. Defaults to "0.0.0.0" (IPv4). .TP 8 .B \-p TCP port to bind to; you cannot combine this with the \-s option. .TP 8 .B \-s Path to the Unix domain socket to bind to; you cannot combine this with the \-p option. .TP 8 .B \-C (PHP only) Number of children to spawn by setting the PHP_FCGI_CHILDREN environment variable. Default is not to overwrite the environment variable; php will spawn no children if the variable is not set (same as setting it to 0). .TP 8 .B \-F Number of children to fork, defaults to 1. This option doesn't work with \-n, have a look at .BR multiwatch(1) if you want to supervise multiple forks on the same socket. .TP 8 .B \-b backlog to allow on the socket (default 1024). This is usually limited by the kernel too, check sysctl net.core.somaxconn (default 128) for linux. .IP backlog is the queue of connections that the kernel accepts before the userspace application sees them. .TP 8 .B \-P Name of the PID file for spawned processes (ignored in no-fork mode) .TP 8 .B \-n No forking should take place (for daemontools) .TP 8 .B \-M Change file mode of the Unix domain socket (octal integer); only used if \-s is given too. Defaults to read+write for user and group (0660) as far as the umask allows it. .TP 8 .B \-?, \-h General usage instructions .TP 8 .B \-v Shows version information and exits .P . The following options are only available if you invoke spawn-fcgi as root: .TP 8 .B \-c Chroot to specified directory; the Unix domain socket is created inside the chroot unless \-S is given. .TP 8 .B \-S Create Unix domain socket before chroot(). .TP 8 .B \-u User ID to change to. .TP 8 .B \-g Group ID to change to. Defaults to primary group of the user given for \-u. .TP 8 .B \-U Change user of the Unix domain socket, defaults to the value of \-u. (only used if \-s is given) .TP 8 .B \-G Change group of the Unix domain socket, defaults to the primary group of the user given for \-U; if \-U wasn't given, defaults to the value of \-g. (only used if \-s is given) . .SH "SEE ALSO" . .BR svc(8), .BR supervise(8), see https://cr.yp.to/daemontools.html .P .BR multiwatch(1), see https://git.lighttpd.net/lighttpd/multiwatch spawn-fcgi-spawn-fcgi-1.6.6/src/000077500000000000000000000000001476066147300164235ustar00rootroot00000000000000spawn-fcgi-spawn-fcgi-1.6.6/src/meson.build000066400000000000000000000002461476066147300205670ustar00rootroot00000000000000configure_file(output: 'config.h', configuration: conf_data) bin_worker = executable( 'spawn-fcgi', 'spawn-fcgi.c', install: true, link_with: socket_deps, ) spawn-fcgi-spawn-fcgi-1.6.6/src/spawn-fcgi.c000066400000000000000000000431051476066147300206300ustar00rootroot00000000000000#define _GNU_SOURCE #define _DEFAULT_SOURCE #define _XOPEN_SOURCE 600 #include "config.h" #include #include #include #include #include #include #include #include #include #include #include #include #define FCGI_LISTENSOCK_FILENO 0 #include #include #include #include #include #include #include #include /* for solaris 2.5 and netbsd 1.3.x */ #ifndef HAVE_SOCKLEN_T typedef int socklen_t; #endif #ifndef HAVE_ISSETUGID static int issetugid() { return (geteuid() != getuid() || getegid() != getgid()); } #endif #ifdef USE_IPV6 #define PACKAGE_FEATURES " (ipv6)" #else #define PACKAGE_FEATURES "" #endif #define PACKAGE_DESC "spawn-fcgi v" PACKAGE_VERSION PACKAGE_FEATURES " - spawns FastCGI processes\n" #define CONST_STR_LEN(s) s, sizeof(s) - 1 static mode_t read_umask(void) { mode_t mask = umask(0); umask(mask); return mask; } static ssize_t write_all(int fildes, const void *buf, size_t nbyte) { size_t rem; for (rem = nbyte; rem > 0;) { ssize_t res = write(fildes, buf, rem); if (-1 == res) { if (EINTR != errno) return res; } else { buf = res + (char const*) buf; rem -= res; } } return nbyte; } static int bind_socket(const char *addr, unsigned short port, const char *unixsocket, uid_t uid, gid_t gid, mode_t mode, int backlog) { int fcgi_fd, socket_type, val; struct sockaddr_un fcgi_addr_un; struct sockaddr_in fcgi_addr_in; #ifdef USE_IPV6 struct sockaddr_in6 fcgi_addr_in6; #endif struct sockaddr *fcgi_addr; socklen_t servlen; if (unixsocket) { memset(&fcgi_addr_un, 0, sizeof(fcgi_addr_un)); fcgi_addr_un.sun_family = AF_UNIX; /* already checked in main() */ if (strlen(unixsocket) > sizeof(fcgi_addr_un.sun_path) - 1) return -1; 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; /* check if some backend is listening on the socket * as if we delete the socket-file and rebind there will be no "socket already in use" error */ if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { fprintf(stderr, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno)); return -1; } if (0 == connect(fcgi_fd, fcgi_addr, servlen)) { fprintf(stderr, "spawn-fcgi: socket is already in use, can't spawn\n"); close(fcgi_fd); return -1; } /* cleanup previous socket if it exists */ if (-1 == unlink(unixsocket)) { switch (errno) { case ENOENT: break; default: fprintf(stderr, "spawn-fcgi: removing old socket failed: %s\n", strerror(errno)); close(fcgi_fd); return -1; } } close(fcgi_fd); } else { memset(&fcgi_addr_in, 0, sizeof(fcgi_addr_in)); fcgi_addr_in.sin_family = AF_INET; fcgi_addr_in.sin_port = htons(port); servlen = sizeof(fcgi_addr_in); socket_type = AF_INET; fcgi_addr = (struct sockaddr *) &fcgi_addr_in; #ifdef USE_IPV6 memset(&fcgi_addr_in6, 0, sizeof(fcgi_addr_in6)); fcgi_addr_in6.sin6_family = AF_INET6; fcgi_addr_in6.sin6_port = fcgi_addr_in.sin_port; #endif if (addr == NULL) { fcgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); /* while inet_pton has a better error API, prefer using the same IPv4 address * parser logic on all platforms. * -1 corresponds to 255.255.255.255 which we don't want to bind to anyway. */ } else if ((in_addr_t)(-1) != (fcgi_addr_in.sin_addr.s_addr = inet_addr(addr))) { /* nothing to do; defaulted to IPv4 socket above */ } #ifdef USE_IPV6 else if (1 == inet_pton(AF_INET6, addr, &fcgi_addr_in6.sin6_addr)) { servlen = sizeof(fcgi_addr_in6); socket_type = AF_INET6; fcgi_addr = (struct sockaddr *) &fcgi_addr_in6; } #endif else { #ifdef USE_IPV6 fprintf(stderr, "spawn-fcgi: '%s' is not a valid IP address\n", addr); #else fprintf(stderr, "spawn-fcgi: '%s' is not a valid IPv4 address\n", addr); #endif return -1; } } if (-1 == (fcgi_fd = socket(socket_type, SOCK_STREAM, 0))) { fprintf(stderr, "spawn-fcgi: couldn't create socket: %s\n", strerror(errno)); return -1; } val = 1; if (setsockopt(fcgi_fd, SOL_SOCKET, SO_REUSEADDR, &val, sizeof(val)) < 0) { fprintf(stderr, "spawn-fcgi: couldn't set SO_REUSEADDR: %s\n", strerror(errno)); close(fcgi_fd); return -1; } if (-1 == bind(fcgi_fd, fcgi_addr, servlen)) { fprintf(stderr, "spawn-fcgi: bind failed: %s\n", strerror(errno)); close(fcgi_fd); return -1; } if (unixsocket) { if (-1 == chmod(unixsocket, mode)) { fprintf(stderr, "spawn-fcgi: couldn't chmod socket: %s\n", strerror(errno)); close(fcgi_fd); unlink(unixsocket); return -1; } if (0 != uid || 0 != gid) { if (0 == uid) uid = -1; if (0 == gid) gid = -1; if (-1 == chown(unixsocket, uid, gid)) { fprintf(stderr, "spawn-fcgi: couldn't chown socket: %s\n", strerror(errno)); close(fcgi_fd); unlink(unixsocket); return -1; } } } if (-1 == listen(fcgi_fd, backlog)) { fprintf(stderr, "spawn-fcgi: listen failed: %s\n", strerror(errno)); close(fcgi_fd); if (unixsocket) unlink(unixsocket); return -1; } return fcgi_fd; } static int fcgi_spawn_connection(char *appPath, char **appArgv, int fcgi_fd, int fork_count, int child_count, int pid_fd, int nofork) { int status, rc = 0; struct timeval tv = { 0, 100 * 1000 }; pid_t child; while (fork_count-- > 0) { if (!nofork) { child = fork(); } else { child = 0; } switch (child) { case 0: { char cgi_childs[64]; int max_fd = 0; int i = 0; if (child_count >= 0) { snprintf(cgi_childs, sizeof(cgi_childs), "PHP_FCGI_CHILDREN=%d", child_count); putenv(cgi_childs); } if(fcgi_fd != FCGI_LISTENSOCK_FILENO) { close(FCGI_LISTENSOCK_FILENO); dup2(fcgi_fd, FCGI_LISTENSOCK_FILENO); close(fcgi_fd); } /* loose control terminal */ if (!nofork) { setsid(); max_fd = open("/dev/null", O_RDWR); if (-1 != max_fd) { if (max_fd != STDOUT_FILENO) dup2(max_fd, STDOUT_FILENO); if (max_fd != STDERR_FILENO) dup2(max_fd, STDERR_FILENO); if (max_fd != STDOUT_FILENO && max_fd != STDERR_FILENO) close(max_fd); } else { fprintf(stderr, "spawn-fcgi: couldn't open and redirect stdout/stderr to '/dev/null': %s\n", strerror(errno)); } } /* we don't need the client socket */ for (i = 3; i < max_fd; i++) { if (i != FCGI_LISTENSOCK_FILENO) close(i); } /* fork and replace shell */ if (appArgv) { execv(appArgv[0], appArgv); } else { char *b = malloc((sizeof("exec ") - 1) + strlen(appPath) + 1); strcpy(b, "exec "); strcat(b, appPath); /* exec the cgi */ execl("/bin/sh", "sh", "-c", b, (char *)NULL); free(b); } /* in nofork mode stderr is still open */ fprintf(stderr, "spawn-fcgi: exec failed: %s\n", strerror(errno)); exit(errno); break; } case -1: /* error */ fprintf(stderr, "spawn-fcgi: fork failed: %s\n", strerror(errno)); break; default: /* father */ /* wait */ select(0, NULL, NULL, NULL, &tv); switch (waitpid(child, &status, WNOHANG)) { case 0: fprintf(stdout, "spawn-fcgi: child spawned successfully: PID: %d\n", child); /* write pid file */ if (-1 != pid_fd) { /* assume a 32bit pid_t */ char pidbuf[12]; snprintf(pidbuf, sizeof(pidbuf) - 1, "%d", child); if (-1 == write_all(pid_fd, pidbuf, strlen(pidbuf))) { fprintf(stderr, "spawn-fcgi: writing pid file failed: %s\n", strerror(errno)); close(pid_fd); pid_fd = -1; } /* avoid eol for the last one */ if (-1 != pid_fd && fork_count != 0) { if (-1 == write_all(pid_fd, "\n", 1)) { fprintf(stderr, "spawn-fcgi: writing pid file failed: %s\n", strerror(errno)); close(pid_fd); pid_fd = -1; } } } break; case -1: break; default: if (WIFEXITED(status)) { fprintf(stderr, "spawn-fcgi: child exited with: %d\n", WEXITSTATUS(status)); rc = WEXITSTATUS(status); } else if (WIFSIGNALED(status)) { fprintf(stderr, "spawn-fcgi: child signaled: %d\n", WTERMSIG(status)); rc = 1; } else { fprintf(stderr, "spawn-fcgi: child died somehow: exit status = %d\n", status); rc = status; } } break; } } if (-1 != pid_fd) { close(pid_fd); } close(fcgi_fd); return rc; } static int find_user_group(const char *user, const char *group, uid_t *uid, gid_t *gid, const char **username) { uid_t my_uid = 0; gid_t my_gid = 0; struct passwd *my_pwd = NULL; struct group *my_grp = NULL; char *endptr = NULL; *uid = 0; *gid = 0; if (username) *username = NULL; if (user) { my_uid = strtol(user, &endptr, 10); if (my_uid <= 0 || *endptr) { if (NULL == (my_pwd = getpwnam(user))) { fprintf(stderr, "spawn-fcgi: can't find user name %s\n", user); return -1; } my_uid = my_pwd->pw_uid; if (my_uid == 0) { fprintf(stderr, "spawn-fcgi: I will not set uid to 0\n"); return -1; } if (username) *username = user; } else { my_pwd = getpwuid(my_uid); if (username && my_pwd) *username = my_pwd->pw_name; } } if (group) { my_gid = strtol(group, &endptr, 10); if (my_gid <= 0 || *endptr) { if (NULL == (my_grp = getgrnam(group))) { fprintf(stderr, "spawn-fcgi: can't find group name %s\n", group); return -1; } my_gid = my_grp->gr_gid; if (my_gid == 0) { fprintf(stderr, "spawn-fcgi: I will not set gid to 0\n"); return -1; } } } else if (my_pwd) { my_gid = my_pwd->pw_gid; if (my_gid == 0) { fprintf(stderr, "spawn-fcgi: I will not set gid to 0\n"); return -1; } } *uid = my_uid; *gid = my_gid; return 0; } static void show_version () { (void) write_all(1, CONST_STR_LEN( PACKAGE_DESC )); } static void show_help () { (void) write_all(1, CONST_STR_LEN( "Usage: spawn-fcgi [options] [-- [fcgi app arguments]]\n" \ "\n" \ PACKAGE_DESC \ "\n" \ "Options:\n" \ " -f filename of the fcgi-application (deprecated; ignored if\n" \ " is given; needs /bin/sh)\n" \ " -d chdir to directory before spawning\n" \ " -a
bind to IPv4/IPv6 address (defaults to 0.0.0.0)\n" \ " -p bind to TCP-port\n" \ " -s bind to Unix domain socket\n" \ " -M change Unix domain socket mode (octal integer, default: allow\n" \ " read+write for user and group as far as umask allows it) \n" \ " -C (PHP only) numbers of childs to spawn (default: not setting\n" \ " the PHP_FCGI_CHILDREN environment variable - PHP defaults to 0)\n" \ " -F number of children to fork (default 1)\n" \ " -b backlog to allow on the socket (default 1024)\n" \ " -P name of PID-file for spawned process (ignored in no-fork mode)\n" \ " -n no fork (for daemontools)\n" \ " -v show version\n" \ " -?, -h show this help\n" \ "(root only)\n" \ " -c chroot to directory\n" \ " -S create socket before chroot() (default is to create the socket\n" \ " in the chroot)\n" \ " -u change to user-id\n" \ " -g change to group-id (default: primary group of user if -u\n" \ " is given)\n" \ " -U change Unix domain socket owner to user-id\n" \ " -G change Unix domain socket group to group-id\n" \ )); } int main(int argc, char **argv) { char *fcgi_app = NULL, *changeroot = NULL, *username = NULL, *groupname = NULL, *unixsocket = NULL, *pid_file = NULL, *sockusername = NULL, *sockgroupname = NULL, *fcgi_dir = NULL, *addr = NULL; char **fcgi_app_argv = { NULL }; char *endptr = NULL; unsigned short port = 0; mode_t sockmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP) & ~read_umask(); int child_count = -1; int fork_count = 1; int backlog = 1024; int i_am_root, o; int pid_fd = -1; int nofork = 0; int sockbeforechroot = 0; struct sockaddr_un un; int fcgi_fd = -1; if (argc < 2) { /* no arguments given */ show_help(); return -1; } i_am_root = (getuid() == 0); while (-1 != (o = getopt(argc, argv, "c:d:f:g:?hna:p:b:u:vC:F:s:P:U:G:M:S"))) { switch(o) { case 'f': fcgi_app = optarg; break; case 'd': fcgi_dir = optarg; break; case 'a': addr = optarg;/* ip addr */ break; case 'p': port = strtol(optarg, &endptr, 10);/* port */ if (*endptr) { fprintf(stderr, "spawn-fcgi: invalid port: %u\n", (unsigned int) port); return -1; } break; case 'C': child_count = strtol(optarg, NULL, 10);/* */ break; case 'F': fork_count = strtol(optarg, NULL, 10);/* */ break; case 'b': backlog = 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 'U': if (i_am_root) { sockusername = optarg; } /* set socket user */ break; case 'G': if (i_am_root) { sockgroupname = optarg; } /* set socket group */ break; case 'S': if (i_am_root) { sockbeforechroot = 1; } /* open socket before chroot() */ break; case 'M': sockmode = strtol(optarg, NULL, 8); /* set socket mode */ break; case 'n': nofork = 1; break; case 'P': pid_file = optarg; /* PID file */ break; case 'v': show_version(); return 0; case '?': case 'h': show_help(); return 0; default: show_help(); return -1; } } if (optind < argc) { fcgi_app_argv = &argv[optind]; } if (NULL == fcgi_app && NULL == fcgi_app_argv) { fprintf(stderr, "spawn-fcgi: no FastCGI application given\n"); return -1; } if (0 == port && NULL == unixsocket) { fprintf(stderr, "spawn-fcgi: no socket given (use either -p or -s)\n"); return -1; } else if (0 != port && NULL != unixsocket) { fprintf(stderr, "spawn-fcgi: either a Unix domain socket or a TCP-port, but not both\n"); return -1; } if (unixsocket && strlen(unixsocket) > sizeof(un.sun_path) - 1) { fprintf(stderr, "spawn-fcgi: path of the Unix domain socket is too long\n"); return -1; } /* SUID handling */ if (!i_am_root && issetugid()) { fprintf(stderr, "spawn-fcgi: Are you nuts? Don't apply a SUID bit to this binary\n"); return -1; } if (nofork) pid_file = NULL; /* ignore pid file in no-fork mode */ 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, "spawn-fcgi: opening PID-file '%s' failed: %s\n", pid_file, strerror(errno)); return -1; } /* ok, file exists */ if (0 != stat(pid_file, &st)) { fprintf(stderr, "spawn-fcgi: stating PID-file '%s' failed: %s\n", pid_file, strerror(errno)); return -1; } /* is it a regular file ? */ if (!S_ISREG(st.st_mode)) { fprintf(stderr, "spawn-fcgi: PID-file exists and isn't regular file: '%s'\n", 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, "spawn-fcgi: opening PID-file '%s' failed: %s\n", pid_file, strerror(errno)); return -1; } } if (i_am_root) { uid_t uid, sockuid; gid_t gid, sockgid; const char* real_username; if (-1 == find_user_group(username, groupname, &uid, &gid, &real_username)) return -1; if (-1 == find_user_group(sockusername, sockgroupname, &sockuid, &sockgid, NULL)) return -1; if (uid != 0 && gid == 0) { fprintf(stderr, "spawn-fcgi: WARNING: couldn't find the user for uid %i and no group was specified, so only the user privileges will be dropped\n", (int) uid); } if (0 == sockuid) sockuid = uid; if (0 == sockgid) sockgid = gid; if (sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode, backlog))) return -1; /* Change group before chroot, when we have access * to /etc/group */ if (gid != 0) { if (-1 == setgid(gid)) { fprintf(stderr, "spawn-fcgi: setgid(%i) failed: %s\n", (int) gid, strerror(errno)); return -1; } if (-1 == setgroups(0, NULL)) { fprintf(stderr, "spawn-fcgi: setgroups(0, NULL) failed: %s\n", strerror(errno)); return -1; } if (real_username) { if (-1 == initgroups(real_username, gid)) { fprintf(stderr, "spawn-fcgi: initgroups('%s', %i) failed: %s\n", real_username, (int) gid, strerror(errno)); return -1; } } } if (changeroot) { if (-1 == chroot(changeroot)) { fprintf(stderr, "spawn-fcgi: chroot('%s') failed: %s\n", changeroot, strerror(errno)); return -1; } if (-1 == chdir("/")) { fprintf(stderr, "spawn-fcgi: chdir('/') failed: %s\n", strerror(errno)); return -1; } } if (!sockbeforechroot && -1 == (fcgi_fd = bind_socket(addr, port, unixsocket, sockuid, sockgid, sockmode, backlog))) return -1; /* drop root privs */ if (uid != 0) { if (-1 == setuid(uid)) { fprintf(stderr, "spawn-fcgi: setuid(%i) failed: %s\n", (int) uid, strerror(errno)); return -1; } } } else { if (-1 == (fcgi_fd = bind_socket(addr, port, unixsocket, 0, 0, sockmode, backlog))) return -1; } if (fcgi_dir && -1 == chdir(fcgi_dir)) { fprintf(stderr, "spawn-fcgi: chdir('%s') failed: %s\n", fcgi_dir, strerror(errno)); return -1; } return fcgi_spawn_connection(fcgi_app, fcgi_app_argv, fcgi_fd, fork_count, child_count, pid_fd, nofork); }