summaryrefslogtreecommitdiff
path: root/src/mod_cgi.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/mod_cgi.c')
-rw-r--r--src/mod_cgi.c1312
1 files changed, 0 insertions, 1312 deletions
diff --git a/src/mod_cgi.c b/src/mod_cgi.c
deleted file mode 100644
index 5e02826a..00000000
--- a/src/mod_cgi.c
+++ /dev/null
@@ -1,1312 +0,0 @@
-#include <sys/types.h>
-
-#include <errno.h>
-#include <stdlib.h>
-#include <string.h>
-#include <signal.h>
-#include <ctype.h>
-#include <assert.h>
-
-#include <stdio.h>
-#include <fcntl.h>
-
-#include "server.h"
-#include "stat_cache.h"
-#include "keyvalue.h"
-#include "log.h"
-#include "connections.h"
-#include "joblist.h"
-#include "fdevent.h"
-#include "inet_ntop_cache.h"
-
-#include "plugin.h"
-#include "http_resp.h"
-
-#include "sys-files.h"
-#include "sys-mmap.h"
-#include "sys-socket.h"
-#include "sys-strings.h"
-#include "sys-process.h"
-
-#include "network_backends.h"
-
-#ifdef HAVE_SYS_FILIO_H
-# include <sys/filio.h>
-#endif
-
-enum {EOL_UNSET, EOL_N, EOL_RN};
-
-typedef struct {
- char **ptr;
-
- size_t size;
- size_t used;
-} char_array;
-
-#define pid_t int
-typedef struct {
- pid_t *ptr;
- size_t used;
- size_t size;
-} buffer_pid_t;
-
-typedef struct {
- array *cgi;
- unsigned short execute_all;
- unsigned short execute_x_only;
-} plugin_config;
-
-typedef struct {
- PLUGIN_DATA;
- buffer_pid_t cgi_pid;
-
- buffer *tmp_buf;
-
- http_resp *resp;
-
- plugin_config **config_storage;
-
- plugin_config conf;
-} plugin_data;
-
-typedef enum {
- CGI_STATE_UNSET,
- CGI_STATE_CONNECTING,
- CGI_STATE_READ_RESPONSE_HEADER,
- CGI_STATE_READ_RESPONSE_CONTENT
-} cgi_state_t;
-
-typedef struct {
- pid_t pid;
-
- iosocket *sock;
- iosocket *sock_err;
- iosocket *wb_sock;
-
- chunkqueue *rb;
- chunkqueue *rb_err;
- chunkqueue *wb;
-
- cgi_state_t state;
-
- connection *remote_con; /* dumb pointer */
-} cgi_session;
-
-static cgi_session * cgi_session_init() {
- cgi_session *sess = calloc(1, sizeof(*sess));
- assert(sess);
-
- sess->sock = iosocket_init();
- sess->sock_err = iosocket_init();
- sess->wb_sock = iosocket_init();
- sess->wb = chunkqueue_init();
- sess->rb = chunkqueue_init();
- sess->rb_err = chunkqueue_init();
-
- return sess;
-}
-
-static void cgi_session_free(cgi_session *sess) {
- if (!sess) return;
-
- iosocket_free(sess->sock);
- iosocket_free(sess->sock_err);
- iosocket_free(sess->wb_sock);
-
- chunkqueue_free(sess->wb);
- chunkqueue_free(sess->rb);
- chunkqueue_free(sess->rb_err);
-
- free(sess);
-}
-
-INIT_FUNC(mod_cgi_init) {
- plugin_data *p;
-
- UNUSED(srv);
-
- p = calloc(1, sizeof(*p));
-
- assert(p);
-
- p->tmp_buf = buffer_init();
- p->resp = http_response_init();
-
- 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];
-
- array_free(s->cgi);
-
- free(s);
- }
- free(p->config_storage);
- }
-
-
- if (r->ptr) free(r->ptr);
-
- buffer_free(p->tmp_buf);
- http_response_free(p->resp);
-
- free(p);
-
- return HANDLER_GO_ON;
-}
-
-#define PLUGIN_NAME "cgi"
-#define CONFIG_ASSIGN PLUGIN_NAME ".assign"
-#define CONFIG_EXECUTE_ALL PLUGIN_NAME ".execute-all"
-#define CONFIG_EXECUTE_X_ONLY PLUGIN_NAME ".execute-x-only"
-
-SETDEFAULTS_FUNC(mod_cgi_set_defaults) {
- plugin_data *p = p_d;
- size_t i = 0;
-
- config_values_t cv[] = {
- { CONFIG_ASSIGN, NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 0 */
- { CONFIG_EXECUTE_ALL, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 1 */
- { CONFIG_EXECUTE_X_ONLY, NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 2 */
- { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET}
- };
-
- if (!p) return HANDLER_ERROR;
-
- p->config_storage = calloc(1, srv->config_context->used * sizeof(specific_config *));
-
- for (i = 0; i < srv->config_context->used; i++) {
- plugin_config *s;
-
- s = calloc(1, sizeof(plugin_config));
- assert(s);
-
- s->cgi = array_init();
- s->execute_all = 0;
- s->execute_x_only = 0;
-
- cv[0].destination = s->cgi;
- cv[1].destination = &(s->execute_all);
- cv[2].destination = &(s->execute_x_only);
-
- p->config_storage[i] = s;
-
- if (0 != config_insert_values_global(srv, ((data_config *)srv->config_context->data[i])->value, cv)) {
- return HANDLER_ERROR;
- }
- }
-
- return HANDLER_GO_ON;
-}
-
-
-static int cgi_pid_add(server *srv, plugin_data *p, pid_t pid) {
- int m = -1;
- size_t i;
- buffer_pid_t *r = &(p->cgi_pid);
-
- UNUSED(srv);
-
- for (i = 0; i < r->used; i++) {
- if (r->ptr[i] > m) m = r->ptr[i];
- }
-
- if (r->size == 0) {
- r->size = 16;
- r->ptr = malloc(sizeof(*r->ptr) * r->size);
- } else if (r->used == r->size) {
- r->size += 16;
- r->ptr = realloc(r->ptr, sizeof(*r->ptr) * r->size);
- }
-
- r->ptr[r->used++] = pid;
-
- return m;
-}
-
-static int cgi_pid_del(server *srv, plugin_data *p, pid_t pid) {
- size_t i;
- buffer_pid_t *r = &(p->cgi_pid);
-
- UNUSED(srv);
-
- for (i = 0; i < r->used; i++) {
- if (r->ptr[i] == pid) break;
- }
-
- if (i != r->used) {
- /* found */
-
- if (i != r->used - 1) {
- r->ptr[i] = r->ptr[r->used - 1];
- }
- r->used--;
- }
-
- return 0;
-}
-
-/**
- * Copy decoded response content to client connection.
- */
-static int cgi_copy_response(server *srv, connection *con, cgi_session *sess) {
- chunk *c;
- int we_have = 0;
-
- UNUSED(srv);
-
- chunkqueue_remove_finished_chunks(sess->rb);
- /* copy the content to the next cq */
- for (c = sess->rb->first; c; c = c->next) {
- if (c->mem->used == 0) continue;
-
- we_have = chunkqueue_steal_chunk(con->send, c);
- sess->rb->bytes_out += we_have;
- con->send->bytes_in += we_have;
- }
- chunkqueue_remove_finished_chunks(sess->rb);
-
- if(sess->rb->is_closed) {
- con->send->is_closed = 1;
- }
- return 0;
-}
-
-
-static int cgi_demux_response(server *srv, connection *con, plugin_data *p) {
- cgi_session *sess = con->plugin_ctx[p->id];
-
- switch(srv->network_backend_read(srv, con, sess->sock, sess->rb)) {
- case NETWORK_STATUS_CONNECTION_CLOSE:
- fdevent_event_del(srv->ev, sess->sock);
-
- /* connection closed. close the read chunkqueue. */
- sess->rb->is_closed = 1;
- case NETWORK_STATUS_SUCCESS:
- /* we got content */
- break;
- case NETWORK_STATUS_WAIT_FOR_EVENT:
- return 0;
- default:
- /* oops */
- ERROR("%s", "oops, read-pipe-read failed and I don't know why");
- return -1;
- }
-
- /* looks like we got some content
- *
- * split off the header from the incoming stream
- */
-
- if (con->file_started == 0) {
- size_t i;
- int have_content_length = 0;
-
- http_response_reset(p->resp);
-
- /* the response header is not fully received yet,
- *
- * extract the http-response header from the rb-cq
- */
- switch (http_response_parse_cq(sess->rb, p->resp)) {
- case PARSE_UNSET:
- case PARSE_ERROR:
- /* parsing failed */
-
- TRACE("%s", "response parser failed");
-
- con->http_status = 502; /* Bad Gateway */
- return -1;
- case PARSE_NEED_MORE:
- if (sess->rb->is_closed) {
- /* backend died before sending a header */
- con->http_status = 502; /* Bad Gateway */
- return -1;
- }
- return 0;
- case PARSE_SUCCESS:
- con->http_status = p->resp->status;
-
- chunkqueue_remove_finished_chunks(sess->rb);
-
- /* copy the http-headers */
- for (i = 0; i < p->resp->headers->used; i++) {
- const char *ign[] = { "Status", "Connection", NULL };
- size_t j;
- data_string *ds;
-
- data_string *header = (data_string *)p->resp->headers->data[i];
-
- /* some headers are ignored by default */
- for (j = 0; ign[j]; j++) {
- if (0 == strcasecmp(ign[j], header->key->ptr)) break;
- }
- if (ign[j]) continue;
-
- if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Location"))) {
- /* CGI/1.1 rev 03 - 7.2.1.2 */
- if (con->http_status == 0) con->http_status = 302;
- } else if (0 == buffer_caseless_compare(CONST_BUF_LEN(header->key), CONST_STR_LEN("Content-Length"))) {
- have_content_length = 1;
- }
-
- if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) {
- ds = data_response_init();
- }
- buffer_copy_string_buffer(ds->key, header->key);
- buffer_copy_string_buffer(ds->value, header->value);
-
- array_insert_unique(con->response.headers, (data_unset *)ds);
- }
-
- con->file_started = 1;
- /* if Status: ... is not set, 200 is our default status-code */
- if (con->http_status == 0) con->http_status = 200;
- sess->state = CGI_STATE_READ_RESPONSE_CONTENT;
-
- if (con->request.http_version == HTTP_VERSION_1_1 &&
- !have_content_length) {
- con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED;
- }
-
- break;
- }
- }
-
- /* FIXME: pass the response-header to the other plugins to
- * setup the filter-queue
- *
- * - use next-queue instead of con->write_queue
- */
-
- /* copy the resopnse content */
- cgi_copy_response(srv, con, sess);
-
- joblist_append(srv, con);
-
- return 0;
-}
-
-static handler_t cgi_connection_close(server *srv, connection *con, plugin_data *p) {
- cgi_session *sess = con->plugin_ctx[p->id];
- int status;
- pid_t pid;
-
- if (NULL == sess) return HANDLER_GO_ON;
- if (con->mode != p->id) return HANDLER_GO_ON;
-
-#ifndef _WIN32
-
- /* the connection to the browser went away, but we still have a connection
- * to the CGI script
- *
- * close cgi-connection
- */
-
- if (sess->sock->fd != -1) {
- /* close connection to the cgi-script */
- fdevent_event_del(srv->ev, sess->sock);
- fdevent_unregister(srv->ev, sess->sock);
- }
-
- if (sess->sock_err->fd != -1) {
- /* close connection to the cgi-script */
- fdevent_event_del(srv->ev, sess->sock_err);
- fdevent_unregister(srv->ev, sess->sock_err);
- }
-
- if (sess->wb_sock->fd != -1) {
- close(sess->wb_sock->fd);
- sess->wb_sock->fd = -1;
- }
-
- pid = sess->pid;
-
- con->plugin_ctx[p->id] = NULL;
-
- /* is this a good idea ? */
- cgi_session_free(sess);
- sess = NULL;
-
- /* if waitpid hasn't been called by response.c yet, do it here */
- if (pid) {
- /* check if the CGI-script is already gone */
-#ifndef _WIN32
- switch(waitpid(pid, &status, WNOHANG)) {
- case 0:
- /* not finished yet */
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", pid);
-#endif
- break;
- case -1:
- /* */
- if (errno == EINTR) break;
-
- /*
- * errno == ECHILD happens if _subrequest catches the process-status before
- * we have read the response of the cgi process
- *
- * -> catch status
- * -> WAIT_FOR_EVENT
- * -> read response
- * -> we get here with waitpid == ECHILD
- *
- */
- if (errno == ECHILD) return HANDLER_GO_ON;
-
- log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
- return HANDLER_ERROR;
- default:
- /* Send an error if we haven't sent any data yet */
- if (0 == con->file_started) {
- if (con->http_status == 0) con->http_status = 500;
- con->mode = DIRECT;
- }
-
- if (WIFEXITED(status)) {
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", pid);
-#endif
- pid = 0;
-
- return HANDLER_GO_ON;
- } else {
- log_error_write(srv, __FILE__, __LINE__, "sd", "cgi died, pid:", pid);
- pid = 0;
- return HANDLER_GO_ON;
- }
- }
-
-
- kill(pid, SIGTERM);
-#endif
- /* cgi-script is still alive, queue the PID for removal */
- cgi_pid_add(srv, p, pid);
- }
-#endif
- return HANDLER_GO_ON;
-}
-
-static handler_t cgi_connection_close_callback(server *srv, connection *con, void *p_d) {
- plugin_data *p = p_d;
-
- return cgi_connection_close(srv, con, p);
-}
-
-static handler_t cgi_handle_fdevent(void *s, void *ctx, int revents) {
- server *srv = (server *)s;
- cgi_session *sess = ctx;
- connection *con = sess->remote_con;
-
- if (revents & FDEVENT_IN) {
- switch (sess->state) {
- case CGI_STATE_READ_RESPONSE_HEADER:
- /* parse the header and set file-started, the demuxer will care about it */
- joblist_append(srv, con);
-
- break;
- case CGI_STATE_READ_RESPONSE_CONTENT:
- /* just forward the content to the out-going queue */
-
- chunkqueue_remove_finished_chunks(sess->rb);
-
- switch (srv->network_backend_read(srv, con, sess->sock, sess->rb)) {
- case NETWORK_STATUS_CONNECTION_CLOSE:
- fdevent_event_del(srv->ev, sess->sock);
-
- /* connection closed. close the read chunkqueue. */
- sess->rb->is_closed = 1;
- case NETWORK_STATUS_SUCCESS:
- /* read even more, do we have all the content */
-
- /* how much do we want to read ? */
-
- /* copy the resopnse content */
- cgi_copy_response(srv, con, sess);
-
- break;
- default:
- ERROR("%s", "oops, we failed to read");
- break;
- }
-
- joblist_append(srv, con);
- break;
- default:
- TRACE("unexpected state for a FDEVENT_IN: %d", sess->state);
- break;
- }
- }
-
- if (revents & FDEVENT_OUT) {
- /* nothing to do */
- }
-
- /* perhaps this issue is already handled */
- if (revents & FDEVENT_HUP) {
- con->send->is_closed = 1;
-
- fdevent_event_del(srv->ev, sess->sock);
-
- joblist_append(srv, con);
- } else if (revents & FDEVENT_ERR) {
- con->send->is_closed = 1;
-
- /* kill all connections to the cgi process */
- fdevent_event_del(srv->ev, sess->sock);
- joblist_append(srv, con);
- }
-
- return HANDLER_FINISHED;
-}
-
-/* so all cgi errors have the same source line as origin */
-static void cgi_log_err(const char *msg) {
- ERROR("error from cgi: %s", msg);
-}
-
-static void cgi_copy_err(chunkqueue *cq) {
- buffer *line = buffer_init();
- chunk *c;
-
- for (c = cq->first; c; c = c->next) {
- off_t we_have;
- char *str, *nl;
-
- if (c->type != MEM_CHUNK) {
- ERROR("%s", "wrong chunk type");
- chunk_set_done(c);
- continue;
- }
-
- we_have = c->mem->used - 1 - c->offset;
- str = c->mem->ptr + c->offset;
- if (we_have <= 0) continue;
-
- for ( ; NULL != (nl = strchr(str, '\n')); str = nl+1) {
- *nl = '\0';
- if (!buffer_is_empty(line)) {
- buffer_append_string(line, str);
- cgi_log_err(SAFE_BUF_STR(line));
- buffer_reset(line);
- } else {
- cgi_log_err(str);
- }
- }
- if (*str) {
- buffer_append_string(line, str);
- }
- chunk_set_done(c);
- }
-
- if (!buffer_is_empty(line)) {
- cgi_log_err(SAFE_BUF_STR(line));
- }
- chunkqueue_remove_finished_chunks(cq);
-}
-
-static handler_t cgi_handle_err_fdevent(void *s, void *ctx, int revents) {
- server *srv = (server *)s;
- cgi_session *sess = ctx;
- connection *con = sess->remote_con;
-
- if (revents & (FDEVENT_IN | FDEVENT_HUP)) {
- switch (srv->network_backend_read(srv, con, sess->sock_err, sess->rb_err)) {
- case NETWORK_STATUS_CONNECTION_CLOSE:
- fdevent_event_del(srv->ev, sess->sock_err);
-
- /* connection closed. close the read chunkqueue. */
- sess->rb_err->is_closed = 1;
- break;
- case NETWORK_STATUS_SUCCESS:
- break;
- default:
- ERROR("%s", "oops, we failed to read");
- break;
- }
-
- cgi_copy_err(sess->rb_err);
- }
-
- if (revents & FDEVENT_ERR) {
- fdevent_event_del(srv->ev, sess->sock_err);
- }
-
- return HANDLER_FINISHED;
-}
-
-
-static int cgi_env_add(char_array *env, const char *key, size_t key_len, const char *val, size_t val_len) {
- char *dst;
-
- if (!key || !val) return -1;
-
- dst = malloc(key_len + val_len + 3);
- 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->size == 0) {
- env->size = 16;
- env->ptr = malloc(env->size * sizeof(*env->ptr));
- } else if (env->size == env->used) {
- env->size += 16;
- env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr));
- }
-
- env->ptr[env->used++] = dst;
-
- return 0;
-}
-
-static const char *sock_addr_to_p(server *srv, sock_addr *addr) {
- switch (addr->plain.sa_family) {
- case AF_INET:
- return inet_ntoa(addr->ipv4.sin_addr);
-#ifdef HAVE_IPV6
- case AF_INET6:
- return inet_ntop_cache_get_ip(srv, addr);
-#endif
-#ifdef HAVE_SYS_UN_H
- case AF_UNIX:
- return addr->un.sun_path;
-#endif
- }
- return "";
-}
-
-static int cgi_create_env(server *srv, connection *con, plugin_data *p, buffer *cgi_handler) {
- pid_t pid;
-
- int to_cgi_fds[2];
- int from_cgi_fds[2];
- int from_cgi_err_fds[2];
- struct stat st;
-
-#ifndef _WIN32
-
- if (cgi_handler && cgi_handler->used > 1) {
- /* stat the exec file */
- if (-1 == (stat(cgi_handler->ptr, &st))) {
- log_error_write(srv, __FILE__, __LINE__, "sbss",
- "stat for cgi-handler", cgi_handler,
- "failed:", strerror(errno));
- return -1;
- }
- }
-
- if (pipe(to_cgi_fds)) {
- log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
- return -1;
- }
-
- if (pipe(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;
- }
-
- if (pipe(from_cgi_err_fds)) {
- close(to_cgi_fds[0]); close(to_cgi_fds[1]);
- close(from_cgi_fds[0]); close(from_cgi_fds[1]);
- log_error_write(srv, __FILE__, __LINE__, "ss", "pipe failed:", strerror(errno));
- return -1;
- }
-
- /* fork, execve */
- switch (pid = fork()) {
- case 0: {
- /* child */
- char **args;
- int argc;
- int i = 0;
- char buf[32];
- size_t n;
- char_array env;
- char *c;
- const char *s;
- server_socket *srv_sock = con->srv_socket;
-
- /* move stdout to from_cgi_fd[1] */
- close(STDOUT_FILENO);
- dup2(from_cgi_fds[1], STDOUT_FILENO);
- close(from_cgi_fds[1]);
- /* not needed */
- close(from_cgi_fds[0]);
-
- /* move stderr to from_cgi_err_fd[1] */
- close(STDERR_FILENO);
- dup2(from_cgi_err_fds[1], STDERR_FILENO);
- close(from_cgi_err_fds[1]);
- /* not needed */
- close(from_cgi_err_fds[0]);
-
- /* move the stdin to to_cgi_fd[0] */
- close(STDIN_FILENO);
- dup2(to_cgi_fds[0], STDIN_FILENO);
- close(to_cgi_fds[0]);
- /* not needed */
- close(to_cgi_fds[1]);
-
- /* create environment */
- env.ptr = NULL;
- env.size = 0;
- env.used = 0;
-
- cgi_env_add(&env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_NAME"/"PACKAGE_VERSION));
-
- s = sock_addr_to_p(srv, &srv_sock->addr);
- cgi_env_add(&env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s));
- /* !!! careful: s maybe reused for SERVER_NAME !!! */
-
- if (!buffer_is_empty(con->server_name)) {
- size_t len = con->server_name->used - 1;
- char *colon = strchr(con->server_name->ptr, ':');
- if (colon) len = colon - con->server_name->ptr;
-
- cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), con->server_name->ptr, len);
- } else {
- /* use SERVER_ADDR */
- cgi_env_add(&env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s));
- }
- cgi_env_add(&env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1"));
-
- s = get_http_version_name(con->request.http_version);
-
- cgi_env_add(&env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s));
-
- LI_ltostr(buf, sock_addr_get_port(&srv_sock->addr));
- cgi_env_add(&env, CONST_STR_LEN("SERVER_PORT"), buf, strlen(buf));
-
- s = get_http_method_name(con->request.http_method);
- cgi_env_add(&env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s));
-
- if (!buffer_is_empty(con->request.pathinfo)) {
- cgi_env_add(&env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo));
- }
- cgi_env_add(&env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200"));
- if (!buffer_is_empty(con->uri.query)) {
- cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query));
- } else {
- /* set a empty QUERY_STRING */
- cgi_env_add(&env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN(""));
- }
- if (!buffer_is_empty(con->request.orig_uri)) {
- cgi_env_add(&env, CONST_STR_LEN("REQUEST_URI"), CONST_BUF_LEN(con->request.orig_uri));
- }
-
- s = sock_addr_to_p(srv, &con->dst_addr);
- cgi_env_add(&env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s));
-
- LI_ltostr(buf, sock_addr_get_port(&con->dst_addr));
- cgi_env_add(&env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf));
-
- if (!buffer_is_empty(con->authed_user)) {
- cgi_env_add(&env, CONST_STR_LEN("REMOTE_USER"),
- CONST_BUF_LEN(con->authed_user));
- }
-
-#ifdef USE_OPENSSL
- if (srv_sock->is_ssl) {
- cgi_env_add(&env, CONST_STR_LEN("HTTPS"), CONST_STR_LEN("on"));
- }
-#endif
-
- /* request.content_length < SSIZE_MAX, see request.c */
- if (con->request.content_length > 0) {
- LI_ltostr(buf, con->request.content_length);
- cgi_env_add(&env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf));
- }
- cgi_env_add(&env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(con->physical.path));
- cgi_env_add(&env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path));
- cgi_env_add(&env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.doc_root));
-
- /* for valgrind */
- if (NULL != (s = getenv("LD_PRELOAD"))) {
- cgi_env_add(&env, CONST_STR_LEN("LD_PRELOAD"), s, strlen(s));
- }
-
- if (NULL != (s = getenv("LD_LIBRARY_PATH"))) {
- cgi_env_add(&env, CONST_STR_LEN("LD_LIBRARY_PATH"), s, strlen(s));
- }
-#ifdef __CYGWIN__
- /* CYGWIN needs SYSTEMROOT */
- if (NULL != (s = getenv("SYSTEMROOT"))) {
- cgi_env_add(&env, CONST_STR_LEN("SYSTEMROOT"), s, strlen(s));
- }
-#endif
-
- for (n = 0; n < con->request.headers->used; n++) {
- data_string *ds;
-
- ds = (data_string *)con->request.headers->data[n];
-
- if (ds->value->used && ds->key->used) {
- size_t j;
-
- buffer_reset(p->tmp_buf);
-
- if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) {
- buffer_copy_string_len(p->tmp_buf, CONST_STR_LEN("HTTP_"));
- p->tmp_buf->used--; /* strip \0 after HTTP_ */
- }
-
- buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
-
- for (j = 0; j < ds->key->used - 1; j++) {
- char cr = '_';
- if (light_isalpha(ds->key->ptr[j])) {
- /* upper-case */
- cr = ds->key->ptr[j] & ~32;
- } else if (light_isdigit(ds->key->ptr[j])) {
- /* copy */
- cr = ds->key->ptr[j];
- }
- p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
- }
- p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
-
- cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
- }
- }
-
- for (n = 0; n < con->environment->used; n++) {
- data_string *ds;
-
- ds = (data_string *)con->environment->data[n];
-
- if (ds->value->used && ds->key->used) {
- size_t j;
-
- buffer_reset(p->tmp_buf);
-
- buffer_prepare_append(p->tmp_buf, ds->key->used + 2);
-
- for (j = 0; j < ds->key->used - 1; j++) {
- char cr = '_';
- if (light_isalpha(ds->key->ptr[j])) {
- /* upper-case */
- cr = ds->key->ptr[j] & ~32;
- } else if (light_isdigit(ds->key->ptr[j])) {
- /* copy */
- cr = ds->key->ptr[j];
- }
- p->tmp_buf->ptr[p->tmp_buf->used++] = cr;
- }
- p->tmp_buf->ptr[p->tmp_buf->used++] = '\0';
-
- cgi_env_add(&env, CONST_BUF_LEN(p->tmp_buf), CONST_BUF_LEN(ds->value));
- }
- }
-
- if (env.size == env.used) {
- env.size += 16;
- env.ptr = realloc(env.ptr, env.size * sizeof(*env.ptr));
- }
-
- env.ptr[env.used] = NULL;
-
- /* set up args */
- argc = 3;
- args = malloc(sizeof(*args) * argc);
- i = 0;
-
- if (cgi_handler && cgi_handler->used > 1) {
- args[i++] = cgi_handler->ptr;
- }
- args[i++] = con->physical.path->ptr;
- args[i++] = NULL;
-
- /* search for the last / */
- if (NULL != (c = strrchr(con->physical.path->ptr, '/'))) {
- *c = '\0';
-
- /* change to the physical directory */
- if (-1 == chdir(con->physical.path->ptr)) {
- log_error_write(srv, __FILE__, __LINE__, "ssb", "chdir failed:", strerror(errno), con->physical.path);
- }
- *c = '/';
- }
-
- /* we don't need the client socket */
- for (i = 3; i < 256; i++) {
- close(i);
- }
-
- /* exec the cgi */
- execve(args[0], args, env.ptr);
-
- /* */
- SEGFAULT("execve(%s) failed: %s", args[0], strerror(errno));
- break;
- }
- case -1:
- /* error */
- ERROR("fork() failed: %s", strerror(errno));
- close(to_cgi_fds[0]); close(to_cgi_fds[1]);
- close(from_cgi_fds[0]); close(from_cgi_fds[1]);
- close(from_cgi_err_fds[0]); close(from_cgi_err_fds[1]);
- return -1;
- break;
- default: {
- cgi_session *sess;
- /* father */
-
- close(from_cgi_fds[1]);
- close(from_cgi_err_fds[1]);
- close(to_cgi_fds[0]);
-
- /* register PID and wait for them asyncronously */
- con->mode = p->id;
- buffer_reset(con->physical.path);
-
- sess = cgi_session_init();
-
- sess->remote_con = con;
- sess->pid = pid;
-
- assert(sess->sock);
-
- sess->sock->fd = from_cgi_fds[0];
- sess->sock->type = IOSOCKET_TYPE_PIPE;
- sess->sock_err->fd = from_cgi_err_fds[0];
- sess->sock_err->type = IOSOCKET_TYPE_PIPE;
- sess->wb_sock->fd = to_cgi_fds[1];
- sess->wb_sock->type = IOSOCKET_TYPE_PIPE;
-
- if (-1 == fdevent_fcntl_set(srv->ev, sess->sock)) {
- log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
-
- cgi_session_free(sess);
-
- return -1;
- }
-
- if (-1 == fdevent_fcntl_set(srv->ev, sess->sock_err)) {
- log_error_write(srv, __FILE__, __LINE__, "ss", "fcntl failed: ", strerror(errno));
-
- cgi_session_free(sess);
-
- return -1;
- }
-
- con->plugin_ctx[p->id] = sess;
-
- fdevent_register(srv->ev, sess->sock, cgi_handle_fdevent, sess);
- fdevent_event_add(srv->ev, sess->sock, FDEVENT_IN);
-
- fdevent_register(srv->ev, sess->sock_err, cgi_handle_err_fdevent, sess);
- fdevent_event_add(srv->ev, sess->sock_err, FDEVENT_IN);
-
- sess->state = CGI_STATE_READ_RESPONSE_HEADER;
-
- break;
- }
- }
-
- return 0;
-#else
- return -1;
-#endif
-}
-
-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_OPTION(cgi);
- PATCH_OPTION(execute_all);
- PATCH_OPTION(execute_x_only);
-
- /* 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(CONFIG_ASSIGN))) {
- PATCH_OPTION(cgi);
- } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXECUTE_ALL))) {
- PATCH_OPTION(execute_all);
- } else if (buffer_is_equal_string(du->key, CONST_STR_LEN(CONFIG_EXECUTE_X_ONLY))) {
- PATCH_OPTION(execute_x_only);
- }
- }
- }
-
- return 0;
-}
-
-URIHANDLER_FUNC(mod_cgi_start_backend) {
- size_t k, s_len;
- plugin_data *p = p_d;
- buffer *fn = con->physical.path;
- stat_cache_entry *sce = NULL;
-
- if (fn->used == 0) return HANDLER_GO_ON;
-
- mod_cgi_patch_connection(srv, con, p);
-
- if (p->conf.cgi->used == 0 && p->conf.execute_all == 0) {
- return HANDLER_GO_ON;
- }
-
- if (con->conf.log_request_handling) {
- TRACE("-- checking request in mod_%s", "cgi");
- }
-
- if (HANDLER_ERROR == stat_cache_get_entry(srv, con, con->physical.path, &sce)) return HANDLER_GO_ON;
- if (!S_ISREG(sce->st.st_mode)) return HANDLER_GO_ON;
- if (p->conf.execute_x_only == 1 && (sce->st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) return HANDLER_GO_ON;
-
- s_len = fn->used - 1;
-
- for (k = 0; k < p->conf.cgi->used; k++) {
- data_string *ds = (data_string *)p->conf.cgi->data[k];
- size_t ct_len = ds->key->used - 1;
-
- if (ds->key->used == 0) continue;
- if (s_len < ct_len) continue;
-
- if (0 == strncmp(fn->ptr + s_len - ct_len, ds->key->ptr, ct_len)) {
- if (cgi_create_env(srv, con, p, ds->value)) {
- con->http_status = 500;
-
- buffer_reset(con->physical.path);
- return HANDLER_FINISHED;
- }
- return HANDLER_FINISHED;
- }
- }
-
- if (p->conf.execute_all) {
- if (cgi_create_env(srv, con, p, NULL)) {
- con->http_status = 500;
-
- buffer_reset(con->physical.path);
- return HANDLER_FINISHED;
- }
- return HANDLER_FINISHED;
- }
-
- return HANDLER_GO_ON;
-}
-
-TRIGGER_FUNC(cgi_trigger) {
- plugin_data *p = p_d;
- size_t ndx;
- /* the trigger handle only cares about lonely PID which we have to wait for */
-#ifndef _WIN32
-
- for (ndx = 0; ndx < p->cgi_pid.used; ndx++) {
- int status;
-
- switch(waitpid(p->cgi_pid.ptr[ndx], &status, WNOHANG)) {
- case 0:
- /* not finished yet */
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) child isn't done yet, pid:", p->cgi_pid.ptr[ndx]);
-#endif
- break;
- case -1:
- log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
-
- return HANDLER_ERROR;
- default:
-
- if (WIFEXITED(status)) {
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sd", "(debug) cgi exited fine, pid:", p->cgi_pid.ptr[ndx]);
-#endif
- } else if (WIFSIGNALED(status)) {
- log_error_write(srv, __FILE__, __LINE__, "sdsd", "cgi signaled, pid:", p->cgi_pid.ptr[ndx], ", signal", WTERMSIG(status));
- } else {
- log_error_write(srv, __FILE__, __LINE__, "sdsd", "cgi died, pid:", p->cgi_pid.ptr[ndx], ", status", status);
- }
-
- cgi_pid_del(srv, p, p->cgi_pid.ptr[ndx]);
- /* del modified the buffer structure
- * and copies the last entry to the current one
- * -> recheck the current index
- */
- ndx--;
- }
- }
-#endif
- return HANDLER_GO_ON;
-}
-
-SUBREQUEST_FUNC(mod_cgi_read_response_content) {
- int status;
- plugin_data *p = p_d;
- cgi_session *sess = con->plugin_ctx[p->id];
-
- if (con->mode != p->id) return HANDLER_GO_ON;
- if (NULL == sess) return HANDLER_GO_ON;
-
- switch (cgi_demux_response(srv, con, p)) {
- case 0:
- break;
- case 1:
- cgi_connection_close(srv, con, p);
-
- /* if we get a IN|HUP and have read everything don't exec the close twice */
- return HANDLER_FINISHED;
- case -1:
- cgi_connection_close(srv, con, p);
-
- if (0 == con->http_status) con->http_status = 500;
- con->mode = DIRECT;
-
- return HANDLER_FINISHED;
- }
-
-#if 0
- log_error_write(srv, __FILE__, __LINE__, "sdd", "subrequest, pid =", sess, sess->pid);
-#endif
- if (sess->pid == 0) return HANDLER_FINISHED;
-#ifndef _WIN32
- switch(waitpid(sess->pid, &status, WNOHANG)) {
- case 0:
- /* we only have for events here if we don't have the header yet,
- * otherwise the event-handler will send us the incoming data */
-
- if (!con->file_started) return HANDLER_WAIT_FOR_EVENT;
- if (con->send->is_closed) return HANDLER_FINISHED;
-
- return HANDLER_GO_ON;
- case -1:
- if (errno == EINTR) return HANDLER_WAIT_FOR_EVENT;
-
- if (errno == ECHILD && con->file_started == 0) {
- /*
- * second round but still not response
- */
- return HANDLER_WAIT_FOR_EVENT;
- }
-
- log_error_write(srv, __FILE__, __LINE__, "ss", "waitpid failed: ", strerror(errno));
- con->mode = DIRECT;
- con->http_status = 500;
-
- sess->pid = 0;
-
- fdevent_event_del(srv->ev, sess->sock);
- fdevent_unregister(srv->ev, sess->sock);
-
- fdevent_event_del(srv->ev, sess->sock_err);
- fdevent_unregister(srv->ev, sess->sock_err);
-
- cgi_session_free(sess);
- sess = NULL;
-
- con->plugin_ctx[p->id] = NULL;
-
- return HANDLER_FINISHED;
- default:
- con->send->is_closed = 1;
-
- if (WIFEXITED(status)) {
- /* nothing */
- } else {
- log_error_write(srv, __FILE__, __LINE__, "s", "cgi died ?");
-
- con->mode = DIRECT;
- con->http_status = 500;
-
- }
-
- sess->pid = 0;
-
- fdevent_event_del(srv->ev, sess->sock);
- fdevent_unregister(srv->ev, sess->sock);
-
- fdevent_event_del(srv->ev, sess->sock_err);
- fdevent_unregister(srv->ev, sess->sock_err);
-
- cgi_session_free(sess);
- sess = NULL;
-
- con->plugin_ctx[p->id] = NULL;
- return HANDLER_FINISHED;
- }
-#else
- return HANDLER_ERROR;
-#endif
-}
-
-URIHANDLER_FUNC(mod_cgi_send_request_content) {
- plugin_data *p = p_d;
- cgi_session *sess = con->plugin_ctx[p->id];
-
- if (p->id != con->mode) return HANDLER_GO_ON;
-
- if (con->request.content_length > 0 && con->recv->bytes_in > con->recv->bytes_out) {
- /* write request content. */
- switch (network_write_chunkqueue_write(srv, con, sess->wb_sock, con->recv)) {
- case NETWORK_STATUS_SUCCESS:
- /** fall through, still have data to write. */
- case NETWORK_STATUS_WAIT_FOR_EVENT:
- /** fall through */
- case NETWORK_STATUS_WAIT_FOR_AIO_EVENT:
- break;
- case NETWORK_STATUS_CONNECTION_CLOSE:
- /* the script might have written a response already. */
- break;
- default:
- TRACE("%s", "(error)");
- return HANDLER_ERROR;
- }
- chunkqueue_remove_finished_chunks(con->recv);
- }
- /* we have to close the pipe to finish the request. */
- if ((con->recv->is_closed && con->recv->bytes_in == con->recv->bytes_out) ||
- con->request.content_length <= 0) {
- close(sess->wb_sock->fd);
- sess->wb_sock->fd = -1;
- } else {
- /* there is more data to write. */
- return HANDLER_GO_ON;
- }
-
- return mod_cgi_read_response_content(srv, con, p_d);
-}
-
-
-LI_EXPORT int mod_cgi_plugin_init(plugin *p);
-LI_EXPORT 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_start_backend = mod_cgi_start_backend;
- p->handle_send_request_content = mod_cgi_send_request_content;
- p->handle_read_response_content = mod_cgi_read_response_content;
-
- p->handle_trigger = cgi_trigger;
- p->init = mod_cgi_init;
- p->cleanup = mod_cgi_free;
- p->set_defaults = mod_cgi_set_defaults;
-
- p->data = NULL;
-
- return 0;
-}