--- embedaddon/lighttpd/src/mod_scgi.c 2014/06/15 20:20:06 1.1.1.2 +++ embedaddon/lighttpd/src/mod_scgi.c 2016/11/02 10:35:00 1.1.1.3 @@ -1,3 +1,5 @@ +#include "first.h" + #include "buffer.h" #include "server.h" #include "keyvalue.h" @@ -25,10 +27,6 @@ #include -#ifdef HAVE_SYS_FILIO_H -# include -#endif - #include "sys-socket.h" #ifdef HAVE_SYS_UIO_H @@ -39,8 +37,6 @@ # include #endif -#include "version.h" - enum {EOL_UNSET, EOL_N, EOL_RN}; /* @@ -148,6 +144,7 @@ typedef struct { */ buffer *host; unsigned short port; + sa_family_t family; /* * Unix Domain Socket @@ -214,6 +211,15 @@ typedef struct { */ unsigned short fix_root_path_name; + + /* + * If the backend includes X-Sendfile in the response + * we use the value as filename and ignore the content. + * + */ + unsigned short xsendfile_allow; + array *xsendfile_docroot; + ssize_t load; /* replace by host->load */ size_t max_id; /* corresponds most of the time to @@ -221,6 +227,9 @@ typedef struct { only if a process is killed max_id waits for the process itself to die and decrements its afterwards */ + + int listen_backlog; + int refcount; } scgi_extension_host; /* @@ -294,9 +303,6 @@ typedef enum { FCGI_STATE_INIT, FCGI_STATE_CONNECT, FC typedef struct { buffer *response; - size_t response_len; - int response_type; - int response_padding; scgi_proc *proc; scgi_extension_host *host; @@ -304,21 +310,17 @@ typedef struct { scgi_connection_state_t state; time_t state_timestamp; - int reconnects; /* number of reconnect attempts */ - - read_buffer *rb; chunkqueue *wb; + off_t wb_reqlen; buffer *response_header; - int delayed; /* flag to mark that the connect() is delayed */ - - size_t request_id; int fd; /* fd to the scgi process */ int fde_ndx; /* index into the fd-event buffer */ pid_t pid; int got_proc; + int reconnects; /* number of reconnect attempts */ plugin_config conf; @@ -358,18 +360,15 @@ static handler_ctx * handler_ctx_init(void) { hctx->response = buffer_init(); hctx->response_header = buffer_init(); - hctx->request_id = 0; hctx->state = FCGI_STATE_INIT; hctx->proc = NULL; - hctx->response_len = 0; - hctx->response_type = 0; - hctx->response_padding = 0; hctx->fd = -1; hctx->reconnects = 0; hctx->wb = chunkqueue_init(); + hctx->wb_reqlen = 0; return hctx; } @@ -380,11 +379,6 @@ static void handler_ctx_free(handler_ctx *hctx) { chunkqueue_free(hctx->wb); - if (hctx->rb) { - if (hctx->rb->ptr) free(hctx->rb->ptr); - free(hctx->rb); - } - free(hctx); } @@ -392,6 +386,7 @@ static scgi_proc *scgi_process_init(void) { scgi_proc *f; f = calloc(1, sizeof(*f)); + force_assert(f); f->socket = buffer_init(); f->prev = NULL; @@ -421,12 +416,17 @@ static scgi_extension_host *scgi_host_init(void) { f->bin_path = buffer_init(); f->bin_env = array_init(); f->bin_env_copy = array_init(); + f->xsendfile_docroot = array_init(); return f; } static void scgi_host_free(scgi_extension_host *h) { if (!h) return; + if (h->refcount) { + --h->refcount; + return; + } buffer_free(h->host); buffer_free(h->unixsocket); @@ -434,6 +434,7 @@ static void scgi_host_free(scgi_extension_host *h) { buffer_free(h->bin_path); array_free(h->bin_env); array_free(h->bin_env_copy); + array_free(h->xsendfile_docroot); scgi_process_free(h->first); scgi_process_free(h->unused_procs); @@ -446,6 +447,7 @@ static scgi_exts *scgi_extensions_init(void) { scgi_exts *f; f = calloc(1, sizeof(*f)); + force_assert(f); return f; } @@ -497,7 +499,7 @@ static int scgi_extension_insert(scgi_exts *ext, buffe fe = calloc(1, sizeof(*fe)); force_assert(fe); fe->key = buffer_init(); - buffer_copy_string_buffer(fe->key, key); + buffer_copy_buffer(fe->key, key); /* */ @@ -535,6 +537,7 @@ INIT_FUNC(mod_scgi_init) { plugin_data *p; p = calloc(1, sizeof(*p)); + force_assert(p); p->scgi_env = buffer_init(); @@ -560,7 +563,7 @@ FREE_FUNC(mod_scgi_free) { plugin_config *s = p->config_storage[i]; scgi_exts *exts; - if (!s) continue; + if (NULL == s) continue; exts = s->exts; @@ -579,7 +582,7 @@ FREE_FUNC(mod_scgi_free) { if (proc->pid != 0) kill(proc->pid, SIGTERM); if (proc->is_local && - !buffer_is_empty(proc->socket)) { + !buffer_string_is_empty(proc->socket)) { unlink(proc->socket->ptr); } } @@ -588,7 +591,7 @@ FREE_FUNC(mod_scgi_free) { if (proc->pid != 0) kill(proc->pid, SIGTERM); if (proc->is_local && - !buffer_is_empty(proc->socket)) { + !buffer_string_is_empty(proc->socket)) { unlink(proc->socket->ptr); } } @@ -614,6 +617,7 @@ static int env_add(char_array *env, const char *key, s if (!key || !val) return -1; dst = malloc(key_len + val_len + 3); + force_assert(dst); memcpy(dst, key, key_len); dst[key_len] = '='; /* add the \0 from the value */ @@ -631,9 +635,11 @@ static int env_add(char_array *env, const char *key, s if (env->size == 0) { env->size = 16; env->ptr = malloc(env->size * sizeof(*env->ptr)); + force_assert(env->ptr); } else if (env->size == env->used) { env->size += 16; env->ptr = realloc(env->ptr, env->size * sizeof(*env->ptr)); + force_assert(env->ptr); } env->ptr[env->used++] = dst; @@ -641,60 +647,82 @@ static int env_add(char_array *env, const char *key, s return 0; } +#if !defined(HAVE_FORK) static int scgi_spawn_connection(server *srv, - plugin_data *p, - scgi_extension_host *host, - scgi_proc *proc) { + plugin_data *p, + scgi_extension_host *host, + scgi_proc *proc) { + UNUSED(srv); + UNUSED(p); + UNUSED(host); + UNUSED(proc); + return -1; +} + +#else /* -> defined(HAVE_FORK) */ + +static int scgi_spawn_connection(server *srv, + plugin_data *p, + scgi_extension_host *host, + scgi_proc *proc) { int scgi_fd; - int socket_type, status; + int status; struct timeval tv = { 0, 100 * 1000 }; #ifdef HAVE_SYS_UN_H struct sockaddr_un scgi_addr_un; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + struct sockaddr_in6 scgi_addr_in6; +#endif struct sockaddr_in scgi_addr_in; struct sockaddr *scgi_addr; socklen_t servlen; -#ifndef HAVE_FORK - return -1; -#endif - if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sdb", "new proc, socket:", proc->port, proc->socket); } - if (!buffer_is_empty(proc->socket)) { - memset(&scgi_addr, 0, sizeof(scgi_addr)); + if (!buffer_string_is_empty(proc->socket)) { #ifdef HAVE_SYS_UN_H + memset(&scgi_addr_un, 0, sizeof(scgi_addr_un)); scgi_addr_un.sun_family = AF_UNIX; - if (proc->socket->used > sizeof(scgi_addr_un.sun_path)) { + if (buffer_string_length(proc->socket) + 1 > sizeof(scgi_addr_un.sun_path)) { log_error_write(srv, __FILE__, __LINE__, "sB", "ERROR: Unix Domain socket filename too long:", proc->socket); return -1; } - memcpy(scgi_addr_un.sun_path, proc->socket->ptr, proc->socket->used); + memcpy(scgi_addr_un.sun_path, proc->socket->ptr, buffer_string_length(proc->socket) + 1); #ifdef SUN_LEN servlen = SUN_LEN(&scgi_addr_un); #else /* stevens says: */ - servlen = proc->socket->used + sizeof(scgi_addr_un.sun_family); + servlen = buffer_string_length(proc->socket) + 1 + sizeof(scgi_addr_un.sun_family); #endif - socket_type = AF_UNIX; scgi_addr = (struct sockaddr *) &scgi_addr_un; #else log_error_write(srv, __FILE__, __LINE__, "s", "ERROR: Unix Domain sockets are not supported."); return -1; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + } else if (host->family == AF_INET6 && !buffer_string_is_empty(host->host)) { + memset(&scgi_addr_in6, 0, sizeof(scgi_addr_in6)); + scgi_addr_in6.sin6_family = AF_INET6; + inet_pton(AF_INET6, host->host->ptr, (char *) &scgi_addr_in6.sin6_addr); + scgi_addr_in6.sin6_port = htons(proc->port); + servlen = sizeof(scgi_addr_in6); + scgi_addr = (struct sockaddr *) &scgi_addr_in6; +#endif } else { + memset(&scgi_addr_in, 0, sizeof(scgi_addr_in)); scgi_addr_in.sin_family = AF_INET; - if (buffer_is_empty(host->host)) { + if (buffer_string_is_empty(host->host)) { scgi_addr_in.sin_addr.s_addr = htonl(INADDR_ANY); } else { struct hostent *he; @@ -726,11 +754,10 @@ static int scgi_spawn_connection(server *srv, scgi_addr_in.sin_port = htons(proc->port); servlen = sizeof(scgi_addr_in); - socket_type = AF_INET; scgi_addr = (struct sockaddr *) &scgi_addr_in; } - if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + if (-1 == (scgi_fd = socket(scgi_addr->sa_family, SOCK_STREAM, 0))) { log_error_write(srv, __FILE__, __LINE__, "ss", "failed:", strerror(errno)); return -1; @@ -741,14 +768,14 @@ static int scgi_spawn_connection(server *srv, pid_t child; int val; - if (!buffer_is_empty(proc->socket)) { + if (!buffer_string_is_empty(proc->socket)) { unlink(proc->socket->ptr); } close(scgi_fd); /* reopen socket */ - if (-1 == (scgi_fd = socket(socket_type, SOCK_STREAM, 0))) { + if (-1 == (scgi_fd = socket(scgi_addr->sa_family, SOCK_STREAM, 0))) { log_error_write(srv, __FILE__, __LINE__, "ss", "socket failed:", strerror(errno)); return -1; @@ -773,14 +800,13 @@ static int scgi_spawn_connection(server *srv, return -1; } - if (-1 == listen(scgi_fd, 1024)) { + if (-1 == listen(scgi_fd, host->listen_backlog)) { log_error_write(srv, __FILE__, __LINE__, "ss", "listen failed:", strerror(errno)); close(scgi_fd); return -1; } -#ifdef HAVE_FORK switch ((child = fork())) { case 0: { buffer *b; @@ -815,11 +841,12 @@ static int scgi_spawn_connection(server *srv, } } } else { - for (i = 0; environ[i]; i++) { + char ** const e = environ; + for (i = 0; e[i]; ++i) { char *eq; - if (NULL != (eq = strchr(environ[i], '='))) { - env_add(&env, environ[i], eq - environ[i], eq+1, strlen(eq+1)); + if (NULL != (eq = strchr(e[i], '='))) { + env_add(&env, e[i], eq - e[i], eq+1, strlen(eq+1)); } } } @@ -855,15 +882,17 @@ static int scgi_spawn_connection(server *srv, log_error_write(srv, __FILE__, __LINE__, "sbs", "execl failed for:", host->bin_path, strerror(errno)); - exit(errno); + _exit(errno); break; } case -1: /* error */ + close(scgi_fd); break; default: /* father */ + close(scgi_fd); /* wait */ select(0, NULL, NULL, NULL, &tv); @@ -902,8 +931,9 @@ static int scgi_spawn_connection(server *srv, break; } -#endif } else { + close(scgi_fd); + proc->is_local = 0; proc->pid = 0; @@ -917,12 +947,30 @@ static int scgi_spawn_connection(server *srv, proc->state = PROC_STATE_RUNNING; host->active_procs++; - close(scgi_fd); - return 0; } +#endif /* HAVE_FORK */ +static scgi_extension_host * unixsocket_is_dup(plugin_data *p, size_t used, buffer *unixsocket) { + size_t i, j, n; + for (i = 0; i < used; ++i) { + scgi_exts *exts = p->config_storage[i]->exts; + for (j = 0; j < exts->used; ++j) { + scgi_extension *ex = exts->exts[j]; + for (n = 0; n < ex->used; ++n) { + scgi_extension_host *host = ex->hosts[n]; + if (!buffer_string_is_empty(host->unixsocket) + && buffer_is_equal(host->unixsocket, unixsocket) + && !buffer_string_is_empty(host->bin_path)) + return host; + } + } + } + + return NULL; +} + SETDEFAULTS_FUNC(mod_scgi_set_defaults) { plugin_data *p = p_d; data_unset *du; @@ -936,12 +984,14 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { }; 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; - array *ca; s = malloc(sizeof(plugin_config)); + force_assert(s); s->exts = scgi_extensions_init(); s->debug = 0; @@ -949,9 +999,8 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { cv[1].destination = &(s->debug); p->config_storage[i] = s; - ca = ((data_config *)srv->config_context->data[i])->value; - if (0 != config_insert_values_global(srv, ca, cv)) { + if (0 != config_insert_values_global(srv, config->value, cv, i == 0 ? T_CONFIG_SCOPE_SERVER : T_CONFIG_SCOPE_CONNECTION)) { goto error; } @@ -959,13 +1008,13 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { * = ( ... ) */ - if (NULL != (du = array_get_element(ca, "scgi.server"))) { + if (NULL != (du = array_get_element(config->value, "scgi.server"))) { size_t j; data_array *da = (data_array *)du; if (du->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "sss", - "unexpected type for key: ", "scgi.server", "array of strings"); + "unexpected type for key: ", "scgi.server", "expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -983,7 +1032,7 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { if (da->value->data[j]->type != TYPE_ARRAY) { log_error_write(srv, __FILE__, __LINE__, "sssbs", "unexpected type for key: ", "scgi.server", - "[", da->value->data[j]->key, "](string)"); + "[", da->value->data[j]->key, "](string); expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -1020,6 +1069,9 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { { "bin-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 11 */ { "bin-copy-environment", NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 12 */ { "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 13 */ + { "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ + { "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ + { "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } @@ -1029,7 +1081,7 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { log_error_write(srv, __FILE__, __LINE__, "ssSBS", "unexpected type for key:", "scgi.server", - "[", da_host->key, "](string)"); + "[", da_host->key, "](string); expected ( \"ext\" => ( \"backend-label\" => ( \"key\" => \"value\" )))"); goto error; } @@ -1043,6 +1095,9 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { df->idle_timeout = 60; df->disable_time = 60; df->fix_root_path_name = 0; + df->listen_backlog = 1024; + df->xsendfile_allow = 0; + df->refcount = 0; fcv[0].destination = df->host; fcv[1].destination = df->docroot; @@ -1060,34 +1115,54 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { fcv[11].destination = df->bin_env; fcv[12].destination = df->bin_env_copy; fcv[13].destination = &(df->fix_root_path_name); + fcv[14].destination = &(df->listen_backlog); + fcv[15].destination = &(df->xsendfile_allow); + fcv[16].destination = df->xsendfile_docroot; - if (0 != config_insert_values_internal(srv, da_host->value, fcv)) { + if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) { goto error; } - if ((!buffer_is_empty(df->host) || df->port) && - !buffer_is_empty(df->unixsocket)) { + if ((!buffer_string_is_empty(df->host) || df->port) && + !buffer_string_is_empty(df->unixsocket)) { log_error_write(srv, __FILE__, __LINE__, "s", "either host+port or socket"); goto error; } - if (!buffer_is_empty(df->unixsocket)) { + if (!buffer_string_is_empty(df->unixsocket)) { /* unix domain socket */ struct sockaddr_un un; - if (df->unixsocket->used > sizeof(un.sun_path) - 2) { + if (buffer_string_length(df->unixsocket) + 1 > sizeof(un.sun_path) - 2) { log_error_write(srv, __FILE__, __LINE__, "s", "path of the unixdomain socket is too large"); goto error; } + + if (!buffer_string_is_empty(df->bin_path)) { + scgi_extension_host *duplicate = unixsocket_is_dup(p, i+1, df->unixsocket); + if (NULL != duplicate) { + if (!buffer_is_equal(df->bin_path, duplicate->bin_path)) { + log_error_write(srv, __FILE__, __LINE__, "sb", + "duplicate unixsocket path:", + df->unixsocket); + goto error; + } + scgi_host_free(df); + df = duplicate; + ++df->refcount; + } + } + + df->family = AF_UNIX; } else { /* tcp/ip */ - if (buffer_is_empty(df->host) && - buffer_is_empty(df->bin_path)) { + if (buffer_string_is_empty(df->host) && + buffer_string_is_empty(df->bin_path)) { log_error_write(srv, __FILE__, __LINE__, "sbbbs", "missing key (string):", da->key, @@ -1105,9 +1180,13 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { "port"); goto error; } + + df->family = (!buffer_string_is_empty(df->host) && NULL != strchr(df->host->ptr, ':')) ? AF_INET6 : AF_INET; } - if (!buffer_is_empty(df->bin_path)) { + if (df->refcount) { + /* already init'd; skip spawning */ + } else if (!buffer_string_is_empty(df->bin_path)) { /* a local socket + self spawning */ size_t pno; @@ -1134,12 +1213,12 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { proc->id = df->num_procs++; df->max_id++; - if (buffer_is_empty(df->unixsocket)) { + if (buffer_string_is_empty(df->unixsocket)) { proc->port = df->port + pno; } else { - buffer_copy_string_buffer(proc->socket, df->unixsocket); + buffer_copy_buffer(proc->socket, df->unixsocket); buffer_append_string_len(proc->socket, CONST_STR_LEN("-")); - buffer_append_long(proc->socket, pno); + buffer_append_int(proc->socket, pno); } if (s->debug) { @@ -1150,7 +1229,8 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { "\n\tcurrent:", pno, "/", df->min_procs); } - if (scgi_spawn_connection(srv, p, df, proc)) { + if (!srv->srvconf.preflight_check + && scgi_spawn_connection(srv, p, df, proc)) { log_error_write(srv, __FILE__, __LINE__, "s", "[ERROR]: spawning fcgi failed."); scgi_process_free(proc); @@ -1171,10 +1251,10 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { df->active_procs++; fp->state = PROC_STATE_RUNNING; - if (buffer_is_empty(df->unixsocket)) { + if (buffer_string_is_empty(df->unixsocket)) { fp->port = df->port; } else { - buffer_copy_string_buffer(fp->socket, df->unixsocket); + buffer_copy_buffer(fp->socket, df->unixsocket); } df->first = fp; @@ -1183,6 +1263,25 @@ SETDEFAULTS_FUNC(mod_scgi_set_defaults) { df->max_procs = 1; } + if (df->xsendfile_docroot->used) { + size_t k; + for (k = 0; k < df->xsendfile_docroot->used; ++k) { + data_string *ds = (data_string *)df->xsendfile_docroot->data[k]; + if (ds->type != TYPE_STRING) { + log_error_write(srv, __FILE__, __LINE__, "s", + "unexpected type for x-sendfile-docroot; expected: \"x-sendfile-docroot\" => ( \"/allowed/path\", ... )"); + goto error; + } + if (ds->value->ptr[0] != '/') { + log_error_write(srv, __FILE__, __LINE__, "SBs", + "x-sendfile-docroot paths must begin with '/'; invalid: \"", ds->value, "\""); + goto error; + } + buffer_path_simplify(ds->value, ds->value); + buffer_append_slash(ds->value); + } + } + /* if extension already exists, take it */ scgi_extension_insert(s->exts, da_ext->key, df); df = NULL; @@ -1206,12 +1305,10 @@ static int scgi_set_state(server *srv, handler_ctx *hc } -static void scgi_connection_cleanup(server *srv, handler_ctx *hctx) { +static void scgi_connection_close(server *srv, handler_ctx *hctx) { plugin_data *p; connection *con; - if (NULL == hctx) return; - p = hctx->plugin_data; con = hctx->remote_conn; @@ -1243,6 +1340,11 @@ static void scgi_connection_cleanup(server *srv, handl handler_ctx_free(hctx); con->plugin_ctx[p->id] = NULL; + + /* finish response (if not already con->file_started, con->file_finished) */ + if (con->mode == p->id) { + http_response_backend_done(srv, con); + } } static int scgi_reconnect(server *srv, handler_ctx *hctx) { @@ -1274,7 +1376,6 @@ static int scgi_reconnect(server *srv, handler_ctx *hc scgi_set_state(srv, hctx, FCGI_STATE_INIT); - hctx->request_id = 0; hctx->reconnects++; if (p->conf.debug) { @@ -1293,9 +1394,9 @@ static int scgi_reconnect(server *srv, handler_ctx *hc static handler_t scgi_connection_reset(server *srv, connection *con, void *p_d) { plugin_data *p = p_d; + handler_ctx *hctx = con->plugin_ctx[p->id]; + if (hctx) scgi_connection_close(srv, hctx); - scgi_connection_cleanup(srv, con->plugin_ctx[p->id]); - return HANDLER_GO_ON; } @@ -1307,14 +1408,12 @@ static int scgi_env_add(buffer *env, const char *key, len = key_len + val_len + 2; - buffer_prepare_append(env, len); + buffer_string_prepare_append(env, len); - memcpy(env->ptr + env->used, key, key_len); - env->ptr[env->used + key_len] = '\0'; - env->used += key_len + 1; - memcpy(env->ptr + env->used, val, val_len); - env->ptr[env->used + val_len] = '\0'; - env->used += val_len + 1; + buffer_append_string_len(env, key, key_len); + buffer_append_string_len(env, "", 1); + buffer_append_string_len(env, val, val_len); + buffer_append_string_len(env, "", 1); return 0; } @@ -1331,6 +1430,9 @@ static int scgi_env_add(buffer *env, const char *key, static int scgi_establish_connection(server *srv, handler_ctx *hctx) { struct sockaddr *scgi_addr; struct sockaddr_in scgi_addr_in; +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + struct sockaddr_in6 scgi_addr_in6; +#endif #ifdef HAVE_SYS_UN_H struct sockaddr_un scgi_addr_un; #endif @@ -1340,31 +1442,40 @@ static int scgi_establish_connection(server *srv, hand scgi_proc *proc = hctx->proc; int scgi_fd = hctx->fd; - memset(&scgi_addr, 0, sizeof(scgi_addr)); - - if (!buffer_is_empty(proc->socket)) { + if (!buffer_string_is_empty(proc->socket)) { #ifdef HAVE_SYS_UN_H /* use the unix domain socket */ + memset(&scgi_addr_un, 0, sizeof(scgi_addr_un)); scgi_addr_un.sun_family = AF_UNIX; - if (proc->socket->used > sizeof(scgi_addr_un.sun_path)) { + if (buffer_string_length(proc->socket) + 1 > sizeof(scgi_addr_un.sun_path)) { log_error_write(srv, __FILE__, __LINE__, "sB", "ERROR: Unix Domain socket filename too long:", proc->socket); return -1; } - memcpy(scgi_addr_un.sun_path, proc->socket->ptr, proc->socket->used); + memcpy(scgi_addr_un.sun_path, proc->socket->ptr, buffer_string_length(proc->socket) + 1); #ifdef SUN_LEN servlen = SUN_LEN(&scgi_addr_un); #else /* stevens says: */ - servlen = proc->socket->used + sizeof(scgi_addr_un.sun_family); + servlen = buffer_string_length(proc->socket) + 1 + sizeof(scgi_addr_un.sun_family); #endif scgi_addr = (struct sockaddr *) &scgi_addr_un; #else return -1; #endif +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) + } else if (host->family == AF_INET6 && !buffer_string_is_empty(host->host)) { + memset(&scgi_addr_in6, 0, sizeof(scgi_addr_in6)); + scgi_addr_in6.sin6_family = AF_INET6; + inet_pton(AF_INET6, host->host->ptr, (char *) &scgi_addr_in6.sin6_addr); + scgi_addr_in6.sin6_port = htons(proc->port); + servlen = sizeof(scgi_addr_in6); + scgi_addr = (struct sockaddr *) &scgi_addr_in6; +#endif } else { + memset(&scgi_addr_in, 0, sizeof(scgi_addr_in)); scgi_addr_in.sin_family = AF_INET; if (0 == inet_aton(host->host->ptr, &(scgi_addr_in.sin_addr))) { log_error_write(srv, __FILE__, __LINE__, "sbs", @@ -1424,22 +1535,15 @@ static int scgi_env_add_request_headers(server *srv, c ds = (data_string *)con->request.headers->data[i]; - if (ds->value->used && ds->key->used) { - size_t j; - buffer_reset(srv->tmp_buf); - - if (0 != strcasecmp(ds->key->ptr, "CONTENT-TYPE")) { - buffer_copy_string_len(srv->tmp_buf, CONST_STR_LEN("HTTP_")); - srv->tmp_buf->used--; + if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) { + /* Do not emit HTTP_PROXY in environment. + * Some executables use HTTP_PROXY to configure + * outgoing proxy. See also https://httpoxy.org/ */ + if (buffer_is_equal_caseless_string(ds->key, CONST_STR_LEN("Proxy"))) { + continue; } - buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); - for (j = 0; j < ds->key->used - 1; j++) { - srv->tmp_buf->ptr[srv->tmp_buf->used++] = - light_isalpha(ds->key->ptr[j]) ? - ds->key->ptr[j] & ~32 : '_'; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; + buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 1); scgi_env_add(p->scgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)); } @@ -1450,18 +1554,9 @@ static int scgi_env_add_request_headers(server *srv, c ds = (data_string *)con->environment->data[i]; - if (ds->value->used && ds->key->used) { - size_t j; - buffer_reset(srv->tmp_buf); + if (!buffer_is_empty(ds->value) && !buffer_is_empty(ds->key)) { + buffer_copy_string_encoded_cgi_varnames(srv->tmp_buf, CONST_BUF_LEN(ds->key), 0); - buffer_prepare_append(srv->tmp_buf, ds->key->used + 2); - for (j = 0; j < ds->key->used - 1; j++) { - srv->tmp_buf->ptr[srv->tmp_buf->used++] = - light_isalnum((unsigned char)ds->key->ptr[j]) ? - toupper((unsigned char)ds->key->ptr[j]) : '_'; - } - srv->tmp_buf->ptr[srv->tmp_buf->used++] = '\0'; - scgi_env_add(p->scgi_env, CONST_BUF_LEN(srv->tmp_buf), CONST_BUF_LEN(ds->value)); } } @@ -1471,7 +1566,7 @@ static int scgi_env_add_request_headers(server *srv, c static int scgi_create_env(server *srv, handler_ctx *hctx) { - char buf[32]; + char buf[LI_ITOSTRING_LENGTH]; const char *s; #ifdef HAVE_IPV6 char b2[INET6_ADDRSTRLEN + 1]; @@ -1487,25 +1582,19 @@ static int scgi_create_env(server *srv, handler_ctx *h sock_addr our_addr; socklen_t our_addr_len; - buffer_prepare_copy(p->scgi_env, 1024); + buffer_string_prepare_copy(p->scgi_env, 1023); /* CGI-SPEC 6.1.2, FastCGI spec 6.3 and SCGI spec */ - /* request.content_length < SSIZE_MAX, see request.c */ - LI_ltostr(buf, con->request.content_length); + li_itostrn(buf, sizeof(buf), con->request.content_length); scgi_env_add(p->scgi_env, CONST_STR_LEN("CONTENT_LENGTH"), buf, strlen(buf)); scgi_env_add(p->scgi_env, CONST_STR_LEN("SCGI"), CONST_STR_LEN("1")); + scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_BUF_LEN(con->conf.server_tag)); - if (buffer_is_empty(con->conf.server_tag)) { - scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_STR_LEN(PACKAGE_DESC)); - } else { - scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_SOFTWARE"), CONST_BUF_LEN(con->conf.server_tag)); - } + if (!buffer_is_empty(con->server_name)) { + size_t len = buffer_string_length(con->server_name); - if (con->server_name->used) { - size_t len = con->server_name->used - 1; - if (con->server_name->ptr[0] == '[') { const char *colon = strstr(con->server_name->ptr, "]:"); if (colon) len = (colon + 1) - con->server_name->ptr; @@ -1525,12 +1614,13 @@ static int scgi_create_env(server *srv, handler_ctx *h #else s = inet_ntoa(srv_sock->addr.ipv4.sin_addr); #endif + force_assert(s); scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_NAME"), s, strlen(s)); } scgi_env_add(p->scgi_env, CONST_STR_LEN("GATEWAY_INTERFACE"), CONST_STR_LEN("CGI/1.1")); - LI_ltostr(buf, + li_utostrn(buf, sizeof(buf), #ifdef HAVE_IPV6 ntohs(srv_sock->addr.plain.sa_family ? srv_sock->addr.ipv6.sin6_port : srv_sock->addr.ipv4.sin_port) #else @@ -1543,14 +1633,15 @@ static int scgi_create_env(server *srv, handler_ctx *h /* get the server-side of the connection to the client */ our_addr_len = sizeof(our_addr); - if (-1 == getsockname(con->fd, &(our_addr.plain), &our_addr_len)) { + if (-1 == getsockname(con->fd, (struct sockaddr *)&our_addr, &our_addr_len) + || our_addr_len > (socklen_t)sizeof(our_addr)) { s = inet_ntop_cache_get_ip(srv, &(srv_sock->addr)); } else { s = inet_ntop_cache_get_ip(srv, &(our_addr)); } scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_ADDR"), s, strlen(s)); - LI_ltostr(buf, + li_utostrn(buf, sizeof(buf), #ifdef HAVE_IPV6 ntohs(con->dst_addr.plain.sa_family ? con->dst_addr.ipv6.sin6_port : con->dst_addr.ipv4.sin_port) #else @@ -1561,6 +1652,7 @@ static int scgi_create_env(server *srv, handler_ctx *h scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_PORT"), buf, strlen(buf)); s = inet_ntop_cache_get_ip(srv, &(con->dst_addr)); + force_assert(s); scgi_env_add(p->scgi_env, CONST_STR_LEN("REMOTE_ADDR"), s, strlen(s)); /* @@ -1571,15 +1663,15 @@ static int scgi_create_env(server *srv, handler_ctx *h scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_NAME"), CONST_BUF_LEN(con->uri.path)); - if (!buffer_is_empty(con->request.pathinfo)) { + if (!buffer_string_is_empty(con->request.pathinfo)) { scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_INFO"), CONST_BUF_LEN(con->request.pathinfo)); /* PATH_TRANSLATED is only defined if PATH_INFO is set */ - if (!buffer_is_empty(host->docroot)) { - buffer_copy_string_buffer(p->path, host->docroot); + if (!buffer_string_is_empty(host->docroot)) { + buffer_copy_buffer(p->path, host->docroot); } else { - buffer_copy_string_buffer(p->path, con->physical.basedir); + buffer_copy_buffer(p->path, con->physical.basedir); } buffer_append_string_buffer(p->path, con->request.pathinfo); scgi_env_add(p->scgi_env, CONST_STR_LEN("PATH_TRANSLATED"), CONST_BUF_LEN(p->path)); @@ -1595,19 +1687,19 @@ static int scgi_create_env(server *srv, handler_ctx *h * parameter. */ - if (!buffer_is_empty(host->docroot)) { + if (!buffer_string_is_empty(host->docroot)) { /* * rewrite SCRIPT_FILENAME * */ - buffer_copy_string_buffer(p->path, host->docroot); + buffer_copy_buffer(p->path, host->docroot); buffer_append_string_buffer(p->path, con->uri.path); scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)); scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(host->docroot)); } else { - buffer_copy_string_buffer(p->path, con->physical.path); + buffer_copy_buffer(p->path, con->physical.path); scgi_env_add(p->scgi_env, CONST_STR_LEN("SCRIPT_FILENAME"), CONST_BUF_LEN(p->path)); scgi_env_add(p->scgi_env, CONST_STR_LEN("DOCUMENT_ROOT"), CONST_BUF_LEN(con->physical.basedir)); @@ -1616,16 +1708,22 @@ static int scgi_create_env(server *srv, handler_ctx *h if (!buffer_is_equal(con->request.uri, con->request.orig_uri)) { scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_URI"), CONST_BUF_LEN(con->request.uri)); } - if (!buffer_is_empty(con->uri.query)) { + if (!buffer_string_is_empty(con->uri.query)) { scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_BUF_LEN(con->uri.query)); } else { scgi_env_add(p->scgi_env, CONST_STR_LEN("QUERY_STRING"), CONST_STR_LEN("")); } s = get_http_method_name(con->request.http_method); + force_assert(s); scgi_env_add(p->scgi_env, CONST_STR_LEN("REQUEST_METHOD"), s, strlen(s)); - scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); /* if php is compiled with --force-redirect */ + /* set REDIRECT_STATUS for php compiled with --force-redirect + * (if REDIRECT_STATUS has not already been set by error handler) */ + if (0 == con->error_handler_saved_status) { + scgi_env_add(p->scgi_env, CONST_STR_LEN("REDIRECT_STATUS"), CONST_STR_LEN("200")); + } s = get_http_version_name(con->request.http_version); + force_assert(s); scgi_env_add(p->scgi_env, CONST_STR_LEN("SERVER_PROTOCOL"), s, strlen(s)); #ifdef USE_OPENSSL @@ -1636,65 +1734,20 @@ static int scgi_create_env(server *srv, handler_ctx *h scgi_env_add_request_headers(srv, con, p); - b = chunkqueue_get_append_buffer(hctx->wb); + b = buffer_init(); - buffer_append_long(b, p->scgi_env->used); + buffer_append_int(b, buffer_string_length(p->scgi_env)); buffer_append_string_len(b, CONST_STR_LEN(":")); - buffer_append_string_len(b, (const char *)p->scgi_env->ptr, p->scgi_env->used); + buffer_append_string_buffer(b, p->scgi_env); buffer_append_string_len(b, CONST_STR_LEN(",")); - hctx->wb->bytes_in += b->used - 1; + hctx->wb_reqlen = buffer_string_length(b); + chunkqueue_append_buffer(hctx->wb, b); + buffer_free(b); if (con->request.content_length) { - chunkqueue *req_cq = con->request_content_queue; - chunk *req_c; - off_t offset; - - /* something to send ? */ - for (offset = 0, req_c = req_cq->first; offset != req_cq->bytes_in; req_c = req_c->next) { - off_t weWant = req_cq->bytes_in - offset; - off_t weHave = 0; - - /* we announce toWrite octects - * now take all the request_content chunk that we need to fill this request - * */ - - switch (req_c->type) { - case FILE_CHUNK: - weHave = req_c->file.length - req_c->offset; - - if (weHave > weWant) weHave = weWant; - - chunkqueue_append_file(hctx->wb, req_c->file.name, req_c->offset, weHave); - - req_c->offset += weHave; - req_cq->bytes_out += weHave; - - hctx->wb->bytes_in += weHave; - - break; - case MEM_CHUNK: - /* append to the buffer */ - weHave = req_c->mem->used - 1 - req_c->offset; - - if (weHave > weWant) weHave = weWant; - - b = chunkqueue_get_append_buffer(hctx->wb); - buffer_append_memory(b, req_c->mem->ptr + req_c->offset, weHave); - b->used++; /* add virtual \0 */ - - req_c->offset += weHave; - req_cq->bytes_out += weHave; - - hctx->wb->bytes_in += weHave; - - break; - default: - break; - } - - offset += weHave; - } + chunkqueue_append_chunkqueue(hctx->wb, con->request_content_queue); + hctx->wb_reqlen += con->request.content_length;/* (eventual) total request size */ } return 0; @@ -1707,7 +1760,7 @@ static int scgi_response_parse(server *srv, connection UNUSED(srv); - buffer_copy_string_buffer(p->parse_response, in); + buffer_copy_buffer(p->parse_response, in); for (s = p->parse_response->ptr; NULL != (ns = (eol == EOL_RN ? strstr(s, "\r\n") : strchr(s, '\n'))); @@ -1766,8 +1819,13 @@ static int scgi_response_parse(server *srv, connection break; case 6: if (0 == strncasecmp(key, "Status", key_len)) { - con->http_status = strtol(value, NULL, 10); - con->parsed_response |= HTTP_STATUS; + int status = strtol(value, NULL, 10); + if (status >= 100 && status < 1000) { + con->http_status = status; + con->parsed_response |= HTTP_STATUS; + } else { + con->http_status = 502; + } } break; case 8: @@ -1783,7 +1841,7 @@ static int scgi_response_parse(server *srv, connection break; case 14: if (0 == strncasecmp(key, "Content-Length", key_len)) { - con->response.content_length = strtol(value, NULL, 10); + con->response.content_length = strtoul(value, NULL, 10); con->parsed_response |= HTTP_CONTENT_LENGTH; } break; @@ -1810,10 +1868,11 @@ static int scgi_demux_response(server *srv, handler_ct while(1) { int n; - buffer_prepare_copy(hctx->response, 1024); + buffer_string_prepare_copy(hctx->response, 1023); if (-1 == (n = read(hctx->fd, hctx->response->ptr, hctx->response->size - 1))) { if (errno == EAGAIN || errno == EINTR) { /* would block, wait for signal */ + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); return 0; } /* error */ @@ -1823,18 +1882,10 @@ static int scgi_demux_response(server *srv, handler_ct if (n == 0) { /* read finished */ - - con->file_finished = 1; - - /* send final chunk */ - http_chunk_append_mem(srv, con, NULL, 0); - joblist_append(srv, con); - return 1; } - hctx->response->ptr[n] = '\0'; - hctx->response->used = n+1; + buffer_commit(hctx->response, n); /* split header from body */ @@ -1852,7 +1903,7 @@ static int scgi_demux_response(server *srv, handler_ct if (0 == strncmp(hctx->response_header->ptr, "HTTP/1.", 7)) in_header = 1; /* search for the \r\n\r\n or \n\n in the string */ - for (c = hctx->response_header->ptr, cp = 0, used = hctx->response_header->used - 1; used; c++, cp++, used--) { + for (c = hctx->response_header->ptr, cp = 0, used = buffer_string_length(hctx->response_header); used; c++, cp++, used--) { if (*c == ':') in_header = 1; else if (*c == '\n') { if (in_header == 0) { @@ -1900,40 +1951,65 @@ static int scgi_demux_response(server *srv, handler_ct if (header_end) { if (c == NULL) { /* no header, but a body */ - - if (con->request.http_version == HTTP_VERSION_1_1) { - con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + if (0 != http_chunk_append_buffer(srv, con, hctx->response_header)) { + /* error writing to tempfile; + * truncate response or send 500 if nothing sent yet */ + return 1; } - - http_chunk_append_mem(srv, con, hctx->response_header->ptr, hctx->response_header->used); - joblist_append(srv, con); } else { - size_t blen = hctx->response_header->used - hlen - 1; + size_t blen = buffer_string_length(hctx->response_header) - hlen; /* a small hack: terminate after at the second \r */ - hctx->response_header->used = hlen; - hctx->response_header->ptr[hlen - 1] = '\0'; + buffer_string_set_length(hctx->response_header, hlen - 1); /* parse the response header */ scgi_response_parse(srv, con, p, hctx->response_header, eol); - /* enable chunked-transfer-encoding */ - if (con->request.http_version == HTTP_VERSION_1_1 && - !(con->parsed_response & HTTP_CONTENT_LENGTH)) { - con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; + if (hctx->host->xsendfile_allow) { + data_string *ds; + if (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile"))) { + http_response_xsendfile(srv, con, ds->value, hctx->host->xsendfile_docroot); + return 1; + } } - if ((hctx->response->used != hlen) && blen > 0) { - http_chunk_append_mem(srv, con, hctx->response_header->ptr + hlen, blen + 1); - joblist_append(srv, con); + if (blen > 0) { + if (0 != http_chunk_append_mem(srv, con, hctx->response_header->ptr + hlen, blen)) { + /* error writing to tempfile; + * truncate response or send 500 if nothing sent yet */ + return 1; + } } } con->file_started = 1; + } else { + /*(reuse MAX_HTTP_REQUEST_HEADER as max size for response headers from backends)*/ + if (buffer_string_length(hctx->response_header) > MAX_HTTP_REQUEST_HEADER) { + log_error_write(srv, __FILE__, __LINE__, "sb", "response headers too large for", con->uri.path); + con->http_status = 502; /* Bad Gateway */ + con->mode = DIRECT; + return 1; + } } } else { - http_chunk_append_mem(srv, con, hctx->response->ptr, hctx->response->used); - joblist_append(srv, con); + if (0 != http_chunk_append_buffer(srv, con, hctx->response)) { + /* error writing to tempfile; + * truncate response or send 500 if nothing sent yet */ + return 1; + } + if ((con->conf.stream_response_body & FDEVENT_STREAM_RESPONSE_BUFMIN) + && chunkqueue_length(con->write_queue) > 65536 - 4096) { + if (!con->is_writable) { + /*(defer removal of FDEVENT_IN interest since + * connection_state_machine() might be able to send data + * immediately, unless !con->is_writable, where + * connection_state_machine() might not loop back to call + * mod_scgi_handle_subrequest())*/ + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + } + break; + } } #if 0 @@ -2186,22 +2262,20 @@ static handler_t scgi_write_request(server *srv, handl log_error_write(srv, __FILE__, __LINE__, "s", "fatal error: host = NULL"); return HANDLER_ERROR; } - if (((!host->host->used || !host->port) && !host->unixsocket->used)) { + if (((buffer_string_is_empty(host->host) || !host->port) && buffer_string_is_empty(host->unixsocket))) { log_error_write(srv, __FILE__, __LINE__, "sxddd", "write-req: error", host, - host->host->used, + buffer_string_length(host->host), host->port, - host->unixsocket->used); + buffer_string_length(host->unixsocket)); return HANDLER_ERROR; } switch(hctx->state) { case FCGI_STATE_INIT: - ret = host->unixsocket->used ? AF_UNIX : AF_INET; - - if (-1 == (hctx->fd = socket(ret, SOCK_STREAM, 0))) { + if (-1 == (hctx->fd = socket(host->family, SOCK_STREAM, 0))) { if (errno == EMFILE || errno == EINTR) { log_error_write(srv, __FILE__, __LINE__, "sd", @@ -2313,6 +2387,7 @@ static handler_t scgi_write_request(server *srv, handl case FCGI_STATE_PREPARE_WRITE: scgi_create_env(srv, hctx); + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); scgi_set_state(srv, hctx, FCGI_STATE_WRITE); /* fall through */ @@ -2337,7 +2412,7 @@ static handler_t scgi_write_request(server *srv, handl scgi_reconnect(srv, hctx); - return HANDLER_WAIT_FOR_FD; + return HANDLER_COMEBACK; } /* not reconnected ... why @@ -2361,46 +2436,59 @@ static handler_t scgi_write_request(server *srv, handl } } - if (hctx->wb->bytes_out == hctx->wb->bytes_in) { - /* we don't need the out event anymore */ - fdevent_event_del(srv->ev, &(hctx->fde_ndx), hctx->fd); - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); + if (hctx->wb->bytes_out == hctx->wb_reqlen) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + #if (defined(__APPLE__) && defined(__MACH__)) \ + || defined(__FreeBSD__) || defined(__NetBSD__) \ + || defined(__OpenBSD__) || defined(__DragonflyBSD__) + /*(*BSD stack on remote might signal POLLHUP and remote + * might treat as socket error instead of half-close)*/ + #else + /*(remote could be different machine running affected OS, + * so only issue shutdown for known local sockets)*/ + if ( '/' == host->host->ptr[0] + || buffer_is_equal_string(host->host, CONST_STR_LEN("127.0.0.1")) + || buffer_is_equal_string(host->host, CONST_STR_LEN("::1"))) { + shutdown(hctx->fd, SHUT_WR); + } + #endif scgi_set_state(srv, hctx, FCGI_STATE_READ); } else { - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); - - return HANDLER_WAIT_FOR_EVENT; + off_t wblen = hctx->wb->bytes_in - hctx->wb->bytes_out; + if (hctx->wb->bytes_in < hctx->wb_reqlen && wblen < 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 (0 == wblen) { + fdevent_event_clr(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + } else { + fdevent_event_add(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); + } } - break; + return HANDLER_WAIT_FOR_EVENT; case FCGI_STATE_READ: /* waiting for a response */ - break; + return HANDLER_WAIT_FOR_EVENT; default: log_error_write(srv, __FILE__, __LINE__, "s", "(debug) unknown state"); return HANDLER_ERROR; } - - return HANDLER_WAIT_FOR_EVENT; } -SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { - plugin_data *p = p_d; - - handler_ctx *hctx = con->plugin_ctx[p->id]; - scgi_proc *proc; - scgi_extension_host *host; - - if (NULL == hctx) return HANDLER_GO_ON; - - /* not my job */ - if (con->mode != p->id) return HANDLER_GO_ON; - +static handler_t scgi_send_request(server *srv, handler_ctx *hctx) { /* ok, create the request */ - switch(scgi_write_request(srv, hctx)) { - case HANDLER_ERROR: - proc = hctx->proc; - host = hctx->host; + handler_t rc = scgi_write_request(srv, hctx); + if (HANDLER_ERROR != rc) { + return rc; + } else { + scgi_proc *proc = hctx->proc; + scgi_extension_host *host = hctx->host; + plugin_data *p = hctx->plugin_data; + connection *con = hctx->remote_conn; if (proc && 0 == proc->is_local && @@ -2449,78 +2537,91 @@ SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { } scgi_restart_dead_procs(srv, p, host); - scgi_connection_cleanup(srv, hctx); + con->mode = DIRECT;/*(avoid changing con->state, con->http_status)*/ + scgi_connection_close(srv, hctx); + con->mode = p->id; - buffer_reset(con->physical.path); - con->mode = DIRECT; - joblist_append(srv, con); - - /* mis-using HANDLER_WAIT_FOR_FD to break out of the loop - * and hope that the childs will be restarted - * - */ - return HANDLER_WAIT_FOR_FD; + return HANDLER_COMEBACK; } else { - scgi_connection_cleanup(srv, hctx); - - buffer_reset(con->physical.path); - con->mode = DIRECT; + scgi_connection_close(srv, hctx); con->http_status = 503; return HANDLER_FINISHED; } - case HANDLER_WAIT_FOR_EVENT: - if (con->file_started == 1) { - return HANDLER_FINISHED; - } else { - return HANDLER_WAIT_FOR_EVENT; - } - case HANDLER_WAIT_FOR_FD: - return HANDLER_WAIT_FOR_FD; - default: - log_error_write(srv, __FILE__, __LINE__, "s", "subrequest write-req default"); - return HANDLER_ERROR; } } -static handler_t scgi_connection_close(server *srv, handler_ctx *hctx) { - connection *con; +static handler_t scgi_recv_response(server *srv, handler_ctx *hctx); + + +SUBREQUEST_FUNC(mod_scgi_handle_subrequest) { + plugin_data *p = p_d; + + handler_ctx *hctx = con->plugin_ctx[p->id]; + if (NULL == hctx) return HANDLER_GO_ON; - con = hctx->remote_conn; + /* not my job */ + if (con->mode != p->id) return HANDLER_GO_ON; - log_error_write(srv, __FILE__, __LINE__, "ssdsd", - "emergency exit: scgi:", - "connection-fd:", con->fd, - "fcgi-fd:", hctx->fd); + 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, which might re-enable FDEVENT_IN */ + handler_t rc = scgi_recv_response(srv, hctx); /*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ + } + } - scgi_connection_cleanup(srv, hctx); + if (0 == hctx->wb->bytes_in + ? con->state == CON_STATE_READ_POST + : hctx->wb->bytes_in < hctx->wb_reqlen) { + /*(64k - 4k to attempt to avoid temporary files + * in conjunction with FDEVENT_STREAM_REQUEST_BUFMIN)*/ + if (hctx->wb->bytes_in - hctx->wb->bytes_out > 65536 - 4096 + && (con->conf.stream_request_body & FDEVENT_STREAM_REQUEST_BUFMIN)){ + con->conf.stream_request_body &= ~FDEVENT_STREAM_REQUEST_POLLIN; + if (0 != hctx->wb->bytes_in) return HANDLER_WAIT_FOR_EVENT; + } else { + handler_t r = connection_handle_read_post_state(srv, con); + chunkqueue *req_cq = con->request_content_queue; + if (0 != hctx->wb->bytes_in && !chunkqueue_is_empty(req_cq)) { + chunkqueue_append_chunkqueue(hctx->wb, req_cq); + if (fdevent_event_get_interest(srv->ev, hctx->fd) & FDEVENT_OUT) { + return (r == HANDLER_GO_ON) ? HANDLER_WAIT_FOR_EVENT : r; + } + } + if (r != HANDLER_GO_ON) return r; + } + } - return HANDLER_FINISHED; + return ((0 == hctx->wb->bytes_in || !chunkqueue_is_empty(hctx->wb)) + && hctx->state != FCGI_STATE_CONNECT) + ? scgi_send_request(srv, hctx) + : HANDLER_WAIT_FOR_EVENT; } -static handler_t scgi_handle_fdevent(server *srv, void *ctx, int revents) { - handler_ctx *hctx = ctx; - connection *con = hctx->remote_conn; - plugin_data *p = hctx->plugin_data; +static handler_t scgi_recv_response(server *srv, handler_ctx *hctx) { - scgi_proc *proc = hctx->proc; - scgi_extension_host *host= hctx->host; - - if ((revents & FDEVENT_IN) && - hctx->state == FCGI_STATE_READ) { switch (scgi_demux_response(srv, hctx)) { case 0: break; case 1: /* we are done */ - scgi_connection_cleanup(srv, hctx); + scgi_connection_close(srv, hctx); - joblist_append(srv, con); return HANDLER_FINISHED; - case -1: + case -1: { + connection *con = hctx->remote_conn; + plugin_data *p = hctx->plugin_data; + + scgi_proc *proc = hctx->proc; + scgi_extension_host *host= hctx->host; + if (proc->pid && proc->state != PROC_STATE_DIED) { int status; @@ -2572,61 +2673,51 @@ static handler_t scgi_handle_fdevent(server *srv, void if (hctx->wb->bytes_out == 0 && hctx->reconnects < 5) { - scgi_reconnect(srv, hctx); log_error_write(srv, __FILE__, __LINE__, "ssdsd", "response not sent, request not sent, reconnection.", "connection-fd:", con->fd, "fcgi-fd:", hctx->fd); - return HANDLER_WAIT_FOR_FD; + scgi_reconnect(srv, hctx); + + return HANDLER_COMEBACK; } log_error_write(srv, __FILE__, __LINE__, "sosdsd", "response not sent, request sent:", hctx->wb->bytes_out, "connection-fd:", con->fd, "fcgi-fd:", hctx->fd); - - scgi_connection_cleanup(srv, hctx); - - connection_set_state(srv, con, CON_STATE_HANDLE_REQUEST); - buffer_reset(con->physical.path); - con->http_status = 500; - con->mode = DIRECT; } else { - /* response might have been already started, kill the connection */ log_error_write(srv, __FILE__, __LINE__, "ssdsd", "response already sent out, termination connection", "connection-fd:", con->fd, "fcgi-fd:", hctx->fd); + } - scgi_connection_cleanup(srv, hctx); + http_response_backend_error(srv, con); + scgi_connection_close(srv, hctx); + return HANDLER_FINISHED; + } + } - connection_set_state(srv, con, CON_STATE_ERROR); - } + return HANDLER_GO_ON; +} - /* */ +static handler_t scgi_handle_fdevent(server *srv, void *ctx, int revents) { + handler_ctx *hctx = ctx; + connection *con = hctx->remote_conn; - joblist_append(srv, con); - return HANDLER_FINISHED; - } + joblist_append(srv, con); + + if (revents & FDEVENT_IN) { + handler_t rc = scgi_recv_response(srv, hctx);/*(might invalidate hctx)*/ + if (rc != HANDLER_GO_ON) return rc; /*(unless HANDLER_GO_ON)*/ } if (revents & FDEVENT_OUT) { - if (hctx->state == FCGI_STATE_CONNECT || - hctx->state == FCGI_STATE_WRITE) { - /* we are allowed to send something out - * - * 1. in a unfinished connect() call - * 2. in a unfinished write() call (long POST request) - */ - return mod_scgi_handle_subrequest(srv, con, p); - } else { - log_error_write(srv, __FILE__, __LINE__, "sd", - "got a FDEVENT_OUT and didn't know why:", - hctx->state); - } + return scgi_send_request(srv, hctx); /*(might invalidate hctx)*/ } /* perhaps this issue is already handled */ @@ -2641,15 +2732,20 @@ static handler_t scgi_handle_fdevent(server *srv, void * FIXME: as it is a bit ugly. * */ - return mod_scgi_handle_subrequest(srv, con, p); - } else if (hctx->state == FCGI_STATE_READ && - hctx->proc->port == 0) { - /* FIXME: - * - * ioctl says 8192 bytes to read from PHP and we receive directly a HUP for the socket - * even if the FCGI_FIN packet is not received yet - */ + scgi_send_request(srv, hctx); + } else 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; + do { + rc = scgi_recv_response(srv,hctx);/*(might invalidate hctx)*/ + } while (rc == HANDLER_GO_ON); /*(unless HANDLER_GO_ON)*/ + return rc; /* HANDLER_FINISHED or HANDLER_ERROR */ } else { + scgi_extension_host *host= hctx->host; log_error_write(srv, __FILE__, __LINE__, "sbSBSDSd", "error: unexpected close of scgi connection for", con->uri.path, @@ -2660,19 +2756,14 @@ static handler_t scgi_handle_fdevent(server *srv, void " ?)", hctx->state); - connection_set_state(srv, con, CON_STATE_ERROR); scgi_connection_close(srv, hctx); - joblist_append(srv, con); } } else if (revents & FDEVENT_ERR) { log_error_write(srv, __FILE__, __LINE__, "s", "fcgi: got a FDEVENT_ERR. Don't know why."); - /* kill all connections to the scgi process */ - - connection_set_state(srv, con, CON_STATE_ERROR); + http_response_backend_error(srv, con); scgi_connection_close(srv, hctx); - joblist_append(srv, con); } return HANDLER_FINISHED; @@ -2727,9 +2818,9 @@ static handler_t scgi_check_extension(server *srv, con fn = uri_path_handler ? con->uri.path : con->physical.path; - if (buffer_is_empty(fn)) return HANDLER_GO_ON; + if (buffer_string_is_empty(fn)) return HANDLER_GO_ON; - s_len = fn->used - 1; + s_len = buffer_string_length(fn); scgi_patch_connection(srv, con, p); @@ -2738,9 +2829,9 @@ static handler_t scgi_check_extension(server *srv, con size_t ct_len; scgi_extension *ext = p->conf.exts->exts[k]; - if (ext->key->used == 0) continue; + if (buffer_is_empty(ext->key)) continue; - ct_len = ext->key->used - 1; + ct_len = buffer_string_length(ext->key); if (s_len < ct_len) continue; @@ -2780,8 +2871,8 @@ static handler_t scgi_check_extension(server *srv, con if (!host) { /* sorry, we don't have a server alive for this ext */ - buffer_reset(con->physical.path); con->http_status = 500; + con->mode = DIRECT; /* only send the 'no handler' once */ if (!extension->note_is_sent) { @@ -2799,6 +2890,18 @@ static handler_t scgi_check_extension(server *srv, con /* a note about no handler is not sent yet */ extension->note_is_sent = 0; + /* SCGI requires that Content-Length be set. + * Send 411 Length Required if Content-Length missing. + * (Alternatively, collect full request body before proceeding + * in mod_scgi_handle_subrequest()) */ + if (0 == con->request.content_length + && array_get_element(con->request.headers, "Transfer-Encoding")) { + con->keep_alive = 0; + con->http_status = 411; /* Length Required */ + con->mode = DIRECT; + return HANDLER_FINISHED; + } + /* * if check-local is disabled, use the uri.path handler * @@ -2854,17 +2957,14 @@ static handler_t scgi_check_extension(server *srv, con /* the rewrite is only done for /prefix/? matches */ if (host->fix_root_path_name && extension->key->ptr[0] == '/' && extension->key->ptr[1] == '\0') { buffer_copy_string(con->request.pathinfo, con->uri.path->ptr); - con->uri.path->used = 1; - con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + buffer_string_set_length(con->uri.path, 0); } else if (extension->key->ptr[0] == '/' && - con->uri.path->used > extension->key->used && - NULL != (pathinfo = strchr(con->uri.path->ptr + extension->key->used - 1, '/'))) { + buffer_string_length(con->uri.path) > buffer_string_length(extension->key) && + NULL != (pathinfo = strchr(con->uri.path->ptr + buffer_string_length(extension->key), '/'))) { /* rewrite uri.path and pathinfo */ buffer_copy_string(con->request.pathinfo, pathinfo); - - con->uri.path->used -= con->request.pathinfo->used - 1; - con->uri.path->ptr[con->uri.path->used - 1] = '\0'; + buffer_string_set_length(con->uri.path, buffer_string_length(con->uri.path) - buffer_string_length(con->request.pathinfo)); } } } else { @@ -2903,42 +3003,7 @@ static handler_t scgi_check_extension_2(server *srv, c return scgi_check_extension(srv, con, p_d, 0); } -JOBLIST_FUNC(mod_scgi_handle_joblist) { - plugin_data *p = p_d; - handler_ctx *hctx = con->plugin_ctx[p->id]; - if (hctx == NULL) return HANDLER_GO_ON; - - if (hctx->fd != -1) { - switch (hctx->state) { - case FCGI_STATE_READ: - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_IN); - - break; - case FCGI_STATE_CONNECT: - case FCGI_STATE_WRITE: - fdevent_event_set(srv->ev, &(hctx->fde_ndx), hctx->fd, FDEVENT_OUT); - - break; - case FCGI_STATE_INIT: - /* at reconnect */ - break; - default: - log_error_write(srv, __FILE__, __LINE__, "sd", "unhandled fcgi.state", hctx->state); - break; - } - } - - return HANDLER_GO_ON; -} - - -static handler_t scgi_connection_close_callback(server *srv, connection *con, void *p_d) { - plugin_data *p = p_d; - - return scgi_connection_close(srv, con->plugin_ctx[p->id]); -} - TRIGGER_FUNC(mod_scgi_handle_trigger) { plugin_data *p = p_d; size_t i, j, n; @@ -3007,12 +3072,12 @@ TRIGGER_FUNC(mod_scgi_handle_trigger) { host->num_procs++; - if (buffer_is_empty(host->unixsocket)) { + if (buffer_string_is_empty(host->unixsocket)) { fp->port = host->port + fp->id; } else { - buffer_copy_string_buffer(fp->socket, host->unixsocket); + buffer_copy_buffer(fp->socket, host->unixsocket); buffer_append_string_len(fp->socket, CONST_STR_LEN("-")); - buffer_append_long(fp->socket, fp->id); + buffer_append_int(fp->socket, fp->id); } if (scgi_spawn_connection(srv, p, host, fp)) { @@ -3140,11 +3205,10 @@ int mod_scgi_plugin_init(plugin *p) { p->cleanup = mod_scgi_free; p->set_defaults = mod_scgi_set_defaults; p->connection_reset = scgi_connection_reset; - p->handle_connection_close = scgi_connection_close_callback; + p->handle_connection_close = scgi_connection_reset; p->handle_uri_clean = scgi_check_extension_1; p->handle_subrequest_start = scgi_check_extension_2; p->handle_subrequest = mod_scgi_handle_subrequest; - p->handle_joblist = mod_scgi_handle_joblist; p->handle_trigger = mod_scgi_handle_trigger; p->data = NULL;