aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Bühler <stbuehler@web.de>2013-10-02 18:43:47 +0200
committerStefan Bühler <stbuehler@web.de>2013-10-02 18:43:47 +0200
commitc91f0380ae898b08a30fff66d85bccd8c95e7f11 (patch)
tree364e34e3030e6468df96562b8d134c39dacc607d
downloadscgi-cgi-c91f0380ae898b08a30fff66d85bccd8c95e7f11.tar.gz
scgi-cgi-c91f0380ae898b08a30fff66d85bccd8c95e7f11.zip
initial commit
-rw-r--r--.gitignore10
-rw-r--r--CMakeLists.txt55
-rw-r--r--COPYING22
-rw-r--r--Makefile.am8
-rw-r--r--README.rst50
-rwxr-xr-xautogen.sh38
-rw-r--r--cmake/AddTargetProperties.cmake13
-rw-r--r--config.h.cmake6
-rw-r--r--configure.ac49
-rw-r--r--scgi-cgi.130
-rw-r--r--scgi-cgi.c1263
11 files changed, 1544 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..0a95137
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,10 @@
+Makefile.in
+aclocal.m4
+autom4te.cache
+compile
+config.h.in
+configure
+depcomp
+install-sh
+ltmain.sh
+missing
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..d032ed2
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,55 @@
+CMAKE_MINIMUM_REQUIRED(VERSION 2.6.0 FATAL_ERROR)
+
+cmake_policy(VERSION 2.6.0)
+
+SET(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake)
+
+INCLUDE(CheckIncludeFiles)
+INCLUDE(CheckLibraryExists)
+INCLUDE(FindPkgConfig)
+INCLUDE(AddTargetProperties)
+
+
+PROJECT(scgi-cgi C)
+SET(PACKAGE_VERSION 0.1.0)
+
+IF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+ SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build, options are: None(CMAKE_CXX_FLAGS or CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel." FORCE)
+ENDIF("${CMAKE_BUILD_TYPE}" STREQUAL "")
+
+OPTION(GCC_LIBEVENT_STATIC "link libevent static")
+
+# libevent
+pkg_check_modules (LIBEVENT REQUIRED libevent>=2.0)
+IF(GCC_LIBEVENT_STATIC)
+ SET(MYLIBEVENT_LDFLAGS -Wl,-Bstatic ${LIBEVENT_LDFLAGS} -Wl,-Bdynamic)
+ELSE(GCC_LIBEVENT_STATIC)
+ SET(MYLIBEVENT_LDFLAGS ${LIBEVENT_LDFLAGS})
+ENDIF(GCC_LIBEVENT_STATIC)
+
+
+SET(MAIN_SOURCE scgi-cgi.c)
+
+SET(PACKAGE_NAME ${CMAKE_PROJECT_NAME})
+SET(PACKAGE_VERSION ${PACKAGE_VERSION})
+CONFIGURE_FILE(${CMAKE_CURRENT_SOURCE_DIR}/config.h.cmake ${CMAKE_BINARY_DIR}/config.h ESCAPE_QUOTES)
+ADD_DEFINITIONS(-DHAVE_CONFIG_H)
+INCLUDE_DIRECTORIES(${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR})
+
+add_executable(scgi-cgi ${MAIN_SOURCE})
+
+ADD_TARGET_PROPERTIES(scgi-cgi COMPILE_FLAGS "-std=gnu99 -Wall -g -Wshadow -W -pedantic -fPIC")
+
+# libev
+TARGET_LINK_LIBRARIES(scgi-cgi ${MYLIBEVENT_LDFLAGS})
+ADD_TARGET_PROPERTIES(scgi-cgi COMPILE_FLAGS ${LIBEVENT_CFLAGS})
+
+INSTALL(TARGETS scgi-cgi DESTINATION bin)
+
+# man page
+
+SET(CMAKE_MAN_DIR "share/man" CACHE STRING
+ "Install location for man pages (relative to prefix).")
+MARK_AS_ADVANCED(CMAKE_MAN_DIR)
+
+INSTALL(FILES scgi-cgi.1 DESTINATION ${CMAKE_MAN_DIR}/man1)
diff --git a/COPYING b/COPYING
new file mode 100644
index 0000000..902847d
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,22 @@
+
+The MIT License
+
+Copyright (c) 2013 Stefan Bühler
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
diff --git a/Makefile.am b/Makefile.am
new file mode 100644
index 0000000..9aba7bf
--- /dev/null
+++ b/Makefile.am
@@ -0,0 +1,8 @@
+EXTRA_DIST=autogen.sh CMakeLists.txt config.h.cmake scgi-cgi.1 README.rst
+man1_MANS=scgi-cgi.1
+
+AM_CFLAGS=$(LIBEVENT_CFLAGS)
+scgi_cgi_LDADD=$(LIBEVENT_LIBS)
+
+bin_PROGRAMS=scgi-cgi
+scgi_cgi_SOURCES=scgi-cgi.c
diff --git a/README.rst b/README.rst
new file mode 100644
index 0000000..46a1840
--- /dev/null
+++ b/README.rst
@@ -0,0 +1,50 @@
+Description
+-----------
+
+:Homepage:
+ http://redmine.lighttpd.net/projects/scgi-cgi/wiki
+
+scgi-cgi is a SCGI application to run normal cgi applications. It doesn't
+make CGI applications faster, but it allows you to run them on a different
+host and with different user permissions (without the need for suexec).
+
+scgi-cgi is released under the `MIT license <http://git.lighttpd.net/scgi-cgi.cgi/tree/COPYING>`_
+
+Usage
+-----
+
+Examples for spawning a scgi-cgi instance with daemontools or runit::
+
+ #!/bin/sh
+ # run script
+
+ exec spawn-scgi -n -s /var/run/scgi-cgi.sock -u www-default -U www-data -- /usr/bin/scgi-cgi
+
+
+Build dependencies
+------------------
+
+* libevent (http://libevent.org/)
+* cmake or autotools (for snapshots/releases the autotool generated files are included)
+
+
+Build
+-----
+
+* snapshot/release with autotools::
+
+ ./configure
+ make
+
+* build from git: ``git clone git://git.lighttpd.net/scgi-cgi.git``
+
+ * with autotools::
+
+ ./autogen.sh
+ ./configure
+ make
+
+ * with cmake (should work with snapshots/releases too)::
+
+ cmake .
+ make
diff --git a/autogen.sh b/autogen.sh
new file mode 100755
index 0000000..bab5b24
--- /dev/null
+++ b/autogen.sh
@@ -0,0 +1,38 @@
+#!/bin/sh
+# Run this to generate all the initial makefiles, etc.
+
+if which glibtoolize >/dev/null 2>&1; then
+ LIBTOOLIZE=${LIBTOOLIZE:-glibtoolize}
+else
+ LIBTOOLIZE=${LIBTOOLIZE:-libtoolize}
+fi
+LIBTOOLIZE_FLAGS="--copy --force"
+ACLOCAL=${ACLOCAL:-aclocal}
+AUTOHEADER=${AUTOHEADER:-autoheader}
+AUTOMAKE=${AUTOMAKE:-automake}
+AUTOMAKE_FLAGS="--add-missing --copy"
+AUTOCONF=${AUTOCONF:-autoconf}
+
+ARGV0=$0
+
+set -e
+
+if [ ! -f configure.ac -o ! -f COPYING ]; then
+ echo "Doesn't look like you're in the source directory" >&2
+ exit 1
+fi
+
+run() {
+ echo "$ARGV0: running \`$@'"
+ $@
+}
+
+run rm -f aclocal.m4 ar-lib config.guess config.sub configure depcomp instal-sh ltmain.sh missing
+run rm -rf m4 autom4te.cache
+
+run $LIBTOOLIZE $LIBTOOLIZE_FLAGS
+run $ACLOCAL $ACLOCAL_FLAGS
+run $AUTOHEADER
+run $AUTOMAKE $AUTOMAKE_FLAGS
+run $AUTOCONF
+echo "Now type './configure ...' and 'make' to compile."
diff --git a/cmake/AddTargetProperties.cmake b/cmake/AddTargetProperties.cmake
new file mode 100644
index 0000000..17ee774
--- /dev/null
+++ b/cmake/AddTargetProperties.cmake
@@ -0,0 +1,13 @@
+MACRO(ADD_TARGET_PROPERTIES _target _name)
+ SET(_properties)
+ FOREACH(_prop ${ARGN})
+ SET(_properties "${_properties} ${_prop}")
+ ENDFOREACH(_prop)
+ GET_TARGET_PROPERTY(_old_properties ${_target} ${_name})
+ MESSAGE(STATUS "adding property to ${_target} ${_name}:" ${_properties})
+ IF(NOT _old_properties)
+ # in case it's NOTFOUND
+ SET(_old_properties)
+ ENDIF(NOT _old_properties)
+ SET_TARGET_PROPERTIES(${_target} PROPERTIES ${_name} "${_old_properties} ${_properties}")
+ENDMACRO(ADD_TARGET_PROPERTIES)
diff --git a/config.h.cmake b/config.h.cmake
new file mode 100644
index 0000000..69bb180
--- /dev/null
+++ b/config.h.cmake
@@ -0,0 +1,6 @@
+/*
+ CMake autogenerated config.h file. Do not edit!
+*/
+
+#define PACKAGE_NAME "${PACKAGE_NAME}"
+#define PACKAGE_VERSION "${PACKAGE_VERSION}"
diff --git a/configure.ac b/configure.ac
new file mode 100644
index 0000000..f089d42
--- /dev/null
+++ b/configure.ac
@@ -0,0 +1,49 @@
+# -*- Autoconf -*-
+# Process this file with autoconf to produce a configure script.
+
+AC_PREREQ([2.63])
+AC_INIT([scgi-cgi], [0.1.0], [lighttpd@stbuehler.de])
+AC_CONFIG_SRCDIR([scgi-cgi.c])
+AC_CONFIG_HEADERS([config.h])
+
+AM_INIT_AUTOMAKE([-Wall -Werror foreign])
+
+# Checks for programs.
+AC_PROG_CC
+AC_PROG_MAKE_SET
+
+# Checks for libraries.
+
+# libevent
+PKG_CHECK_MODULES(LIBEVENT, libevent >= 2.0, [],[AC_MSG_ERROR("libevent >= 2.0 not found")])
+
+# Checks for header files.
+AC_CHECK_HEADERS([arpa/inet.h fcntl.h stdlib.h string.h sys/socket.h unistd.h])
+
+# Checks for typedefs, structures, and compiler characteristics.
+AC_TYPE_PID_T
+AC_TYPE_SIZE_T
+
+# Checks for library functions.
+AC_FUNC_FORK
+AC_CHECK_FUNCS([dup2])
+
+# check for extra compiler options (warning options)
+if test "${GCC}" = "yes"; then
+ CFLAGS="${CFLAGS} -Wall -W -Wshadow -pedantic -std=gnu99"
+fi
+
+AC_ARG_ENABLE(extra-warnings,
+ AC_HELP_STRING([--enable-extra-warnings],[enable extra warnings (gcc specific)]),
+ [case "${enableval}" in
+ yes) extrawarnings=true ;;
+ no) extrawarnings=false ;;
+ *) AC_MSG_ERROR(bad value ${enableval} for --enable-extra-warnings) ;;
+ esac],[extrawarnings=false])
+
+if test x$extrawarnings = xtrue; then
+ CFLAGS="${CFLAGS} -g -O2 -g2 -Wall -Wmissing-declarations -Wdeclaration-after-statement -Wno-pointer-sign -Wcast-align -Winline -Wsign-compare -Wnested-externs -Wpointer-arith -Wl,--as-needed -Wformat-security"
+fi
+
+AC_CONFIG_FILES([Makefile])
+AC_OUTPUT
diff --git a/scgi-cgi.1 b/scgi-cgi.1
new file mode 100644
index 0000000..842dcc2
--- /dev/null
+++ b/scgi-cgi.1
@@ -0,0 +1,30 @@
+.TH scgi-cgi 1 "Oct 1, 2013"
+.
+.SH NAME
+.
+scgi-cgi \- a SCGI application to run cgi applications
+.
+.SH OPTIONS
+.
+.TP 8
+.B \-b <binary>
+Executable to start instead of INTERPRETER or SCRIPT_FILENAME from SCGI environment.
+.TP 8
+.B \-c <number>
+Maximum number of connections (default 16)
+.TP 8
+.B \-v
+Shows version information and exits
+.
+.SH DESCRIPTION
+scgi-cgi is a SCGI application to run normal cgi applications. It doesn't
+make CGI applications faster, but it allows you to run them on a different
+host and with different user permissions (without the need for suexec).
+.P
+For running you probably want spawn-fcgi (http://redmine.lighttpd.net/projects/spawn-fcgi)
+.SH EXAMPLE
+Example usage:
+
+spawn-fcgi -n -s /var/run/scgi-cgi.sock -u www-default -U www-data -- /usr/bin/scgi-cgi
+.SH AUTHOR
+scgi-cgi was written by Stefan Bühler.
diff --git a/scgi-cgi.c b/scgi-cgi.c
new file mode 100644
index 0000000..584ceb5
--- /dev/null
+++ b/scgi-cgi.c
@@ -0,0 +1,1263 @@
+
+#ifdef HAVE_CONFIG_H
+#include "config.h"
+#endif
+
+#include <errno.h>
+#include <fcntl.h>
+#include <limits.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include <event2/event.h>
+
+#define MAX_BUFFER_SIZE (64u*1024u)
+#define MAX_STRING_BUFFER_SIZE (64u*1024u) /* env keys and values */
+
+#define CONST_STR_LEN(x) (x), sizeof(x) - 1
+#define GSTR_LEN(x) (x) ? (x)->str : "", (x) ? (x)->len : 0
+#define UNUSED(x) ((void)(x))
+
+#ifdef __GNUC__
+#define ATTR_WARN_UNUSED_RESULT \
+ __attribute__((warn_unused_result))
+#define ATTR_FORMAT(fmt, args) \
+ __attribute__(( format(printf, fmt, args) ))
+#else
+#define ATTR_WARN_UNUSED_RESULT
+#define ATTR_FORMAT(fmt, args)
+#endif
+
+#define ERROR(...) printerr(__LINE__, __VA_ARGS__)
+
+#ifdef NDEBUG
+# define DEBUG(...) do { } while (0)
+#else
+# define DEBUG(...) printerr(__LINE__, "DEBUG: " __VA_ARGS__)
+#endif
+
+#define PACKAGE_DESC PACKAGE_NAME " v" PACKAGE_VERSION " - SCGI application to run normal cgi applications"
+
+/* force asserts to be enabled */
+#undef NDEBUG
+#include <assert.h>
+
+/****************************************************************************
+ * logging *
+ ***************************************************************************/
+
+static void printerr(unsigned int line, const char *fmt, ...) ATTR_FORMAT(2, 3);
+static void printerr(unsigned int line, const char *fmt, ...) {
+ va_list ap;
+
+ fprintf(stderr, "scgi-cgi.c:%u:", line);
+ va_start(ap, fmt);
+ vfprintf(stderr, fmt, ap);
+ va_end(ap);
+ fprintf(stderr, "\n");
+}
+
+/****************************************************************************
+ * STRING BUFFER *
+ ***************************************************************************/
+
+typedef struct string_buffer string_buffer;
+struct string_buffer {
+ /* always 0-terminated string (unless data == NULL), but don't count terminating 0 in `used' */
+ unsigned char *data;
+ unsigned int used, size;
+};
+
+static void string_buffer_init(string_buffer *buf);
+static void string_buffer_clear(string_buffer *buf);
+static unsigned char* string_buffer_extract(string_buffer *buf) ATTR_WARN_UNUSED_RESULT; /* resets buffer, returns string. free string with free() */
+static int string_buffer_reserve(string_buffer *buf, unsigned int len) ATTR_WARN_UNUSED_RESULT;
+static int string_buffer_append(string_buffer *buf, const unsigned char *data, unsigned int len) ATTR_WARN_UNUSED_RESULT;
+static int string_buffer_append_char(string_buffer *buf, unsigned char c) ATTR_WARN_UNUSED_RESULT;
+static int string_buffer_equal(string_buffer *buf, const char *data, unsigned int len);
+
+#define STRING_BUFFER_EQUAL(buf, str) string_buffer_equal(buf, str, sizeof(str) - 1)
+
+
+static void string_buffer_init(string_buffer *buf) {
+ buf->data = NULL;
+ buf->used = buf->size = 0;
+}
+
+static void string_buffer_clear(string_buffer *buf) {
+ if (NULL != buf->data) free(buf->data);
+ buf->data = NULL;
+ buf->used = buf->size = 0;
+}
+
+static unsigned char* string_buffer_extract(string_buffer *buf) {
+ unsigned char *str = buf->data;
+ string_buffer_init(buf);
+ return str;
+}
+
+static int string_buffer_reserve(string_buffer *buf, unsigned int len) {
+ assert(buf->used <= buf->size);
+ assert(buf->size <= MAX_STRING_BUFFER_SIZE);
+
+ if (1 > UINT_MAX - len || len + 1 > MAX_STRING_BUFFER_SIZE - buf->used) {
+ DEBUG("string buffer: overflow");
+ return 0;
+ }
+ unsigned int newlen = buf->used + len;
+ if (newlen + 1 > buf->size) {
+ unsigned int need_size = newlen + 1;
+ unsigned int want_size = (need_size + 127) & ~127; /* round up to next multiple of 128 */
+ unsigned char *newdata;
+
+ if (want_size < need_size) want_size = need_size; /* overflow handling */
+ newdata = (unsigned char*) realloc(buf->data, want_size);
+ if (NULL == newdata) {
+ DEBUG("string buffer: realloc failed");
+ return 0;
+ }
+
+ buf->data = newdata;
+ buf->size = want_size;
+ }
+
+ return 1;
+}
+
+static int string_buffer_append(string_buffer *buf, const unsigned char *data, unsigned int len) {
+ if (!string_buffer_reserve(buf, len)) return 0;
+
+ if (len > 0) memcpy(buf->data + buf->used, data, len);
+ buf->used += len;
+ buf->data[buf->used] = '\0';
+
+ return 1;
+}
+
+static int string_buffer_append_char(string_buffer *buf, unsigned char c) {
+ if (!string_buffer_reserve(buf, 1)) return 0;
+
+ buf->data[buf->used++] = c;
+ buf->data[buf->used] = '\0';
+
+ return 1;
+}
+
+static int string_buffer_equal(string_buffer *buf, const char *data, unsigned int len) {
+ if (buf->used != len) return 0;
+ if (0 == len) return 1;
+ return 0 == memcmp(buf->data, data, len);
+}
+
+
+/****************************************************************************
+ * SCGI PARSER *
+ ***************************************************************************/
+
+typedef enum scgi_req_parser_state {
+ SCGI_REQ_PARSER_HEADER_LEN,
+ SCGI_REQ_PARSER_HEADER_ENV_KEY,
+ SCGI_REQ_PARSER_HEADER_ENV_VALUE,
+ SCGI_REQ_PARSER_HEADER_DONE,
+ SCGI_REQ_PARSER_HEADER_ERROR
+} scgi_req_parser_state;
+
+typedef struct scgi_req_parser scgi_req_parser;
+struct scgi_req_parser {
+ scgi_req_parser_state state;
+ unsigned int header_length, is_scgi;
+ unsigned long long content_length;
+
+ unsigned char **environ; /* list terminated by NULL entry */
+ unsigned int environ_used, environ_size;
+
+ string_buffer key_value;
+ unsigned int key_length;
+};
+
+static int key_value_has_key(scgi_req_parser *parser, char *key, unsigned int keylen);
+static void scgi_parser_init(scgi_req_parser *parser);
+static void scgi_parser_clear(scgi_req_parser *parser);
+static void scgi_parser_clear_environment(scgi_req_parser *parser);
+static int scgi_parser_environ_reserve(scgi_req_parser *parser);
+static int scgi_parser_environ_append(scgi_req_parser *parser, unsigned char *kvstr, int keylength);
+static int scgi_parser_environ_copy(scgi_req_parser *parser, const char *key, unsigned int keylength);
+static const char* scgi_parser_environ_get(scgi_req_parser *parser, const char *key, unsigned int keylength);
+static int scgi_parse(scgi_req_parser *parser, unsigned char *data, int len) ATTR_WARN_UNUSED_RESULT;
+
+
+static int key_value_has_key(scgi_req_parser *parser, char *key, unsigned int keylen) {
+ if (parser->key_length != keylen) return 0;
+ return 0 == memcmp(parser->key_value.data, key, keylen);
+}
+
+#define KEY_VALUE_HAS_KEY(parser, key) key_value_has_key(parser, key, sizeof(key) - 1)
+
+
+static void scgi_parser_init(scgi_req_parser *parser) {
+ parser->state = SCGI_REQ_PARSER_HEADER_LEN;
+ parser->header_length = parser->content_length = parser->is_scgi = 0;
+
+ parser->environ = NULL;
+ parser->environ_used = parser->environ_size = 0;
+
+ string_buffer_init(&parser->key_value);
+ parser->key_length = 0;
+}
+
+static void scgi_parser_clear(scgi_req_parser *parser) {
+ unsigned int i;
+
+ for (i = 0; i < parser->environ_used; ++i) {
+ free(parser->environ[i]);
+ }
+ free(parser->environ);
+
+ string_buffer_clear(&parser->key_value);
+
+ parser->state = SCGI_REQ_PARSER_HEADER_LEN;
+ parser->header_length = parser->content_length = parser->is_scgi = 0;
+
+ parser->environ = NULL;
+ parser->environ_used = parser->environ_size = 0;
+
+ parser->key_length = 0;
+}
+
+static void scgi_parser_clear_environment(scgi_req_parser *parser) {
+ unsigned int i;
+
+ for (i = 0; i < parser->environ_used; ++i) {
+ free(parser->environ[i]);
+ }
+ free(parser->environ);
+
+ parser->environ = NULL;
+ parser->environ_used = parser->environ_size = 0;
+}
+
+static int scgi_parser_environ_reserve(scgi_req_parser *parser) {
+ if (2 > UINT_MAX/sizeof(unsigned char*) - parser->environ_used) {
+ DEBUG("scgi environment: entries overflow");
+ return 0; /* overflow: can't append */
+ }
+ if (parser->environ_used + 2 > parser->environ_size) {
+ unsigned int need_size = sizeof(unsigned char*) * (parser->environ_used + 2);
+ unsigned int want_size = (need_size + 1024) & ~1023; /* round up to next multiple of 1023 (for 8-byte pointers: 128 entries) */
+ unsigned char **newdata;
+
+ if (want_size < need_size) want_size = need_size; /* overflow handling */
+ newdata = (unsigned char**) realloc(parser->environ, want_size);
+ if (NULL == newdata) {
+ DEBUG("scgi environment: realloc failed");
+ return 0; /* ENOMEM */
+ }
+
+ parser->environ = newdata;
+ parser->environ_size = want_size / sizeof(unsigned char*);
+ }
+
+ return 1;
+}
+
+/* kvstr: "KEY=VALUE", keylength = strlen("KEY")
+ * return values: 0: not appended: key already exists (not overwriting), or some other error; 1: appended
+ * kvstr is free()d if it wasn't appended.
+ */
+static int scgi_parser_environ_append(scgi_req_parser *parser, unsigned char *kvstr, int keylength) {
+ unsigned int i;
+
+ for (i = 0; i < parser->environ_used; ++i) {
+ const char *e = (const char*) parser->environ[i];
+ if (0 == strncmp(e, (const char *) kvstr, keylength + 1)) {
+ DEBUG("scgi req header: duplicate key for entry '%s', already have '%s'", kvstr, e);
+ goto fail; /* found */
+ }
+ }
+ if (!scgi_parser_environ_reserve(parser)) goto fail;
+
+ parser->environ[parser->environ_used++] = kvstr;
+ parser->environ[parser->environ_used] = NULL;
+
+ return 1;
+
+fail:
+ free(kvstr);
+ return 0;
+}
+
+static int scgi_parser_environ_copy(scgi_req_parser *parser, const char *key, unsigned int keylength) {
+ const char *val = getenv(key);
+ unsigned int vallen;
+ unsigned char *kvstr;
+ unsigned int i;
+
+ if (NULL == val) return 1;
+
+ vallen = strlen(val);
+ kvstr = malloc(keylength + 2 + vallen);
+ if (NULL == kvstr) return 0;
+
+ memcpy(kvstr, key, keylength);
+ kvstr[keylength] = '=';
+ memcpy(kvstr + keylength + 1, val, vallen + 1);
+
+ for (i = 0; i < parser->environ_used; ++i) {
+ const char *e = (const char*) parser->environ[i];
+ if (0 == strncmp(e, (const char *) kvstr, keylength + 1)) {
+ /* found. overwrite: */
+ free(parser->environ[i]);
+ parser->environ[i] = kvstr;
+ }
+ }
+
+ if (!scgi_parser_environ_reserve(parser)) {
+ free(kvstr);
+ return 0;
+ }
+
+ parser->environ[parser->environ_used++] = kvstr;
+ parser->environ[parser->environ_used] = NULL;
+
+ return 1;
+}
+
+static const char* scgi_parser_environ_get(scgi_req_parser *parser, const char *key, unsigned int keylength) {
+ unsigned int i;
+
+ for (i = 0; i < parser->environ_used; ++i) {
+ const char *e = (const char*) parser->environ[i];
+ if (0 == strncmp(e, (const char *) key, keylength) && '=' == e[keylength]) {
+ return (const char*) e + keylength + 1;
+ }
+ }
+
+ return NULL;
+}
+
+/* return values:
+ * -2: error
+ * -1: need more data for header
+ * l>=0: trailing l bytes of data are request body data; header finished.
+ */
+static int scgi_parse(scgi_req_parser *parser, unsigned char *data, int len) {
+ assert(len > 0);
+
+ for (; len > 0; ++data, --len) {
+ unsigned char c = *data;
+
+ switch (parser->state) {
+ case SCGI_REQ_PARSER_HEADER_LEN:
+ {
+ unsigned int digit;
+
+ if (c == ':') {
+ /* require at least 'CONTENT_LENGTH=0;SCGI=1;' in header ('=' and ';' encoded as '\0') */
+ if (parser->header_length < 24) {
+ DEBUG("scgi req header too small: %u", parser->header_length);
+ goto fail;
+ }
+ parser->state = SCGI_REQ_PARSER_HEADER_ENV_KEY;
+ break;
+ }
+
+ if (c < '0' || c > '9') {
+ DEBUG("scgi req header: expected digit or ':' in header length, got '%c'", c);
+ goto fail;
+ }
+ if (0 == parser->header_length && c == '0') {
+ /* extra leading zeroes are prohibited; zero header length is not permitted either:
+ * require CONTENT_LENGTH and SCGI vars
+ * => starting 0 digit is never allowed
+ */
+ DEBUG("scgi req header: header length starting with 0");
+ goto fail;
+ }
+
+ digit = c - '0';
+ if (parser->header_length > UINT_MAX / 10 - digit) {
+ DEBUG("scgi req header: header length overflow");
+ goto fail; /* overflow */
+ }
+ parser->header_length = 10*parser->header_length + digit;
+ }
+ break;
+ case SCGI_REQ_PARSER_HEADER_ENV_KEY:
+ if (0 == parser->header_length) {
+ if (',' != c) {
+ DEBUG("scgi req header: require ',' before request body, got '%c'", c);
+ goto fail; /* after header require a ',' */
+ }
+ if (0 != parser->key_value.used) {
+ DEBUG("scgi req header: headers ended with partial key '%s'", parser->key_value.data);
+ goto fail; /* partial header on headers end */
+ }
+ if (!parser->is_scgi) {
+ DEBUG("scgi req header: missing SCGI=1");
+ goto fail; /* is_scgi is only set after CONTENT_LENGTH (always first header) is parsed, and then SCGI=1 was found */
+ }
+ parser->state = SCGI_REQ_PARSER_HEADER_DONE;
+ return len - 1;
+ }
+ --parser->header_length;
+
+ if (0 != c) {
+ if ('=' == c) {
+ DEBUG("scgi req header: key must not include '='");
+ goto fail; /* '=' can't be allowed in keys */
+ }
+ if (!string_buffer_append_char(&parser->key_value, c)) goto fail;
+ break;
+ }
+
+ /* key end */
+ parser->key_length = parser->key_value.used;
+ if (!string_buffer_append_char(&parser->key_value, '=')) goto fail;
+ parser->state = SCGI_REQ_PARSER_HEADER_ENV_VALUE;
+ break;
+ case SCGI_REQ_PARSER_HEADER_ENV_VALUE:
+ if (0 == parser->header_length) {
+ DEBUG("scgi req header: headers ended with partial value '%s'", parser->key_value.data);
+ goto fail; /* partial header on headers end */
+ }
+ --parser->header_length;
+
+ if (0 != c) {
+ int vallen = 1;
+ while (vallen < len && 0 != data[vallen]) ++vallen;
+ if (!string_buffer_append(&parser->key_value, data, vallen)) goto fail;
+ data += (vallen - 1);
+ len -= (vallen - 1);
+ parser->header_length -= (vallen - 1);
+ break;
+ }
+
+ /* value end */
+
+ if (0 == parser->environ_used) {
+ long long clen;
+ char *endptr = NULL, *startptr = (char*) parser->key_value.data + parser->key_length + 1;
+
+ if (!KEY_VALUE_HAS_KEY(parser, "CONTENT_LENGTH")) {
+ DEBUG("scgi req header: first header isn't CONTENT_LENGTH: '%s'", parser->key_value.data);
+ goto fail; /* first entry must be CONTENT_LENGTH */
+ }
+ if (parser->key_value.used == parser->key_length + 1) {
+ DEBUG("scgi req header: CONTENT_LENGTH is empty");
+ goto fail; /* empty CONTENT_LENGTH */
+ }
+
+ errno = 0;
+ clen = strtoll(startptr, &endptr, 10);
+ if (0 != errno) {
+ DEBUG("scgi req header: parsing '%s' failed: %s", parser->key_value.data, strerror(errno));
+ goto fail;
+ }
+ if (endptr != (char*) parser->key_value.data + parser->key_value.used) {
+ DEBUG("scgi req header: parsing '%s' failed: contained more than number", parser->key_value.data);
+ goto fail; /* number didn't cover complete value */
+ }
+ if (clen < 0) {
+ DEBUG("scgi req header: parsing '%s' failed: negative length", parser->key_value.data);
+ goto fail; /* number didn't cover complete value */
+ }
+ parser->content_length = (unsigned long long) clen;
+ } else if (KEY_VALUE_HAS_KEY(parser, "SCGI")) {
+ if (STRING_BUFFER_EQUAL(&parser->key_value, "SCGI=1")) {
+ parser->is_scgi = 1;
+ } else {
+ DEBUG("scgi req header: SCGI is not 1: '%s'", parser->key_value.data);
+ goto fail; /* SCGI must be 1 */
+ }
+ }
+ if (!scgi_parser_environ_append(parser, string_buffer_extract(&parser->key_value), parser->key_length)) goto fail; /* duplicate key / ENOMEM */
+ parser->key_length = 0;
+ parser->state = SCGI_REQ_PARSER_HEADER_ENV_KEY;
+
+ break;
+ case SCGI_REQ_PARSER_HEADER_DONE:
+ return len;
+ case SCGI_REQ_PARSER_HEADER_ERROR:
+ goto fail;
+ }
+ }
+ return -1;
+
+fail:
+ parser->state = SCGI_REQ_PARSER_HEADER_ERROR;
+ return -2;
+}
+
+/****************************************************************************
+ * SCGI RING BUFFERS *
+ ***************************************************************************/
+
+/* ring buffer */
+typedef struct scgi_cgi_buffer scgi_cgi_buffer;
+struct scgi_cgi_buffer {
+ unsigned int pos, len;
+ unsigned char data[MAX_BUFFER_SIZE];
+};
+
+static void scgi_buffer_input_location(scgi_cgi_buffer *buf, unsigned char **location, ssize_t *location_size);
+static void scgi_buffer_fill(scgi_cgi_buffer *buf, ssize_t n);
+static void scgi_buffer_output_location(scgi_cgi_buffer *buf, unsigned char **location, ssize_t *location_size);
+static void scgi_buffer_drain(scgi_cgi_buffer *buf, ssize_t n);
+static int scgi_buffer_is_input_open(scgi_cgi_buffer *buf);
+static void scgi_buffer_set(scgi_cgi_buffer *buf, const char *data, ssize_t len);
+
+#define SCGI_BUFFER_SET(buf, str) scgi_buffer_set(buf, str, sizeof(str)-1)
+
+static void scgi_buffer_input_location(scgi_cgi_buffer *buf, unsigned char **location, ssize_t *location_size) {
+ if (buf->pos + buf->len >= MAX_BUFFER_SIZE) {
+ *location = buf->data + (buf->pos + buf->len - MAX_BUFFER_SIZE);
+ *location_size = MAX_BUFFER_SIZE - buf->len;
+ } else {
+ *location = buf->data + (buf->pos + buf->len);
+ *location_size = MAX_BUFFER_SIZE - (buf->pos + buf->len);
+ }
+}
+
+static void scgi_buffer_fill(scgi_cgi_buffer *buf, ssize_t n) {
+ assert(n >= 0 && (unsigned int) n <= MAX_BUFFER_SIZE - buf->len);
+ buf->len += n;
+}
+
+static void scgi_buffer_output_location(scgi_cgi_buffer *buf, unsigned char **location, ssize_t *location_size) {
+ *location = buf->data + buf->pos;
+ if (buf->pos + buf->len >= MAX_BUFFER_SIZE) {
+ *location_size = MAX_BUFFER_SIZE - buf->pos;
+ } else {
+ *location_size = buf->len;
+ }
+}
+
+static void scgi_buffer_drain(scgi_cgi_buffer *buf, ssize_t n) {
+ assert(n >= 0 && (unsigned int) n <= buf->len);
+ buf->pos = (buf->pos + n) % MAX_BUFFER_SIZE;
+ buf->len -= n;
+ if (0 == buf->len) buf->pos = 0;
+}
+
+static int scgi_buffer_is_input_open(scgi_cgi_buffer *buf) {
+ return buf->len < MAX_BUFFER_SIZE;
+}
+
+static void scgi_buffer_set(scgi_cgi_buffer *buf, const char *data, ssize_t len) {
+ assert(len >= 0 && len <= MAX_BUFFER_SIZE);
+ memcpy(buf->data, data, len);
+ buf->pos = 0;
+ buf->len = len;
+}
+
+/****************************************************************************
+ * SCGI CHILD + SERVER *
+ ***************************************************************************/
+
+typedef struct scgi_cgi_server scgi_cgi_server;
+typedef struct scgi_cgi_child scgi_cgi_child;
+
+struct scgi_cgi_server {
+ struct event_base *base;
+ struct event *listen_watcher;
+
+ struct event
+ *sig_w_CHLD,
+ *sig_w_INT,
+ *sig_w_TERM,
+ *sig_w_HUP;
+
+ const char *binary;
+
+ unsigned int children_used, children_size;
+ scgi_cgi_child **children;
+};
+
+struct scgi_cgi_child {
+ unsigned int ndx;
+ scgi_cgi_server *srv;
+
+ struct event *sock_in_watcher, *sock_out_watcher;
+ scgi_req_parser req_parser;
+
+ pid_t pid;
+
+ int child_stdout, child_stdin;
+ struct event *pipe_in_watcher, *pipe_out_watcher;
+
+ scgi_cgi_buffer request_buf;
+ scgi_cgi_buffer response_buf;
+};
+
+static void fd_init(int fd);
+static void _my_event_free_with_fd(struct event **event);
+static void _my_event_free(struct event **event);
+#define MY_EVENT_FREE_WITH_FD(event) _my_event_free_with_fd(&(event))
+#define MY_EVENT_FREE(event) _my_event_free(&(event))
+
+static scgi_cgi_child* scgi_cgi_child_create(scgi_cgi_server *srv, int fd);
+static void scgi_cgi_child_free(scgi_cgi_child *cld);
+static void scgi_cgi_child_check_done(scgi_cgi_child *cld);
+static void scgi_cgi_child_exec(scgi_cgi_child *cld);
+static void scgi_cgi_child_start(scgi_cgi_child *cld);
+static void scgi_cgi_close_socket(scgi_cgi_child *cld);
+static void scgi_cgi_child_sock_in_cb(evutil_socket_t fd, short what, void *arg);
+static void scgi_cgi_child_sock_out_cb(evutil_socket_t fd, short what, void *arg);
+static void scgi_cgi_child_pipe_in_cb(evutil_socket_t fd, short what, void *arg);
+static void scgi_cgi_child_pipe_out_cb(evutil_socket_t fd, short what, void *arg);
+
+static scgi_cgi_server* scgi_cgi_server_create(int fd, const char *binary, unsigned int maxconns);
+static void scgi_cgi_server_free(scgi_cgi_server* srv);
+static void scgi_cgi_server_child_finished(scgi_cgi_server *srv, scgi_cgi_child *cld);
+static void scgi_cgi_server_accept(evutil_socket_t fd, short what, void *arg);
+static void sigint_cb(evutil_socket_t fd, short what, void *arg);
+static void sigchld_cb(evutil_socket_t fd, short what, void *arg);
+
+
+/****************************************************************************
+ * SCGI utils implementation *
+ ***************************************************************************/
+
+static void fd_init(int fd) {
+#ifdef _WIN32
+ int i = 1;
+#endif
+#ifdef FD_CLOEXEC
+ /* close fd on exec (cgi) */
+ fcntl(fd, F_SETFD, FD_CLOEXEC);
+#endif
+#ifdef O_NONBLOCK
+ fcntl(fd, F_SETFL, O_NONBLOCK | O_RDWR);
+#elif defined _WIN32
+ ioctlsocket(fd, FIONBIO, &i);
+#endif
+}
+
+static void _my_event_free_with_fd(struct event **event) {
+ int fd;
+ if (NULL == *event) return;
+ fd = event_get_fd(*event);
+ event_free(*event);
+ *event = NULL;
+ if (-1 != fd) close(fd);
+}
+
+static void _my_event_free(struct event **event) {
+ if (NULL == *event) return;
+ event_free(*event);
+ *event = NULL;
+}
+
+/****************************************************************************
+ * SCGI CHILD implementation *
+ ***************************************************************************/
+
+
+static scgi_cgi_child* scgi_cgi_child_create(scgi_cgi_server *srv, int fd) {
+ struct event_base *base = srv->base;
+ scgi_cgi_child *cld;
+ int pipe_stdout[2], pipe_stdin[2];
+ if (-1 == pipe(pipe_stdout)) {
+ ERROR("couldn't create pipe: %s", strerror(errno));
+ return NULL;
+ }
+ if (-1 == pipe(pipe_stdin)) {
+ ERROR("couldn't create pipe: %s", strerror(errno));
+ close(pipe_stdout[0]); close(pipe_stdout[1]); return NULL;
+ }
+
+ cld = calloc(1, sizeof(scgi_cgi_child));
+ if (NULL == cld) {
+ ERROR("couldn't alloc: %s", strerror(errno));
+ close(pipe_stdout[0]); close(pipe_stdout[1]); close(pipe_stdin[0]); close(pipe_stdin[1]); return NULL;
+ }
+
+ cld->srv = srv;
+ cld->pid = -1;
+ cld->child_stdin = pipe_stdin[0];
+ cld->child_stdout = pipe_stdout[1];
+
+ scgi_parser_init(&cld->req_parser);
+
+ cld->sock_in_watcher = event_new(base, fd, EV_READ | EV_PERSIST, scgi_cgi_child_sock_in_cb, cld);
+ cld->sock_out_watcher = event_new(base, fd, EV_WRITE | EV_PERSIST, scgi_cgi_child_sock_out_cb, cld);
+ fd_init(pipe_stdin[1]);
+ cld->pipe_out_watcher = event_new(base, pipe_stdin[1], EV_WRITE | EV_PERSIST, scgi_cgi_child_pipe_out_cb, cld);
+ fd_init(pipe_stdout[0]);
+ cld->pipe_in_watcher = event_new(base, pipe_stdout[0], EV_READ | EV_PERSIST, scgi_cgi_child_pipe_in_cb, cld);
+
+ if (NULL == cld->sock_in_watcher || NULL == cld->sock_out_watcher || NULL == cld->pipe_in_watcher || NULL == cld->pipe_out_watcher) {
+ ERROR("couldn't alloc: %s", strerror(errno));
+ if (NULL == cld->pipe_in_watcher) close(pipe_stdin[1]);
+ if (NULL == cld->pipe_out_watcher) close(pipe_stdout[0]);
+ scgi_cgi_child_free(cld);
+ return NULL;
+ }
+
+ /* start handling */
+ event_add(cld->sock_in_watcher, NULL);
+
+ return cld;
+}
+
+static void scgi_cgi_child_free(scgi_cgi_child *cld) {
+ /* shared fd */
+ if (NULL != cld->sock_in_watcher) {
+ MY_EVENT_FREE(cld->sock_out_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->sock_in_watcher);
+ } else {
+ MY_EVENT_FREE_WITH_FD(cld->sock_out_watcher);
+ }
+
+ MY_EVENT_FREE_WITH_FD(cld->pipe_in_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->pipe_out_watcher);
+
+ if (-1 != cld->child_stdin) {
+ close(cld->child_stdin);
+ cld->child_stdin = -1;
+ }
+ if (-1 != cld->child_stdout) {
+ close(cld->child_stdout);
+ cld->child_stdout = -1;
+ }
+
+ scgi_parser_clear(&cld->req_parser);
+
+ if (-1 != cld->pid) {
+ kill(cld->pid, SIGTERM);
+ cld->pid = -1;
+ }
+
+ free(cld);
+}
+
+static void scgi_cgi_child_check_done(scgi_cgi_child *cld) {
+ if (-1 == cld->pid && NULL == cld->sock_out_watcher) {
+ MY_EVENT_FREE_WITH_FD(cld->sock_in_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->pipe_in_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->pipe_out_watcher);
+ }
+
+ if (-1 != cld->pid || NULL != cld->sock_out_watcher || NULL != cld->sock_in_watcher || NULL != cld->pipe_in_watcher || NULL != cld->pipe_out_watcher) return;
+
+ scgi_cgi_server_child_finished(cld->srv, cld);
+}
+
+static const char http_503_message[] =
+ "Status: 503 Service Unavailable\r\n"
+ "Content-Length: 0\r\n"
+ "\r\n"
+ ;
+
+static void scgi_cgi_child_exec(scgi_cgi_child *cld) {
+ char **newenv;
+ const char *path = cld->srv->binary;
+ char * args[] = { NULL, NULL };
+
+ if (cld->child_stdin != 0) {
+ dup2(cld->child_stdin, 0);
+ close(cld->child_stdin);
+ }
+ if (cld->child_stdout != 1) {
+ dup2(cld->child_stdout, 1);
+ close(cld->child_stdout);
+ }
+#ifdef FD_CLOEXEC
+ /* UNDO close fd on exec (cgi) */
+ fcntl(0, F_SETFD, 0);
+ fcntl(1, F_SETFD, 0);
+#endif
+
+ if (NULL == path) path = scgi_parser_environ_get(&cld->req_parser, CONST_STR_LEN("INTERPRETER"));
+ if (NULL == path) path = scgi_parser_environ_get(&cld->req_parser, CONST_STR_LEN("SCRIPT_FILENAME"));
+ args[0] = (char*) path;
+
+ /* try changing the directory. don't care about memleaks, execve() coming soon :) */
+ {
+ char *dir = strdup(path), *sep;
+ if (NULL == (sep = strrchr(dir, '/'))) {
+ chdir("/");
+ } else {
+ *sep = '\0';
+ chdir(dir);
+ }
+ }
+
+ scgi_parser_environ_copy(&cld->req_parser, CONST_STR_LEN("PATH"));
+ newenv = (char**) cld->req_parser.environ;
+
+ execve(path, args, newenv);
+
+ fprintf(stderr, "couldn't execve '%s': %s\n", path, strerror(errno));
+
+ write(1, http_503_message, sizeof(http_503_message));
+ exit(-1);
+}
+
+static void scgi_cgi_child_start(scgi_cgi_child *cld) {
+ cld->pid = fork();
+ switch (cld->pid) {
+ case 0:
+ /* child process */
+ scgi_cgi_child_exec(cld);
+ break;
+ case -1:
+ /* error */
+ fprintf(stderr, "couldn't fork: %s\n", strerror(errno));
+
+ SCGI_BUFFER_SET(&cld->response_buf, http_503_message);
+ if (NULL != cld->sock_out_watcher) event_add(cld->sock_out_watcher, NULL);
+
+ /* don't need those anymore */
+ scgi_parser_clear_environment(&cld->req_parser);
+ close(cld->child_stdout); cld->child_stdout = -1;
+ close(cld->child_stdin); cld->child_stdin = -1;
+
+ /* close pipe stuff */
+ MY_EVENT_FREE_WITH_FD(cld->pipe_in_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->pipe_out_watcher);
+
+ break;
+ default:
+ /* don't need those anymore */
+ scgi_parser_clear_environment(&cld->req_parser);
+ close(cld->child_stdout); cld->child_stdout = -1;
+ close(cld->child_stdin); cld->child_stdin = -1;
+
+ /* start reading */
+ event_add(cld->pipe_in_watcher, NULL);
+ break;
+ }
+}
+
+static void scgi_cgi_close_socket(scgi_cgi_child *cld) {
+ cld->response_buf.pos = cld->response_buf.len = 0;
+ /* shared fd */
+ if (NULL != cld->sock_in_watcher) {
+ MY_EVENT_FREE(cld->sock_out_watcher);
+ MY_EVENT_FREE_WITH_FD(cld->sock_in_watcher);
+ } else {
+ MY_EVENT_FREE_WITH_FD(cld->sock_out_watcher);
+ }
+ if (NULL != cld->pipe_out_watcher) event_add(cld->pipe_out_watcher, NULL);
+ if (NULL != cld->pipe_in_watcher) event_add(cld->pipe_in_watcher, NULL);
+
+ scgi_cgi_child_check_done(cld);
+}
+
+static void scgi_cgi_child_sock_in_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_child *cld = (scgi_cgi_child*) arg;
+ UNUSED(what);
+ assert(NULL != cld->sock_in_watcher);
+
+ for (;;) {
+ unsigned char *buf;
+ ssize_t r;
+
+ scgi_buffer_input_location(&cld->request_buf, &buf, &r);
+ if (0 == r) { /* buffer is full */
+ event_del(cld->sock_in_watcher);
+ break;
+ }
+ r = read(fd, buf, r);
+ if (0 == r) { /* eof */
+ /* shared fd */
+ if (NULL == cld->sock_out_watcher) {
+ MY_EVENT_FREE_WITH_FD(cld->sock_in_watcher);
+ } else {
+ MY_EVENT_FREE(cld->sock_in_watcher);
+ }
+ break;
+ } else if (0 > r) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ break; /* try again later */
+ default:
+ goto close_sock;
+ }
+ break;
+ } else {
+ if (SCGI_REQ_PARSER_HEADER_DONE != cld->req_parser.state) {
+ ssize_t result;
+ assert(0 == cld->request_buf.len);
+ result = scgi_parse(&cld->req_parser, buf, r);
+ DEBUG("scgi req parse result: %i", (int) result);
+ if (result >= 0) {
+ assert(SCGI_REQ_PARSER_HEADER_DONE == cld->req_parser.state);
+ if (result > 0) {
+ cld->request_buf.pos = r - result;
+ cld->request_buf.len = result;
+ }
+ scgi_cgi_child_start(cld);
+ } else if (result < -1) {
+ goto close_sock;
+ }
+ } else if (NULL != cld->pipe_out_watcher) {
+ scgi_buffer_fill(&cld->request_buf, r);
+ } else {
+ goto close_sock;
+ }
+ }
+ }
+
+ if (cld->request_buf.len > cld->req_parser.content_length) {
+ cld->request_buf.len = cld->req_parser.content_length;
+ goto close_sock;
+ }
+
+ if (NULL == cld->sock_in_watcher || 0 < cld->request_buf.len || 0 == cld->req_parser.content_length) {
+ if (NULL != cld->pipe_out_watcher) event_add(cld->pipe_out_watcher, NULL);
+ }
+
+ scgi_cgi_child_check_done(cld);
+ return;
+
+close_sock:
+ scgi_cgi_close_socket(cld);
+}
+
+static void scgi_cgi_child_sock_out_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_child *cld = (scgi_cgi_child*) arg;
+ UNUSED(what);
+ assert(NULL != cld->sock_out_watcher);
+
+ for (;;) {
+ unsigned char *buf;
+ ssize_t r;
+
+ scgi_buffer_output_location(&cld->response_buf, &buf, &r);
+ if (0 == r) { /* buffer empty */
+ event_del(cld->sock_out_watcher);
+ break;
+ }
+ r = write(fd, buf, r);
+ if (0 >= r) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ break; /* try again later */
+ default:
+ goto close_sock;
+ }
+ break;
+ }
+ scgi_buffer_drain(&cld->response_buf, r);
+ }
+
+ if (0 == cld->response_buf.len && NULL == cld->pipe_in_watcher) {
+ shutdown(fd, SHUT_RDWR);
+ goto close_sock;
+ } else if (NULL != cld->pipe_in_watcher && scgi_buffer_is_input_open(&cld->response_buf)) {
+ event_add(cld->pipe_in_watcher, NULL);
+ }
+
+ scgi_cgi_child_check_done(cld);
+ return;
+
+close_sock:
+ scgi_cgi_close_socket(cld);
+}
+
+static void scgi_cgi_child_pipe_in_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_child *cld = (scgi_cgi_child*) arg;
+ UNUSED(what);
+ assert(NULL != cld->pipe_in_watcher);
+
+ for (;;) {
+ unsigned char *buf;
+ ssize_t r;
+
+ scgi_buffer_input_location(&cld->response_buf, &buf, &r);
+ if (0 == r) { /* buffer full */
+ event_del(cld->pipe_in_watcher);
+ break;
+ }
+ r = read(fd, buf, r);
+ if (0 == r) { /* eof */
+ MY_EVENT_FREE_WITH_FD(cld->pipe_in_watcher);
+ break;
+ } else if (0 > r) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ break; /* try again later */
+ default:
+ MY_EVENT_FREE_WITH_FD(cld->pipe_in_watcher);
+ break;
+ }
+ break;
+ } else {
+ if (NULL != cld->sock_out_watcher) {
+ scgi_buffer_fill(&cld->response_buf, r);
+ }
+ }
+ }
+
+ if (NULL == cld->pipe_in_watcher || cld->response_buf.len > 0) {
+ if (NULL != cld->sock_out_watcher) event_add(cld->sock_out_watcher, NULL);
+ }
+
+ scgi_cgi_child_check_done(cld);
+}
+
+static void scgi_cgi_child_pipe_out_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_child *cld = (scgi_cgi_child*) arg;
+ UNUSED(what);
+ assert(NULL != cld->pipe_out_watcher);
+
+ for (;;) {
+ unsigned char *buf;
+ ssize_t r;
+
+ scgi_buffer_output_location(&cld->request_buf, &buf, &r);
+ assert(cld->req_parser.content_length >= (size_t) r);
+
+ if (0 == r) { /* buffer empty */
+ event_del(cld->pipe_out_watcher);
+ break;
+ }
+ r = write(fd, buf, r);
+ if (0 >= r) {
+ switch (errno) {
+ case EINTR:
+ case EAGAIN:
+#if EWOULDBLOCK != EAGAIN
+ case EWOULDBLOCK:
+#endif
+ break; /* try again later */
+ default:
+ goto close_pipe;
+ }
+ break;
+ }
+ cld->req_parser.content_length -= r;
+ scgi_buffer_drain(&cld->request_buf, r);
+ }
+
+ if (0 == cld->req_parser.content_length || (0 == cld->request_buf.len && NULL == cld->sock_in_watcher)) {
+ goto close_pipe;
+ } else if (NULL != cld->sock_in_watcher && scgi_buffer_is_input_open(&cld->request_buf)) {
+ event_add(cld->sock_in_watcher, NULL);
+ }
+
+ scgi_cgi_child_check_done(cld);
+ return;
+
+close_pipe:
+ MY_EVENT_FREE_WITH_FD(cld->pipe_out_watcher);
+ cld->request_buf.pos = cld->request_buf.len = 0;
+ if (NULL != cld->sock_in_watcher) event_add(cld->sock_in_watcher, NULL);
+
+ scgi_cgi_child_check_done(cld);
+}
+
+/****************************************************************************
+ * SCGI SERVER implementation *
+ ***************************************************************************/
+
+#define CATCH_SIGNAL(cb, n) do { \
+ srv->sig_w_##n = event_new(srv->base, SIG##n, EV_SIGNAL|EV_PERSIST, cb, srv); \
+ assert(NULL != srv->sig_w_##n); \
+ event_add(srv->sig_w_##n, NULL); \
+} while (0)
+
+#define UNCATCH_SIGNAL(n) MY_EVENT_FREE(srv->sig_w_##n)
+
+static scgi_cgi_server* scgi_cgi_server_create(int fd, const char *binary, unsigned int maxconns) {
+ scgi_cgi_server* srv = calloc(1, sizeof(scgi_cgi_server));
+
+ srv->children = (scgi_cgi_child**) calloc(maxconns, sizeof(scgi_cgi_child*));
+ assert(NULL != srv->children);
+ srv->children_used = 0;
+ srv->children_size = maxconns;
+
+ srv->binary = binary;
+
+ srv->base = event_base_new();
+ fd_init(fd);
+ srv->listen_watcher = event_new(srv->base, fd, EV_READ | EV_PERSIST, scgi_cgi_server_accept, srv);
+ event_add(srv->listen_watcher, NULL);
+
+ CATCH_SIGNAL(sigint_cb, INT);
+ CATCH_SIGNAL(sigint_cb, TERM);
+ CATCH_SIGNAL(sigint_cb, HUP);
+ CATCH_SIGNAL(sigchld_cb, CHLD);
+
+ return srv;
+}
+
+static void scgi_cgi_server_free(scgi_cgi_server* srv) {
+ while (srv->children_used > 0) {
+ scgi_cgi_server_child_finished(srv, srv->children[0]);
+ }
+
+ MY_EVENT_FREE_WITH_FD(srv->listen_watcher);
+
+ UNCATCH_SIGNAL(INT);
+ UNCATCH_SIGNAL(TERM);
+ UNCATCH_SIGNAL(HUP);
+ UNCATCH_SIGNAL(CHLD);
+
+ free(srv->children);
+ srv->children = NULL;
+ srv->children_size = 0;
+ free(srv);
+}
+
+static void scgi_cgi_server_child_finished(scgi_cgi_server *srv, scgi_cgi_child *cld) {
+ unsigned int ndx = cld->ndx;
+ assert(srv->children[ndx] == cld);
+ assert(ndx < srv->children_used);
+
+ DEBUG("Child %i finished", ndx);
+
+ --srv->children_used;
+ if (ndx != srv->children_used) {
+ srv->children[ndx] = srv->children[srv->children_used];
+ srv->children[ndx]->ndx = ndx;
+ }
+ srv->children[srv->children_used] = NULL;
+
+ scgi_cgi_child_free(cld);
+
+ if (NULL == srv->listen_watcher && 0 == srv->children_used) UNCATCH_SIGNAL(CHLD); /* shutdown */
+ if (NULL != srv->listen_watcher) event_add(srv->listen_watcher, NULL);
+}
+
+static void scgi_cgi_server_accept(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_server *srv = (scgi_cgi_server*) arg;
+ int confd;
+ UNUSED(what);
+
+ if (srv->children_used == srv->children_size) {
+ event_del(srv->listen_watcher);
+ return;
+ }
+
+ while (srv->children_used < srv->children_size) {
+ if (-1 != (confd = accept(fd, NULL, NULL))) {
+ scgi_cgi_child *cld;
+
+ fd_init(confd);
+ cld = scgi_cgi_child_create(srv, confd);
+ if (NULL == cld) {
+ if (0 == srv->children_used) {
+ ERROR("no children running, and child creation failed. abort.");
+ exit(-2);
+ }
+ ERROR("child creation failed, disable listening temporarily until next child finishes");
+ close(confd);
+ event_del(srv->listen_watcher);
+ return;
+ }
+ srv->children[srv->children_used++] = cld;
+ } else {
+ break;
+ }
+ }
+}
+
+static void sigint_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_server *srv = (scgi_cgi_server*) arg;
+ UNUSED(fd);
+ UNUSED(what);
+
+ UNCATCH_SIGNAL(INT);
+ UNCATCH_SIGNAL(TERM);
+ UNCATCH_SIGNAL(HUP);
+
+ MY_EVENT_FREE_WITH_FD(srv->listen_watcher);
+
+ if (0 == srv->children_used) UNCATCH_SIGNAL(CHLD);
+
+ fprintf(stderr, "Got signal, shutdown (%i children remaining)\n", srv->children_used);
+}
+
+static void sigchld_cb(evutil_socket_t fd, short what, void *arg) {
+ scgi_cgi_server *srv = (scgi_cgi_server*) arg;
+ pid_t pid;
+ int status;
+ UNUSED(fd);
+ UNUSED(what);
+
+ while (srv->children_used > 0) {
+ if (-1 != (pid = waitpid(-1, &status, WNOHANG))) {
+ unsigned int i;
+ for (i = 0; i < srv->children_used; ++i) {
+ scgi_cgi_child *cld = srv->children[i];
+ if (cld->pid == pid) {
+ DEBUG("child %i terminated with status %i", i, status);
+ cld->pid = -1;
+ scgi_cgi_child_check_done(cld);
+ break;
+ }
+ }
+ } else {
+ break;
+ }
+ }
+}
+
+static void show_help() {
+ fprintf(stderr, PACKAGE_DESC "\n");
+ fprintf(stderr,
+ "Usage: scgi-cgi [-b binary] [-c maxconns] [-h] [-v] -- [binary]\n"
+ "Options:\n"
+ " -b binary the executable to call instead of INTERPRETER\n"
+ " or SCRIPT_FILENAME from SCGI environment (default: none)\n"
+ " -c maxconns how many connections to accept at the same time (default: 16)\n"
+ " -v show version\n"
+ " -h show this help\n"
+ );
+}
+
+int main (int argc, char **argv) {
+ scgi_cgi_server *srv;
+ const char *binary = NULL;
+ unsigned int maxconn = 16;
+ int o;
+
+ while(-1 != (o = getopt(argc, argv, "b:c:hv"))) {
+ switch(o) {
+ case 'b':
+ binary = optarg;
+ break;
+ case 'c':
+ maxconn = atoi(optarg);
+ break;
+ case 'v':
+ fprintf(stderr, PACKAGE_DESC "\n");
+ return 0;
+ case 'h':
+ show_help();
+ return 0;
+ default:
+ show_help();
+ return -1;
+ }
+ }
+
+ if (optind < argc && argv[optind] && NULL == binary) binary = argv[optind];
+
+ srv = scgi_cgi_server_create(0, binary, maxconn);
+ event_base_loop(srv->base, 0);
+ scgi_cgi_server_free(srv);
+ return 0;
+}