diff --git a/doc/outdated/fastcgi.txt b/doc/outdated/fastcgi.txt index eee5f791..ee3c0b92 100644 --- a/doc/outdated/fastcgi.txt +++ b/doc/outdated/fastcgi.txt @@ -107,7 +107,8 @@ fastcgi.server "max-procs" => , # OPTIONAL "broken-scriptfilename" => , # OPTIONAL "disable-time" => , # optional - "allow-x-send-file" => , # optional + "x-sendfile" => , # optional (replaces "allow-x-send-file") + "x-sendfile-docroot" => , # optional "kill-signal" => , # OPTIONAL "fix-root-scriptname" => , # OPTIONAL @@ -143,8 +144,10 @@ fastcgi.server PHP can extract PATH_INFO from it (default: disabled) :"disable-time": time to wait before a disabled backend is checked again - :"allow-x-send-file": controls if X-LIGHTTPD-send-file headers - are allowed + :"x-sendfile": controls if X-Sendfile backend response header is allowed + (deprecated headers: X-Sendfile2 and X-LIGHTTPD-send-file) + ("x-sendfile" replaces "allow-x-sendfile") + :"x-sendfile-docroot": list of directory trees permitted with X-Sendfile :"fix-root-scriptname": fix broken path-info split for "/" extension ("prefix") If bin-path is set: diff --git a/src/http-header-glue.c b/src/http-header-glue.c index 15ea8291..898917ec 100644 --- a/src/http-header-glue.c +++ b/src/http-header-glue.c @@ -529,7 +529,7 @@ void http_response_send_file (server *srv, connection *con, buffer *path) { stat_cache_entry *sce = NULL; buffer *mtime = NULL; data_string *ds; - int allow_caching = 1; + int allow_caching = (0 == con->http_status || 200 == con->http_status); if (HANDLER_ERROR == stat_cache_get_entry(srv, con, path, &sce)) { con->http_status = (errno == ENOENT) ? 404 : 403; @@ -613,7 +613,9 @@ void http_response_send_file (server *srv, connection *con, buffer *path) { } } - if (con->request.http_range && con->conf.range_requests && con->http_status < 300) { + if (con->request.http_range && con->conf.range_requests + && (200 == con->http_status || 0 == con->http_status) + && NULL == array_get_element(con->response.headers, "Content-Encoding")) { int do_range_request = 1; /* check if we have a conditional GET */ @@ -665,3 +667,58 @@ void http_response_send_file (server *srv, connection *con, buffer *path) { con->http_status = 403; } } + +void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot) { + const int status = con->http_status; + int valid = 1; + + /* reset Content-Length, if set by backend + * Content-Length might later be set to size of X-Sendfile static file, + * determined by open(), fstat() to reduces race conditions if the file + * is modified between stat() (stat_cache_get_entry()) and open(). */ + if (con->parsed_response & HTTP_CONTENT_LENGTH) { + data_string *ds = (data_string *) array_get_element(con->response.headers, "Content-Length"); + if (ds) buffer_reset(ds->value); + con->parsed_response &= ~HTTP_CONTENT_LENGTH; + con->response.content_length = -1; + } + + buffer_urldecode_path(path); + buffer_path_simplify(path, path); + if (con->conf.force_lowercase_filenames) { + buffer_to_lower(path); + } + + /* check that path is under xdocroot(s) + * - xdocroot should have trailing slash appended at config time + * - con->conf.force_lowercase_filenames is not a server-wide setting, + * and so can not be definitively applied to xdocroot at config time*/ + if (xdocroot->used) { + size_t i, xlen = buffer_string_length(path); + for (i = 0; i < xdocroot->used; ++i) { + data_string *ds = (data_string *)xdocroot->data[i]; + size_t dlen = buffer_string_length(ds->value); + if (dlen <= xlen + && (!con->conf.force_lowercase_filenames + ? 0 == memcmp(path->ptr, ds->value->ptr, dlen) + : 0 == strncasecmp(path->ptr, ds->value->ptr, dlen))) { + break; + } + } + if (i == xdocroot->used) { + log_error_write(srv, __FILE__, __LINE__, "SBs", + "X-Sendfile (", path, + ") not under configured x-sendfile-docroot(s)"); + con->http_status = 403; + valid = 0; + } + } + + if (valid) http_response_send_file(srv, con, path); + + if (con->http_status >= 400 && status < 300) { + con->mode = DIRECT; + } else if (0 != status && 200 != status) { + con->http_status = status; + } +} diff --git a/src/mod_fastcgi.c b/src/mod_fastcgi.c index 73b9de71..36dcd687 100644 --- a/src/mod_fastcgi.c +++ b/src/mod_fastcgi.c @@ -226,11 +226,12 @@ typedef struct { unsigned short fix_root_path_name; /* - * If the backend includes X-LIGHTTPD-send-file in the response + * If the backend includes X-Sendfile in the response * we use the value as filename and ignore the content. * */ - unsigned short allow_xsendfile; + unsigned short xsendfile_allow; + array *xsendfile_docroot; ssize_t load; /* replace by host->load */ @@ -549,6 +550,7 @@ static fcgi_extension_host *fastcgi_host_init(void) { f->bin_env = array_init(); f->bin_env_copy = array_init(); f->strip_request_uri = buffer_init(); + f->xsendfile_docroot = array_init(); return f; } @@ -564,6 +566,7 @@ static void fastcgi_host_free(fcgi_extension_host *h) { buffer_free(h->strip_request_uri); array_free(h->bin_env); array_free(h->bin_env_copy); + array_free(h->xsendfile_docroot); fastcgi_process_free(h->first); fastcgi_process_free(h->unused_procs); @@ -1287,6 +1290,8 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { { "kill-signal", NULL, T_CONFIG_SHORT, T_CONFIG_SCOPE_CONNECTION }, /* 14 */ { "fix-root-scriptname", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 15 */ { "listen-backlog", NULL, T_CONFIG_INT, T_CONFIG_SCOPE_CONNECTION }, /* 16 */ + { "x-sendfile", NULL, T_CONFIG_BOOLEAN, T_CONFIG_SCOPE_CONNECTION }, /* 17 */ + { "x-sendfile-docroot",NULL, T_CONFIG_ARRAY, T_CONFIG_SCOPE_CONNECTION }, /* 18 */ { NULL, NULL, T_CONFIG_UNSET, T_CONFIG_SCOPE_UNSET } }; @@ -1310,7 +1315,7 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { host->mode = FCGI_RESPONDER; host->disable_time = 1; host->break_scriptfilename_for_php = 0; - host->allow_xsendfile = 0; /* handle X-LIGHTTPD-send-file */ + host->xsendfile_allow = 0; host->kill_signal = SIGTERM; host->fix_root_path_name = 0; host->listen_backlog = 1024; @@ -1329,11 +1334,13 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { fcv[9].destination = host->bin_env; fcv[10].destination = host->bin_env_copy; fcv[11].destination = &(host->break_scriptfilename_for_php); - fcv[12].destination = &(host->allow_xsendfile); + fcv[12].destination = &(host->xsendfile_allow); fcv[13].destination = host->strip_request_uri; fcv[14].destination = &(host->kill_signal); fcv[15].destination = &(host->fix_root_path_name); fcv[16].destination = &(host->listen_backlog); + fcv[17].destination = &(host->xsendfile_allow); + fcv[18].destination = host->xsendfile_docroot; if (0 != config_insert_values_internal(srv, da_host->value, fcv, T_CONFIG_SCOPE_CONNECTION)) { goto error; @@ -1484,6 +1491,25 @@ SETDEFAULTS_FUNC(mod_fastcgi_set_defaults) { } } + if (host->xsendfile_docroot->used) { + size_t k; + for (k = 0; k < host->xsendfile_docroot->used; ++k) { + data_string *ds = (data_string *)host->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 */ fastcgi_extension_insert(s->exts, da_ext->key, host); host = NULL; @@ -2132,7 +2158,6 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf for (s = in->ptr; NULL != (ns = strchr(s, '\n')); s = ns + 1) { char *key, *value; int key_len; - data_string *ds = NULL; /* a good day. Someone has read the specs and is sending a \r\n to us */ @@ -2162,6 +2187,7 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf /* don't forward Status: */ if (0 != strncasecmp(key, "Status", key_len)) { + data_string *ds; if (NULL == (ds = (data_string *)array_get_unused_element(con->response.headers, TYPE_STRING))) { ds = data_response_init(); } @@ -2201,7 +2227,7 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf } break; case 11: - if (host->allow_xsendfile && 0 == strncasecmp(key, "X-Sendfile2", key_len)&& hctx->send_content_body) { + if (host->xsendfile_allow && 0 == strncasecmp(key, "X-Sendfile2", key_len) && hctx->send_content_body) { char *pos = value; have_sendfile2 = 1; @@ -2227,6 +2253,30 @@ static int fcgi_response_parse(server *srv, connection *con, plugin_data *p, buf for (pos = ++range; *pos && *pos != ' ' && *pos != ','; pos++) ; buffer_urldecode_path(srv->tmp_buf); + buffer_path_simplify(srv->tmp_buf, srv->tmp_buf); + if (con->conf.force_lowercase_filenames) { + buffer_to_lower(srv->tmp_buf); + } + if (host->xsendfile_docroot->used) { + size_t i, xlen = buffer_string_length(srv->tmp_buf); + for (i = 0; i < host->xsendfile_docroot->used; ++i) { + data_string *ds = (data_string *)host->xsendfile_docroot->data[i]; + size_t dlen = buffer_string_length(ds->value); + if (dlen <= xlen + && (!con->conf.force_lowercase_filenames + ? 0 == memcmp(srv->tmp_buf->ptr, ds->value->ptr, dlen) + : 0 == strncasecmp(srv->tmp_buf->ptr, ds->value->ptr, dlen))) { + break; + } + } + if (i == host->xsendfile_docroot->used) { + log_error_write(srv, __FILE__, __LINE__, "SBs", + "X-Sendfile2 (", srv->tmp_buf, + ") not under configured x-sendfile-docroot(s)"); + return 403; + } + } + if (HANDLER_ERROR == stat_cache_get_entry(srv, con, srv->tmp_buf, &sce)) { if (p->conf.debug) { log_error_write(srv, __FILE__, __LINE__, "sb", @@ -2526,44 +2576,26 @@ static int fcgi_demux_response(server *srv, handler_ctx *hctx) { hctx->send_content_body = 0; } - if (host->allow_xsendfile && hctx->send_content_body && + if (host->xsendfile_allow && hctx->send_content_body && (NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-LIGHTTPD-send-file")) || NULL != (ds = (data_string *) array_get_element(con->response.headers, "X-Sendfile")))) { - if (0 == http_chunk_append_file(srv, con, ds->value)) { - /* found */ - data_string *dcls = (data_string *) array_get_element(con->response.headers, "Content-Length"); - if (dcls) buffer_reset(dcls->value); - con->parsed_response &= ~HTTP_CONTENT_LENGTH; - con->response.content_length = -1; - hctx->send_content_body = 0; /* ignore the content */ - } else { - log_error_write(srv, __FILE__, __LINE__, "sb", - "send-file error: couldn't get stat_cache entry for:", - ds->value); - con->http_status = 404; - hctx->send_content_body = 0; - con->file_started = 1; + http_response_xsendfile(srv, con, ds->value, host->xsendfile_docroot); + if (con->mode == DIRECT) { + fin = 1; break; } + + hctx->send_content_body = 0; /* ignore the content */ } - - if (hctx->send_content_body && buffer_string_length(packet.b) > 0) { - /* 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; - } - - http_chunk_append_buffer(srv, con, packet.b); - } - } else if (hctx->send_content_body && !buffer_string_is_empty(packet.b)) { + /* enable chunked-transfer-encoding */ if (con->request.http_version == HTTP_VERSION_1_1 && !(con->parsed_response & HTTP_CONTENT_LENGTH)) { - /* enable chunked-transfer-encoding */ con->response.transfer_encoding = HTTP_TRANSFER_ENCODING_CHUNKED; } + } + if (hctx->send_content_body && !buffer_string_is_empty(packet.b)) { http_chunk_append_buffer(srv, con, packet.b); } break; diff --git a/src/response.h b/src/response.h index f38b8413..6ccb87a8 100644 --- a/src/response.h +++ b/src/response.h @@ -17,6 +17,7 @@ handler_t http_response_prepare(server *srv, connection *con); int http_response_redirect_to_directory(server *srv, connection *con); int http_response_handle_cachable(server *srv, connection *con, buffer * mtime); void http_response_send_file (server *srv, connection *con, buffer *path); +void http_response_xsendfile (server *srv, connection *con, buffer *path, const array *xdocroot); buffer * strftime_cache_get(server *srv, time_t last_mod); #endif