| /* |
| * 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_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. |
| */ |
| |
| 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; |
| 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); |
| } |
| |