Author: Andy Green Date: Wed Sep 09 09:28:56 2026 +0100 server: recognize being re-exec'd as an lws_stub child lws plugins that need privileged help (eg, lws-cert-dist-client) spawn it by re-exec'ing their host process with --lws-stub=. Both the plugin and lwsws_get_config_vhosts() detect that via lws_cmdline_option_cx(), which needs the context to have been given argc / argv. sai-server never passed those in, so a stub child would have come up as a complete second sai-server: parsing the real vhosts, contending for the listen port and the websrv SS listener, opening the databases, dropping to the configured uid, and never reaching the plugin's stub branch at all. Hand argc / argv to lws via lws_cmdline_option_handle_builtin(), and when that reports we are a stub child, follow lwsws: keep our privileges (VH_SKIP_PRIV_DROP), force global TLS init, and bring up none of our own protocols or the SS policy, so only the plugin protocols are instantiated on the stub-dummy vhost that lwsws_get_config_vhosts() creates for stubs. No behaviour change when started normally. This is the sai side of using lws-cert-dist-client to receive cert updates without a restart; the plugin still needs to apply the received cert to the live vhost in-process. Co-Authored-By: Claude Fable 5.1 diff --git a/src/server/s-conf.c b/src/server/s-conf.c index c03a236..66f0df3 100644 --- a/src/server/s-conf.c +++ b/src/server/s-conf.c @@ -31,7 +31,7 @@ struct lws_context * sai_lws_context_from_json(const char *config_dir, struct lws_context_creation_info *info, const struct lws_protocols **pprotocols, - const char *jpol) + const char *jpol, int argc, const char **argv) { int cs_len = SAI_CONFIG_STRING_SIZE - 1; struct lws_context *context; @@ -54,6 +54,32 @@ sai_lws_context_from_json(const char *config_dir, LWS_SERVER_OPTION_VALIDATE_UTF8; info->pss_policies_json = jpol; + /* + * Let lws see our commandline: lws_cmdline_option_cx() needs it, and + * it is how lws_stub children (lws plugins that spawn a privileged + * helper by re-exec'ing us with --lws-stub=) are recognized, + * both by the plugins themselves and by lwsws_get_config_vhosts() + */ + lws_cmdline_option_handle_builtin(argc, argv, info); + + if (info->lws_stub) { + /* + * We are a stub child, not a sai-server. We exist only to + * host the plugin protocol that spawned us, on the stub-dummy + * vhost lwsws_get_config_vhosts() creates instead of parsing + * our real vhosts. So none of our own protocols or the SS + * websrv listener should come up, and we must keep our + * privileges rather than dropping to the configured uid / gid, + * since the stub's whole purpose is to do the privileged work. + */ + lwsl_notice("%s: lws stub child '%s'\n", __func__, + info->lws_stub); + info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT | + LWS_SERVER_OPTION_VH_SKIP_PRIV_DROP; + info->pss_policies_json = NULL; + pprotocols = NULL; + } + lwsl_notice("Using config dir: \"%s\"\n", config_dir); /* diff --git a/src/server/s-private.h b/src/server/s-private.h index 765d440..afe20be 100644 --- a/src/server/s-private.h +++ b/src/server/s-private.h @@ -277,7 +277,7 @@ extern struct lws_context * sai_lws_context_from_json(const char *config_dir, struct lws_context_creation_info *info, const struct lws_protocols **pprotocols, - const char *jpol); + const char *jpol, int argc, const char **argv); extern const struct lws_protocols protocol_ws, protocol_ws_power; int diff --git a/src/server/s-sai.c b/src/server/s-sai.c index 2668880..84e328d 100644 --- a/src/server/s-sai.c +++ b/src/server/s-sai.c @@ -87,7 +87,7 @@ int main(int argc, const char **argv) conf = p; context = sai_lws_context_from_json(conf, &info, pprotocols, - default_ss_policy); + default_ss_policy, argc, argv); if (!context) { lwsl_err("lws init failed\n"); return 1;