diff options
Diffstat (limited to 'src')
30 files changed, 1162 insertions, 305 deletions
diff --git a/src/nodejs/unit-http/http.js b/src/nodejs/unit-http/http.js index 3a25fa2f..d298a35f 100644 --- a/src/nodejs/unit-http/http.js +++ b/src/nodejs/unit-http/http.js @@ -5,19 +5,22 @@ 'use strict'; -const server = require('unit-http/http_server'); - -const { Server } = server; +const { + Server, + ServerRequest, + ServerResponse, +} = require('./http_server'); function createServer (requestHandler) { return new Server(requestHandler); } +const http = require("http") module.exports = { + ...http, Server, - STATUS_CODES: server.STATUS_CODES, createServer, - IncomingMessage: server.ServerRequest, - ServerResponse: server.ServerResponse + IncomingMessage: ServerRequest, + ServerResponse, }; diff --git a/src/nodejs/unit-http/http_server.js b/src/nodejs/unit-http/http_server.js index e59296ae..89964ec3 100644 --- a/src/nodejs/unit-http/http_server.js +++ b/src/nodejs/unit-http/http_server.js @@ -444,17 +444,30 @@ Server.prototype.setTimeout = function setTimeout(msecs, callback) { Server.prototype.listen = function (...args) { this.unit.listen(); - const cb = args.pop(); - - if (typeof cb === 'function') { - this.once('listening', cb); + if (typeof args[args.length - 1] === 'function') { + this.once('listening', args[args.length - 1]); } - this.emit('listening'); + /* + * Some express.js apps use the returned server object inside the listening + * callback, so we timeout the listening event to occur after this function + * returns. + */ + setImmediate(function() { + this.emit('listening') + }.bind(this)) return this; }; +Server.prototype.address = function () { + return { + family: "IPv4", + address: "127.0.0.1", + port: 80 + } +} + Server.prototype.emit_request = function (req, res) { if (req._websocket_handshake && this._upgradeListenerCount > 0) { this.emit('upgrade', req, req.socket); @@ -530,7 +543,6 @@ function connectionListener(socket) { } module.exports = { - STATUS_CODES: http.STATUS_CODES, Server, ServerResponse, ServerRequest, diff --git a/src/nodejs/unit-http/loader.js b/src/nodejs/unit-http/loader.js new file mode 100644 index 00000000..e5aa3558 --- /dev/null +++ b/src/nodejs/unit-http/loader.js @@ -0,0 +1,27 @@ +// can only be ran as part of a --require param on the node process +if (module.parent && module.parent.id === "internal/preload") { + const { Module } = require("module") + + if (!Module.prototype.require.__unit_loader) { + const http = require("./http") + const websocket = require("./websocket") + + const original = Module.prototype.require; + + Module.prototype.require = function (id) { + switch(id) { + case "http": + case "unit-http": + return http + + case "websocket": + case "unit-http/websocket": + return websocket + } + + return original.apply(this, arguments); + } + + Module.prototype.require.__unit_loader = true; + } +} diff --git a/src/nodejs/unit-http/loader.mjs b/src/nodejs/unit-http/loader.mjs new file mode 100644 index 00000000..067d63d4 --- /dev/null +++ b/src/nodejs/unit-http/loader.mjs @@ -0,0 +1,18 @@ +// must be ran as part of a --loader or --experimental-loader param +export async function resolve(specifier, context, defaultResolver) { + switch (specifier) { + case "websocket": + return { + url: new URL("./websocket.js", import.meta.url).href, + format: "cjs" + } + + case "http": + return { + url: new URL("./http.js", import.meta.url).href, + format: "cjs" + } + } + + return defaultResolver(specifier, context, defaultResolver) +} diff --git a/src/nxt_application.h b/src/nxt_application.h index 632c5632..45e7fa48 100644 --- a/src/nxt_application.h +++ b/src/nxt_application.h @@ -54,6 +54,7 @@ typedef struct { nxt_str_t protocol; uint32_t threads; uint32_t thread_stack_size; + nxt_conf_value_t *targets; } nxt_python_app_conf_t; diff --git a/src/nxt_cert.c b/src/nxt_cert.c index 3cdb69c1..1806bc19 100644 --- a/src/nxt_cert.c +++ b/src/nxt_cert.c @@ -48,6 +48,7 @@ static nxt_conf_value_t *nxt_cert_name_details(nxt_mp_t *mp, X509 *x509, nxt_bool_t issuer); static nxt_conf_value_t *nxt_cert_alt_names_details(nxt_mp_t *mp, STACK_OF(GENERAL_NAME) *alt_names); +static void nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data); static nxt_lvlhsh_t nxt_cert_info; @@ -1073,6 +1074,9 @@ nxt_cert_store_get(nxt_task_t *task, nxt_str_t *name, nxt_mp_t *mp, goto fail; } + nxt_mp_retain(mp); + b->completion_handler = nxt_cert_buf_completion; + nxt_buf_cpystr(b, name); *b->mem.free++ = '\0'; @@ -1102,6 +1106,21 @@ fail: } +static void +nxt_cert_buf_completion(nxt_task_t *task, void *obj, void *data) +{ + nxt_mp_t *mp; + nxt_buf_t *b; + + b = obj; + mp = b->data; + nxt_assert(b->next == NULL); + + nxt_mp_free(mp, b); + nxt_mp_release(mp); +} + + void nxt_cert_store_get_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg) { diff --git a/src/nxt_conf_validation.c b/src/nxt_conf_validation.c index 8c5d1ec7..06ae2847 100644 --- a/src/nxt_conf_validation.c +++ b/src/nxt_conf_validation.c @@ -75,6 +75,8 @@ static nxt_int_t nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...); static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_str_t *value); +nxt_inline nxt_int_t nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_mtypes(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); @@ -87,6 +89,10 @@ static nxt_int_t nxt_conf_vldt_listener(nxt_conf_validation_t *vldt, #if (NXT_TLS) static nxt_int_t nxt_conf_vldt_certificate(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +#if (NXT_HAVE_OPENSSL_CONF_CMD) +static nxt_int_t nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +#endif static nxt_int_t nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); #endif @@ -98,6 +104,8 @@ static nxt_int_t nxt_conf_vldt_return(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_python(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_python_path_element(nxt_conf_validation_t *vldt, @@ -154,16 +162,16 @@ static nxt_int_t nxt_conf_vldt_array_iterator(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); static nxt_int_t nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); +static nxt_int_t nxt_conf_vldt_targets_exclusive( + nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_targets(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data); +static nxt_int_t nxt_conf_vldt_target(nxt_conf_validation_t *vldt, + nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_argument(nxt_conf_validation_t *vldt, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_targets_exclusive( - nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data); -static nxt_int_t nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, - nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value); static nxt_int_t nxt_conf_vldt_java_classpath(nxt_conf_validation_t *vldt, @@ -200,8 +208,10 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_static_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[]; #endif static nxt_conf_vldt_object_t nxt_conf_vldt_match_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_php_options_members[]; +static nxt_conf_vldt_object_t nxt_conf_vldt_php_target_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_common_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_limits_members[]; static nxt_conf_vldt_object_t nxt_conf_vldt_app_processes_members[]; @@ -357,7 +367,17 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_tls_members[] = { { .name = nxt_string("certificate"), .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + .flags = NXT_CONF_VLDT_REQUIRED, .validator = nxt_conf_vldt_certificate, + }, { + .name = nxt_string("conf_commands"), + .type = NXT_CONF_VLDT_OBJECT, +#if (NXT_HAVE_OPENSSL_CONF_CMD) + .validator = nxt_conf_vldt_object_conf_commands, +#else + .validator = nxt_conf_vldt_unsupported, + .u.string = "conf_commands", +#endif }, NXT_CONF_VLDT_END @@ -455,9 +475,34 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_share_action_members[] = { .name = nxt_string("share"), .type = NXT_CONF_VLDT_STRING, }, { + .name = nxt_string("types"), + .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, + .validator = nxt_conf_vldt_match_patterns, + }, { .name = nxt_string("fallback"), .type = NXT_CONF_VLDT_OBJECT, .validator = nxt_conf_vldt_action, + }, { + .name = nxt_string("chroot"), + .type = NXT_CONF_VLDT_STRING, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "chroot", +#endif + }, { + .name = nxt_string("follow_symlinks"), + .type = NXT_CONF_VLDT_BOOLEAN, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "follow_symlinks", +#endif + }, { + .name = nxt_string("traverse_mounts"), + .type = NXT_CONF_VLDT_BOOLEAN, +#if !(NXT_HAVE_OPENAT2) + .validator = nxt_conf_vldt_unsupported, + .u.string = "traverse_mounts", +#endif }, NXT_CONF_VLDT_END @@ -491,7 +536,7 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_external_members[] = { }; -static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { +static nxt_conf_vldt_object_t nxt_conf_vldt_python_common_members[] = { { .name = nxt_string("home"), .type = NXT_CONF_VLDT_STRING, @@ -500,13 +545,6 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { .type = NXT_CONF_VLDT_STRING | NXT_CONF_VLDT_ARRAY, .validator = nxt_conf_vldt_python_path, }, { - .name = nxt_string("module"), - .type = NXT_CONF_VLDT_STRING, - .flags = NXT_CONF_VLDT_REQUIRED, - }, { - .name = nxt_string("callable"), - .type = NXT_CONF_VLDT_STRING, - }, { .name = nxt_string("protocol"), .type = NXT_CONF_VLDT_STRING, .validator = nxt_conf_vldt_python_protocol, @@ -523,27 +561,77 @@ static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { NXT_CONF_VLDT_NEXT(nxt_conf_vldt_common_members) }; +static nxt_conf_vldt_object_t nxt_conf_vldt_python_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .validator = nxt_conf_vldt_targets_exclusive, + .u.string = "module", + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + .validator = nxt_conf_vldt_targets_exclusive, + .u.string = "callable", + }, { + .name = nxt_string("targets"), + .type = NXT_CONF_VLDT_OBJECT, + .validator = nxt_conf_vldt_targets, + .u.members = nxt_conf_vldt_python_target_members + }, + + NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members) +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_python_target_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + }, + + NXT_CONF_VLDT_END +}; + + +static nxt_conf_vldt_object_t nxt_conf_vldt_python_notargets_members[] = { + { + .name = nxt_string("module"), + .type = NXT_CONF_VLDT_STRING, + .flags = NXT_CONF_VLDT_REQUIRED, + }, { + .name = nxt_string("callable"), + .type = NXT_CONF_VLDT_STRING, + }, + + NXT_CONF_VLDT_NEXT(nxt_conf_vldt_python_common_members) +}; + static nxt_conf_vldt_object_t nxt_conf_vldt_php_members[] = { { .name = nxt_string("root"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "root", }, { .name = nxt_string("script"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "script", }, { .name = nxt_string("index"), .type = NXT_CONF_VLDT_ANY_TYPE, - .validator = nxt_conf_vldt_php_targets_exclusive, + .validator = nxt_conf_vldt_targets_exclusive, .u.string = "index", }, { .name = nxt_string("targets"), .type = NXT_CONF_VLDT_OBJECT, - .validator = nxt_conf_vldt_php_targets, + .validator = nxt_conf_vldt_targets, + .u.members = nxt_conf_vldt_php_target_members }, NXT_CONF_VLDT_NEXT(nxt_conf_vldt_php_common_members) @@ -1032,6 +1120,15 @@ nxt_conf_vldt_error(nxt_conf_validation_t *vldt, const char *fmt, ...) } +nxt_inline nxt_int_t +nxt_conf_vldt_unsupported(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + return nxt_conf_vldt_error(vldt, "Unit is built without the \"%s\" " + "option support.", data); +} + + static nxt_int_t nxt_conf_vldt_var(nxt_conf_validation_t *vldt, const char *option, nxt_str_t *value) @@ -1137,7 +1234,7 @@ nxt_conf_vldt_mtypes_extension(nxt_conf_validation_t *vldt, dup_type = nxt_http_static_mtypes_hash_find(&ctx->hash, &ext); - if (dup_type != NULL) { + if (dup_type->length != 0) { return nxt_conf_vldt_error(vldt, "The \"%V\" file extension has been " "declared for \"%V\" and \"%V\" " "MIME types at the same time.", @@ -1384,6 +1481,25 @@ nxt_conf_vldt_proxy(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, static nxt_int_t +nxt_conf_vldt_python(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_conf_value_t *targets; + + static nxt_str_t targets_str = nxt_string("targets"); + + targets = nxt_conf_get_object_member(value, &targets_str, NULL); + + if (targets != NULL) { + return nxt_conf_vldt_object(vldt, value, nxt_conf_vldt_python_members); + } + + return nxt_conf_vldt_object(vldt, value, + nxt_conf_vldt_python_notargets_members); +} + + +static nxt_int_t nxt_conf_vldt_python_path(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) { @@ -1869,6 +1985,38 @@ nxt_conf_vldt_certificate_element(nxt_conf_validation_t *vldt, return NXT_OK; } + +#if (NXT_HAVE_OPENSSL_CONF_CMD) + +static nxt_int_t +nxt_conf_vldt_object_conf_commands(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + uint32_t index; + nxt_int_t ret; + nxt_str_t name; + nxt_conf_value_t *member; + + index = 0; + + for ( ;; ) { + member = nxt_conf_next_object_member(value, &name, &index); + + if (member == NULL) { + break; + } + + ret = nxt_conf_vldt_type(vldt, &name, member, NXT_CONF_VLDT_STRING); + if (ret != NXT_OK) { + return ret; + } + } + + return NXT_OK; +} + +#endif + #endif @@ -1923,7 +2071,7 @@ nxt_conf_vldt_app(nxt_conf_validation_t *vldt, nxt_str_t *name, } types[] = { { nxt_conf_vldt_object, nxt_conf_vldt_external_members }, - { nxt_conf_vldt_object, nxt_conf_vldt_python_members }, + { nxt_conf_vldt_python, NULL }, { nxt_conf_vldt_php, NULL }, { nxt_conf_vldt_object, nxt_conf_vldt_perl_members }, { nxt_conf_vldt_object, nxt_conf_vldt_ruby_members }, @@ -2250,6 +2398,57 @@ nxt_conf_vldt_environment(nxt_conf_validation_t *vldt, nxt_str_t *name, static nxt_int_t +nxt_conf_vldt_targets_exclusive(nxt_conf_validation_t *vldt, + nxt_conf_value_t *value, void *data) +{ + return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " + "with the \"targets\" object.", data); +} + + +static nxt_int_t +nxt_conf_vldt_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, + void *data) +{ + nxt_int_t ret; + nxt_uint_t n; + + n = nxt_conf_object_members_count(value); + + if (n > 254) { + return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " + "contain more than 254 members."); + } + + vldt->ctx = data; + + ret = nxt_conf_vldt_object_iterator(vldt, value, &nxt_conf_vldt_target); + + vldt->ctx = NULL; + + return ret; +} + + +static nxt_int_t +nxt_conf_vldt_target(nxt_conf_validation_t *vldt, nxt_str_t *name, + nxt_conf_value_t *value) +{ + if (name->length == 0) { + return nxt_conf_vldt_error(vldt, + "The target name must not be empty."); + } + + if (nxt_conf_type(value) != NXT_CONF_OBJECT) { + return nxt_conf_vldt_error(vldt, "The \"%V\" target must be " + "an object.", name); + } + + return nxt_conf_vldt_object(vldt, value, vldt->ctx); +} + + +static nxt_int_t nxt_conf_vldt_clone_namespaces(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, void *data) { @@ -2417,51 +2616,6 @@ nxt_conf_vldt_php(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, static nxt_int_t -nxt_conf_vldt_php_targets_exclusive(nxt_conf_validation_t *vldt, - nxt_conf_value_t *value, void *data) -{ - return nxt_conf_vldt_error(vldt, "The \"%s\" option is mutually exclusive " - "with the \"targets\" object.", data); -} - - -static nxt_int_t -nxt_conf_vldt_php_targets(nxt_conf_validation_t *vldt, nxt_conf_value_t *value, - void *data) -{ - nxt_uint_t n; - - n = nxt_conf_object_members_count(value); - - if (n > 254) { - return nxt_conf_vldt_error(vldt, "The \"targets\" object must not " - "contain more than 254 members."); - } - - return nxt_conf_vldt_object_iterator(vldt, value, - &nxt_conf_vldt_php_target); -} - - -static nxt_int_t -nxt_conf_vldt_php_target(nxt_conf_validation_t *vldt, nxt_str_t *name, - nxt_conf_value_t *value) -{ - if (name->length == 0) { - return nxt_conf_vldt_error(vldt, - "The PHP target name must not be empty."); - } - - if (nxt_conf_type(value) != NXT_CONF_OBJECT) { - return nxt_conf_vldt_error(vldt, "The \"%V\" PHP target must be " - "an object.", name); - } - - return nxt_conf_vldt_object(vldt, value, &nxt_conf_vldt_php_target_members); -} - - -static nxt_int_t nxt_conf_vldt_php_option(nxt_conf_validation_t *vldt, nxt_str_t *name, nxt_conf_value_t *value) { diff --git a/src/nxt_errno.h b/src/nxt_errno.h index 40bcfa3f..ec700537 100644 --- a/src/nxt_errno.h +++ b/src/nxt_errno.h @@ -22,6 +22,7 @@ typedef int nxt_err_t; #define NXT_EACCES EACCES #define NXT_EBUSY EBUSY #define NXT_EEXIST EEXIST +#define NXT_ELOOP ELOOP #define NXT_EXDEV EXDEV #define NXT_ENOTDIR ENOTDIR #define NXT_EISDIR EISDIR diff --git a/src/nxt_file.c b/src/nxt_file.c index a9595dd9..5d38d57e 100644 --- a/src/nxt_file.c +++ b/src/nxt_file.c @@ -42,6 +42,50 @@ nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, } +#if (NXT_HAVE_OPENAT2) + +nxt_int_t +nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, + nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd, + nxt_uint_t resolve) +{ + struct open_how how; + + nxt_memzero(&how, sizeof(how)); + + /* O_NONBLOCK is to prevent blocking on FIFOs, special devices, etc. */ + mode |= (O_NONBLOCK | create); + + how.flags = mode; + how.mode = access; + how.resolve = resolve; + + file->fd = syscall(SYS_openat2, dfd, file->name, &how, sizeof(how)); + + file->error = (file->fd == -1) ? nxt_errno : 0; + +#if (NXT_DEBUG) + nxt_thread_time_update(task->thread); +#endif + + nxt_debug(task, "openat2(%FD, \"%FN\"): %FD err:%d", dfd, file->name, + file->fd, file->error); + + if (file->fd != -1) { + return NXT_OK; + } + + if (file->log_level != 0) { + nxt_log(task, file->log_level, "openat2(%FD, \"%FN\") failed %E", dfd, + file->name, file->error); + } + + return NXT_ERROR; +} + +#endif + + void nxt_file_close(nxt_task_t *task, nxt_file_t *file) { diff --git a/src/nxt_file.h b/src/nxt_file.h index 4f56e746..4846305b 100644 --- a/src/nxt_file.h +++ b/src/nxt_file.h @@ -109,6 +109,12 @@ typedef struct { NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file, nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access); +#if (NXT_HAVE_OPENAT2) +NXT_EXPORT nxt_int_t nxt_file_openat2(nxt_task_t *task, nxt_file_t *file, + nxt_uint_t mode, nxt_uint_t create, nxt_file_access_t access, nxt_fd_t dfd, + nxt_uint_t resolve); +#endif + /* The file open access modes. */ #define NXT_FILE_RDONLY O_RDONLY @@ -116,6 +122,32 @@ NXT_EXPORT nxt_int_t nxt_file_open(nxt_task_t *task, nxt_file_t *file, #define NXT_FILE_RDWR O_RDWR #define NXT_FILE_APPEND (O_WRONLY | O_APPEND) +#if (NXT_HAVE_OPENAT2) + +#if defined(O_DIRECTORY) +#define NXT_FILE_DIRECTORY O_DIRECTORY +#else +#define NXT_FILE_DIRECTORY 0 +#endif + +#if defined(O_SEARCH) +#define NXT_FILE_SEARCH (O_SEARCH|NXT_FILE_DIRECTORY) + +#elif defined(O_EXEC) +#define NXT_FILE_SEARCH (O_EXEC|NXT_FILE_DIRECTORY) + +#else +/* + * O_PATH is used in combination with O_RDONLY. The last one is ignored + * if O_PATH is used, but it allows Unit to not fail when it was built on + * modern system (i.e. glibc 2.14+) and run with a kernel older than 2.6.39. + * Then O_PATH is unknown to the kernel and ignored, while O_RDONLY is used. + */ +#define NXT_FILE_SEARCH (O_PATH|O_RDONLY|NXT_FILE_DIRECTORY) +#endif + +#endif /* NXT_HAVE_OPENAT2 */ + /* The file creation modes. */ #define NXT_FILE_CREATE_OR_OPEN O_CREAT #define NXT_FILE_OPEN 0 diff --git a/src/nxt_h1proto.c b/src/nxt_h1proto.c index d3da6942..d3da6942 100644..100755 --- a/src/nxt_h1proto.c +++ b/src/nxt_h1proto.c diff --git a/src/nxt_http.h b/src/nxt_http.h index e30bfeb4..f82d837e 100644 --- a/src/nxt_http.h +++ b/src/nxt_http.h @@ -197,7 +197,8 @@ struct nxt_http_request_s { }; -typedef struct nxt_http_route_s nxt_http_route_t; +typedef struct nxt_http_route_s nxt_http_route_t; +typedef struct nxt_http_route_rule_s nxt_http_route_rule_t; struct nxt_http_action_s { @@ -206,16 +207,25 @@ struct nxt_http_action_s { nxt_http_action_t *action); union { nxt_http_route_t *route; - nxt_app_t *application; - nxt_http_action_t *fallback; nxt_upstream_t *upstream; uint32_t upstream_number; nxt_http_status_t return_code; nxt_var_t *var; + + struct { + nxt_app_t *application; + nxt_int_t target; + } app; + + struct { + nxt_str_t chroot; + nxt_uint_t resolve; + nxt_http_route_rule_t *types; + nxt_http_action_t *fallback; + } share; } u; nxt_str_t name; - nxt_int_t target; }; @@ -298,6 +308,8 @@ nxt_int_t nxt_http_pass_segments(nxt_mp_t *mp, nxt_str_t *pass, nxt_str_t *segments, nxt_uint_t n); nxt_http_action_t *nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, nxt_str_t *name); +nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, + nxt_http_route_rule_t *rule, u_char *start, size_t length); nxt_int_t nxt_upstreams_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *conf); diff --git a/src/nxt_http_request.c b/src/nxt_http_request.c index 650c1a89..779cfcf8 100644 --- a/src/nxt_http_request.c +++ b/src/nxt_http_request.c @@ -348,9 +348,9 @@ nxt_http_application_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_set(&r->server_name, "localhost"); } - r->app_target = action->target; + r->app_target = action->u.app.target; - nxt_router_process_http_request(task, r, action->u.application); + nxt_router_process_http_request(task, r, action->u.app.application); return NULL; } diff --git a/src/nxt_http_route.c b/src/nxt_http_route.c index 28545fc9..15b85544 100644 --- a/src/nxt_http_route.c +++ b/src/nxt_http_route.c @@ -50,8 +50,12 @@ typedef struct { nxt_conf_value_t *pass; nxt_conf_value_t *ret; nxt_str_t location; - nxt_conf_value_t *share; nxt_conf_value_t *proxy; + nxt_conf_value_t *share; + nxt_str_t chroot; + nxt_conf_value_t *follow_symlinks; + nxt_conf_value_t *traverse_mounts; + nxt_conf_value_t *types; nxt_conf_value_t *fallback; } nxt_http_route_action_conf_t; @@ -112,7 +116,7 @@ typedef struct { } nxt_http_cookie_t; -typedef struct { +struct nxt_http_route_rule_s { /* The object must be the first field. */ nxt_http_route_object_t object:8; uint32_t items; @@ -128,7 +132,7 @@ typedef struct { } u; nxt_http_route_pattern_t pattern[0]; -} nxt_http_route_rule_t; +}; typedef struct { @@ -195,8 +199,9 @@ static nxt_http_route_t *nxt_http_route_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); static nxt_http_route_match_t *nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv); -static nxt_int_t nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *cv, nxt_http_action_t *action); +static nxt_int_t nxt_http_route_action_create(nxt_task_t *task, + nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, + nxt_http_action_t *action); static nxt_http_route_table_t *nxt_http_route_table_create(nxt_task_t *task, nxt_mp_t *mp, nxt_conf_value_t *table_cv, nxt_http_route_object_t object, nxt_bool_t case_sensitive, nxt_http_route_encoding_t encoding); @@ -274,8 +279,6 @@ static nxt_http_name_value_t *nxt_http_route_cookie(nxt_array_t *array, u_char *name, size_t name_length, u_char *start, u_char *end); static nxt_int_t nxt_http_route_test_cookie(nxt_http_request_t *r, nxt_http_route_rule_t *rule, nxt_array_t *array); -static nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, - nxt_http_route_rule_t *rule, u_char *start, size_t length); static nxt_int_t nxt_http_route_pattern(nxt_http_request_t *r, nxt_http_route_pattern_t *pattern, u_char *start, size_t length); static nxt_int_t nxt_http_route_memcmp(u_char *start, u_char *test, @@ -457,7 +460,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, match_conf = nxt_conf_get_path(cv, &match_path); n = (match_conf != NULL) ? nxt_conf_object_members_count(match_conf) : 0; - size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_rule_t *); + size = sizeof(nxt_http_route_match_t) + n * sizeof(nxt_http_route_test_t *); mp = tmcf->router_conf->mem_pool; @@ -473,7 +476,7 @@ nxt_http_route_match_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, return NULL; } - ret = nxt_http_route_action_create(tmcf, action_conf, &match->action); + ret = nxt_http_route_action_create(task, tmcf, action_conf, &match->action); if (nxt_slow_path(ret != NXT_OK)) { return NULL; } @@ -627,14 +630,34 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { offsetof(nxt_http_route_action_conf_t, location) }, { + nxt_string("proxy"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, proxy) + }, + { nxt_string("share"), NXT_CONF_MAP_PTR, offsetof(nxt_http_route_action_conf_t, share) }, { - nxt_string("proxy"), + nxt_string("chroot"), + NXT_CONF_MAP_STR, + offsetof(nxt_http_route_action_conf_t, chroot) + }, + { + nxt_string("follow_symlinks"), NXT_CONF_MAP_PTR, - offsetof(nxt_http_route_action_conf_t, proxy) + offsetof(nxt_http_route_action_conf_t, follow_symlinks) + }, + { + nxt_string("traverse_mounts"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, traverse_mounts) + }, + { + nxt_string("types"), + NXT_CONF_MAP_PTR, + offsetof(nxt_http_route_action_conf_t, types) }, { nxt_string("fallback"), @@ -645,14 +668,20 @@ static nxt_conf_map_t nxt_http_route_action_conf[] = { static nxt_int_t -nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, - nxt_http_action_t *action) +nxt_http_route_action_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, + nxt_conf_value_t *cv, nxt_http_action_t *action) { +#if (NXT_HAVE_OPENAT2) + u_char *p; + uint8_t slash; + nxt_str_t *chroot; +#endif nxt_mp_t *mp; nxt_int_t ret; nxt_str_t name, *string; nxt_uint_t encode; nxt_conf_value_t *conf; + nxt_http_route_rule_t *rule; nxt_http_route_action_conf_t accf; nxt_memzero(&accf, sizeof(accf)); @@ -700,14 +729,14 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, return NXT_OK; } - conf = accf.pass; - if (accf.share != NULL) { conf = accf.share; - action->handler = nxt_http_static_handler; } else if (accf.proxy != NULL) { conf = accf.proxy; + + } else { + conf = accf.pass; } nxt_conf_get_string(conf, &name); @@ -717,14 +746,70 @@ nxt_http_route_action_create(nxt_router_temp_conf_t *tmcf, nxt_conf_value_t *cv, return NXT_ERROR; } - if (accf.fallback != NULL) { - action->u.fallback = nxt_mp_alloc(mp, sizeof(nxt_http_action_t)); - if (nxt_slow_path(action->u.fallback == NULL)) { - return NXT_ERROR; + if (accf.share != NULL) { + action->handler = nxt_http_static_handler; + +#if (NXT_HAVE_OPENAT2) + string = &accf.chroot; + chroot = &action->u.share.chroot; + + if (string->length > 0) { + action->u.share.resolve |= RESOLVE_IN_ROOT; + + slash = (string->start[string->length - 1] != '/'); + + chroot->length = string->length + (slash ? 1 : 0); + + chroot->start = nxt_mp_alloc(mp, chroot->length + 1); + if (nxt_slow_path(chroot->start == NULL)) { + return NXT_ERROR; + } + + p = nxt_cpymem(chroot->start, string->start, string->length); + + if (slash) { + *p++ = '/'; + } + + *p = '\0'; + } + + if (accf.follow_symlinks != NULL + && !nxt_conf_get_boolean(accf.follow_symlinks)) + { + action->u.share.resolve |= RESOLVE_NO_SYMLINKS; + } + + if (accf.traverse_mounts != NULL + && !nxt_conf_get_boolean(accf.traverse_mounts)) + { + action->u.share.resolve |= RESOLVE_NO_XDEV; + } +#endif + + if (accf.types != NULL) { + rule = nxt_http_route_rule_create(task, mp, accf.types, 0, + NXT_HTTP_ROUTE_PATTERN_LOWCASE, + NXT_HTTP_ROUTE_ENCODING_NONE); + if (nxt_slow_path(rule == NULL)) { + return NXT_ERROR; + } + + action->u.share.types = rule; } - return nxt_http_route_action_create(tmcf, accf.fallback, - action->u.fallback); + if (accf.fallback != NULL) { + action->u.share.fallback = nxt_mp_alloc(mp, + sizeof(nxt_http_action_t)); + if (nxt_slow_path(action->u.share.fallback == NULL)) { + return NXT_ERROR; + } + + return nxt_http_route_action_create(task, tmcf, accf.fallback, + action->u.share.fallback); + } + + return NXT_OK; } if (accf.proxy != NULL) { @@ -1411,9 +1496,10 @@ nxt_http_action_resolve(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, if (action->handler != NULL) { if (action->handler == nxt_http_static_handler - && action->u.fallback != NULL) + && action->u.share.fallback != NULL) { - return nxt_http_action_resolve(task, tmcf, action->u.fallback); + return nxt_http_action_resolve(task, tmcf, + action->u.share.fallback); } return NXT_OK; @@ -1533,14 +1619,14 @@ nxt_http_pass_find(nxt_task_t *task, nxt_mp_t *mp, nxt_router_conf_t *rtcf, } if (segments[2].length != 0) { - targets = action->u.application->targets; + targets = action->u.app.application->targets; for (i = 0; !nxt_strstr_eq(&segments[2], &targets[i]); i++); - action->target = i; + action->u.app.target = i; } else { - action->target = 0; + action->u.app.target = 0; } return NXT_OK; @@ -1678,7 +1764,7 @@ nxt_http_pass_application(nxt_task_t *task, nxt_router_conf_t *rtcf, (void) nxt_router_listener_application(rtcf, name, action); - action->target = 0; + action->u.app.target = 0; return action; } @@ -2426,7 +2512,7 @@ nxt_http_route_test_cookie(nxt_http_request_t *r, } -static nxt_int_t +nxt_int_t nxt_http_route_test_rule(nxt_http_request_t *r, nxt_http_route_rule_t *rule, u_char *start, size_t length) { diff --git a/src/nxt_http_static.c b/src/nxt_http_static.c index df2655fc..c8b73fac 100644 --- a/src/nxt_http_static.c +++ b/src/nxt_http_static.c @@ -31,15 +31,15 @@ nxt_http_action_t * nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_action_t *action) { - size_t alloc, encode; - u_char *p; + size_t length, encode; + u_char *p, *fname; struct tm tm; nxt_buf_t *fb; nxt_int_t ret; - nxt_str_t index, extension, *mtype; + nxt_str_t index, extension, *mtype, *chroot; nxt_uint_t level; nxt_bool_t need_body; - nxt_file_t *f; + nxt_file_t *f, file; nxt_file_info_t fi; nxt_http_field_t *field; nxt_http_status_t status; @@ -49,8 +49,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, if (nxt_slow_path(!nxt_str_eq(r->method, "GET", 3))) { if (!nxt_str_eq(r->method, "HEAD", 4)) { - if (action->u.fallback != NULL) { - return action->u.fallback; + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; } nxt_http_request_error(task, r, NXT_HTTP_METHOD_NOT_ALLOWED); @@ -63,13 +63,6 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, need_body = 1; } - f = nxt_mp_zget(r->mem_pool, sizeof(nxt_file_t)); - if (nxt_slow_path(f == NULL)) { - goto fail; - } - - f->fd = NXT_FILE_INVALID; - if (r->path->start[r->path->length - 1] == '/') { /* TODO: dynamic index setting. */ nxt_str_set(&index, "index.html"); @@ -80,23 +73,110 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_str_null(&extension); } - alloc = action->name.length + r->path->length + index.length + 1; + f = NULL; + + rtcf = r->conf->socket_conf->router_conf; + + mtype = NULL; + + if (action->u.share.types != NULL && extension.start == NULL) { + nxt_http_static_extract_extension(r->path, &extension); + mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, + &extension); + + ret = nxt_http_route_test_rule(r, action->u.share.types, + mtype->start, mtype->length); + if (nxt_slow_path(ret == NXT_ERROR)) { + goto fail; + } + + if (ret == 0) { + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; + } - f->name = nxt_mp_nget(r->mem_pool, alloc); - if (nxt_slow_path(f->name == NULL)) { + nxt_http_request_error(task, r, NXT_HTTP_FORBIDDEN); + return NULL; + } + } + + length = action->name.length + r->path->length + index.length; + + fname = nxt_mp_nget(r->mem_pool, length + 1); + if (nxt_slow_path(fname == NULL)) { goto fail; } - p = f->name; + p = fname; p = nxt_cpymem(p, action->name.start, action->name.length); p = nxt_cpymem(p, r->path->start, r->path->length); p = nxt_cpymem(p, index.start, index.length); *p = '\0'; - ret = nxt_file_open(task, f, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + nxt_memzero(&file, sizeof(nxt_file_t)); + + file.name = fname; + + chroot = &action->u.share.chroot; + +#if (NXT_HAVE_OPENAT2) + + if (action->u.share.resolve != 0) { + + if (chroot->length > 0) { + file.name = chroot->start; + + if (length > chroot->length + && nxt_memcmp(fname, chroot->start, chroot->length) == 0) + { + fname += chroot->length; + ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, + 0); + + } else { + file.error = NXT_EACCES; + ret = NXT_ERROR; + } + + } else if (fname[0] == '/') { + file.name = (u_char *) "/"; + ret = nxt_file_open(task, &file, NXT_FILE_SEARCH, NXT_FILE_OPEN, 0); + + } else { + file.name = (u_char *) "."; + file.fd = AT_FDCWD; + ret = NXT_OK; + } + + if (nxt_fast_path(ret == NXT_OK)) { + nxt_file_t af; + + af = file; + nxt_memzero(&file, sizeof(nxt_file_t)); + file.name = fname; + + ret = nxt_file_openat2(task, &file, NXT_FILE_RDONLY, + NXT_FILE_OPEN, 0, af.fd, + action->u.share.resolve); + + if (af.fd != AT_FDCWD) { + nxt_file_close(task, &af); + } + } + + } else { + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + } + +#else + + ret = nxt_file_open(task, &file, NXT_FILE_RDONLY, NXT_FILE_OPEN, 0); + +#endif if (nxt_slow_path(ret != NXT_OK)) { - switch (f->error) { + + switch (file.error) { /* * For Unix domain sockets "errno" is set to: @@ -117,6 +197,10 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, break; case NXT_EACCES: +#if (NXT_HAVE_OPENAT2) + case NXT_ELOOP: + case NXT_EXDEV: +#endif level = NXT_LOG_ERR; status = NXT_HTTP_FORBIDDEN; break; @@ -127,18 +211,32 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, break; } - if (level == NXT_LOG_ERR && action->u.fallback != NULL) { - return action->u.fallback; + if (level == NXT_LOG_ERR && action->u.share.fallback != NULL) { + return action->u.share.fallback; } if (status != NXT_HTTP_NOT_FOUND) { - nxt_log(task, level, "open(\"%FN\") failed %E", f->name, f->error); + if (chroot->length > 0) { + nxt_log(task, level, "opening \"%s\" at \"%V\" failed %E", + fname, chroot, file.error); + + } else { + nxt_log(task, level, "opening \"%s\" failed %E", + fname, file.error); + } } nxt_http_request_error(task, r, status); return NULL; } + f = nxt_mp_get(r->mem_pool, sizeof(nxt_file_t)); + if (nxt_slow_path(f == NULL)) { + goto fail; + } + + *f = file; + ret = nxt_file_info(f, &fi); if (nxt_slow_path(ret != NXT_OK)) { goto fail; @@ -172,15 +270,15 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_field_name_set(field, "ETag"); - alloc = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; + length = NXT_TIME_T_HEXLEN + NXT_OFF_T_HEXLEN + 3; - p = nxt_mp_nget(r->mem_pool, alloc); + p = nxt_mp_nget(r->mem_pool, length); if (nxt_slow_path(p == NULL)) { goto fail; } field->value = p; - field->value_length = nxt_sprintf(p, p + alloc, "\"%xT-%xO\"", + field->value_length = nxt_sprintf(p, p + length, "\"%xT-%xO\"", nxt_file_mtime(&fi), nxt_file_size(&fi)) - p; @@ -189,12 +287,12 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_static_extract_extension(r->path, &extension); } - rtcf = r->conf->socket_conf->router_conf; - - mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, - &extension); + if (mtype == NULL) { + mtype = nxt_http_static_mtypes_hash_find(&rtcf->mtypes_hash, + &extension); + } - if (mtype != NULL) { + if (mtype->length != 0) { field = nxt_list_zero_add(r->resp.fields); if (nxt_slow_path(field == NULL)) { goto fail; @@ -230,8 +328,8 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_file_close(task, f); if (nxt_slow_path(!nxt_is_dir(&fi))) { - if (action->u.fallback != NULL) { - return action->u.fallback; + if (action->u.share.fallback != NULL) { + return action->u.share.fallback; } nxt_log(task, NXT_LOG_ERR, "\"%FN\" is not a regular file", @@ -254,19 +352,19 @@ nxt_http_static_handler(nxt_task_t *task, nxt_http_request_t *r, nxt_http_field_name_set(field, "Location"); encode = nxt_encode_uri(NULL, r->path->start, r->path->length); - alloc = r->path->length + encode * 2 + 1; + length = r->path->length + encode * 2 + 1; if (r->args->length > 0) { - alloc += 1 + r->args->length; + length += 1 + r->args->length; } - p = nxt_mp_nget(r->mem_pool, alloc); + p = nxt_mp_nget(r->mem_pool, length); if (nxt_slow_path(p == NULL)) { goto fail; } field->value = p; - field->value_length = alloc; + field->value_length = length; if (encode > 0) { p = (u_char *) nxt_encode_uri(p, r->path->start, r->path->length); @@ -294,7 +392,7 @@ fail: nxt_http_request_error(task, r, NXT_HTTP_INTERNAL_SERVER_ERROR); - if (f != NULL && f->fd != NXT_FILE_INVALID) { + if (f != NULL) { nxt_file_close(task, f); } @@ -539,6 +637,8 @@ nxt_http_static_mtypes_init(nxt_mp_t *mp, nxt_lvlhsh_t *hash) { nxt_string("application/octet-stream"), ".deb" }, { nxt_string("application/octet-stream"), ".rpm" }, + + { nxt_string("application/x-httpd-php"), ".php" }, }; for (i = 0; i < nxt_nitems(default_types); i++) { @@ -605,6 +705,8 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) nxt_lvlhsh_query_t lhq; nxt_http_static_mtype_t *mtype; + static nxt_str_t empty = nxt_string(""); + lhq.key = *extension; lhq.key_hash = nxt_djb_hash_lowcase(lhq.key.start, lhq.key.length); lhq.proto = &nxt_http_static_mtypes_hash_proto; @@ -614,7 +716,7 @@ nxt_http_static_mtypes_hash_find(nxt_lvlhsh_t *hash, nxt_str_t *extension) return mtype->type; } - return NULL; + return ∅ } diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c index f20f2c2c..00f336f6 100644 --- a/src/nxt_main_process.c +++ b/src/nxt_main_process.c @@ -211,6 +211,12 @@ static nxt_conf_map_t nxt_python_app_conf[] = { }, { + nxt_string("targets"), + NXT_CONF_MAP_PTR, + offsetof(nxt_common_app_conf_t, u.python.targets), + }, + + { nxt_string("thread_stack_size"), NXT_CONF_MAP_INT32, offsetof(nxt_common_app_conf_t, u.python.thread_stack_size), diff --git a/src/nxt_openssl.c b/src/nxt_openssl.c index 9b86150f..2fd5d1bf 100644 --- a/src/nxt_openssl.c +++ b/src/nxt_openssl.c @@ -5,6 +5,7 @@ */ #include <nxt_main.h> +#include <nxt_conf.h> #include <openssl/ssl.h> #include <openssl/conf.h> #include <openssl/err.h> @@ -42,9 +43,14 @@ static unsigned long nxt_openssl_thread_id(void); static void nxt_openssl_locks_free(void); #endif static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, - nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t last); + nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, + nxt_bool_t last); static nxt_int_t nxt_openssl_chain_file(nxt_task_t *task, SSL_CTX *ctx, nxt_tls_conf_t *conf, nxt_mp_t *mp, nxt_bool_t single); +#if (NXT_HAVE_OPENSSL_CONF_CMD) +static nxt_int_t nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, + nxt_conf_value_t *value, nxt_mp_t *mp); +#endif static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp); static nxt_int_t nxt_openssl_bundle_hash_test(nxt_lvlhsh_query_t *lhq, @@ -66,6 +72,8 @@ static void nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data); static nxt_int_t nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, nxt_err_t sys_err, nxt_openssl_io_t io); +static void nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj, + void *data); static void nxt_cdecl nxt_openssl_conn_error(nxt_task_t *task, nxt_err_t err, const char *fmt, ...); static nxt_uint_t nxt_openssl_log_error_level(nxt_err_t err); @@ -258,7 +266,7 @@ nxt_openssl_locks_free(void) static nxt_int_t nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, - nxt_mp_t *mp, nxt_bool_t last) + nxt_mp_t *mp, nxt_conf_value_t *conf_cmds, nxt_bool_t last) { SSL_CTX *ctx; const char *ciphers, *ca_certificate; @@ -318,6 +326,7 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, goto fail; } */ + ciphers = (conf->ciphers != NULL) ? conf->ciphers : "HIGH:!aNULL:!MD5"; if (SSL_CTX_set_cipher_list(ctx, ciphers) == 0) { @@ -327,6 +336,14 @@ nxt_openssl_server_init(nxt_task_t *task, nxt_tls_conf_t *conf, goto fail; } +#if (NXT_HAVE_OPENSSL_CONF_CMD) + if (conf_cmds != NULL + && nxt_ssl_conf_commands(task, ctx, conf_cmds, mp) != NXT_OK) + { + goto fail; + } +#endif + SSL_CTX_set_options(ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); if (conf->ca_certificate != NULL) { @@ -482,6 +499,89 @@ clean: } +#if (NXT_HAVE_OPENSSL_CONF_CMD) + +static nxt_int_t +nxt_ssl_conf_commands(nxt_task_t *task, SSL_CTX *ctx, nxt_conf_value_t *conf, + nxt_mp_t *mp) +{ + int ret; + char *zcmd, *zvalue; + uint32_t index; + nxt_str_t cmd, value; + SSL_CONF_CTX *cctx; + nxt_conf_value_t *member; + + cctx = SSL_CONF_CTX_new(); + if (nxt_slow_path(cctx == NULL)) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "SSL_CONF_CTX_new() failed"); + return NXT_ERROR; + } + + SSL_CONF_CTX_set_flags(cctx, SSL_CONF_FLAG_FILE + | SSL_CONF_FLAG_SERVER + | SSL_CONF_FLAG_CERTIFICATE + | SSL_CONF_FLAG_SHOW_ERRORS); + + SSL_CONF_CTX_set_ssl_ctx(cctx, ctx); + + index = 0; + + for ( ;; ) { + member = nxt_conf_next_object_member(conf, &cmd, &index); + if (nxt_slow_path(member == NULL)) { + break; + } + + nxt_conf_get_string(member, &value); + + zcmd = nxt_str_cstrz(mp, &cmd); + zvalue = nxt_str_cstrz(mp, &value); + + if (nxt_slow_path(zcmd == NULL || zvalue == NULL)) { + goto fail; + } + + ret = SSL_CONF_cmd(cctx, zcmd, zvalue); + if (ret == -2) { + nxt_openssl_log_error(task, NXT_LOG_ERR, + "unknown command \"%s\" in " + "\"conf_commands\" option", zcmd); + goto fail; + } + + if (ret <= 0) { + nxt_openssl_log_error(task, NXT_LOG_ERR, + "invalid value \"%s\" for command \"%s\" " + "in \"conf_commands\" option", + zvalue, zcmd); + goto fail; + } + + nxt_debug(task, "SSL_CONF_cmd(\"%s\", \"%s\")", zcmd, zvalue); + } + + if (SSL_CONF_CTX_finish(cctx) != 1) { + nxt_openssl_log_error(task, NXT_LOG_ALERT, + "SSL_CONF_finish() failed"); + goto fail; + } + + SSL_CONF_CTX_free(cctx); + + return NXT_OK; + +fail: + + SSL_CONF_CTX_free(cctx); + + return NXT_ERROR; +} + +#endif + + static nxt_uint_t nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, nxt_mp_t *mp) @@ -548,7 +648,7 @@ nxt_openssl_cert_get_names(nxt_task_t *task, X509 *cert, nxt_tls_conf_t *conf, NULL, 0); if (len <= 0) { nxt_log(task, NXT_LOG_WARN, "certificate \"%V\" has neither " - "Subject Alternative Name nor Common Name", bundle->name); + "Subject Alternative Name nor Common Name", &bundle->name); return NXT_OK; } @@ -627,7 +727,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, if (item->name.length == 0 || item->name.start[0] != '.') { nxt_log(task, NXT_LOG_WARN, "ignored invalid name \"%V\" " "in certificate \"%V\": missing \".\" " - "after wildcard symbol", &str, item->bundle->name); + "after wildcard symbol", &str, &item->bundle->name); return NXT_OK; } } @@ -642,7 +742,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, ret = nxt_lvlhsh_insert(lvlhsh, &lhq); if (nxt_fast_path(ret == NXT_OK)) { nxt_debug(task, "name \"%V\" for certificate \"%V\" is inserted", - &str, item->bundle->name); + &str, &item->bundle->name); return NXT_OK; } @@ -651,7 +751,7 @@ nxt_openssl_bundle_hash_insert(nxt_task_t *task, nxt_lvlhsh_t *lvlhsh, if (old->bundle != item->bundle) { nxt_log(task, NXT_LOG_WARN, "ignored duplicate name \"%V\" " "in certificate \"%V\", identical name appears in \"%V\"", - &str, old->bundle->name, item->bundle->name); + &str, &old->bundle->name, &item->bundle->name); old->bundle = item->bundle; } @@ -728,8 +828,8 @@ nxt_openssl_servername(SSL *s, int *ad, void *arg) if (bundle != NULL) { nxt_debug(c->socket.task, "new tls context found for \"%V\": \"%V\" " - "(old: \"%V\")", &str, bundle->name, - conf->bundle->name); + "(old: \"%V\")", &str, &bundle->name, + &conf->bundle->name); if (bundle != conf->bundle) { if (SSL_set_SSL_CTX(s, bundle->ctx) == NULL) { @@ -839,11 +939,7 @@ nxt_openssl_conn_init(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_conn_t *c) c->sendfile = NXT_CONN_SENDFILE_OFF; nxt_openssl_conn_handshake(task, c, c->socket.data); - /* - * TLS configuration might be destroyed after the TLS connection - * is established. - */ - tls->conf = NULL; + return; fail: @@ -1099,6 +1195,10 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data) SSL_set_quiet_shutdown(s, quiet); + if (tls->conf->no_wait_shutdown) { + mode |= SSL_RECEIVED_SHUTDOWN; + } + once = 1; for ( ;; ) { @@ -1153,7 +1253,8 @@ nxt_openssl_conn_io_shutdown(nxt_task_t *task, void *obj, void *data) break; case NXT_AGAIN: - nxt_timer_add(task->thread->engine, &c->read_timer, 5000); + c->write_timer.handler = nxt_openssl_conn_io_shutdown_timeout; + nxt_timer_add(task->thread->engine, &c->write_timer, 5000); return; default: @@ -1237,6 +1338,23 @@ nxt_openssl_conn_test_error(nxt_task_t *task, nxt_conn_t *c, int ret, } +static void +nxt_openssl_conn_io_shutdown_timeout(nxt_task_t *task, void *obj, void *data) +{ + nxt_conn_t *c; + nxt_timer_t *timer; + + timer = obj; + + nxt_debug(task, "openssl conn shutdown timeout"); + + c = nxt_write_timer_conn(timer); + + c->socket.timedout = 1; + nxt_openssl_conn_io_shutdown(task, c, NULL); +} + + static void nxt_cdecl nxt_openssl_conn_error(nxt_task_t *task, nxt_err_t err, const char *fmt, ...) { diff --git a/src/nxt_pcre2.c b/src/nxt_pcre2.c index 22c4d2d4..cb51062c 100644 --- a/src/nxt_pcre2.c +++ b/src/nxt_pcre2.c @@ -144,7 +144,7 @@ nxt_regex_match(nxt_regex_t *re, u_char *subject, size_t length, if (pcre2_get_error_message(ret, errptr, ERR_BUF_SIZE) < 0) { nxt_thread_log_error(NXT_LOG_ERR, "pcre2_match() failed: %d on \"%*s\" " - "using \"%V\"", ret, subject, length, subject, + "using \"%V\"", ret, length, subject, &re->pattern); } else { diff --git a/src/nxt_php_sapi.c b/src/nxt_php_sapi.c index 8fbe7f65..3fb3b0db 100644 --- a/src/nxt_php_sapi.c +++ b/src/nxt_php_sapi.c @@ -163,7 +163,8 @@ static const zend_function_entry nxt_php_ext_functions[] = { }; -zif_handler nxt_php_chdir_handler; +zif_handler nxt_php_chdir_handler; +zend_auto_global *nxt_php_server_ag; static zend_module_entry nxt_php_unit_module = { @@ -211,6 +212,7 @@ ZEND_NAMED_FUNCTION(nxt_php_chdir) PHP_FUNCTION(fastcgi_finish_request) { + zend_auto_global *ag; nxt_php_run_ctx_t *ctx; if (nxt_slow_path(zend_parse_parameters_none() == FAILURE)) { @@ -240,6 +242,16 @@ PHP_FUNCTION(fastcgi_finish_request) php_header(TSRMLS_C); #endif + ag = nxt_php_server_ag; + + if (ag->armed) { +#ifdef NXT_PHP7 + ag->armed = ag->auto_global_callback(ag->name); +#else + ag->armed = ag->auto_global_callback(ag->name, ag->name_len TSRMLS_CC); +#endif + } + nxt_unit_request_done(ctx->req, NXT_UNIT_OK); ctx->req = NULL; @@ -411,6 +423,19 @@ nxt_php_setup(nxt_task_t *task, nxt_process_t *process, nxt_php_set_options(task, value, ZEND_INI_USER); } +#ifdef NXT_PHP7 + nxt_php_server_ag = zend_hash_str_find_ptr(CG(auto_globals), "_SERVER", + nxt_length("_SERVER")); +#else + zend_hash_quick_find(CG(auto_globals), "_SERVER", sizeof("_SERVER"), + zend_hash_func("_SERVER", sizeof("_SERVER")), + (void **) &nxt_php_server_ag); +#endif + if (nxt_slow_path(nxt_php_server_ag == NULL)) { + nxt_alert(task, "failed to find $_SERVER auto global"); + return NXT_ERROR; + } + return NXT_OK; } @@ -1076,10 +1101,20 @@ nxt_php_execute(nxt_php_run_ctx_t *ctx, nxt_unit_request_t *r) nxt_memzero(&file_handle, sizeof(file_handle)); file_handle.type = ZEND_HANDLE_FILENAME; +#if (PHP_VERSION_ID >= 80100) + file_handle.filename = zend_string_init((char *) ctx->script_filename.start, + ctx->script_filename.length, 0); + file_handle.primary_script = 1; +#else file_handle.filename = (char *) ctx->script_filename.start; +#endif php_execute_script(&file_handle TSRMLS_CC); +#if (PHP_VERSION_ID >= 80100) + zend_destroy_file_handle(&file_handle); +#endif + /* Prevention of consuming possible unread request body. */ #if (PHP_VERSION_ID < 50600) read_post = sapi_module.read_post; diff --git a/src/nxt_router.c b/src/nxt_router.c index 8524b358..015ae226 100644 --- a/src/nxt_router.c +++ b/src/nxt_router.c @@ -41,8 +41,11 @@ typedef struct { #if (NXT_TLS) typedef struct { - nxt_str_t name; - nxt_socket_conf_t *conf; + nxt_str_t name; + nxt_socket_conf_t *socket_conf; + nxt_router_temp_conf_t *temp_conf; + nxt_conf_value_t *conf_cmds; + nxt_bool_t last; nxt_queue_link_t link; /* for nxt_socket_conf_t.tls */ } nxt_router_tlssock_t; @@ -117,12 +120,11 @@ static void nxt_router_listen_socket_ready(nxt_task_t *task, static void nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); #if (NXT_TLS) -static void nxt_router_tls_rpc_create(nxt_task_t *task, - nxt_router_temp_conf_t *tmcf, nxt_router_tlssock_t *tls, nxt_bool_t last); static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data); static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *value, nxt_socket_conf_t *skcf); + nxt_conf_value_t *value, nxt_socket_conf_t *skcf, + nxt_conf_value_t * conf_cmds); #endif static void nxt_router_app_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_app_t *app); @@ -773,7 +775,7 @@ fail: msg->port_msg.stream, 0, NULL); if (tmcf != NULL) { - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } cleanup: @@ -954,8 +956,10 @@ nxt_router_conf_apply(nxt_task_t *task, void *obj, void *data) tls = nxt_queue_link_data(qlk, nxt_router_tlssock_t, link); - nxt_router_tls_rpc_create(task, tmcf, tls, - nxt_queue_is_empty(&tmcf->tls)); + tls->last = nxt_queue_is_empty(&tmcf->tls); + + nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, + nxt_router_tls_rpc_handler, tls); return; } #endif @@ -1061,7 +1065,7 @@ nxt_router_conf_ready(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_mp_destroy(rtcf->mem_pool); } - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } @@ -1120,7 +1124,7 @@ nxt_router_conf_error(nxt_task_t *task, nxt_router_temp_conf_t *tmcf) nxt_router_conf_send(task, tmcf, NXT_PORT_MSG_RPC_ERROR); - nxt_mp_destroy(tmcf->mem_pool); + nxt_mp_release(tmcf->mem_pool); } @@ -1337,7 +1341,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_router_t *router; nxt_app_joint_t *app_joint; #if (NXT_TLS) - nxt_conf_value_t *certificate; + nxt_conf_value_t *certificate, *conf_cmds; #endif nxt_conf_value_t *conf, *http, *value, *websocket; nxt_conf_value_t *applications, *application; @@ -1358,6 +1362,7 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, static nxt_str_t access_log_path = nxt_string("/access_log"); #if (NXT_TLS) static nxt_str_t certificate_path = nxt_string("/tls/certificate"); + static nxt_str_t conf_commands_path = nxt_string("/tls/conf_commands"); #endif static nxt_str_t static_path = nxt_string("/settings/http/static"); static nxt_str_t websocket_path = nxt_string("/settings/http/websocket"); @@ -1736,6 +1741,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, certificate = nxt_conf_get_path(listener, &certificate_path); if (certificate != NULL) { + conf_cmds = nxt_conf_get_path(listener, &conf_commands_path); + if (nxt_conf_type(certificate) == NXT_CONF_ARRAY) { n = nxt_conf_array_elements_count(certificate); @@ -1744,7 +1751,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, nxt_assert(value != NULL); - ret = nxt_router_conf_tls_insert(tmcf, value, skcf); + ret = nxt_router_conf_tls_insert(tmcf, value, skcf, + conf_cmds); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1752,7 +1760,8 @@ nxt_router_conf_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, } else { /* NXT_CONF_STRING */ - ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf); + ret = nxt_router_conf_tls_insert(tmcf, certificate, skcf, + conf_cmds); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -1846,25 +1855,20 @@ fail: static nxt_int_t nxt_router_conf_tls_insert(nxt_router_temp_conf_t *tmcf, - nxt_conf_value_t *value, nxt_socket_conf_t *skcf) + nxt_conf_value_t *value, nxt_socket_conf_t *skcf, + nxt_conf_value_t *conf_cmds) { - nxt_mp_t *mp; - nxt_str_t str; nxt_router_tlssock_t *tls; - mp = tmcf->router_conf->mem_pool; - - tls = nxt_mp_get(mp, sizeof(nxt_router_tlssock_t)); + tls = nxt_mp_get(tmcf->mem_pool, sizeof(nxt_router_tlssock_t)); if (nxt_slow_path(tls == NULL)) { return NXT_ERROR; } - tls->conf = skcf; - nxt_conf_get_string(value, &str); - - if (nxt_slow_path(nxt_str_dup(mp, &tls->name, &str) == NULL)) { - return NXT_ERROR; - } + tls->socket_conf = skcf; + tls->conf_cmds = conf_cmds; + tls->temp_conf = tmcf; + nxt_conf_get_string(value, &tls->name); nxt_queue_insert_tail(&tmcf->tls, &tls->link); @@ -2144,7 +2148,7 @@ nxt_router_listener_application(nxt_router_conf_t *rtcf, nxt_str_t *name, return NXT_DECLINED; } - action->u.application = app; + action->u.app.application = app; action->handler = nxt_http_application_handler; return NXT_OK; @@ -2428,42 +2432,20 @@ nxt_router_listen_socket_error(nxt_task_t *task, nxt_port_recv_msg_t *msg, #if (NXT_TLS) static void -nxt_router_tls_rpc_create(nxt_task_t *task, nxt_router_temp_conf_t *tmcf, - nxt_router_tlssock_t *tls, nxt_bool_t last) -{ - nxt_socket_rpc_t *rpc; - - rpc = nxt_mp_alloc(tmcf->mem_pool, sizeof(nxt_socket_rpc_t)); - if (rpc == NULL) { - nxt_router_conf_error(task, tmcf); - return; - } - - rpc->name = &tls->name; - rpc->socket_conf = tls->conf; - rpc->temp_conf = tmcf; - rpc->last = last; - - nxt_cert_store_get(task, &tls->name, tmcf->mem_pool, - nxt_router_tls_rpc_handler, rpc); -} - - -static void nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, void *data) { nxt_mp_t *mp; nxt_int_t ret; nxt_tls_conf_t *tlscf; - nxt_socket_rpc_t *rpc; + nxt_router_tlssock_t *tls; nxt_tls_bundle_conf_t *bundle; nxt_router_temp_conf_t *tmcf; nxt_debug(task, "tls rpc handler"); - rpc = data; - tmcf = rpc->temp_conf; + tls = data; + tmcf = tls->temp_conf; if (msg == NULL || msg->port_msg.type == _NXT_PORT_MSG_RPC_ERROR) { goto fail; @@ -2471,16 +2453,17 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, mp = tmcf->router_conf->mem_pool; - if (rpc->socket_conf->tls == NULL){ + if (tls->socket_conf->tls == NULL){ tlscf = nxt_mp_zget(mp, sizeof(nxt_tls_conf_t)); if (nxt_slow_path(tlscf == NULL)) { goto fail; } - rpc->socket_conf->tls = tlscf; + tlscf->no_wait_shutdown = 1; + tls->socket_conf->tls = tlscf; } else { - tlscf = rpc->socket_conf->tls; + tlscf = tls->socket_conf->tls; } bundle = nxt_mp_get(mp, sizeof(nxt_tls_bundle_conf_t)); @@ -2488,12 +2471,16 @@ nxt_router_tls_rpc_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg, goto fail; } - bundle->name = rpc->name; + if (nxt_slow_path(nxt_str_dup(mp, &bundle->name, &tls->name) == NULL)) { + goto fail; + } + bundle->chain_file = msg->fd[0]; bundle->next = tlscf->bundle; tlscf->bundle = bundle; - ret = task->thread->runtime->tls->server_init(task, tlscf, mp, rpc->last); + ret = task->thread->runtime->tls->server_init(task, tlscf, mp, + tls->conf_cmds, tls->last); if (nxt_slow_path(ret != NXT_OK)) { goto fail; } @@ -3223,12 +3210,11 @@ nxt_router_listen_socket_update(nxt_task_t *task, void *obj, void *data) static void nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data) { - nxt_joint_job_t *job; - nxt_socket_conf_t *skcf; - nxt_listen_event_t *lev; - nxt_event_engine_t *engine; + nxt_socket_conf_t *skcf; + nxt_listen_event_t *lev; + nxt_event_engine_t *engine; + nxt_socket_conf_joint_t *joint; - job = obj; skcf = data; engine = task->thread->engine; @@ -3240,15 +3226,13 @@ nxt_router_listen_socket_delete(nxt_task_t *task, void *obj, void *data) nxt_debug(task, "engine %p: listen socket delete: %d", engine, lev->socket.fd); + joint = lev->socket.data; + joint->close_job = obj; + lev->timer.handler = nxt_router_listen_socket_close; lev->timer.work_queue = &engine->fast_work_queue; nxt_timer_add(engine, &lev->timer, 0); - - job->work.next = NULL; - job->work.handler = nxt_router_conf_wait; - - nxt_event_engine_post(job->tmcf->engine, &job->work); } @@ -3273,6 +3257,7 @@ static void nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data) { nxt_timer_t *timer; + nxt_joint_job_t *job; nxt_listen_event_t *lev; nxt_socket_conf_joint_t *joint; @@ -3292,6 +3277,12 @@ nxt_router_listen_socket_close(nxt_task_t *task, void *obj, void *data) nxt_router_listen_socket_release(task, joint->socket_conf); + job = joint->close_job; + job->work.next = NULL; + job->work.handler = nxt_router_conf_wait; + + nxt_event_engine_post(job->tmcf->engine, &job->work); + nxt_router_listen_event_release(task, lev, joint); } diff --git a/src/nxt_router.h b/src/nxt_router.h index 5804840f..b1ccdf51 100644 --- a/src/nxt_router.h +++ b/src/nxt_router.h @@ -205,6 +205,8 @@ typedef struct { nxt_event_engine_t *engine; nxt_socket_conf_t *socket_conf; + nxt_joint_job_t *close_job; + nxt_upstream_t **upstreams; /* Modules configuraitons. */ diff --git a/src/nxt_tls.h b/src/nxt_tls.h index c44bfe56..63c49ee4 100644 --- a/src/nxt_tls.h +++ b/src/nxt_tls.h @@ -8,6 +8,9 @@ #define _NXT_TLS_H_INCLUDED_ +#include <nxt_conf.h> + + /* * The SSL/TLS libraries lack vector I/O interface yet add noticeable * overhead to each SSL/TLS record so buffering allows to decrease the @@ -32,6 +35,7 @@ typedef struct { nxt_int_t (*server_init)(nxt_task_t *task, nxt_tls_conf_t *conf, nxt_mp_t *mp, + nxt_conf_value_t *conf_cmds, nxt_bool_t last); void (*server_free)(nxt_task_t *task, nxt_tls_conf_t *conf); @@ -49,7 +53,7 @@ struct nxt_tls_bundle_conf_s { void *ctx; nxt_fd_t chain_file; - nxt_str_t *name; + nxt_str_t name; nxt_tls_bundle_conf_t *next; }; @@ -69,6 +73,8 @@ struct nxt_tls_conf_s { char *ca_certificate; size_t buffer_size; + + uint8_t no_wait_shutdown; /* 1 bit */ }; diff --git a/src/nxt_unix.h b/src/nxt_unix.h index 609f7e95..393f61d9 100644 --- a/src/nxt_unix.h +++ b/src/nxt_unix.h @@ -242,6 +242,10 @@ #include <sys/mount.h> #endif +#if (NXT_HAVE_OPENAT2) +#include <linux/openat2.h> +#endif + #if (NXT_TEST_BUILD) #include <nxt_test_build.h> #endif diff --git a/src/python/nxt_python.c b/src/python/nxt_python.c index d8204937..588a147a 100644 --- a/src/python/nxt_python.c +++ b/src/python/nxt_python.c @@ -24,6 +24,8 @@ typedef struct { static nxt_int_t nxt_python_start(nxt_task_t *task, nxt_process_data_t *data); +static nxt_int_t nxt_python_set_target(nxt_task_t *task, + nxt_python_target_t *target, nxt_conf_value_t *conf); static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value); static int nxt_python_init_threads(nxt_python_app_conf_t *c); static int nxt_python_ready_handler(nxt_unit_ctx_t *ctx); @@ -49,7 +51,7 @@ NXT_EXPORT nxt_app_module_t nxt_app_module = { }; static PyObject *nxt_py_stderr_flush; -PyObject *nxt_py_application; +nxt_python_targets_t *nxt_py_targets; #if PY_MAJOR_VERSION == 3 static wchar_t *nxt_py_home; @@ -66,18 +68,19 @@ static nxt_int_t nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) { int rc; - char *nxt_py_module; - size_t len; + size_t len, size; + uint32_t next; PyObject *obj, *module; - nxt_str_t proto; - const char *callable; + nxt_str_t proto, probe_proto, name; + nxt_int_t ret, n, i; nxt_unit_ctx_t *unit_ctx; nxt_unit_init_t python_init; + nxt_conf_value_t *cv; + nxt_python_targets_t *targets; nxt_common_app_conf_t *app_conf; nxt_python_app_conf_t *c; #if PY_MAJOR_VERSION == 3 char *path; - size_t size; nxt_int_t pep405; static const char pyvenv[] = "/pyvenv.cfg"; @@ -190,38 +193,42 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) Py_CLEAR(obj); - nxt_py_module = nxt_alloca(c->module.length + 1); - nxt_memcpy(nxt_py_module, c->module.start, c->module.length); - nxt_py_module[c->module.length] = '\0'; + n = (c->targets != NULL ? nxt_conf_object_members_count(c->targets) : 1); - module = PyImport_ImportModule(nxt_py_module); - if (nxt_slow_path(module == NULL)) { - nxt_alert(task, "Python failed to import module \"%s\"", nxt_py_module); - nxt_python_print_exception(); + size = sizeof(nxt_python_targets_t) + n * sizeof(nxt_python_target_t); + + targets = nxt_unit_malloc(NULL, size); + if (nxt_slow_path(targets == NULL)) { + nxt_alert(task, "Could not allocate targets"); goto fail; } - callable = (c->callable != NULL) ? c->callable : "application"; + memset(targets, 0, size); - obj = PyDict_GetItemString(PyModule_GetDict(module), callable); - if (nxt_slow_path(obj == NULL)) { - nxt_alert(task, "Python failed to get \"%s\" " - "from module \"%s\"", callable, nxt_py_module); - goto fail; - } + targets->count = n; + nxt_py_targets = targets; - if (nxt_slow_path(PyCallable_Check(obj) == 0)) { - nxt_alert(task, "\"%s\" in module \"%s\" " - "is not a callable object", callable, nxt_py_module); - goto fail; - } + if (c->targets != NULL) { + next = 0; - nxt_py_application = obj; - obj = NULL; + for (i = 0; /* void */; i++) { + cv = nxt_conf_next_object_member(c->targets, &name, &next); + if (cv == NULL) { + break; + } - Py_INCREF(nxt_py_application); + ret = nxt_python_set_target(task, &targets->target[i], cv); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } - Py_CLEAR(module); + } else { + ret = nxt_python_set_target(task, &targets->target[0], app_conf->self); + if (nxt_slow_path(ret != NXT_OK)) { + goto fail; + } + } nxt_unit_default_init(task, &python_init); @@ -232,7 +239,18 @@ nxt_python_start(nxt_task_t *task, nxt_process_data_t *data) proto = c->protocol; if (proto.length == 0) { - proto = nxt_python_asgi_check(nxt_py_application) ? asgi : wsgi; + proto = nxt_python_asgi_check(targets->target[0].application) + ? asgi : wsgi; + + for (i = 1; i < targets->count; i++) { + probe_proto = nxt_python_asgi_check(targets->target[i].application) + ? asgi : wsgi; + if (probe_proto.start != proto.start) { + nxt_alert(task, "A mix of ASGI & WSGI targets is forbidden, " + "specify protocol in config if incorrect"); + goto fail; + } + } } if (nxt_strstr_eq(&proto, &asgi)) { @@ -299,6 +317,81 @@ fail: static nxt_int_t +nxt_python_set_target(nxt_task_t *task, nxt_python_target_t *target, + nxt_conf_value_t *conf) +{ + char *callable, *module_name; + PyObject *module, *obj; + nxt_str_t str; + nxt_conf_value_t *value; + + static nxt_str_t module_str = nxt_string("module"); + static nxt_str_t callable_str = nxt_string("callable"); + + module = obj = NULL; + + value = nxt_conf_get_object_member(conf, &module_str, NULL); + if (nxt_slow_path(value == NULL)) { + goto fail; + } + + nxt_conf_get_string(value, &str); + + module_name = nxt_alloca(str.length + 1); + nxt_memcpy(module_name, str.start, str.length); + module_name[str.length] = '\0'; + + module = PyImport_ImportModule(module_name); + if (nxt_slow_path(module == NULL)) { + nxt_alert(task, "Python failed to import module \"%s\"", module_name); + nxt_python_print_exception(); + goto fail; + } + + value = nxt_conf_get_object_member(conf, &callable_str, NULL); + if (value == NULL) { + callable = nxt_alloca(12); + nxt_memcpy(callable, "application", 12); + + } else { + nxt_conf_get_string(value, &str); + + callable = nxt_alloca(str.length + 1); + nxt_memcpy(callable, str.start, str.length); + callable[str.length] = '\0'; + } + + obj = PyDict_GetItemString(PyModule_GetDict(module), callable); + if (nxt_slow_path(obj == NULL)) { + nxt_alert(task, "Python failed to get \"%s\" from module \"%s\"", + callable, module); + goto fail; + } + + if (nxt_slow_path(PyCallable_Check(obj) == 0)) { + nxt_alert(task, "\"%s\" in module \"%s\" is not a callable object", + callable, module); + goto fail; + } + + target->application = obj; + obj = NULL; + + Py_INCREF(target->application); + Py_CLEAR(module); + + return NXT_OK; + +fail: + + Py_XDECREF(obj); + Py_XDECREF(module); + + return NXT_ERROR; +} + + +static nxt_int_t nxt_python_set_path(nxt_task_t *task, nxt_conf_value_t *value) { int ret; @@ -596,12 +689,21 @@ nxt_python_done_strings(nxt_python_string_t *pstr) static void nxt_python_atexit(void) { + nxt_int_t i; + if (nxt_py_proto.done != NULL) { nxt_py_proto.done(); } Py_XDECREF(nxt_py_stderr_flush); - Py_XDECREF(nxt_py_application); + + if (nxt_py_targets != NULL) { + for (i = 0; i < nxt_py_targets->count; i++) { + Py_XDECREF(nxt_py_targets->target[i].application); + } + + nxt_unit_free(NULL, nxt_py_targets); + } Py_Finalize(); diff --git a/src/python/nxt_python.h b/src/python/nxt_python.h index b581dd46..a5c1d9a6 100644 --- a/src/python/nxt_python.h +++ b/src/python/nxt_python.h @@ -37,13 +37,28 @@ #define NXT_HAVE_ASGI 1 #endif -extern PyObject *nxt_py_application; + +typedef struct { + PyObject *application; + nxt_bool_t asgi_legacy; +} nxt_python_target_t; + + +typedef struct { + nxt_int_t count; + nxt_python_target_t target[0]; +} nxt_python_targets_t; + + +extern nxt_python_targets_t *nxt_py_targets; + typedef struct { nxt_str_t string; PyObject **object_p; } nxt_python_string_t; + typedef struct { int (*ctx_data_alloc)(void **pdata); void (*ctx_data_free)(void *data); diff --git a/src/python/nxt_python_asgi.c b/src/python/nxt_python_asgi.c index a6f94507..1d220678 100644 --- a/src/python/nxt_python_asgi.c +++ b/src/python/nxt_python_asgi.c @@ -43,8 +43,6 @@ static void nxt_py_asgi_shm_ack_handler(nxt_unit_ctx_t *ctx); static PyObject *nxt_py_asgi_port_read(PyObject *self, PyObject *args); static void nxt_python_asgi_done(void); - -int nxt_py_asgi_legacy; static PyObject *nxt_py_port_read; static nxt_unit_port_t *nxt_py_shared_port; @@ -137,6 +135,7 @@ int nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) { PyObject *func; + nxt_int_t i; PyCodeObject *code; nxt_unit_debug(NULL, "asgi_init"); @@ -161,21 +160,23 @@ nxt_python_asgi_init(nxt_unit_init_t *init, nxt_python_proto_t *proto) return NXT_UNIT_ERROR; } - func = nxt_python_asgi_get_func(nxt_py_application); - if (nxt_slow_path(func == NULL)) { - nxt_unit_alert(NULL, "Python cannot find function for callable"); - return NXT_UNIT_ERROR; - } + for (i = 0; i < nxt_py_targets->count; i++) { + func = nxt_python_asgi_get_func(nxt_py_targets->target[i].application); + if (nxt_slow_path(func == NULL)) { + nxt_unit_alert(NULL, "Python cannot find function for callable"); + return NXT_UNIT_ERROR; + } - code = (PyCodeObject *) PyFunction_GET_CODE(func); + code = (PyCodeObject *) PyFunction_GET_CODE(func); - if ((code->co_flags & CO_COROUTINE) == 0) { - nxt_unit_debug(NULL, "asgi: callable is not a coroutine function " - "switching to legacy mode"); - nxt_py_asgi_legacy = 1; - } + if ((code->co_flags & CO_COROUTINE) == 0) { + nxt_unit_debug(NULL, "asgi: callable is not a coroutine function " + "switching to legacy mode"); + nxt_py_targets->target[i].asgi_legacy = 1; + } - Py_DECREF(func); + Py_DECREF(func); + } init->callbacks.request_handler = nxt_py_asgi_request_handler; init->callbacks.data_handler = nxt_py_asgi_http_data_handler; @@ -408,6 +409,7 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) { PyObject *scope, *res, *task, *receive, *send, *done, *asgi; PyObject *stage2; + nxt_python_target_t *target; nxt_py_asgi_ctx_data_t *ctx_data; if (req->request->websocket_handshake) { @@ -456,17 +458,18 @@ nxt_py_asgi_request_handler(nxt_unit_request_info_t *req) } req->data = asgi; + target = &nxt_py_targets->target[req->request->app_target]; - if (!nxt_py_asgi_legacy) { + if (!target->asgi_legacy) { nxt_unit_req_debug(req, "Python call ASGI 3.0 application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, + res = PyObject_CallFunctionObjArgs(target->application, scope, receive, send, NULL); } else { nxt_unit_req_debug(req, "Python call legacy application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); + res = PyObject_CallFunctionObjArgs(target->application, scope, NULL); if (nxt_slow_path(res == NULL)) { nxt_unit_req_error(req, "Python failed to call legacy app stage1"); diff --git a/src/python/nxt_python_asgi.h b/src/python/nxt_python_asgi.h index 37f2a099..20702065 100644 --- a/src/python/nxt_python_asgi.h +++ b/src/python/nxt_python_asgi.h @@ -33,7 +33,7 @@ typedef struct { PyObject *loop_remove_reader; PyObject *quit_future; PyObject *quit_future_set_result; - PyObject *lifespan; + PyObject **target_lifespans; nxt_unit_port_t *port; } nxt_py_asgi_ctx_data_t; @@ -69,6 +69,4 @@ int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data); int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx); -extern int nxt_py_asgi_legacy; - #endif /* _NXT_PYTHON_ASGI_H_INCLUDED_ */ diff --git a/src/python/nxt_python_asgi_lifespan.c b/src/python/nxt_python_asgi_lifespan.c index 506eaf4d..1fc0e6b7 100644 --- a/src/python/nxt_python_asgi_lifespan.c +++ b/src/python/nxt_python_asgi_lifespan.c @@ -27,7 +27,10 @@ typedef struct { PyObject *receive_future; } nxt_py_asgi_lifespan_t; - +static PyObject *nxt_py_asgi_lifespan_target_startup( + nxt_py_asgi_ctx_data_t *ctx_data, nxt_python_target_t *target); +static int nxt_py_asgi_lifespan_target_shutdown( + nxt_py_asgi_lifespan_t *lifespan); static PyObject *nxt_py_asgi_lifespan_receive(PyObject *self, PyObject *none); static PyObject *nxt_py_asgi_lifespan_send(PyObject *self, PyObject *dict); static PyObject *nxt_py_asgi_lifespan_send_startup( @@ -69,24 +72,60 @@ static PyTypeObject nxt_py_asgi_lifespan_type = { int nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) { - int rc; + size_t size; + PyObject *lifespan; + PyObject **target_lifespans; + nxt_int_t i; + nxt_python_target_t *target; + + size = nxt_py_targets->count * sizeof(PyObject*); + + target_lifespans = nxt_unit_malloc(NULL, size); + if (nxt_slow_path(target_lifespans == NULL)) { + nxt_unit_alert(NULL, "Failed to allocate lifespan data"); + return NXT_UNIT_ERROR; + } + + memset(target_lifespans, 0, size); + + for (i = 0; i < nxt_py_targets->count; i++) { + target = &nxt_py_targets->target[i]; + + lifespan = nxt_py_asgi_lifespan_target_startup(ctx_data, target); + if (nxt_slow_path(lifespan == NULL)) { + return NXT_UNIT_ERROR; + } + + target_lifespans[i] = lifespan; + } + + ctx_data->target_lifespans = target_lifespans; + + return NXT_UNIT_OK; +} + + +static PyObject * +nxt_py_asgi_lifespan_target_startup(nxt_py_asgi_ctx_data_t *ctx_data, + nxt_python_target_t *target) +{ PyObject *scope, *res, *py_task, *receive, *send, *done; PyObject *stage2; - nxt_py_asgi_lifespan_t *lifespan; + nxt_py_asgi_lifespan_t *lifespan, *ret; if (nxt_slow_path(PyType_Ready(&nxt_py_asgi_lifespan_type) != 0)) { nxt_unit_alert(NULL, "Python failed to initialize the 'asgi_lifespan' type object"); - return NXT_UNIT_ERROR; + return NULL; } lifespan = PyObject_New(nxt_py_asgi_lifespan_t, &nxt_py_asgi_lifespan_type); if (nxt_slow_path(lifespan == NULL)) { nxt_unit_alert(NULL, "Python failed to create lifespan object"); - return NXT_UNIT_ERROR; + return NULL; } - rc = NXT_UNIT_ERROR; + ret = NULL; receive = PyObject_GetAttrString((PyObject *) lifespan, "receive"); if (nxt_slow_path(receive == NULL)) { @@ -130,23 +169,25 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) goto release_future; } - if (!nxt_py_asgi_legacy) { + if (!target->asgi_legacy) { nxt_unit_req_debug(NULL, "Python call ASGI 3.0 application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, + res = PyObject_CallFunctionObjArgs(target->application, scope, receive, send, NULL); } else { nxt_unit_req_debug(NULL, "Python call legacy application"); - res = PyObject_CallFunctionObjArgs(nxt_py_application, scope, NULL); + res = PyObject_CallFunctionObjArgs(target->application, scope, NULL); if (nxt_slow_path(res == NULL)) { nxt_unit_log(NULL, NXT_UNIT_LOG_INFO, "ASGI Lifespan processing exception"); nxt_python_print_exception(); lifespan->disabled = 1; - rc = NXT_UNIT_OK; + + Py_INCREF(lifespan); + ret = lifespan; goto release_scope; } @@ -211,10 +252,9 @@ nxt_py_asgi_lifespan_startup(nxt_py_asgi_ctx_data_t *ctx_data) Py_DECREF(res); if (lifespan->startup_sent == 1 || lifespan->disabled) { - ctx_data->lifespan = (PyObject *) lifespan; - Py_INCREF(ctx_data->lifespan); + Py_INCREF(lifespan); - rc = NXT_UNIT_OK; + ret = lifespan; } release_task: @@ -232,20 +272,41 @@ release_receive: release_lifespan: Py_DECREF(lifespan); - return rc; + return (PyObject *) ret; } int nxt_py_asgi_lifespan_shutdown(nxt_unit_ctx_t *ctx) { - PyObject *msg, *future, *res; + nxt_int_t i, ret; nxt_py_asgi_lifespan_t *lifespan; nxt_py_asgi_ctx_data_t *ctx_data; ctx_data = ctx->data; - lifespan = (nxt_py_asgi_lifespan_t *) ctx_data->lifespan; + for (i = 0; i < nxt_py_targets->count; i++) { + lifespan = (nxt_py_asgi_lifespan_t *)ctx_data->target_lifespans[i]; + + ret = nxt_py_asgi_lifespan_target_shutdown(lifespan); + if (nxt_slow_path(ret != NXT_UNIT_OK)) { + return NXT_UNIT_ERROR; + } + } + + nxt_unit_free(NULL, ctx_data->target_lifespans); + + return NXT_UNIT_OK; +} + + +static int +nxt_py_asgi_lifespan_target_shutdown(nxt_py_asgi_lifespan_t *lifespan) +{ + PyObject *msg, *future, *res; + nxt_py_asgi_ctx_data_t *ctx_data; + + ctx_data = lifespan->ctx_data; if (nxt_slow_path(lifespan == NULL || lifespan->disabled)) { return NXT_UNIT_OK; diff --git a/src/python/nxt_python_wsgi.c b/src/python/nxt_python_wsgi.c index 77c45af5..b80d10fa 100644 --- a/src/python/nxt_python_wsgi.c +++ b/src/python/nxt_python_wsgi.c @@ -302,7 +302,7 @@ nxt_python_request_handler(nxt_unit_request_info_t *req) { int rc; PyObject *environ, *args, *response, *iterator, *item; - PyObject *close, *result; + PyObject *close, *result, *application; nxt_bool_t prepare_environ; nxt_python_ctx_t *pctx; @@ -348,7 +348,8 @@ nxt_python_request_handler(nxt_unit_request_info_t *req) Py_INCREF(pctx->start_resp); PyTuple_SET_ITEM(args, 1, pctx->start_resp); - response = PyObject_CallObject(nxt_py_application, args); + application = nxt_py_targets->target[req->request->app_target].application; + response = PyObject_CallObject(application, args); Py_DECREF(args); diff --git a/src/ruby/nxt_ruby_stream_io.c b/src/ruby/nxt_ruby_stream_io.c index 69bf289e..82ad3908 100644 --- a/src/ruby/nxt_ruby_stream_io.c +++ b/src/ruby/nxt_ruby_stream_io.c @@ -25,7 +25,9 @@ nxt_ruby_stream_io_input_init(void) { VALUE stream_io; - stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cData); + stream_io = rb_define_class("NGINX_Unit_Stream_IO_Read", rb_cObject); + + rb_undef_alloc_func(stream_io); rb_gc_register_address(&stream_io); @@ -46,7 +48,9 @@ nxt_ruby_stream_io_error_init(void) { VALUE stream_io; - stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cData); + stream_io = rb_define_class("NGINX_Unit_Stream_IO_Error", rb_cObject); + + rb_undef_alloc_func(stream_io); rb_gc_register_address(&stream_io); |