/*
 * libwebsockets - small server side websockets and web server implementation
 *
 * Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to
 * deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
 * IN THE SOFTWARE.
 */

#include "private-lib-core.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)
{
	int ret = 0;

	lws_start_foreach_dll_safe(struct lws_dll2 *, p, p1,
			lws_dll2_get_head(&pt->tls.dll_pending_tls_owner)) {
		struct lws *wsi = lws_container_of(p, struct lws,
						   tls.dll_pending_tls);

		/*
		 * ... allow custom event loop to override our POLLIN-setting
		 * implementation if it knows how to do it better for its case
		 */
					   
		if (pt->context->event_loop_ops &&
		    pt->context->event_loop_ops->fake_POLLIN_override)
			pt->context->event_loop_ops->fake_POLLIN_override(
							pt->context, pt->tid);
		else {					
			if (wsi->position_in_fds_table >= 0) {

				pt->fds[wsi->position_in_fds_table].revents = (short)
					(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;

				// lwsl_notice("%s: faked POLLIN for %s, revents=0x%x\n", __func__, lws_wsi_tag(wsi), pt->fds[wsi->position_in_fds_table].revents);
			}
		}

	} lws_end_foreach_dll_safe(p, p1);

	return !!ret;
}

void
__lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
{
	lws_dll2_remove(&wsi->tls.dll_pending_tls);
}

void
lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi)
{
	struct lws_context_per_thread *pt = &wsi->a.context->pt[(int)wsi->tsi];

	lws_pt_lock(pt, __func__);
	__lws_ssl_remove_wsi_from_buffered_list(wsi);
	lws_pt_unlock(pt);
}

struct lws_tls_ctx_ref *
lws_tls_ctx_ref_create(struct lws_vhost *vh, lws_tls_ctx *ctx)
{
	struct lws_tls_ctx_ref *ref;

	if (!ctx)
		return NULL;

	ref = lws_zalloc(sizeof(*ref), "ctx_ref");
	if (!ref)
		return NULL;

	ref->vh = vh;
	ref->ctx = ctx;
	ref->refcount = 1;

	return ref;
}

struct lws_tls_ctx_ref *
lws_tls_ctx_ref_get(struct lws_vhost *vh)
{
	struct lws_tls_ctx_ref *ref;

	lws_vhost_lock(vh);
	ref = vh->tls.active_ctx_ref;
	if (ref)
		ref->refcount++;
	lws_vhost_unlock(vh);

	return ref;
}

void
lws_tls_ctx_ref_unref(struct lws_tls_ctx_ref *ref)
{
	struct lws_vhost *vh;

	if (!ref)
		return;

	vh = ref->vh;
	lws_vhost_lock(vh);
	if (--ref->refcount == 0) {
		lws_dll2_remove(&ref->list);
		lws_tls_vhost_backend_free_ctx(ref->ctx);
		lws_free(ref);
	}
	lws_vhost_unlock(vh);
}

void
lws_tls_ctx_ref_destroy_all(struct lws_vhost *vhost)
{
	if (vhost->tls.active_ctx_ref) {
		lws_tls_ctx_ref_unref(vhost->tls.active_ctx_ref);
		vhost->tls.active_ctx_ref = NULL;
		vhost->tls.ssl_ctx = NULL;
	}

	lws_start_foreach_dll_safe(struct lws_dll2 *, d, d1,
				   lws_dll2_get_head(&vhost->tls.retired_ctx_list)) {
		struct lws_tls_ctx_ref *r = lws_container_of(d, struct lws_tls_ctx_ref, list);
		lwsl_vhost_err(vhost, "Retired ctx_ref %p leaked with refcount %d", r, r->refcount);
		/* forcefully free it to avoid memory leak if WSI leaked */
		lws_dll2_remove(&r->list);
		lws_tls_vhost_backend_free_ctx(r->ctx);
		lws_free(r);
	} lws_end_foreach_dll_safe(d, d1);
}


#if defined(LWS_WITH_SERVER)
int
lws_tls_check_cert_lifetime(struct lws_vhost *v)
{
	time_t now = (time_t)lws_now_secs(), life = 0;
	struct lws_acme_cert_aging_args caa;
	union lws_tls_cert_info_results ir;
	int n;

	/* Check if the active cert context needs rotation under grace period policy */
	if (v->tls.ssl_ctx && v->tls.cfg_alloc_cert_path && v->tls.cfg_key_path &&
	    (strstr(v->tls.cfg_alloc_cert_path, "-latest.crt") ||
	     strstr(v->tls.cfg_alloc_cert_path, "-latest-fullchain.crt"))) {
		char resolved_cert[256];
		char resolved_key[256];
		time_t resolved_from = 0, resolved_to = 0;
		union lws_tls_cert_info_results loaded_from, loaded_to;

		/* Resolve what certificate path should be active right now */
		if (lws_tls_resolve_grace_period_certs(v->context,
						       v->tls.cfg_alloc_cert_path,
						       v->tls.cfg_key_path,
						       resolved_cert, sizeof(resolved_cert),
						       resolved_key, sizeof(resolved_key)) == 0) {
			/* Get validity of resolved cert file */
			if (lws_tls_cert_get_x509_validity(v->context, resolved_cert,
							   &resolved_from, &resolved_to) == 0) {
				/* Get validity of currently loaded cert context */
				if (lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_FROM,
							    &loaded_from, 0) == 0 &&
				    lws_tls_vhost_cert_info(v, LWS_TLS_CERT_INFO_VALIDITY_TO,
							    &loaded_to, 0) == 0) {
					if (resolved_from != loaded_from.time ||
					    resolved_to != loaded_to.time) {
						lwsl_notice("%s: Active certificate for vhost %s is out of date. Rotating dynamically.\n",
							    __func__, v->name);
						lws_tls_cert_updated(v->context,
								     v->tls.cfg_alloc_cert_path,
								     v->tls.cfg_key_path,
								     NULL, 0, NULL, 0);
					}
				}
			}
		}
	}

	if (v->tls.ssl_ctx && !v->tls.skipped_certs) {

		if (now < 1542933698) /* Nov 23 2018 00:42 UTC */
			/* 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_vhost_notice(v, "   vhost %s: cert expiry: %lldd", v->name,
			    (long long)life);
	} else
		lwsl_vhost_info(v, "   vhost %s: no cert", v->name);

	memset(&caa, 0, sizeof(caa));
	caa.vh = v;
	lws_broadcast(&v->context->pt[0], 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;
}

/*
 * 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 = (int)lws_tls_use_any_upgrade_check_extant(cert);
	if (n == LWS_TLS_EXTANT_ALTERNATIVE)
		return LWS_TLS_EXTANT_ALTERNATIVE;
	m = (int)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_vhost_notice(vhost, "Ignoring missing %s or %s", cert, private_key);
		vhost->tls.skipped_certs = 1;

		return LWS_TLS_EXTANT_NO;
	}

	/*
	 * the cert + key exist
	 */

	return LWS_TLS_EXTANT_YES;
}

