summaryrefslogtreecommitdiff
path: root/data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c
diff options
context:
space:
mode:
Diffstat (limited to 'data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c')
-rw-r--r--data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c1081
1 files changed, 1081 insertions, 0 deletions
diff --git a/data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c b/data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c
new file mode 100644
index 000000000..aeec06f98
--- /dev/null
+++ b/data/lighttpd/lighttpd-1.4.53/src/mod_cgi.c
@@ -0,0 +1,1081 @@
+#include "first.h"
+
+#include "base.h"
+#include "stat_cache.h"
+#include "http_kv.h"
+#include "log.h"
+#include "connections.h"
+#include "joblist.h"
+#include "response.h"
+#include "http_chunk.h"
+#include "http_header.h"
+
+#include "plugin.h"
+
+#include <sys/types.h>
+#include "sys-mmap.h"
+#include "sys-socket.h"
+# include <sys/wait.h>
+
+#include <unistd.h>
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fdevent.h>
+
+#include <fcntl.h>
+#include <signal.h>
+
+static int pipe_cloexec(int pipefd[2]) {
+ #ifdef HAVE_PIPE2
+ if (0 == pipe2(pipefd, O_CLOEXEC)) return 0;
+ #endif
+ return 0 == pipe(pipefd)
+ #ifdef FD_CLOEXEC
+ && 0 == fcntl(pipefd[0], F_SETFD, FD_CLOEXEC)
+ && 0 == fcntl(pipefd[1], F_SETFD, FD_CLOEXEC)
+ #endif
+ ? 0
+ : -1;
+}
+
+typedef struct {
+ char *ptr;
+ size_t used;
+ size_t size;
+ size_t *offsets;
+ size_t osize;
+ size_t oused;
+ char **eptr;
+ size_t esize;
+ buffer *ld_preload;
+ buffer *ld_library_path;
+ #ifdef __CYGWIN__
+ buffer *systemroot;
+ #endif
+} env_accum;
+
+typedef struct {
+ struct { pid_t pid; void *ctx; } *ptr;
+ size_t used;
+ size_t size;
+} buffer_pid_t;
+
+typedef struct {
+ array *cgi;
+ unsigned short execute_x_only;
+ unsigned short local_redir;
+ unsigned short xsendfile_allow;
+ unsigned short upgrade;
+ array *xsendfile_docroot;
+} plugin_config;
+
+typedef struct {
+ PLUGIN_DATA;
+ plugin_config **config_storage;
+ plugin_config conf;
+ buffer_pid_t cgi_pid;
+ env_accum env;
+} plugin_data;
+
+typedef struct {
+ pid_t pid;
+ int fd;
+ int fdtocgi;
+ int fde_ndx; /* index into the fd-event buffer */
+ int fde_ndx_tocgi; /* index into the fd-event buffer */
+
+ connection *remote_conn; /* dumb pointer */
+ plugin_data *plugin_data; /* dumb pointer */
+
+ buffer *response;
+ buffer *cgi_handler; /* dumb pointer */
+ http_response_opts opts;
+ plugin_config conf;
+} handler_ctx;
+
+static handler_ctx * cgi_handler_ctx_init(void) {
+ handler_ctx *hctx = calloc(1, sizeof(*hctx));
+
+ force_assert(hctx);
+
+ hctx->response = chunk_buffer_acquire();
+ hctx->fd = -1;
+ hctx->fdtocgi = -1;
+
+ return hctx;
+}
+
+static void cgi_handler_ctx_free(handler_ctx *hctx) {
+ chunk_buffer_release(hctx->response);
+ free(hctx);
+}
+
+INIT_FUNC(mod_cgi_init) {
+ plugin_data *p;
+ const char *s;
+
+ p = calloc(1, sizeof(*p));
+
+ force_assert(p);
+
+ /* for valgrind */
+ s = getenv("LD_PRELOAD");
+ if (s) p->env.ld_preload = buffer_init_string(s);
+ s = getenv("LD_LIBRARY_PATH");
+ if (s) p->env.ld_library_path = buffer_init_string(s);
+ #ifdef __CYGWIN__
+ /* CYGWIN needs SYSTEMROOT */
+ s = getenv("SYSTEMROOT");
+ if (s) p->env.systemroot = buffer_init_string(s);
+ #endif
+
+ return p;
+}
+
+
+FREE_FUNC(mod_cgi_free) {
+ plugin_data *p = p_d;
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ UNUSED(srv);
+
+ if (p->config_storage) {
+ size_t i;
+ for (i = 0; i < srv->config_context->used; i++) {
+ plugin_config *s = p->config_storage[i];
+
+ if (NULL == s) continue;
+
+ array_free(s->cgi);
+ array_free(s->xsendfile_docroot);
+
+ free(s);
+ }
+ free(p->config_storage);
+ }
+
+
+ if (r->ptr) free(r->ptr);
+ free(p->env.ptr);
+ free(p->env.offsets);
+ free(p->env.eptr);
+ buffer_free(p->env.ld_preload);
+ buffer_free(p->env.ld_library_path);
+ #ifdef __CYGWIN__
+ buffer_free(p->env.systemroot);
+ #endif
+ free(p);
+
+ return HANDLER_GO_ON;
+}
+
+SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) {
+ plugin_data *p = p_d;
+ size_t i = 0;
+
+ config_values_t cv[] = {
+ { "cgi.assign", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
+ { "cgi.execute-x-only", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
+ { "cgi.x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
+ { "cgi.x-sendfile-docroot", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 3 */
+ { "cgi.local-redir", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 4 */
+ { "cgi.upgrade", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 5 */
+ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
+ };
+
+ if (!p) return HANDLER_ERROR;
+
+ p->config_storage = calloc(1, srv->config_context->used * sizeof(plugin_config *));
+ force_assert(p->config_storage);
+
+ for (i = 0; i < srv->config_context->used; i++) {
+ data_config const* config = (data_config const*)srv->config_context->data[i];
+ plugin_config *s;
+
+ s = calloc(1, sizeof(plugin_config));
+ force_assert(s);
+
+ s->cgi = array_init();
+ s->execute_x_only = 0;
+ s->local_redir = 0;
+ s->xsendfile_allow= 0;
+ s->xsendfile_docroot = array_init();
+ s->upgrade = 0;
+
+ cv[0].destination = s->cgi;
+ cv[1].destination = &(s->execute_x_only);
+ cv[2].destination = &(s->xsendfile_allow);
+ cv[3].destination = s->xsendfile_docroot;
+ cv[4].destination = &(s->local_redir);
+ cv[5].destination = &(s->upgrade);
+
+ p->config_storage[i] = s;
+
+ if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) {
+ return HANDLER_ERROR;
+ }
+
+ if (!array_is_kvstring(s->cgi)) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected value for cgi.assign; expected list of \"ext\" => \"exepath\"");
+ return HANDLER_ERROR;
+ }
+
+ if (s->xsendfile_docroot->used) {
+ size_t j;
+ for (j = 0; j < s->xsendfile_docroot->used; ++j) {
+ data_string *ds = (data_string *)s->xsendfile_docroot->data[j];
+ if (ds->type != TYPE_STRING) {
+ log_error_write(srv, __FILE__, __LINE__, "s",
+ "unexpected type for key cgi.x-sendfile-docroot; expected: cgi.x-sendfile-docroot = ( \"/allowed/path\", ... )");
+ return HANDLER_ERROR;
+ }
+ if (ds->value->ptr[0] != '/') {
+ log_error_write(srv, __FILE__, __LINE__, "SBs",
+ "cgi.x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\"");
+ return HANDLER_ERROR;
+ }
+ buffer_path_simplify(ds->value, ds->value);
+ buffer_append_slash(ds->value);
+ }
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static void cgi_pid_add(plugin_data *p, pid_t pid, void *ctx) {
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ if (r->size == 0) {
+ r->size = 16;
+ r->ptr = malloc(sizeof(*r->ptr) * r->size);
+ force_assert(r->ptr);
+ } else if (r->used == r->size) {
+ r->size += 16;
+ r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
+ force_assert(r->ptr);
+ }
+
+ r->ptr[r->used].pid = pid;
+ r->ptr[r->used].ctx = ctx;
+ ++r->used;
+}
+
+static void cgi_pid_kill(plugin_data *p, pid_t pid) {
+ buffer_pid_t *r = &(p->cgi_pid);
+ for (size_t i = 0; i < r->used; ++i) {
+ if (r->ptr[i].pid == pid) {
+ r->ptr[i].ctx = NULL;
+ kill(pid, SIGTERM);
+ return;
+ }
+ }
+}
+
+static void cgi_pid_del(plugin_data *p, size_t i) {
+ buffer_pid_t *r = &(p->cgi_pid);
+
+ if (i != r->used - 1) {
+ r->ptr[i] = r->ptr[r->used - 1];
+ }
+ r->used--;
+}
+
+
+static void cgi_connection_close_fdtocgi(server *srv, handler_ctx *hctx) {
+ /*(closes only hctx->fdtocgi)*/
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi);
+ /*fdevent_unregister(srv->ev, hctx->fdtocgi);*//*(handled below)*/
+ fdevent_sched_close(srv->ev, hctx->fdtocgi, 0);
+ hctx->fdtocgi = -1;
+}
+
+static void cgi_connection_close(server *srv, handler_ctx *hctx) {
+ plugin_data *p = hctx->plugin_data;
+ connection *con = hctx->remote_conn;
+
+ /* the connection to the browser went away, but we still have a connection
+ * to the CGI script
+ *
+ * close cgi-connection
+ */
+
+ if (hctx->fd != -1) {
+ /* close connection to the cgi-script */
+ fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd);
+ /*fdevent_unregister(srv->ev, hctx->fd);*//*(handled below)*/
+ fdevent_sched_close(srv->ev, hctx->fd, 0);
+ }
+
+ if (hctx->fdtocgi != -1) {
+ cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
+ }
+
+ if (hctx->pid > 0) {
+ cgi_pid_kill(p, hctx->pid);
+ }
+
+ con->plugin_ctx[p->id] = NULL;
+
+ cgi_handler_ctx_free(hctx);
+
+ /* finish response (if not already con->file_started, con->file_finished) */
+ if (con->mode == p->id) {
+ http_response_backend_done(srv, con);
+ }
+}
+
+static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ if (hctx) cgi_connection_close(srv, hctx);
+
+ return HANDLER_GO_ON;
+}
+
+
+static int cgi_write_request(server *srv, handler_ctx *hctx, int fd);
+
+
+static handler_t cgi_handle_fdevent_send (server *srv, void *ctx, int revents) {
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+
+ /*(joblist only actually necessary here in mod_cgi fdevent send if returning HANDLER_ERROR)*/
+ joblist_append(srv, con);
+
+ if (revents & FDEVENT_OUT) {
+ if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) {
+ cgi_connection_close(srv, hctx);
+ return HANDLER_ERROR;
+ }
+ /* more request body to be sent to CGI */
+ }
+
+ if (revents & FDEVENT_HUP) {
+ /* skip sending remaining data to CGI */
+ if (con->request.content_length) {
+ chunkqueue *cq = con->request_content_queue;
+ chunkqueue_mark_written(cq, chunkqueue_length(cq));
+ if (cq->bytes_in != (off_t)con->request.content_length) {
+ con->keep_alive = 0;
+ }
+ }
+
+ cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
+ } else if (revents & FDEVENT_ERR) {
+ /* kill all connections to the cgi process */
+#if 1
+ log_error_write(srv, __FILE__, __LINE__, "s", "cgi-FDEVENT_ERR");
+#endif
+ cgi_connection_close(srv, hctx);
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+static handler_t cgi_response_headers(server *srv, connection *con, struct http_response_opts_t *opts) {
+ /* response headers just completed */
+ handler_ctx *hctx = (handler_ctx *)opts->pdata;
+
+ if (con->response.htags & HTTP_HEADER_UPGRADE) {
+ if (hctx->conf.upgrade && con->http_status == 101) {
+ /* 101 Switching Protocols; transition to transparent proxy */
+ http_response_upgrade_read_body_unknown(srv, con);
+ }
+ else {
+ con->response.htags &= ~HTTP_HEADER_UPGRADE;
+ #if 0
+ /* preserve prior questionable behavior; likely broken behavior
+ * anyway if backend thinks connection is being upgraded but client
+ * does not receive Connection: upgrade */
+ http_header_response_unset(con, HTTP_HEADER_UPGRADE,
+ CONST_STR_LEN("Upgrade"));
+ #endif
+ }
+ }
+
+ if (hctx->conf.upgrade && !(con->response.htags & HTTP_HEADER_UPGRADE)) {
+ chunkqueue *cq = con->request_content_queue;
+ hctx->conf.upgrade = 0;
+ if (cq->bytes_out == (off_t)con->request.content_length) {
+ cgi_connection_close_fdtocgi(srv, hctx); /*(closes hctx->fdtocgi)*/
+ }
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+static int cgi_recv_response(server *srv, handler_ctx *hctx) {
+ switch (http_response_read(srv, hctx->remote_conn, &hctx->opts,
+ hctx->response, hctx->fd, &hctx->fde_ndx)) {
+ default:
+ return HANDLER_GO_ON;
+ case HANDLER_ERROR:
+ http_response_backend_error(srv, hctx->remote_conn);
+ /* fall through */
+ case HANDLER_FINISHED:
+ cgi_connection_close(srv, hctx);
+ return HANDLER_FINISHED;
+ case HANDLER_COMEBACK:
+ /* hctx->conf.local_redir */
+ buffer_clear(hctx->response);
+ connection_response_reset(srv, hctx->remote_conn); /*(includes con->http_status = 0)*/
+ plugins_call_connection_reset(srv, hctx->remote_conn);
+ /*cgi_connection_close(srv, hctx);*//*(already cleaned up and hctx is now invalid)*/
+ return HANDLER_COMEBACK;
+ }
+}
+
+
+static handler_t cgi_handle_fdevent(server *srv, void *ctx, int revents) {
+ handler_ctx *hctx = ctx;
+ connection *con = hctx->remote_conn;
+
+ joblist_append(srv, con);
+
+ if (revents & FDEVENT_IN) {
+ handler_t rc = cgi_recv_response(srv, hctx);/*(might invalidate hctx)*/
+ if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
+ }
+
+ /* perhaps this issue is already handled */
+ if (revents & (FDEVENT_HUP|FDEVENT_RDHUP)) {
+ if (con->file_started) {
+ /* drain any remaining data from kernel pipe buffers
+ * even if (con->conf.stream_response_body
+ * & FDEVENT_STREAM_RESPONSE_BUFMIN)
+ * since event loop will spin on fd FDEVENT_HUP event
+ * until unregistered. */
+ handler_t rc;
+ const unsigned short flags = con->conf.stream_response_body;
+ con->conf.stream_response_body &= ~FDEVENT_STREAM_RESPONSE_BUFMIN;
+ con->conf.stream_response_body |= FDEVENT_STREAM_RESPONSE_POLLRDHUP;
+ do {
+ rc = cgi_recv_response(srv,hctx);/*(might invalidate hctx)*/
+ } while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/
+ con->conf.stream_response_body = flags;
+ return rc; /* HANDLER_FINISHED or HANDLER_COMEBACK or HANDLER_ERROR */
+ } else if (!buffer_string_is_empty(hctx->response)) {
+ /* unfinished header package which is a body in reality */
+ con->file_started = 1;
+ if (0 != http_chunk_append_buffer(srv, con, hctx->response)) {
+ cgi_connection_close(srv, hctx);
+ return HANDLER_ERROR;
+ }
+ if (0 == con->http_status) con->http_status = 200; /* OK */
+ }
+ cgi_connection_close(srv, hctx);
+ } else if (revents & FDEVENT_ERR) {
+ /* kill all connections to the cgi process */
+ cgi_connection_close(srv, hctx);
+ return HANDLER_ERROR;
+ }
+
+ return HANDLER_FINISHED;
+}
+
+
+static int cgi_env_add(void *venv, const char *key, size_t key_len, const char *val, size_t val_len) {
+ env_accum *env = venv;
+ char *dst;
+
+ if (!key || !val) return -1;
+
+ if (env->size - env->used < key_len + val_len + 2) {
+ if (0 == env->size) env->size = 4096;
+ do { env->size *= 2; } while (env->size - env->used < key_len + val_len + 2);
+ env->ptr = realloc(env->ptr, env->size);
+ force_assert(env->ptr);
+ }
+
+ dst = env->ptr + env->used;
+ memcpy(dst, key, key_len);
+ dst[key_len] = '=';
+ memcpy(dst + key_len + 1, val, val_len);
+ dst[key_len + 1 + val_len] = '\0';
+
+ if (env->osize == env->oused) {
+ env->osize += 16;
+ env->offsets = realloc(env->offsets, env->osize * sizeof(*env->offsets));
+ force_assert(env->offsets);
+ }
+ env->offsets[env->oused++] = env->used;
+ env->used += key_len + val_len + 2;
+
+ return 0;
+}
+
+/*(improved from network_write_mmap.c)*/
+static off_t mmap_align_offset(off_t start) {
+ static off_t pagemask = 0;
+ if (0 == pagemask) {
+ long pagesize = sysconf(_SC_PAGESIZE);
+ if (-1 == pagesize) pagesize = 4096;
+ pagemask = ~((off_t)pagesize - 1); /* pagesize always power-of-2 */
+ }
+ return (start & pagemask);
+}
+
+/* returns: 0: continue, -1: fatal error, -2: connection reset */
+/* similar to network_write_file_chunk_mmap, but doesn't use send on windows (because we're on pipes),
+ * also mmaps and sends complete chunk instead of only small parts - the files
+ * are supposed to be temp files with reasonable chunk sizes.
+ *
+ * Also always use mmap; the files are "trusted", as we created them.
+ */
+static ssize_t cgi_write_file_chunk_mmap(server *srv, connection *con, int fd, chunkqueue *cq) {
+ chunk* const c = cq->first;
+ off_t offset, toSend, file_end;
+ ssize_t r;
+ size_t mmap_offset, mmap_avail;
+ char *data = NULL;
+
+ force_assert(NULL != c);
+ force_assert(FILE_CHUNK == c->type);
+ force_assert(c->offset >= 0 && c->offset <= c->file.length);
+
+ offset = c->file.start + c->offset;
+ toSend = c->file.length - c->offset;
+ file_end = c->file.start + c->file.length; /* offset to file end in this chunk */
+
+ if (0 == toSend) {
+ chunkqueue_remove_finished_chunks(cq);
+ return 0;
+ }
+
+ /*(simplified from chunk.c:chunkqueue_open_file_chunk())*/
+ UNUSED(con);
+ if (-1 == c->file.fd) {
+ if (-1 == (c->file.fd = fdevent_open_cloexec(c->mem->ptr, O_RDONLY, 0))) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "open failed:", strerror(errno), c->mem);
+ return -1;
+ }
+ }
+
+ /* (re)mmap the buffer if range is not covered completely */
+ if (MAP_FAILED == c->file.mmap.start
+ || offset < c->file.mmap.offset
+ || file_end > (off_t)(c->file.mmap.offset + c->file.mmap.length)) {
+
+ if (MAP_FAILED != c->file.mmap.start) {
+ munmap(c->file.mmap.start, c->file.mmap.length);
+ c->file.mmap.start = MAP_FAILED;
+ }
+
+ c->file.mmap.offset = mmap_align_offset(offset);
+ c->file.mmap.length = file_end - c->file.mmap.offset;
+
+ if (MAP_FAILED == (c->file.mmap.start = mmap(NULL, c->file.mmap.length, PROT_READ, MAP_PRIVATE, c->file.fd, c->file.mmap.offset))) {
+ if (toSend > 65536) toSend = 65536;
+ data = malloc(toSend);
+ force_assert(data);
+ if (-1 == lseek(c->file.fd, offset, SEEK_SET)
+ || 0 >= (toSend = read(c->file.fd, data, toSend))) {
+ if (-1 == toSend) {
+ log_error_write(srv, __FILE__, __LINE__, "ssbdo", "lseek/read failed:",
+ strerror(errno), c->mem, c->file.fd, offset);
+ } else { /*(0 == toSend)*/
+ log_error_write(srv, __FILE__, __LINE__, "sbdo", "unexpected EOF (input truncated?):",
+ c->mem, c->file.fd, offset);
+ }
+ free(data);
+ return -1;
+ }
+ }
+ }
+
+ if (MAP_FAILED != c->file.mmap.start) {
+ force_assert(offset >= c->file.mmap.offset);
+ mmap_offset = offset - c->file.mmap.offset;
+ force_assert(c->file.mmap.length > mmap_offset);
+ mmap_avail = c->file.mmap.length - mmap_offset;
+ force_assert(toSend <= (off_t) mmap_avail);
+
+ data = c->file.mmap.start + mmap_offset;
+ }
+
+ r = write(fd, data, toSend);
+
+ if (MAP_FAILED == c->file.mmap.start) free(data);
+
+ if (r < 0) {
+ switch (errno) {
+ case EAGAIN:
+ case EINTR:
+ return 0;
+ case EPIPE:
+ case ECONNRESET:
+ return -2;
+ default:
+ log_error_write(srv, __FILE__, __LINE__, "ssd",
+ "write failed:", strerror(errno), fd);
+ return -1;
+ }
+ }
+
+ if (r >= 0) {
+ chunkqueue_mark_written(cq, r);
+ }
+
+ return r;
+}
+
+static int cgi_write_request(server *srv, handler_ctx *hctx, int fd) {
+ connection *con = hctx->remote_conn;
+ chunkqueue *cq = con->request_content_queue;
+ chunk *c;
+
+ /* old comment: windows doesn't support select() on pipes - wouldn't be easy to fix for all platforms.
+ * solution: if this is still a problem on windows, then substitute
+ * socketpair() for pipe() and closesocket() for close() on windows.
+ */
+
+ for (c = cq->first; c; c = cq->first) {
+ ssize_t r = -1;
+
+ switch(c->type) {
+ case FILE_CHUNK:
+ r = cgi_write_file_chunk_mmap(srv, con, fd, cq);
+ break;
+
+ case MEM_CHUNK:
+ if ((r = write(fd, c->mem->ptr + c->offset, buffer_string_length(c->mem) - c->offset)) < 0) {
+ switch(errno) {
+ case EAGAIN:
+ case EINTR:
+ /* ignore and try again */
+ r = 0;
+ break;
+ case EPIPE:
+ case ECONNRESET:
+ /* connection closed */
+ r = -2;
+ break;
+ default:
+ /* fatal error */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "write failed due to: ", strerror(errno));
+ r = -1;
+ break;
+ }
+ } else if (r > 0) {
+ chunkqueue_mark_written(cq, r);
+ }
+ break;
+ }
+
+ if (0 == r) break; /*(might block)*/
+
+ switch (r) {
+ case -1:
+ /* fatal error */
+ return -1;
+ case -2:
+ /* connection reset */
+ log_error_write(srv, __FILE__, __LINE__, "s", "failed to send post data to cgi, connection closed by CGI");
+ /* skip all remaining data */
+ chunkqueue_mark_written(cq, chunkqueue_length(cq));
+ break;
+ default:
+ break;
+ }
+ }
+
+ if (cq->bytes_out == (off_t)con->request.content_length && !hctx->conf.upgrade) {
+ /* sent all request body input */
+ /* close connection to the cgi-script */
+ if (-1 == hctx->fdtocgi) { /*(received request body sent in initial send to pipe buffer)*/
+ --srv->cur_fds;
+ if (close(fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "sds", "cgi stdin close failed ", fd, strerror(errno));
+ }
+ } else {
+ cgi_connection_close_fdtocgi(srv, hctx); /*(closes only hctx->fdtocgi)*/
+ }
+ } else {
+ off_t cqlen = cq->bytes_in - cq->bytes_out;
+ if (cq->bytes_in != con->request.content_length && cqlen < 65536 - 16384) {
+ /*(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST)*/
+ if (!(con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_POLLIN)) {
+ con->conf.stream_request_body |= FDEVENT_STREAM_REQUEST_POLLIN;
+ con->is_readable = 1; /* trigger optimistic read from client */
+ }
+ }
+ if (-1 == hctx->fdtocgi) { /*(not registered yet)*/
+ hctx->fdtocgi = fd;
+ hctx->fde_ndx_tocgi = -1;
+ fdevent_register(srv->ev, hctx->fdtocgi, cgi_handle_fdevent_send, hctx);
+ }
+ if (0 == cqlen) { /*(chunkqueue_is_empty(cq))*/
+ if ((fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT)) {
+ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, 0);
+ }
+ } else {
+ /* more request body remains to be sent to CGI so register for fdevents */
+ fdevent_event_set(srv->ev, &(hctx->fde_ndx_tocgi), hctx->fdtocgi, FDEVENT_OUT);
+ }
+ }
+
+ return 0;
+}
+
+static struct stat * cgi_stat(server *srv, connection *con, buffer *path, struct stat *st) {
+ /* CGI might be executable even if it is not readable
+ * (stat_cache_get_entry() currently checks file is readable)*/
+ stat_cache_entry *sce;
+ return (HANDLER_ERROR != stat_cache_get_entry(srv, con, path, &sce))
+ ? &sce->st
+ : (0 == stat(path->ptr, st)) ? st : NULL;
+}
+
+static int cgi_create_env(server *srv, connection *con, plugin_data *p, handler_ctx *hctx, buffer *cgi_handler) {
+ char *args[3];
+ int to_cgi_fds[2];
+ int from_cgi_fds[2];
+ int dfd = -1;
+ UNUSED(p);
+
+ if (!buffer_string_is_empty(cgi_handler)) {
+ /* stat the exec file */
+ struct stat st;
+ if (NULL == cgi_stat(srv, con, cgi_handler, &st)) {
+ log_error_write(srv, __FILE__, __LINE__, "sbss",
+ "stat for cgi-handler", cgi_handler,
+ "failed:", strerror(errno));
+ return -1;
+ }
+ }
+
+ if (pipe_cloexec(to_cgi_fds)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+ if (pipe_cloexec(from_cgi_fds)) {
+ close(to_cgi_fds[0]);
+ close(to_cgi_fds[1]);
+ log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
+ return -1;
+ }
+ fdevent_setfd_cloexec(to_cgi_fds[1]);
+ fdevent_setfd_cloexec(from_cgi_fds[0]);
+
+ {
+ size_t i = 0;
+ http_cgi_opts opts = { 0, 0, NULL, NULL };
+ env_accum *env = &p->env;
+ env->used = 0;
+ env->oused = 0;
+
+ /* create environment */
+
+ http_cgi_headers(srv, con, &opts, cgi_env_add, env);
+
+ /* for valgrind */
+ if (p->env.ld_preload) {
+ cgi_env_add(env, CONST_STR_LEN("LD_PRELOAD"), CONST_BUF_LEN(p->env.ld_preload));
+ }
+ if (p->env.ld_library_path) {
+ cgi_env_add(env, CONST_STR_LEN("LD_LIBRARY_PATH"), CONST_BUF_LEN(p->env.ld_library_path));
+ }
+ #ifdef __CYGWIN__
+ /* CYGWIN needs SYSTEMROOT */
+ if (p->env.systemroot) {
+ cgi_env_add(env, CONST_STR_LEN("SYSTEMROOT"), CONST_BUF_LEN(p->env.systemroot));
+ }
+ #endif
+
+ if (env->esize <= env->oused) {
+ env->esize = (env->oused + 1 + 0xf) & ~(0xfuL);
+ env->eptr = realloc(env->eptr, env->esize * sizeof(*env->eptr));
+ force_assert(env->eptr);
+ }
+ for (i = 0; i < env->oused; ++i) {
+ env->eptr[i] = env->ptr + env->offsets[i];
+ }
+ env->eptr[env->oused] = NULL;
+
+ /* set up args */
+ i = 0;
+
+ if (!buffer_string_is_empty(cgi_handler)) {
+ args[i++] = cgi_handler->ptr;
+ }
+ args[i++] = con->physical.path->ptr;
+ args[i ] = NULL;
+ }
+
+ dfd = fdevent_open_dirname(con->physical.path->ptr);
+ if (-1 == dfd) {
+ log_error_write(srv, __FILE__, __LINE__, "ssb", "open dirname failed:", strerror(errno), con->physical.path);
+ }
+
+ hctx->pid = (dfd >= 0) ? fdevent_fork_execve(args[0], args, p->env.eptr, to_cgi_fds[0], from_cgi_fds[1], -1, dfd) : -1;
+
+ if (-1 == hctx->pid) {
+ /* log error with errno prior to calling close() (might change errno) */
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fork failed:", strerror(errno));
+ if (-1 != dfd) close(dfd);
+ close(from_cgi_fds[0]);
+ close(from_cgi_fds[1]);
+ close(to_cgi_fds[0]);
+ close(to_cgi_fds[1]);
+ return -1;
+ } else {
+ if (-1 != dfd) close(dfd);
+ close(from_cgi_fds[1]);
+ close(to_cgi_fds[0]);
+
+ hctx->fd = from_cgi_fds[0];
+ hctx->fde_ndx = -1;
+
+ ++srv->cur_fds;
+
+ cgi_pid_add(p, hctx->pid, hctx);
+
+ if (0 == con->request.content_length) {
+ close(to_cgi_fds[1]);
+ } else {
+ /* there is content to send */
+ if (-1 == fdevent_fcntl_set_nb(srv->ev, to_cgi_fds[1])) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+ close(to_cgi_fds[1]);
+ cgi_connection_close(srv, hctx);
+ return -1;
+ }
+
+ if (0 != cgi_write_request(srv, hctx, to_cgi_fds[1])) {
+ close(to_cgi_fds[1]);
+ cgi_connection_close(srv, hctx);
+ return -1;
+ }
+
+ ++srv->cur_fds;
+ }
+
+ fdevent_register(srv->ev, hctx->fd, cgi_handle_fdevent, hctx);
+ if (-1 == fdevent_fcntl_set_nb(srv->ev, hctx->fd)) {
+ log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
+ cgi_connection_close(srv, hctx);
+ return -1;
+ }
+ fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN | FDEVENT_RDHUP);
+
+ return 0;
+ }
+}
+
+#define PATCH(x) \
+ p->conf.x = s->x;
+static int mod_cgi_patch_connection(server *srv, connection *con, plugin_data *p) {
+ size_t i, j;
+ plugin_config *s = p->config_storage[0];
+
+ PATCH(cgi);
+ PATCH(execute_x_only);
+ PATCH(local_redir);
+ PATCH(upgrade);
+ PATCH(xsendfile_allow);
+ PATCH(xsendfile_docroot);
+
+ /* skip the first, the global context */
+ for (i = 1; i < srv->config_context->used; i++) {
+ data_config *dc = (data_config *)srv->config_context->data[i];
+ s = p->config_storage[i];
+
+ /* condition didn't match */
+ if (!config_check_cond(srv, con, dc)) continue;
+
+ /* merge config */
+ for (j = 0; j < dc->value->used; j++) {
+ data_unset *du = dc->value->data[j];
+
+ if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.assign"))) {
+ PATCH(cgi);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.execute-x-only"))) {
+ PATCH(execute_x_only);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.local-redir"))) {
+ PATCH(local_redir);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.upgrade"))) {
+ PATCH(upgrade);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.x-sendfile"))) {
+ PATCH(xsendfile_allow);
+ } else if (buffer_is_equal_string(du->key, CONST_STR_LEN("cgi.x-sendfile-docroot"))) {
+ PATCH(xsendfile_docroot);
+ }
+ }
+ }
+
+ return 0;
+}
+#undef PATCH
+
+URIHANDLER_FUNC(cgi_is_handled) {
+ plugin_data *p = p_d;
+ struct stat stbuf;
+ struct stat *st;
+ data_string *ds;
+
+ if (con->mode != DIRECT) return HANDLER_GO_ON;
+ if (buffer_is_empty(con->physical.path)) return HANDLER_GO_ON;
+
+ mod_cgi_patch_connection(srv, con, p);
+
+ ds = (data_string *)array_match_key_suffix(p->conf.cgi, con->physical.path);
+ if (NULL == ds) return HANDLER_GO_ON;
+
+ st = cgi_stat(srv, con, con->physical.path, &stbuf);
+ if (NULL == st) return HANDLER_GO_ON;
+
+ if (!S_ISREG(st->st_mode)) return HANDLER_GO_ON;
+ if (p->conf.execute_x_only == 1 && (st->st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return HANDLER_GO_ON;
+
+ {
+ handler_ctx *hctx = cgi_handler_ctx_init();
+ hctx->remote_conn = con;
+ hctx->plugin_data = p;
+ hctx->cgi_handler = ds->value;
+ memcpy(&hctx->conf, &p->conf, sizeof(plugin_config));
+ hctx->conf.upgrade =
+ hctx->conf.upgrade
+ && con->request.http_version == HTTP_VERSION_1_1
+ && NULL != http_header_request_get(con, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade"));
+ hctx->opts.fdfmt = S_IFIFO;
+ hctx->opts.backend = BACKEND_CGI;
+ hctx->opts.authorizer = 0;
+ hctx->opts.local_redir = hctx->conf.local_redir;
+ hctx->opts.xsendfile_allow = hctx->conf.xsendfile_allow;
+ hctx->opts.xsendfile_docroot = hctx->conf.xsendfile_docroot;
+ hctx->opts.pdata = hctx;
+ hctx->opts.headers = cgi_response_headers;
+ con->plugin_ctx[p->id] = hctx;
+ con->mode = p->id;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+/*
+ * - HANDLER_GO_ON : not our job
+ * - HANDLER_FINISHED: got response
+ * - HANDLER_WAIT_FOR_EVENT: waiting for response
+ */
+SUBREQUEST_FUNC(mod_cgi_handle_subrequest) {
+ plugin_data *p = p_d;
+ handler_ctx *hctx = con->plugin_ctx[p->id];
+ chunkqueue *cq = con->request_content_queue;
+
+ if (con->mode != p->id) return HANDLER_GO_ON;
+ if (NULL == hctx) return HANDLER_GO_ON;
+
+ if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN)
+ && con->file_started) {
+ if (chunkqueue_length(con->write_queue) > 65536 - 4096) {
+ fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ } else if (!(fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_IN)) {
+ /* optimistic read from backend */
+ handler_t rc = cgi_recv_response(srv, hctx); /*(might invalidate hctx)*/
+ if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/
+ fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN);
+ }
+ }
+
+ if (cq->bytes_in != (off_t)con->request.content_length) {
+ /*(64k - 4k to attempt to avoid temporary files
+ * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/
+ if (cq->bytes_in - cq->bytes_out > 65536 - 4096
+ && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){
+ con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN;
+ if (-1 != hctx->fd) return HANDLER_WAIT_FOR_EVENT;
+ } else {
+ handler_t r = connection_handle_read_post_state(srv, con);
+ if (!chunkqueue_is_empty(cq)) {
+ if (fdevent_event_get_interest(srv->ev, hctx->fdtocgi) & FDEVENT_OUT) {
+ return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r;
+ }
+ }
+ if (r != HANDLER_GO_ON) return r;
+
+ /* CGI environment requires that Content-Length be set.
+ * Send 411 Length Required if Content-Length missing.
+ * (occurs here if client sends Transfer-Encoding: chunked
+ * and module is flagged to stream request body to backend) */
+ if (-1 == con->request.content_length) {
+ return connection_handle_read_post_error(srv, con, 411);
+ }
+ }
+ }
+
+ if (-1 == hctx->fd) {
+ if (cgi_create_env(srv, con, p, hctx, hctx->cgi_handler)) {
+ con->http_status = 500;
+ con->mode = DIRECT;
+
+ return HANDLER_FINISHED;
+ }
+ } else if (!chunkqueue_is_empty(con->request_content_queue)) {
+ if (0 != cgi_write_request(srv, hctx, hctx->fdtocgi)) {
+ cgi_connection_close(srv, hctx);
+ return HANDLER_ERROR;
+ }
+ }
+
+ /* if not done, wait for CGI to close stdout, so we read EOF on pipe */
+ return HANDLER_WAIT_FOR_EVENT;
+}
+
+
+static handler_t cgi_waitpid_cb(server *srv, void *p_d, pid_t pid, int status) {
+ plugin_data *p = (plugin_data *)p_d;
+ for (size_t i = 0; i < p->cgi_pid.used; ++i) {
+ handler_ctx *hctx;
+ if (pid != p->cgi_pid.ptr[i].pid) continue;
+
+ hctx = (handler_ctx *)p->cgi_pid.ptr[i].ctx;
+ if (hctx) hctx->pid = -1;
+ cgi_pid_del(p, i);
+
+ if (WIFEXITED(status)) {
+ /* (skip logging (non-zero) CGI exit; might be very noisy) */
+ }
+ else if (WIFSIGNALED(status)) {
+ /* ignore SIGTERM if sent by cgi_connection_close() (NULL == hctx)*/
+ if (WTERMSIG(status) != SIGTERM || NULL != hctx) {
+ log_error_write(srv, __FILE__, __LINE__, "sdsd", "CGI pid", pid,
+ "died with signal", WTERMSIG(status));
+ }
+ }
+ else {
+ log_error_write(srv, __FILE__, __LINE__, "sds",
+ "CGI pid", pid, "ended unexpectedly");
+ }
+
+ return HANDLER_FINISHED;
+ }
+
+ return HANDLER_GO_ON;
+}
+
+
+int mod_cgi_plugin_init(plugin *p);
+int mod_cgi_plugin_init(plugin *p) {
+ p->version = LIGHTTPD_VERSION_ID;
+ p->name = buffer_init_string("cgi");
+
+ p->connection_reset = cgi_connection_close_callback;
+ p->handle_subrequest_start = cgi_is_handled;
+ p->handle_subrequest = mod_cgi_handle_subrequest;
+ p->handle_waitpid = cgi_waitpid_cb;
+ p->init = mod_cgi_init;
+ p->cleanup = mod_cgi_free;
+ p->set_defaults = mod_fastcgi_set_defaults;
+
+ p->data = NULL;
+
+ return 0;
+}