mirror of
https://github.com/warmcat/libwebsockets.git
synced 2025-03-16 00:00:07 +01:00

This changes the vhost destroy flow to only hand off the listen socket if another vhost sharing it, and mark the vhost as being_destroyed. Each tsi calls lws_check_deferred_free() once a second, if it sees any vhost being_destroyed there, it closes all wsi on its tsi on the same vhost, one time. As the wsi on the vhost complete close (ie, after libuv async close if on libuv event loop), they decrement a reference count for all wsi open on the vhost. The tsi who closes the last one then completes the destroy flow for the vhost itself... it's random which tsi completes the vhost destroy but since there are no wsi left on the vhost, and it holds the context lock, nothing can conflict. The advantage of this is that owning tsi do the close for wsi that are bound to the vhost under destruction, at a time when they are guaranteed to be idle for service, and they do it with both vhost and context locks owned, so no other service thread can conflict for stuff protected by those either. For the situation the user code may have allocations attached to the vhost, this adds args to lws_vhost_destroy() to allow destroying the user allocations just before the vhost is freed.
522 lines
12 KiB
C
522 lines
12 KiB
C
/*
|
|
* libwebsockets - small server side websockets and web server implementation
|
|
*
|
|
* Copyright (C) 2010-2017 Andy Green <andy@warmcat.com>
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation:
|
|
* version 2.1 of the License.
|
|
*
|
|
* This library is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
|
* MA 02110-1301 USA
|
|
*/
|
|
|
|
#include "core/private.h"
|
|
|
|
/*
|
|
* fakes POLLIN on all tls guys with buffered rx
|
|
*
|
|
* returns nonzero if any tls guys had POLLIN faked
|
|
*/
|
|
|
|
int
|
|
lws_tls_fake_POLLIN_for_buffered(struct lws_context_per_thread *pt)
|
|
{
|
|
struct lws *wsi, *wsi_next;
|
|
int ret = 0;
|
|
|
|
wsi = pt->tls.pending_read_list;
|
|
while (wsi && wsi->position_in_fds_table != LWS_NO_FDS_POS) {
|
|
wsi_next = wsi->tls.pending_read_list_next;
|
|
pt->fds[wsi->position_in_fds_table].revents |=
|
|
pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN;
|
|
ret |= pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN;
|
|
|
|
wsi = wsi_next;
|
|
}
|
|
|
|
return !!ret;
|
|
}
|
|
|
|
void
|
|
__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
|
|
{
|
|
struct lws_context *context = wsi->context;
|
|
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
|
|
|
|
if (!wsi->tls.pending_read_list_prev &&
|
|
!wsi->tls.pending_read_list_next &&
|
|
pt->tls.pending_read_list != wsi)
|
|
/* we are not on the list */
|
|
return;
|
|
|
|
/* point previous guy's next to our next */
|
|
if (!wsi->tls.pending_read_list_prev)
|
|
pt->tls.pending_read_list = wsi->tls.pending_read_list_next;
|
|
else
|
|
wsi->tls.pending_read_list_prev->tls.pending_read_list_next =
|
|
wsi->tls.pending_read_list_next;
|
|
|
|
/* point next guy's previous to our previous */
|
|
if (wsi->tls.pending_read_list_next)
|
|
wsi->tls.pending_read_list_next->tls.pending_read_list_prev =
|
|
wsi->tls.pending_read_list_prev;
|
|
|
|
wsi->tls.pending_read_list_prev = NULL;
|
|
wsi->tls.pending_read_list_next = NULL;
|
|
}
|
|
|
|
void
|
|
lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
|
|
{
|
|
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
|
|
|
|
lws_pt_lock(pt, __func__);
|
|
__lws_ssl_remove_wsi_from_buffered_list(wsi);
|
|
lws_pt_unlock(pt);
|
|
}
|
|
|
|
#if defined(LWS_WITH_ESP32)
|
|
int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
|
|
lws_filepos_t *amount)
|
|
{
|
|
nvs_handle nvh;
|
|
size_t s;
|
|
int n = 0;
|
|
|
|
ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh));
|
|
if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) {
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
*buf = lws_malloc(s + 1, "alloc_file");
|
|
if (!*buf) {
|
|
n = 2;
|
|
goto bail;
|
|
}
|
|
if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) {
|
|
lws_free(*buf);
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
*amount = s;
|
|
(*buf)[s] = '\0';
|
|
|
|
lwsl_notice("%s: nvs: read %s, %d bytes\n", __func__, filename, (int)s);
|
|
|
|
bail:
|
|
nvs_close(nvh);
|
|
|
|
return n;
|
|
}
|
|
#else
|
|
int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf,
|
|
lws_filepos_t *amount)
|
|
{
|
|
FILE *f;
|
|
size_t s;
|
|
int n = 0;
|
|
|
|
f = fopen(filename, "rb");
|
|
if (f == NULL) {
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
if (fseek(f, 0, SEEK_END) != 0) {
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
s = ftell(f);
|
|
if (s == (size_t)-1) {
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
if (fseek(f, 0, SEEK_SET) != 0) {
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
*buf = lws_malloc(s, "alloc_file");
|
|
if (!*buf) {
|
|
n = 2;
|
|
goto bail;
|
|
}
|
|
|
|
if (fread(*buf, s, 1, f) != 1) {
|
|
lws_free(*buf);
|
|
n = 1;
|
|
goto bail;
|
|
}
|
|
|
|
*amount = s;
|
|
|
|
bail:
|
|
if (f)
|
|
fclose(f);
|
|
|
|
return n;
|
|
|
|
}
|
|
#endif
|
|
|
|
int
|
|
lws_tls_alloc_pem_to_der_file(struct lws_context *context, const char *filename,
|
|
const char *inbuf, lws_filepos_t inlen,
|
|
uint8_t **buf, lws_filepos_t *amount)
|
|
{
|
|
const uint8_t *pem, *p, *end;
|
|
uint8_t *q;
|
|
lws_filepos_t len;
|
|
int n;
|
|
|
|
if (filename) {
|
|
n = alloc_file(context, filename, (uint8_t **)&pem, &len);
|
|
if (n)
|
|
return n;
|
|
} else {
|
|
pem = (const uint8_t *)inbuf;
|
|
len = inlen;
|
|
}
|
|
|
|
/* trim the first line */
|
|
|
|
p = pem;
|
|
end = p + len;
|
|
if (strncmp((char *)p, "-----", 5))
|
|
goto bail;
|
|
p += 5;
|
|
while (p < end && *p != '\n' && *p != '-')
|
|
p++;
|
|
|
|
if (*p != '-')
|
|
goto bail;
|
|
|
|
while (p < end && *p != '\n')
|
|
p++;
|
|
|
|
if (p >= end)
|
|
goto bail;
|
|
|
|
p++;
|
|
|
|
/* trim the last line */
|
|
|
|
q = (uint8_t *)end - 2;
|
|
|
|
while (q > pem && *q != '\n')
|
|
q--;
|
|
|
|
if (*q != '\n')
|
|
goto bail;
|
|
|
|
*q = '\0';
|
|
|
|
*amount = lws_b64_decode_string((char *)p, (char *)pem,
|
|
(int)(long long)len);
|
|
*buf = (uint8_t *)pem;
|
|
|
|
return 0;
|
|
|
|
bail:
|
|
lws_free((uint8_t *)pem);
|
|
|
|
return 4;
|
|
}
|
|
|
|
int
|
|
lws_tls_check_cert_lifetime(struct lws_vhost *v)
|
|
{
|
|
union lws_tls_cert_info_results ir;
|
|
time_t now = (time_t)lws_now_secs(), life = 0;
|
|
struct lws_acme_cert_aging_args caa;
|
|
int n;
|
|
|
|
if (v->tls.ssl_ctx && !v->tls.skipped_certs) {
|
|
|
|
if (now < 1464083026) /* May 2016 */
|
|
/* our clock is wrong and we can't judge the certs */
|
|
return -1;
|
|
|
|
n = lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_TO, &ir, 0);
|
|
if (n)
|
|
return 1;
|
|
|
|
life = (ir.time - now) / (24 * 3600);
|
|
lwsl_notice(" vhost %s: cert expiry: %dd\n", v->name, (int)life);
|
|
} else
|
|
lwsl_notice(" vhost %s: no cert\n", v->name);
|
|
|
|
memset(&caa, 0, sizeof(caa));
|
|
caa.vh = v;
|
|
lws_broadcast(v->context, LWS_CALLBACK_VHOST_CERT_AGING, (void *)&caa,
|
|
(size_t)(ssize_t)life);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int
|
|
lws_tls_check_all_cert_lifetimes(struct lws_context *context)
|
|
{
|
|
struct lws_vhost *v = context->vhost_list;
|
|
|
|
while (v) {
|
|
if (lws_tls_check_cert_lifetime(v) < 0)
|
|
return -1;
|
|
v = v->vhost_next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#if !defined(LWS_WITH_ESP32) && !defined(LWS_PLAT_OPTEE)
|
|
static int
|
|
lws_tls_extant(const char *name)
|
|
{
|
|
/* it exists if we can open it... */
|
|
int fd = open(name, O_RDONLY), n;
|
|
char buf[1];
|
|
|
|
if (fd < 0)
|
|
return 1;
|
|
|
|
/* and we can read at least one byte out of it */
|
|
n = read(fd, buf, 1);
|
|
close(fd);
|
|
|
|
return n != 1;
|
|
}
|
|
#endif
|
|
/*
|
|
* Returns 0 if the filepath "name" exists and can be read from.
|
|
*
|
|
* In addition, if "name".upd exists, backup "name" to "name.old.1"
|
|
* and rename "name".upd to "name" before reporting its existence.
|
|
*
|
|
* There are four situations and three results possible:
|
|
*
|
|
* 1) LWS_TLS_EXTANT_NO: There are no certs at all (we are waiting for them to
|
|
* be provisioned). We also feel like this if we need privs we don't have
|
|
* any more to look in the directory.
|
|
*
|
|
* 2) There are provisioned certs written (xxx.upd) and we still have root
|
|
* privs... in this case we rename any existing cert to have a backup name
|
|
* and move the upd cert into place with the correct name. This then becomes
|
|
* situation 4 for the caller.
|
|
*
|
|
* 3) LWS_TLS_EXTANT_ALTERNATIVE: There are provisioned certs written (xxx.upd)
|
|
* but we no longer have the privs needed to read or rename them. In this
|
|
* case, indicate that the caller should use temp copies if any we do have
|
|
* rights to access. This is normal after we have updated the cert.
|
|
*
|
|
* But if we dropped privs, we can't detect the provisioned xxx.upd cert +
|
|
* key, because we can't see in the dir. So we have to upgrade NO to
|
|
* ALTERNATIVE when we actually have the in-memory alternative.
|
|
*
|
|
* 4) LWS_TLS_EXTANT_YES: The certs are present with the correct name and we
|
|
* have the rights to read them.
|
|
*/
|
|
enum lws_tls_extant
|
|
lws_tls_use_any_upgrade_check_extant(const char *name)
|
|
{
|
|
#if !defined(LWS_PLAT_OPTEE)
|
|
|
|
int n;
|
|
|
|
#if !defined(LWS_WITH_ESP32)
|
|
char buf[256];
|
|
|
|
lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
|
|
if (!lws_tls_extant(buf)) {
|
|
/* ah there is an updated file... how about the desired file? */
|
|
if (!lws_tls_extant(name)) {
|
|
/* rename the desired file */
|
|
for (n = 0; n < 50; n++) {
|
|
lws_snprintf(buf, sizeof(buf) - 1,
|
|
"%s.old.%d", name, n);
|
|
if (!rename(name, buf))
|
|
break;
|
|
}
|
|
if (n == 50) {
|
|
lwsl_notice("unable to rename %s\n", name);
|
|
|
|
return LWS_TLS_EXTANT_ALTERNATIVE;
|
|
}
|
|
lws_snprintf(buf, sizeof(buf) - 1, "%s.upd", name);
|
|
}
|
|
/* desired file is out of the way, rename the updated file */
|
|
if (rename(buf, name)) {
|
|
lwsl_notice("unable to rename %s to %s\n", buf, name);
|
|
|
|
return LWS_TLS_EXTANT_ALTERNATIVE;
|
|
}
|
|
}
|
|
|
|
if (lws_tls_extant(name))
|
|
return LWS_TLS_EXTANT_NO;
|
|
#else
|
|
nvs_handle nvh;
|
|
size_t s = 8192;
|
|
|
|
if (nvs_open("lws-station", NVS_READWRITE, &nvh)) {
|
|
lwsl_notice("%s: can't open nvs\n", __func__);
|
|
return LWS_TLS_EXTANT_NO;
|
|
}
|
|
|
|
n = nvs_get_blob(nvh, name, NULL, &s);
|
|
nvs_close(nvh);
|
|
|
|
if (n)
|
|
return LWS_TLS_EXTANT_NO;
|
|
#endif
|
|
#endif
|
|
return LWS_TLS_EXTANT_YES;
|
|
}
|
|
|
|
/*
|
|
* LWS_TLS_EXTANT_NO : skip adding the cert
|
|
* LWS_TLS_EXTANT_YES : use the cert and private key paths normally
|
|
* LWS_TLS_EXTANT_ALTERNATIVE: normal paths not usable, try alternate if poss
|
|
*/
|
|
enum lws_tls_extant
|
|
lws_tls_generic_cert_checks(struct lws_vhost *vhost, const char *cert,
|
|
const char *private_key)
|
|
{
|
|
int n, m;
|
|
|
|
/*
|
|
* The user code can choose to either pass the cert and
|
|
* key filepaths using the info members like this, or it can
|
|
* leave them NULL; force the vhost SSL_CTX init using the info
|
|
* options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and
|
|
* set up the cert himself using the user callback
|
|
* LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which
|
|
* happened just above and has the vhost SSL_CTX * in the user
|
|
* parameter.
|
|
*/
|
|
|
|
if (!cert || !private_key)
|
|
return LWS_TLS_EXTANT_NO;
|
|
|
|
n = lws_tls_use_any_upgrade_check_extant(cert);
|
|
if (n == LWS_TLS_EXTANT_ALTERNATIVE)
|
|
return LWS_TLS_EXTANT_ALTERNATIVE;
|
|
m = lws_tls_use_any_upgrade_check_extant(private_key);
|
|
if (m == LWS_TLS_EXTANT_ALTERNATIVE)
|
|
return LWS_TLS_EXTANT_ALTERNATIVE;
|
|
|
|
if ((n == LWS_TLS_EXTANT_NO || m == LWS_TLS_EXTANT_NO) &&
|
|
(vhost->options & LWS_SERVER_OPTION_IGNORE_MISSING_CERT)) {
|
|
lwsl_notice("Ignoring missing %s or %s\n", cert, private_key);
|
|
vhost->tls.skipped_certs = 1;
|
|
|
|
return LWS_TLS_EXTANT_NO;
|
|
}
|
|
|
|
/*
|
|
* the cert + key exist
|
|
*/
|
|
|
|
return LWS_TLS_EXTANT_YES;
|
|
}
|
|
|
|
#if !defined(LWS_NO_SERVER)
|
|
/*
|
|
* update the cert for every vhost using the given path
|
|
*/
|
|
|
|
LWS_VISIBLE int
|
|
lws_tls_cert_updated(struct lws_context *context, const char *certpath,
|
|
const char *keypath,
|
|
const char *mem_cert, size_t len_mem_cert,
|
|
const char *mem_privkey, size_t len_mem_privkey)
|
|
{
|
|
struct lws wsi;
|
|
|
|
wsi.context = context;
|
|
|
|
lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) {
|
|
wsi.vhost = v; /* not a real bound wsi */
|
|
if (v->tls.alloc_cert_path && v->tls.key_path &&
|
|
!strcmp(v->tls.alloc_cert_path, certpath) &&
|
|
!strcmp(v->tls.key_path, keypath)) {
|
|
lws_tls_server_certs_load(v, &wsi, certpath, keypath,
|
|
mem_cert, len_mem_cert,
|
|
mem_privkey, len_mem_privkey);
|
|
|
|
if (v->tls.skipped_certs)
|
|
lwsl_notice("%s: vhost %s: cert unset\n",
|
|
__func__, v->name);
|
|
}
|
|
} lws_end_foreach_ll(v, vhost_next);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
int
|
|
lws_gate_accepts(struct lws_context *context, int on)
|
|
{
|
|
struct lws_vhost *v = context->vhost_list;
|
|
|
|
lwsl_notice("%s: on = %d\n", __func__, on);
|
|
|
|
#if defined(LWS_WITH_STATS)
|
|
context->updated = 1;
|
|
#endif
|
|
|
|
while (v) {
|
|
if (v->tls.use_ssl && v->lserv_wsi &&
|
|
lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on,
|
|
(LWS_POLLIN) * on))
|
|
lwsl_notice("Unable to set accept POLLIN %d\n", on);
|
|
|
|
v = v->vhost_next;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* comma-separated alpn list, like "h2,http/1.1" to openssl alpn format */
|
|
|
|
int
|
|
lws_alpn_comma_to_openssl(const char *comma, uint8_t *os, int len)
|
|
{
|
|
uint8_t *oos = os, *plen = NULL;
|
|
|
|
while (*comma && len > 1) {
|
|
if (!plen && *comma == ' ') {
|
|
comma++;
|
|
continue;
|
|
}
|
|
if (!plen) {
|
|
plen = os++;
|
|
len--;
|
|
}
|
|
|
|
if (*comma == ',') {
|
|
*plen = lws_ptr_diff(os, plen + 1);
|
|
plen = NULL;
|
|
comma++;
|
|
} else {
|
|
*os++ = *comma++;
|
|
len--;
|
|
}
|
|
}
|
|
|
|
if (plen)
|
|
*plen = lws_ptr_diff(os, plen + 1);
|
|
|
|
return lws_ptr_diff(os, oos);
|
|
}
|
|
|