/*
 * update the cert for every vhost using the given path
 */

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.a.context = context;

	lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) {
		wsi.a.vhost = v; /* not a real bound wsi */
		if (v->tls.cfg_alloc_cert_path && v->tls.cfg_key_path &&
		    !strcmp(v->tls.cfg_alloc_cert_path, certpath) &&
		    !strcmp(v->tls.cfg_key_path, keypath)) {

			lws_tls_ctx *old_ctx = v->tls.ssl_ctx;
			struct lws_tls_ctx_ref *old_ref = v->tls.active_ctx_ref;

			if (lws_tls_vhost_backend_create_ctx(v)) {
				lwsl_vhost_err(v, "Failed to recreate SSL_CTX");
				continue;
			}

			struct lws_tls_ctx_ref *new_ref = lws_tls_ctx_ref_create(v, v->tls.ssl_ctx);
			if (!new_ref) {
				lws_tls_vhost_backend_free_ctx(v->tls.ssl_ctx);
				v->tls.ssl_ctx = old_ctx;
				continue;
			}

			if (lws_tls_server_certs_load(v, &wsi, certpath, keypath,
						  mem_cert, len_mem_cert,
						  mem_privkey, len_mem_privkey)) {
				/* Failed to load new certs. Revert to old context */
				lws_tls_ctx_ref_unref(new_ref);
				v->tls.ssl_ctx = old_ctx;
				lwsl_vhost_err(v, "Failed to load updated certs");
				continue;
			}

			/* Successfully loaded. Commit new ref and retire old ref */
			v->tls.active_ctx_ref = new_ref;

			if (old_ref) {
				lws_dll2_add_tail(&old_ref->list, &v->tls.retired_ctx_list);
				lws_tls_ctx_ref_unref(old_ref);
			}

			if (v->tls.skipped_certs)
				lwsl_vhost_notice(v, "vhost %s: cert unset", v->name);
		}
	} lws_end_foreach_ll(v, vhost_next);

	return 0;
}

int
lws_gate_accepts(struct lws_context *context, int on)
{
	struct lws_vhost *v = context->vhost_list;

	if (context->tls_gate_accepts == (char)on)
		return 0;

	lwsl_cx_info(context, "on = %d", on);

	context->tls_gate_accepts = (char)on;

	while (v) {
		lws_start_foreach_dll(struct lws_dll2 *, d,
				      lws_dll2_get_head(&v->listen_wsi)) {
			struct lws *wsi = lws_container_of(d, struct lws,
							   listen_list);

			if (v->tls.use_ssl &&
			    lws_change_pollfd(wsi, on ? LWS_POLLIN : 0,
						   on ? 0 : LWS_POLLIN))
				lwsl_cx_notice(context, "Unable to set POLLIN %d", on);
		} lws_end_foreach_dll(d);

		v = v->vhost_next;
	}

	return 0;
}
#endif

/* 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;

	if (!comma)
		return 0;

	while (*comma && len > 0) {
		if (!plen && *comma == ' ') {
			comma++;
			continue;
		}
		if (!plen) {
			if (len < 2)
				break;
			plen = os++;
			len--;
		}

		if (*comma == ',') {
			*plen = (uint8_t)lws_ptr_diff(os, plen + 1);
			plen = NULL;
			comma++;
		} else {
			*os++ = (uint8_t)*comma++;
			len--;
		}
	}

	if (plen)
		*plen = (uint8_t)lws_ptr_diff(os, plen + 1);

	*os = 0;

	return lws_ptr_diff(os, oos);
}




void
lws_tls_cleanup_process(void)
{
#if defined(LWS_WITH_MBEDTLS)
	if (tls_ops_mbedtls.process_cleanup)
		tls_ops_mbedtls.process_cleanup();
#elif defined(LWS_WITH_SCHANNEL)
	if (tls_ops_schannel.process_cleanup)
		tls_ops_schannel.process_cleanup();
#elif defined(LWS_WITH_GNUTLS)
	if (tls_ops_gnutls.process_cleanup)
		tls_ops_gnutls.process_cleanup();
#elif defined(LWS_WITH_BEARSSL)
	if (tls_ops_bearssl.process_cleanup)
		tls_ops_bearssl.process_cleanup();
#elif defined(LWS_WITH_OPENHITLS)
	if (tls_ops_openhitls.process_cleanup)
		tls_ops_openhitls.process_cleanup();
#else
	if (tls_ops_openssl.process_cleanup)
		tls_ops_openssl.process_cleanup();
#endif
}
