Ejemplo n.º 1
0
void
lws_free_wsi(struct lws *wsi)
{
	if (!wsi)
		return;

	/* Protocol user data may be allocated either internally by lws
	 * or by specified the user.
	 * We should only free what we allocated. */
	if (wsi->protocol && wsi->protocol->per_session_data_size &&
	    wsi->user_space && !wsi->user_space_externally_allocated)
		lws_free(wsi->user_space);

	lws_free_set_NULL(wsi->rxflow_buffer);
	lws_free_set_NULL(wsi->trunc_alloc);

	if (wsi->u.hdr.ah)
		/* we're closing, losing some rx is OK */
		wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;

	/* we may not have an ah, but may be on the waiting list... */
	lws_header_table_detach(wsi, 0);

	wsi->context->count_wsi_allocated--;
	lwsl_debug("%s: %p, remaining wsi %d\n", __func__, wsi,
			wsi->context->count_wsi_allocated);

	lws_free(wsi);
}
Ejemplo n.º 2
0
void
lws_free_wsi(struct lws *wsi)
{
	if (!wsi)
		return;

	/* Protocol user data may be allocated either internally by lws
	 * or by specified the user.
	 * We should only free what we allocated. */
	if (wsi->protocol && wsi->protocol->per_session_data_size &&
	    wsi->user_space && !wsi->user_space_externally_allocated)
		lws_free(wsi->user_space);

	lws_free_set_NULL(wsi->rxflow_buffer);
	lws_free_set_NULL(wsi->trunc_alloc);
	/*
	 * These union members have an ah at the start
	 *
	 * 	struct _lws_http_mode_related http;
	 *	struct _lws_http2_related http2;
	 *	struct _lws_header_related hdr;
	 *
	 * basically ws-related union member does not
	 */
	if (wsi->mode != LWSCM_WS_CLIENT &&
	    wsi->mode != LWSCM_WS_SERVING)
		if (wsi->u.hdr.ah)
			lws_free_header_table(wsi);
	lws_free(wsi);
}
Ejemplo n.º 3
0
static int
elops_destroy_context2_uv(struct lws_context *context)
{
	struct lws_context_per_thread *pt;
	int n, internal = 0;

	for (n = 0; n < context->count_threads; n++) {
		pt = &context->pt[n];

		/* only for internal loops... */

		if (!pt->event_loop_foreign && pt->uv.io_loop) {
			internal = 1;
			if (!context->finalize_destroy_after_internal_loops_stopped)
				uv_stop(pt->uv.io_loop);
			else {
#if UV_VERSION_MAJOR > 0
				uv_loop_close(pt->uv.io_loop);
#endif
				lws_free_set_NULL(pt->uv.io_loop);
			}
		}
	}

	return internal;
}
Ejemplo n.º 4
0
LWS_VISIBLE LWS_EXTERN void
lws_email_destroy(struct lws_email *email)
{
	if (email->content)
		lws_free_set_NULL(email->content);

	uv_timer_stop(&email->timeout_email);
}
Ejemplo n.º 5
0
void
lws_tls_acme_sni_cert_destroy(struct lws_vhost *vhost)
{
	if (!vhost->tls.ss)
		return;

	EVP_PKEY_free(vhost->tls.ss->pkey);
	X509_free(vhost->tls.ss->x509);
	lws_free_set_NULL(vhost->tls.ss);
}
Ejemplo n.º 6
0
void
lws_x509_destroy(struct lws_x509_cert **x509)
{
	if (!*x509)
		return;

	if ((*x509)->cert) {
		X509_free((*x509)->cert);
		(*x509)->cert = NULL;
	}

	lws_free_set_NULL(*x509);
}
Ejemplo n.º 7
0
LWS_VISIBLE LWS_EXTERN void
lws_ring_destroy(struct lws_ring *ring)
{
	if (ring->destroy_element)
		while (ring->oldest_tail != ring->head) {
			ring->destroy_element((uint8_t *)ring->buf +
					      ring->oldest_tail);
			ring->oldest_tail =
				(ring->oldest_tail + ring->element_len) %
				ring->buflen;
		}
	if (ring->buf)
		lws_free_set_NULL(ring->buf);

	lws_free(ring);
}
Ejemplo n.º 8
0
void
lws_cgi_remove_and_kill(struct lws *wsi)
{
	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
	struct lws_cgi **pcgi = &pt->http.cgi_list;

	/* remove us from the cgi list */
	lwsl_debug("%s: remove cgi %p from list\n", __func__, wsi->http.cgi);
	while (*pcgi) {
		if (*pcgi == wsi->http.cgi) {
			/* drop us from the pt cgi list */
			*pcgi = (*pcgi)->cgi_list;
			break;
		}
		pcgi = &(*pcgi)->cgi_list;
	}
	if (wsi->http.cgi->headers_buf) {
		lwsl_debug("close: freed cgi headers\n");
		lws_free_set_NULL(wsi->http.cgi->headers_buf);
	}
	/* we have a cgi going, we must kill it */
	wsi->http.cgi->being_closed = 1;
	lws_cgi_kill(wsi);
}
Ejemplo n.º 9
0
/*
 * this may now get called after the vhost creation, when certs become
 * available.
 */
int
lws_tls_server_certs_load(struct lws_vhost *vhost, struct lws *wsi,
			  const char *cert, const char *private_key,
			  const char *mem_cert, size_t mem_cert_len,
			  const char *mem_privkey, size_t mem_privkey_len)
{
#if !defined(OPENSSL_NO_EC)
	const char *ecdh_curve = "prime256v1";
#if !defined(LWS_WITH_BORINGSSL) && defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
	STACK_OF(X509) *extra_certs = NULL;
#endif
	EC_KEY *ecdh, *EC_key = NULL;
	EVP_PKEY *pkey;
	X509 *x = NULL;
	int ecdh_nid;
	int KeyType;
#endif
	unsigned long error;
	lws_filepos_t flen;
	uint8_t *p;
	int ret;

	int n = lws_tls_generic_cert_checks(vhost, cert, private_key), m;

	(void)ret;

	if (!cert && !private_key)
		n = LWS_TLS_EXTANT_ALTERNATIVE;

	if (n == LWS_TLS_EXTANT_NO && (!mem_cert || !mem_privkey))
		return 0;
	if (n == LWS_TLS_EXTANT_NO)
		n = LWS_TLS_EXTANT_ALTERNATIVE;

	if (n == LWS_TLS_EXTANT_ALTERNATIVE && (!mem_cert || !mem_privkey))
		return 1; /* no alternative */

	if (n == LWS_TLS_EXTANT_ALTERNATIVE) {

#if OPENSSL_VERSION_NUMBER >= 0x10100000L

		/*
		 * Although we have prepared update certs, we no longer have
		 * the rights to read our own cert + key we saved.
		 *
		 * If we were passed copies in memory buffers, use those
		 * in favour of the filepaths we normally want.
		 */
		cert = NULL;
		private_key = NULL;
	}

	/*
	 * use the multi-cert interface for backwards compatibility in the
	 * both simple files case
	 */

	if (n != LWS_TLS_EXTANT_ALTERNATIVE && cert) {

		/* set the local certificate from CertFile */
		m = SSL_CTX_use_certificate_chain_file(vhost->tls.ssl_ctx, cert);
		if (m != 1) {
			error = ERR_get_error();
			lwsl_err("problem getting cert '%s' %lu: %s\n",
				 cert, error, ERR_error_string(error,
				       (char *)vhost->context->pt[0].serv_buf));

			return 1;
		}

		if (private_key) {
			/* set the private key from KeyFile */
			if (SSL_CTX_use_PrivateKey_file(vhost->tls.ssl_ctx, private_key,
							SSL_FILETYPE_PEM) != 1) {
				error = ERR_get_error();
				lwsl_err("ssl problem getting key '%s' %lu: %s\n",
					 private_key, error,
					 ERR_error_string(error,
					      (char *)vhost->context->pt[0].serv_buf));
				return 1;
			}
		} else {
			if (vhost->protocols[0].callback(wsi,
				      LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
							 vhost->tls.ssl_ctx, NULL, 0)) {
				lwsl_err("ssl private key not set\n");

				return 1;
			}
		}

		return 0;
	}

	/* otherwise allow for DER or PEM, file or memory image */

	if (lws_tls_alloc_pem_to_der_file(vhost->context, cert, mem_cert,
					  mem_cert_len, &p, &flen)) {
		lwsl_err("%s: couldn't read cert file\n", __func__);

		return 1;
	}

#if !defined(USE_WOLFSSL)
	ret = SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx, (int)flen, p);
#else
	ret = wolfSSL_CTX_use_certificate_buffer(vhost->tls.ssl_ctx,
						 (uint8_t *)p, (int)flen,
						 WOLFSSL_FILETYPE_ASN1);
#endif
	lws_free_set_NULL(p);
	if (ret != 1) {
		lwsl_err("%s: Problem loading cert\n", __func__);

		return 1;
	}

	if (lws_tls_alloc_pem_to_der_file(vhost->context, private_key,
					  mem_privkey, mem_privkey_len,
					  &p, &flen)) {
		lwsl_notice("unable to convert memory privkey\n");

		return 1;
	}

#if !defined(USE_WOLFSSL)
	ret = SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA, vhost->tls.ssl_ctx, p,
					  (long)(long long)flen);
	if (ret != 1) {
		ret = SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_EC,
						  vhost->tls.ssl_ctx, p,
						  (long)(long long)flen);
	}
#else
	ret = wolfSSL_CTX_use_PrivateKey_buffer(vhost->tls.ssl_ctx, p, flen,
						WOLFSSL_FILETYPE_ASN1);
#endif
	lws_free_set_NULL(p);
	if (ret != 1)  {
		lwsl_notice("unable to use memory privkey\n");

		return 1;
	}

#else
		/*
		 * Although we have prepared update certs, we no longer have
		 * the rights to read our own cert + key we saved.
		 *
		 * If we were passed copies in memory buffers, use those
		 * instead.
		 *
		 * The passed memory-buffer cert image is in DER, and the
		 * memory-buffer private key image is PEM.
		 */
#ifndef USE_WOLFSSL
		if (SSL_CTX_use_certificate_ASN1(vhost->tls.ssl_ctx,
						 (int)mem_cert_len,
						 (uint8_t *)mem_cert) != 1) {
#else
		if (wolfSSL_CTX_use_certificate_buffer(vhost->tls.ssl_ctx,
						 (uint8_t *)mem_cert,
						 (int)mem_cert_len,
						 WOLFSSL_FILETYPE_ASN1) != 1) {

#endif
			lwsl_err("Problem loading update cert\n");

			return 1;
		}

		if (lws_tls_alloc_pem_to_der_file(vhost->context, NULL,
						  mem_privkey, mem_privkey_len,
						  &p, &flen)) {
			lwsl_notice("unable to convert memory privkey\n");

			return 1;
		}
#ifndef USE_WOLFSSL
		if (SSL_CTX_use_PrivateKey_ASN1(EVP_PKEY_RSA,
						vhost->tls.ssl_ctx, p,
						(long)(long long)flen) != 1) {
#else
		if (wolfSSL_CTX_use_PrivateKey_buffer(vhost->tls.ssl_ctx, p,
					    flen, WOLFSSL_FILETYPE_ASN1) != 1) {
#endif
			lwsl_notice("unable to use memory privkey\n");

			return 1;
		}

		goto check_key;
	}

	/* set the local certificate from CertFile */
	m = SSL_CTX_use_certificate_chain_file(vhost->tls.ssl_ctx, cert);
	if (m != 1) {
		error = ERR_get_error();
		lwsl_err("problem getting cert '%s' %lu: %s\n",
			 cert, error, ERR_error_string(error,
			       (char *)vhost->context->pt[0].serv_buf));

		return 1;
	}

	if (n != LWS_TLS_EXTANT_ALTERNATIVE && private_key) {
		/* set the private key from KeyFile */
		if (SSL_CTX_use_PrivateKey_file(vhost->tls.ssl_ctx, private_key,
					        SSL_FILETYPE_PEM) != 1) {
			error = ERR_get_error();
			lwsl_err("ssl problem getting key '%s' %lu: %s\n",
				 private_key, error,
				 ERR_error_string(error,
				      (char *)vhost->context->pt[0].serv_buf));
			return 1;
		}
	} else {
		if (vhost->protocols[0].callback(wsi,
			      LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY,
						 vhost->tls.ssl_ctx, NULL, 0)) {
			lwsl_err("ssl private key not set\n");

			return 1;
		}
	}

check_key:
#endif

	/* verify private key */
	if (!SSL_CTX_check_private_key(vhost->tls.ssl_ctx)) {
		lwsl_err("Private SSL key doesn't match cert\n");

		return 1;
	}


#if !defined(OPENSSL_NO_EC)
	if (vhost->tls.ecdh_curve[0])
		ecdh_curve = vhost->tls.ecdh_curve;

	ecdh_nid = OBJ_sn2nid(ecdh_curve);
	if (NID_undef == ecdh_nid) {
		lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve);
		return 1;
	}

	ecdh = EC_KEY_new_by_curve_name(ecdh_nid);
	if (NULL == ecdh) {
		lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve);
		return 1;
	}
	SSL_CTX_set_tmp_ecdh(vhost->tls.ssl_ctx, ecdh);
	EC_KEY_free(ecdh);

	SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_SINGLE_ECDH_USE);

	lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve);

	if (lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH))
		lwsl_notice(" Using ECDH certificate support\n");

	/* Get X509 certificate from ssl context */
#if !defined(LWS_WITH_BORINGSSL)
#if !defined(LWS_HAVE_SSL_EXTRA_CHAIN_CERTS)
	x = sk_X509_value(vhost->tls.ssl_ctx->extra_certs, 0);
#else
	SSL_CTX_get_extra_chain_certs_only(vhost->tls.ssl_ctx, &extra_certs);
	if (extra_certs)
		x = sk_X509_value(extra_certs, 0);
	else
		lwsl_info("%s: no extra certs\n", __func__);
#endif
	if (!x) {
		//lwsl_err("%s: x is NULL\n", __func__);
		goto post_ecdh;
	}
#else
	return 0;
#endif
	/* Get the public key from certificate */
	pkey = X509_get_pubkey(x);
	if (!pkey) {
		lwsl_err("%s: pkey is NULL\n", __func__);

		return 1;
	}
	/* Get the key type */
	KeyType = EVP_PKEY_type(EVP_PKEY_id(pkey));

	if (EVP_PKEY_EC != KeyType) {
		lwsl_notice("Key type is not EC\n");
		return 0;
	}
	/* Get the key */
	EC_key = EVP_PKEY_get1_EC_KEY(pkey);
	/* Set ECDH parameter */
	if (!EC_key) {
		lwsl_err("%s: ECDH key is NULL \n", __func__);
		return 1;
	}
	SSL_CTX_set_tmp_ecdh(vhost->tls.ssl_ctx, EC_key);

	EC_KEY_free(EC_key);
#else
	lwsl_notice(" OpenSSL doesn't support ECDH\n");
#endif
#if !defined(OPENSSL_NO_EC) && !defined(LWS_WITH_BORINGSSL)
post_ecdh:
#endif
	vhost->tls.skipped_certs = 0;

	return 0;
}

int
lws_tls_server_vhost_backend_init(const struct lws_context_creation_info *info,
				  struct lws_vhost *vhost, struct lws *wsi)
{
	unsigned long error;
	SSL_METHOD *method = (SSL_METHOD *)SSLv23_server_method();

	if (!method) {
		error = ERR_get_error();
		lwsl_err("problem creating ssl method %lu: %s\n",
				error, ERR_error_string(error,
				      (char *)vhost->context->pt[0].serv_buf));
		return 1;
	}
	vhost->tls.ssl_ctx = SSL_CTX_new(method);	/* create context */
	if (!vhost->tls.ssl_ctx) {
		error = ERR_get_error();
		lwsl_err("problem creating ssl context %lu: %s\n",
				error, ERR_error_string(error,
				      (char *)vhost->context->pt[0].serv_buf));
		return 1;
	}

	SSL_CTX_set_ex_data(vhost->tls.ssl_ctx,
			    openssl_SSL_CTX_private_data_index,
			    (char *)vhost->context);
	/* Disable SSLv2 and SSLv3 */
	SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_NO_SSLv2 |
						SSL_OP_NO_SSLv3);
#ifdef SSL_OP_NO_COMPRESSION
	SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_NO_COMPRESSION);
#endif
	SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_SINGLE_DH_USE);
	SSL_CTX_set_options(vhost->tls.ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);

	if (info->ssl_cipher_list)
		SSL_CTX_set_cipher_list(vhost->tls.ssl_ctx, info->ssl_cipher_list);

#if defined(LWS_HAVE_SSL_CTX_set_ciphersuites)
	if (info->tls1_3_plus_cipher_list)
		SSL_CTX_set_ciphersuites(vhost->tls.ssl_ctx,
					 info->tls1_3_plus_cipher_list);
#endif

#if !defined(OPENSSL_NO_TLSEXT)
	SSL_CTX_set_tlsext_servername_callback(vhost->tls.ssl_ctx,
					       lws_ssl_server_name_cb);
	SSL_CTX_set_tlsext_servername_arg(vhost->tls.ssl_ctx, vhost->context);
#endif

	if (info->ssl_ca_filepath &&
	    !SSL_CTX_load_verify_locations(vhost->tls.ssl_ctx,
					   info->ssl_ca_filepath, NULL)) {
		lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n",
			 __func__);
	}

	if (info->ssl_options_set)
		SSL_CTX_set_options(vhost->tls.ssl_ctx, info->ssl_options_set);

/* SSL_clear_options introduced in 0.9.8m */
#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL)
	if (info->ssl_options_clear)
		SSL_CTX_clear_options(vhost->tls.ssl_ctx,
				      info->ssl_options_clear);
#endif

	lwsl_info(" SSL options 0x%lX\n",
			(unsigned long)SSL_CTX_get_options(vhost->tls.ssl_ctx));
	if (!vhost->tls.use_ssl ||
	    (!info->ssl_cert_filepath && !info->server_ssl_cert_mem))
		return 0;

	lws_ssl_bind_passphrase(vhost->tls.ssl_ctx, info);

	return lws_tls_server_certs_load(vhost, wsi, info->ssl_cert_filepath,
					 info->ssl_private_key_filepath,
					 info->server_ssl_cert_mem,
					 info->server_ssl_cert_mem_len,
					 info->server_ssl_private_key_mem,
					 info->server_ssl_private_key_mem_len);
}
Ejemplo n.º 10
0
int
lws_client_socket_service(struct lws_context *context, struct lws *wsi,
			  struct lws_pollfd *pollfd)
{
	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
	char *p = (char *)&pt->serv_buf[0];
	const char *cce = NULL;
	unsigned char c;
	char *sb = p;
	int n = 0, len = 0;
#if defined(LWS_WITH_SOCKS5)
	char conn_mode = 0, pending_timeout = 0;
#endif

	switch (wsi->mode) {

	case LWSCM_WSCL_WAITING_CONNECT:

		/*
		 * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
		 * timeout protection set in client-handshake.c
		 */

		if (!lws_client_connect_2(wsi)) {
			/* closed */
			lwsl_client("closed\n");
			return -1;
		}

		/* either still pending connection, or changed mode */
		return 0;

#if defined(LWS_WITH_SOCKS5)
	/* SOCKS Greeting Reply */
	case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY:

		/* handle proxy hung up on us */

		if (pollfd->revents & LWS_POLLHUP) {

			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
				  (void *)wsi, pollfd->fd);

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		}

		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
		if (n < 0) {
			if (LWS_ERRNO == LWS_EAGAIN) {
				lwsl_debug("SOCKS read returned EAGAIN..."
					"retrying\n");
				return 0;
			}

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR reading from SOCKS socket\n");
			return 0;
		}

		/* processing greeting reply */
		if (pt->serv_buf[0] == SOCKS_VERSION_5
			&& pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH)
		{
			lwsl_client("%s\n", "SOCKS greeting reply received "
				"- No Authentication Method");
			socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);

			conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
			pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
			lwsl_client("%s\n", "Sending SOCKS connect command");
		}
		else if (pt->serv_buf[0] == SOCKS_VERSION_5
				&& pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD)
		{
			lwsl_client("%s\n", "SOCKS greeting reply received "
				"- User Name Password Method");
			socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD,
				(size_t *)&len);

			conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY;
			pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY;
			lwsl_client("%s\n", "Sending SOCKS user/password");
		}
		else
		{
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR SOCKS greeting reply failed, method "
				"code: %d\n", pt->serv_buf[1]);
			return 0;
		}

		n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
			 MSG_NOSIGNAL);
		if (n < 0) {
			lwsl_debug("ERROR writing socks command to socks proxy "
				"socket\n");
			return 0;
		}

		lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
		wsi->mode = conn_mode;

		break;
	/* SOCKS auth Reply */
	case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY:

		/* handle proxy hung up on us */

		if (pollfd->revents & LWS_POLLHUP) {

			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
				  (void *)wsi, pollfd->fd);

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		}

		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
		if (n < 0) {
			if (LWS_ERRNO == LWS_EAGAIN) {
				lwsl_debug("SOCKS read returned EAGAIN... "
					"retrying\n");
				return 0;
			}

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR reading from socks socket\n");
			return 0;
		}

		/* processing auth reply */
		if (pt->serv_buf[0] == SOCKS_SUBNEGOTIATION_VERSION_1
			&& pt->serv_buf[1] == SOCKS_SUBNEGOTIATION_STATUS_SUCCESS)
		{
			lwsl_client("%s\n", "SOCKS password reply recieved - "
				"successful");
			socks_generate_msg(wsi, SOCKS_MSG_CONNECT, (size_t *)&len);

			conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY;
			pending_timeout =
				PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY;
			lwsl_client("%s\n", "Sending SOCKS connect command");
		}
		else
		{
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR : SOCKS user/password reply failed, "
				"error code: %d\n", pt->serv_buf[1]);
			return 0;
		}

		n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len,
			 MSG_NOSIGNAL);
		if (n < 0) {
			lwsl_debug("ERROR writing connect command to SOCKS "
				"socket\n");
			return 0;
		}

		lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT);
		wsi->mode = conn_mode;

		break;

	/* SOCKS connect command Reply */
	case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY:

		/* handle proxy hung up on us */

		if (pollfd->revents & LWS_POLLHUP) {

			lwsl_warn("SOCKS connection %p (fd=%d) dead\n",
				  (void *)wsi, pollfd->fd);

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		}

		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
		if (n < 0) {
			if (LWS_ERRNO == LWS_EAGAIN) {
				lwsl_debug("SOCKS read returned EAGAIN... "
					"retrying\n");
				return 0;
			}

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR reading from socks socket\n");
			return 0;
		}

		/* processing connect reply */
		if (pt->serv_buf[0] == SOCKS_VERSION_5
			&& pt->serv_buf[1] == SOCKS_REQUEST_REPLY_SUCCESS)
		{
			lwsl_client("%s\n", "SOCKS connect reply recieved - "
				"successful");
		}
		else
		{
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR SOCKS connect reply failed, error "
				"code: %d\n", pt->serv_buf[1]);
			return 0;
		}

		/* free stash since we are done with it */
		lws_free_set_NULL(wsi->u.hdr.stash);

		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
			wsi->vhost->socks_proxy_address))
			goto bail3;
		wsi->c_port = wsi->vhost->socks_proxy_port;

		/* clear his proxy connection timeout */

		lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

		goto start_ws_hanshake;
#endif
	case LWSCM_WSCL_WAITING_PROXY_REPLY:

		/* handle proxy hung up on us */

		if (pollfd->revents & LWS_POLLHUP) {

			lwsl_warn("Proxy connection %p (fd=%d) dead\n",
				  (void *)wsi, pollfd->fd);

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		}

		n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0);
		if (n < 0) {
			if (LWS_ERRNO == LWS_EAGAIN) {
				lwsl_debug("Proxy read returned EAGAIN... retrying\n");
				return 0;
			}

			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR reading from proxy socket\n");
			return 0;
		}

		pt->serv_buf[13] = '\0';
		if (strcmp(sb, "HTTP/1.0 200 ") &&
		    strcmp(sb, "HTTP/1.1 200 ")) {
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			lwsl_err("ERROR proxy: %s\n", sb);
			return 0;
		}

		/* clear his proxy connection timeout */

		lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

		/* fallthru */

	case LWSCM_WSCL_ISSUE_HANDSHAKE:

		/*
		 * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE
		 * timeout protection set in client-handshake.c
		 *
		 * take care of our lws_callback_on_writable
		 * happening at a time when there's no real connection yet
		 */
#if defined(LWS_WITH_SOCKS5)
start_ws_hanshake:
#endif
		if (lws_change_pollfd(wsi, LWS_POLLOUT, 0))
			return -1;

#ifdef LWS_OPENSSL_SUPPORT
		/* we can retry this... just cook the SSL BIO the first time */

		if (wsi->use_ssl && !wsi->ssl) {
			if (lws_ssl_client_bio_create(wsi))
				return -1;
		}

		if (wsi->use_ssl) {
			n = lws_ssl_client_connect1(wsi);
			if (!n)
				return 0;
			if (n < 0) {
				cce = "lws_ssl_client_connect1 failed";
				goto bail3;
			}
		} else
			wsi->ssl = NULL;

		/* fallthru */

	case LWSCM_WSCL_WAITING_SSL:

		if (wsi->use_ssl) {
			n = lws_ssl_client_connect2(wsi);
			if (!n)
				return 0;
			if (n < 0) {
				cce = "lws_ssl_client_connect2 failed";
				goto bail3;
			}
		} else
			wsi->ssl = NULL;
#endif

		wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2;
		lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND,
				context->timeout_secs);

		/* fallthru */

	case LWSCM_WSCL_ISSUE_HANDSHAKE2:
		p = lws_generate_client_handshake(wsi, p);
		if (p == NULL) {
			if (wsi->mode == LWSCM_RAW)
				return 0;

			lwsl_err("Failed to generate handshake for client\n");
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		}

		/* send our request to the server */

		lws_latency_pre(context, wsi);

		n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb);
		lws_latency(context, wsi, "send lws_issue_raw", n,
			    n == p - sb);
		switch (n) {
		case LWS_SSL_CAPABLE_ERROR:
			lwsl_debug("ERROR writing to client socket\n");
			lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
			return 0;
		case LWS_SSL_CAPABLE_MORE_SERVICE:
			lws_callback_on_writable(wsi);
			break;
		}

		if (wsi->client_http_body_pending) {
			wsi->mode = LWSCM_WSCL_ISSUE_HTTP_BODY;
			lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
					context->timeout_secs);
			/* user code must ask for writable callback */
			break;
		}

		goto client_http_body_sent;

	case LWSCM_WSCL_ISSUE_HTTP_BODY:
		if (wsi->client_http_body_pending) {
			lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD,
					context->timeout_secs);
			/* user code must ask for writable callback */
			break;
		}
client_http_body_sent:
		wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART;
		wsi->u.hdr.lextable_pos = 0;
		wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY;
		lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE,
				context->timeout_secs);
		break;

	case LWSCM_WSCL_WAITING_SERVER_REPLY:

		/* handle server hung up on us */

		if (pollfd->revents & LWS_POLLHUP) {

			lwsl_debug("Server connection %p (fd=%d) dead\n",
				(void *)wsi, pollfd->fd);
			cce = "Peer hung up";
			goto bail3;
		}

		if (!(pollfd->revents & LWS_POLLIN))
			break;

		/* interpret the server response */

		/*
		 *  HTTP/1.1 101 Switching Protocols
		 *  Upgrade: websocket
		 *  Connection: Upgrade
		 *  Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo=
		 *  Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC==
		 *  Sec-WebSocket-Protocol: chat
		 */

		/*
		 * we have to take some care here to only take from the
		 * socket bytewise.  The browser may (and has been seen to
		 * in the case that onopen() performs websocket traffic)
		 * coalesce both handshake response and websocket traffic
		 * in one packet, since at that point the connection is
		 * definitively ready from browser pov.
		 */
		len = 1;
		while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE &&
		       len > 0) {
			n = lws_ssl_capable_read(wsi, &c, 1);
			lws_latency(context, wsi, "send lws_issue_raw", n,
				    n == 1);
			switch (n) {
			case 0:
			case LWS_SSL_CAPABLE_ERROR:
				cce = "read failed";
				goto bail3;
			case LWS_SSL_CAPABLE_MORE_SERVICE:
				return 0;
			}

			if (lws_parse(wsi, c)) {
				lwsl_warn("problems parsing header\n");
				goto bail3;
			}
		}

		/*
		 * hs may also be coming in multiple packets, there is a 5-sec
		 * libwebsocket timeout still active here too, so if parsing did
		 * not complete just wait for next packet coming in this state
		 */

		if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE)
			break;

		/*
		 * otherwise deal with the handshake.  If there's any
		 * packet traffic already arrived we'll trigger poll() again
		 * right away and deal with it that way
		 */

		return lws_client_interpret_server_handshake(wsi);

bail3:
		lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n");
		if (cce)
			lwsl_info("reason: %s\n", cce);
		wsi->protocol->callback(wsi,
			LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
			wsi->user_space, (void *)cce, cce ? strlen(cce) : 0);
		wsi->already_did_cce = 1;
		lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
		return -1;

	case LWSCM_WSCL_WAITING_EXTENSION_CONNECT:
		lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n");
		break;

	case LWSCM_WSCL_PENDING_CANDIDATE_CHILD:
		lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n");
		break;
	default:
		break;
	}

	return 0;
}
Ejemplo n.º 11
0
/**
 * lws_context_destroy() - Destroy the websocket context
 * @context:	Websocket context
 *
 *	This function closes any active connections and then frees the
 *	context.  After calling this, any further use of the context is
 *	undefined.
 */
LWS_VISIBLE void
lws_context_destroy(struct lws_context *context)
{
    const struct lws_protocols *protocol = NULL;
    struct lws wsi;
    int n, m;

    lwsl_notice("%s\n", __func__);

    if (!context)
        return;

    m = context->count_threads;
    context->being_destroyed = 1;

    memset(&wsi, 0, sizeof(wsi));
    wsi.context = context;

#ifdef LWS_LATENCY
    if (context->worst_latency_info[0])
        lwsl_notice("Worst latency: %s\n", context->worst_latency_info);
#endif

    while (m--)
        for (n = 0; n < context->pt[m].fds_count; n++) {
            struct lws *wsi = wsi_from_fd(context, context->pt[m].fds[n].fd);
            if (!wsi)
                continue;
            lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
                               /* no protocol close */);
            n--;
        }

    /*
     * give all extensions a chance to clean up any per-context
     * allocations they might have made
     */

    n = lws_ext_cb_all_exts(context, NULL,
                            LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0);

    n = lws_ext_cb_all_exts(context, NULL,
                            LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0);

    /*
     * inform all the protocols that they are done and will have no more
     * callbacks
     */
    protocol = context->protocols;
    if (protocol) {
        while (protocol->callback) {
            protocol->callback(&wsi,
                               LWS_CALLBACK_PROTOCOL_DESTROY,
                               NULL, NULL, 0);
            protocol++;
        }
    }
#ifdef LWS_USE_LIBEV
    ev_io_stop(context->io_loop, &context->w_accept.watcher);
    if(context->use_ev_sigint)
        ev_signal_stop(context->io_loop, &context->w_sigint.watcher);
#endif /* LWS_USE_LIBEV */

    for (n = 0; n < context->count_threads; n++)
        lws_free_set_NULL(context->pt[n].serv_buf);

    lws_plat_context_early_destroy(context);
    lws_ssl_context_destroy(context);
    if (context->pt[0].fds)
        lws_free(context->pt[0].fds);
    if (context->ah_pool)
        lws_free(context->ah_pool);
    if (context->http_header_data)
        lws_free(context->http_header_data);

    lws_plat_context_late_destroy(context);

    lws_free(context);
}
Ejemplo n.º 12
0
int
lws_client_interpret_server_handshake(struct lws *wsi)
{
	struct lws_context *context = wsi->context;
	int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
	int n, len, okay = 0, isErrorCodeReceived = 0;
	const char *pc;
	char *p;
#ifndef LWS_NO_EXTENSIONS
	const struct lws_extension *ext;
	char ext_name[128];
	const char *c;
	int more = 1;
	void *v;
#endif

	/*
	 * well, what the server sent looked reasonable for syntax.
	 * Now let's confirm it sent all the necessary headers
	 */

	if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
		lwsl_info("no ACCEPT\n");
		p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
		isErrorCodeReceived = 1;
		goto bail3;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
	if (!p) {
		lwsl_info("no URI\n");
		goto bail3;
	}
	if (p && strncmp(p, "101", 3)) {
		lwsl_warn(
		       "lws_client_handshake: got bad HTTP response '%s'\n", p);
		goto bail3;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
	if (!p) {
		lwsl_info("no UPGRADE\n");
		goto bail3;
	}
	strtolower(p);
	if (strcmp(p, "websocket")) {
		lwsl_warn(
		      "lws_client_handshake: got bad Upgrade header '%s'\n", p);
		goto bail3;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION);
	if (!p) {
		lwsl_info("no Connection hdr\n");
		goto bail3;
	}
	strtolower(p);
	if (strcmp(p, "upgrade")) {
		lwsl_warn("lws_client_int_s_hs: bad header %s\n", p);
		goto bail3;
	}

	pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
	if (!pc) {
		lwsl_parser("lws_client_int_s_hs: no protocol list\n");
	} else
		lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);

	/*
	 * confirm the protocol the server wants to talk was in the list
	 * of protocols we offered
	 */

	len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
	if (!len) {

		lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n");
		/*
		 * no protocol name to work from,
		 * default to first protocol
		 */
		wsi->protocol = &context->protocols[0];
		goto check_extensions;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
	len = strlen(p);

	while (pc && *pc && !okay) {
		if (!strncmp(pc, p, len) &&
		    (pc[len] == ',' || pc[len] == '\0')) {
			okay = 1;
			continue;
		}
		while (*pc && *pc++ != ',')
			;
		while (*pc && *pc == ' ')
			pc++;
	}

	if (!okay) {
		lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p);
		goto bail2;
	}

	/*
	 * identify the selected protocol struct and set it
	 */
	n = 0;
	wsi->protocol = NULL;
	while (context->protocols[n].callback && !wsi->protocol) {
		if (strcmp(p, context->protocols[n].name) == 0) {
			wsi->protocol = &context->protocols[n];
			break;
		}
		n++;
	}

	if (wsi->protocol == NULL) {
		lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p);
		goto bail2;
	}


check_extensions:
#ifndef LWS_NO_EXTENSIONS
	/* instantiate the accepted extensions */

	if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
		lwsl_ext("no client extenstions allowed by server\n");
		goto check_accept;
	}

	/*
	 * break down the list of server accepted extensions
	 * and go through matching them or identifying bogons
	 */

	if (lws_hdr_copy(wsi, (char *)context->serv_buf,
		   sizeof(context->serv_buf), WSI_TOKEN_EXTENSIONS) < 0) {
		lwsl_warn("ext list from server failed to copy\n");
		goto bail2;
	}

	c = (char *)context->serv_buf;
	n = 0;
	while (more) {

		if (*c && (*c != ',' && *c != ' ' && *c != '\t')) {
			ext_name[n] = *c++;
			if (n < sizeof(ext_name) - 1)
				n++;
			continue;
		}
		ext_name[n] = '\0';
		if (!*c)
			more = 0;
		else {
			c++;
			if (!n)
				continue;
		}

		/* check we actually support it */

		lwsl_ext("checking client ext %s\n", ext_name);

		n = 0;
		ext = lws_get_context(wsi)->extensions;
		while (ext && ext->callback) {
			if (strcmp(ext_name, ext->name)) {
				ext++;
				continue;
			}

			n = 1;
			lwsl_ext("instantiating client ext %s\n", ext_name);

			/* instantiate the extension on this conn */

			wsi->active_extensions_user[
				wsi->count_active_extensions] =
					 lws_zalloc(ext->per_session_data_size);
			if (wsi->active_extensions_user[
				wsi->count_active_extensions] == NULL) {
				lwsl_err("Out of mem\n");
				goto bail2;
			}
			wsi->active_extensions[
				  wsi->count_active_extensions] = ext;

			/* allow him to construct his context */

			ext->callback(lws_get_context(wsi), ext, wsi,
				      LWS_EXT_CALLBACK_CLIENT_CONSTRUCT,
				      wsi->active_extensions_user[
					 wsi->count_active_extensions],
				      NULL, 0);

			wsi->count_active_extensions++;

			ext++;
		}

		if (n == 0) {
			lwsl_warn("Unknown ext '%s'!\n", ext_name);
			goto bail2;
		}

		n = 0;
	}

check_accept:
#endif

	/*
	 * Confirm his accept token is the one we precomputed
	 */

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
	if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) {
		lwsl_warn("lws_client_int_s_hs: accept %s wrong vs %s\n", p,
				  wsi->u.hdr.ah->initial_handshake_hash_base64);
		goto bail2;
	}

	/* allocate the per-connection user memory (if any) */
	if (lws_ensure_user_space(wsi)) {
		lwsl_err("Problem allocating wsi user mem\n");
		goto bail2;
	}

	/*
	 * we seem to be good to go, give client last chance to check
	 * headers and OK it
	 */
	if (wsi->protocol->callback(wsi,
				    LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
				    wsi->user_space, NULL, 0))
		goto bail2;

	/* clear his proxy connection timeout */

	lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

	/* free up his parsing allocations */

	lws_free(wsi->u.hdr.ah);

	lws_union_transition(wsi, LWSCM_WS_CLIENT);
	wsi->state = LWSS_ESTABLISHED;

	wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;

	/*
	 * create the frame buffer for this connection according to the
	 * size mentioned in the protocol definition.  If 0 there, then
	 * use a big default for compatibility
	 */

	n = wsi->protocol->rx_buffer_size;
	if (!n)
		n = LWS_MAX_SOCKET_IO_BUF;
	n += LWS_SEND_BUFFER_PRE_PADDING + LWS_SEND_BUFFER_POST_PADDING;
	wsi->u.ws.rx_user_buffer = lws_malloc(n);
	if (!wsi->u.ws.rx_user_buffer) {
		lwsl_err("Out of Mem allocating rx buffer %d\n", n);
		goto bail2;
	}
	lwsl_info("Allocating client RX buffer %d\n", n);

	if (setsockopt(wsi->sock, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
		       sizeof n)) {
		lwsl_warn("Failed to set SNDBUF to %d", n);
		goto bail3;
	}

	lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);

	/* call him back to inform him he is up */

	if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
				    wsi->user_space, NULL, 0))
		goto bail3;
#ifndef LWS_NO_EXTENSIONS
	/*
	 * inform all extensions, not just active ones since they
	 * already know
	 */

	ext = context->extensions;

	while (ext && ext->callback) {
		v = NULL;
		for (n = 0; n < wsi->count_active_extensions; n++)
			if (wsi->active_extensions[n] == ext)
				v = wsi->active_extensions_user[n];

		ext->callback(context, ext, wsi,
			  LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0);
		ext++;
	}
#endif

	return 0;

bail3:
	lws_free_set_NULL(wsi->u.ws.rx_user_buffer);
	close_reason = LWS_CLOSE_STATUS_NOSTATUS;

bail2:
	if (wsi->protocol) {
		if (isErrorCodeReceived && p) {
			wsi->protocol->callback(wsi,
				LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
						wsi->user_space, p,
						(unsigned int)strlen(p));
		} else {
			wsi->protocol->callback(wsi,
				LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
						wsi->user_space, NULL, 0);
		}
	}

	lwsl_info("closing connection due to bail2 connection error\n");

	/* free up his parsing allocations */
	lws_free_set_NULL(wsi->u.hdr.ah);
	lws_close_free_wsi(wsi, close_reason);

	return 1;
}
Ejemplo n.º 13
0
LWS_VISIBLE int
lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int tsi)
{
	struct lws_context_per_thread *pt = &context->pt[tsi];
	lws_sockfd_type our_fd = 0, tmp_fd;
	struct lws_tokens eff_buf;
	unsigned int pending = 0;
	struct lws *wsi, *wsi1;
	char draining_flow = 0;
	int timed_out = 0;
	time_t now;
	int n, m;
	int more;

	/*
	 * you can call us with pollfd = NULL to just allow the once-per-second
	 * global timeout checks; if less than a second since the last check
	 * it returns immediately then.
	 */

	time(&now);

	/* TODO: if using libev, we should probably use timeout watchers... */
	if (context->last_timeout_check_s != now) {
		context->last_timeout_check_s = now;

		lws_plat_service_periodic(context);

		/* global timeout check once per second */

		if (pollfd)
			our_fd = pollfd->fd;

		wsi = context->pt[tsi].timeout_list;
		while (wsi) {
			/* we have to take copies, because he may be deleted */
			wsi1 = wsi->timeout_list;
			tmp_fd = wsi->sock;
			if (lws_service_timeout_check(wsi, (unsigned int)now)) {
				/* he did time out... */
				if (tmp_fd == our_fd)
					/* it was the guy we came to service! */
					timed_out = 1;
					/* he's gone, no need to mark as handled */
			}
			wsi = wsi1;
		}
#if 0
		{
			char s[300], *p = s;

			for (n = 0; n < context->count_threads; n++)
				p += sprintf(p, " %7lu (%5d), ",
					     context->pt[n].count_conns,
					     context->pt[n].fds_count);

			lwsl_notice("load: %s\n", s);
		}
#endif
	}

	/* the socket we came to service timed out, nothing to do */
	if (timed_out)
		return 0;

	/* just here for timeout management? */
	if (!pollfd)
		return 0;

	/* no, here to service a socket descriptor */
	wsi = wsi_from_fd(context, pollfd->fd);
	if (!wsi)
		/* not lws connection ... leave revents alone and return */
		return 0;

	/*
	 * so that caller can tell we handled, past here we need to
	 * zero down pollfd->revents after handling
	 */

#if LWS_POSIX

	/* handle session socket closed */

	if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) &&
	    (pollfd->revents & LWS_POLLHUP)) {
		wsi->socket_is_permanently_unusable = 1;
		lwsl_debug("Session Socket %p (fd=%d) dead\n",
						       (void *)wsi, pollfd->fd);

		goto close_and_handled;
	}

#ifdef _WIN32
	if (pollfd->revents & LWS_POLLOUT)
		wsi->sock_send_blocking = FALSE;
#endif

#endif

	/* okay, what we came here to do... */

	switch (wsi->mode) {
	case LWSCM_HTTP_SERVING:
	case LWSCM_HTTP_SERVING_ACCEPTED:
	case LWSCM_SERVER_LISTENER:
	case LWSCM_SSL_ACK_PENDING:
		n = lws_server_socket_service(context, wsi, pollfd);
		if (n) /* closed by above */
			return 1;
		pending = lws_ssl_pending(wsi);
		if (pending)
			goto handle_pending;
		goto handled;

	case LWSCM_WS_SERVING:
	case LWSCM_WS_CLIENT:
	case LWSCM_HTTP2_SERVING:

		/* 1: something requested a callback when it was OK to write */

		if ((pollfd->revents & LWS_POLLOUT) &&
		    (wsi->state == LWSS_ESTABLISHED ||
		     wsi->state == LWSS_HTTP2_ESTABLISHED ||
		     wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS ||
		     wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
		     wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) &&
		    lws_handle_POLLOUT_event(wsi, pollfd)) {
			if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY)
				wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE;
			lwsl_info("lws_service_fd: closing\n");
			goto close_and_handled;
		}
#if 1
		if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY ||
		    wsi->state == LWSS_AWAITING_CLOSE_ACK) {
			/*
			 * we stopped caring about anything except control
			 * packets.  Force flow control off, defeat tx
			 * draining.
			 */
			lws_rx_flow_control(wsi, 1);
			wsi->u.ws.tx_draining_ext = 0;
		}
#endif
		if (wsi->u.ws.tx_draining_ext) {
			/* we cannot deal with new RX until the TX ext
			 * path has been drained.  It's because new
			 * rx will, eg, crap on the wsi rx buf that
			 * may be needed to retain state.
			 *
			 * TX ext drain path MUST go through event loop
			 * to avoid blocking.
			 */
			break;
		}

		if (!(wsi->rxflow_change_to & LWS_RXFLOW_ALLOW))
			/* We cannot deal with any kind of new RX
			 * because we are RX-flowcontrolled.
			 */
			break;

		/* 2: RX Extension needs to be drained
		 */

		if (wsi->state == LWSS_ESTABLISHED &&
		    wsi->u.ws.rx_draining_ext) {

			lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__);
#ifndef LWS_NO_CLIENT
			if (wsi->mode == LWSCM_WS_CLIENT) {
				n = lws_client_rx_sm(wsi, 0);
				if (n < 0)
					/* we closed wsi */
					n = 0;
			} else
#endif
				n = lws_rx_sm(wsi, 0);

			goto handled;
		}

		if (wsi->u.ws.rx_draining_ext)
			/*
			 * We have RX EXT content to drain, but can't do it
			 * right now.  That means we cannot do anything lower
			 * priority either.
			 */
			break;

		/* 3: RX Flowcontrol buffer needs to be drained
		 */

		if (wsi->rxflow_buffer) {
			lwsl_info("draining rxflow (len %d)\n",
				wsi->rxflow_len - wsi->rxflow_pos
			);
			/* well, drain it */
			eff_buf.token = (char *)wsi->rxflow_buffer +
						wsi->rxflow_pos;
			eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos;
			draining_flow = 1;
			goto drain;
		}

		/* 4: any incoming data ready?
		 * notice if rx flow going off raced poll(), rx flow wins
		 */
		if (!(pollfd->revents & pollfd->events & LWS_POLLIN))
			break;
read:
		eff_buf.token_len = lws_ssl_capable_read(wsi, pt->serv_buf,
					pending ? pending : LWS_MAX_SOCKET_IO_BUF);
		switch (eff_buf.token_len) {
		case 0:
			lwsl_info("service_fd: closing due to 0 length read\n");
			goto close_and_handled;
		case LWS_SSL_CAPABLE_MORE_SERVICE:
			lwsl_info("SSL Capable more service\n");
			n = 0;
			goto handled;
		case LWS_SSL_CAPABLE_ERROR:
			lwsl_info("Closing when error\n");
			goto close_and_handled;
		}

		/*
		 * give any active extensions a chance to munge the buffer
		 * before parse.  We pass in a pointer to an lws_tokens struct
		 * prepared with the default buffer and content length that's in
		 * there.  Rather than rewrite the default buffer, extensions
		 * that expect to grow the buffer can adapt .token to
		 * point to their own per-connection buffer in the extension
		 * user allocation.  By default with no extensions or no
		 * extension callback handling, just the normal input buffer is
		 * used then so it is efficient.
		 */

		eff_buf.token = (char *)pt->serv_buf;
drain:

		do {
			more = 0;

			m = lws_ext_cb_active(wsi,
				LWS_EXT_CB_PACKET_RX_PREPARSE, &eff_buf, 0);
			if (m < 0)
				goto close_and_handled;
			if (m)
				more = 1;

			/* service incoming data */

			if (eff_buf.token_len) {
				/*
				 * if draining from rxflow buffer, not
				 * critical to track what was used since at the
				 * use it bumps wsi->rxflow_pos.  If we come
				 * around again it will pick up from where it
				 * left off.
				 */
				n = lws_read(wsi, (unsigned char *)eff_buf.token,
					     eff_buf.token_len);
				if (n < 0) {
					/* we closed wsi */
					n = 0;
					goto handled;
				}
			}

			eff_buf.token = NULL;
			eff_buf.token_len = 0;
		} while (more);

		pending = lws_ssl_pending(wsi);
		if (pending) {
handle_pending:
			pending = pending > LWS_MAX_SOCKET_IO_BUF ?
					LWS_MAX_SOCKET_IO_BUF : pending;
			goto read;
		}

		if (draining_flow && wsi->rxflow_buffer &&
				 wsi->rxflow_pos == wsi->rxflow_len) {
			lwsl_info("flow buffer: drained\n");
			lws_free_set_NULL(wsi->rxflow_buffer);
			/* having drained the rxflow buffer, can rearm POLLIN */
#ifdef LWS_NO_SERVER
			n =
#endif
			_lws_rx_flow_control(wsi);
			/* n ignored, needed for NO_SERVER case */
		}

		break;

	default:
#ifdef LWS_NO_CLIENT
		break;
#else
		n = lws_client_socket_service(context, wsi, pollfd);
		if (n)
			return 1;
		goto handled;
#endif
	}

	n = 0;
	goto handled;

close_and_handled:
	lwsl_debug("Close and handled\n");
	lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS);
	/*
	 * pollfd may point to something else after the close
	 * due to pollfd swapping scheme on delete on some platforms
	 * we can't clear revents now because it'd be the wrong guy's revents
	 */
	return 1;

handled:
	pollfd->revents = 0;
	return n;
}
Ejemplo n.º 14
0
int
lws_x509_jwk_privkey_pem(struct lws_jwk *jwk, void *pem, size_t len,
			 const char *passphrase)
{
	BIO* bio = BIO_new(BIO_s_mem());
	BIGNUM *mpi, *dummy[6];
	EVP_PKEY *pkey = NULL;
	EC_KEY *ecpriv = NULL;
	RSA *rsapriv = NULL;
	const BIGNUM *cmpi;
	int n, m, ret = -1;

	BIO_write(bio, pem, len);
	PEM_read_bio_PrivateKey(bio, &pkey, lws_x509_jwk_privkey_pem_pp_cb,
				(void *)passphrase);
	BIO_free(bio);
	lws_explicit_bzero((void *)pem, len);
	if (!pkey) {
		lwsl_err("%s: unable to parse PEM privkey\n", __func__);
		lws_tls_err_describe();

		return -1;
	}

	/* confirm the key type matches the existing jwk situation */

	switch (jwk->kty) {
	case LWS_GENCRYPTO_KTY_EC:
		if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_EC) {
			lwsl_err("%s: jwk is EC but privkey isn't\n", __func__);

			goto bail;
		}
		ecpriv = EVP_PKEY_get1_EC_KEY(pkey);
		if (!ecpriv) {
			lwsl_notice("%s: missing EC key\n", __func__);

			goto bail;
		}

		cmpi = EC_KEY_get0_private_key(ecpriv);

		/* quick size check first */

		n = BN_num_bytes(cmpi);
		if (jwk->e[LWS_GENCRYPTO_EC_KEYEL_Y].len != (uint32_t)n) {
			lwsl_err("%s: jwk key size doesn't match\n", __func__);

			goto bail1;
		}

		/* TODO.. check public curve / group + point */

		jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len = n;
		jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf = lws_malloc(n, "ec");
		if (!jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf)
			goto bail1;

		m = BN_bn2binpad(cmpi, jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].buf,
				      jwk->e[LWS_GENCRYPTO_EC_KEYEL_D].len);
		if (m != BN_num_bytes(cmpi))
			goto bail1;

		break;

	case LWS_GENCRYPTO_KTY_RSA:
		if (EVP_PKEY_type(EVP_PKEY_id(pkey)) != EVP_PKEY_RSA) {
			lwsl_err("%s: RSA jwk, non-RSA privkey\n", __func__);

			goto bail;
		}
		rsapriv = EVP_PKEY_get1_RSA(pkey);
		if (!rsapriv) {
			lwsl_notice("%s: missing RSA key\n", __func__);

			goto bail;
		}

#if defined(LWS_HAVE_RSA_SET0_KEY)
		RSA_get0_key(rsapriv, (const BIGNUM **)&dummy[0], /* n */
				      (const BIGNUM **)&dummy[1], /* e */
				      (const BIGNUM **)&mpi);	  /* d */
		RSA_get0_factors(rsapriv, (const BIGNUM **)&dummy[4],  /* p */
					  (const BIGNUM **)&dummy[5]); /* q */
#else
		dummy[0] = rsapriv->n;
		dummy[1] = rsapriv->e;
		dummy[4] = rsapriv->p;
		dummy[5] = rsapriv->q;
		mpi = rsapriv->d;
#endif

		/* quick size check first */

		n = BN_num_bytes(mpi);
		if (jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len != (uint32_t)n) {
			lwsl_err("%s: jwk key size doesn't match\n", __func__);

			goto bail1;
		}

		/* then check that n & e match what we got from the cert */

		dummy[2] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].buf,
				     jwk->e[LWS_GENCRYPTO_RSA_KEYEL_N].len,
				     NULL);
		dummy[3] = BN_bin2bn(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].buf,
				     jwk->e[LWS_GENCRYPTO_RSA_KEYEL_E].len,
				     NULL);

		m = BN_cmp(dummy[2], dummy[0]) | BN_cmp(dummy[3], dummy[1]);
		BN_clear_free(dummy[2]);
		BN_clear_free(dummy[3]);
		if (m) {
			lwsl_err("%s: privkey doesn't match jwk pubkey\n",
				 __func__);

			goto bail1;
		}

		/* accept d from the PEM privkey into the JWK */

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].len = n;
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf = lws_malloc(n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf)
			goto bail1;

		BN_bn2bin(mpi, jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);

		/* accept p and q from the PEM privkey into the JWK */

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].len = BN_num_bytes(dummy[4]);
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf = lws_malloc(n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf) {
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
			goto bail1;
		}
		BN_bn2bin(dummy[4], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);

		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].len = BN_num_bytes(dummy[5]);
		jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf = lws_malloc(n, "privjk");
		if (!jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf) {
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_D].buf);
			lws_free_set_NULL(jwk->e[LWS_GENCRYPTO_RSA_KEYEL_P].buf);
			goto bail1;
		}
		BN_bn2bin(dummy[5], jwk->e[LWS_GENCRYPTO_RSA_KEYEL_Q].buf);
		break;
	default:
		lwsl_err("%s: JWK has unknown kty %d\n", __func__, jwk->kty);
		return -1;
	}

	ret = 0;

bail1:
	if (jwk->kty == LWS_GENCRYPTO_KTY_EC)
		EC_KEY_free(ecpriv);
	else
		RSA_free(rsapriv);

bail:
	EVP_PKEY_free(pkey);

	return ret;
}
Ejemplo n.º 15
0
LWS_VISIBLE void
lws_context_destroy(struct lws_context *context)
{
	const struct lws_protocols *protocol = NULL;
	struct lws_context_per_thread *pt;
	struct lws_vhost *vh = NULL, *vh1;
	struct lws wsi;
	int n, m;

	lwsl_notice("%s\n", __func__);

	if (!context)
		return;

	m = context->count_threads;
	context->being_destroyed = 1;

	memset(&wsi, 0, sizeof(wsi));
	wsi.context = context;

#ifdef LWS_LATENCY
	if (context->worst_latency_info[0])
		lwsl_notice("Worst latency: %s\n", context->worst_latency_info);
#endif

	while (m--) {
		pt = &context->pt[m];

		for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) {
			struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd);
			if (!wsi)
				continue;

			lws_close_free_wsi(wsi,
				LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY
				/* no protocol close */);
			n--;
		}
		lws_pt_mutex_destroy(pt);
	}
	/*
	 * give all extensions a chance to clean up any per-context
	 * allocations they might have made
	 */

	n = lws_ext_cb_all_exts(context, NULL,
				LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0);

	n = lws_ext_cb_all_exts(context, NULL,
				LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0);

	/*
	 * inform all the protocols that they are done and will have no more
	 * callbacks.
	 *
	 * We can't free things until after the event loop shuts down.
	 */
	if (context->protocol_init_done)
		vh = context->vhost_list;
	while (vh) {
		wsi.vhost = vh;
		protocol = vh->protocols;
		if (protocol) {
			n = 0;
			while (n < vh->count_protocols) {
				wsi.protocol = protocol;
				protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY,
						   NULL, NULL, 0);
				protocol++;
				n++;
			}
		}

		vh = vh->vhost_next;
	}

	for (n = 0; n < context->count_threads; n++) {
		pt = &context->pt[n];

		lws_libev_destroyloop(context, n);
		lws_libuv_destroyloop(context, n);

		lws_free_set_NULL(context->pt[n].serv_buf);
		if (pt->ah_pool)
			lws_free(pt->ah_pool);
		if (pt->http_header_data)
			lws_free(pt->http_header_data);
	}
	lws_plat_context_early_destroy(context);
	lws_ssl_context_destroy(context);

	if (context->pt[0].fds)
		lws_free_set_NULL(context->pt[0].fds);

	/* free all the vhost allocations */

	vh = context->vhost_list;
	while (vh) {
		protocol = vh->protocols;
		if (protocol) {
			n = 0;
			while (n < vh->count_protocols) {
				if (vh->protocol_vh_privs &&
				    vh->protocol_vh_privs[n]) {
					lws_free(vh->protocol_vh_privs[n]);
					vh->protocol_vh_privs[n] = NULL;
				}
				protocol++;
				n++;
			}
		}
		if (vh->protocol_vh_privs)
			lws_free(vh->protocol_vh_privs);
		lws_ssl_SSL_CTX_destroy(vh);
		lws_free(vh->same_vh_protocol_list);
#ifdef LWS_WITH_PLUGINS
		if (context->plugin_list)
			lws_free((void *)vh->protocols);
#ifndef LWS_NO_EXTENSIONS
		if (context->plugin_extension_count)
			lws_free((void *)vh->extensions);
#endif
#endif
#ifdef LWS_WITH_ACCESS_LOG
		if (vh->log_fd != (int)LWS_INVALID_FILE)
			close(vh->log_fd);
#endif

		vh1 = vh->vhost_next;
		lws_free(vh);
		vh = vh1;
	}

	lws_plat_context_late_destroy(context);

	lws_free(context);
}
Ejemplo n.º 16
0
LWS_VISIBLE LWS_EXTERN int
lws_cgi_write_split_stdout_headers(struct lws *wsi)
{
	int n, m, cmd;
	unsigned char buf[LWS_PRE + 4096], *start = &buf[LWS_PRE], *p = start,
			*end = &buf[sizeof(buf) - 1 - LWS_PRE], *name,
			*value = NULL;
	char c, hrs;

	if (!wsi->http.cgi)
		return -1;

	while (wsi->hdr_state != LHCS_PAYLOAD) {
		/*
		 * We have to separate header / finalize and payload chunks,
		 * since they need to be handled separately
		 */
		switch (wsi->hdr_state) {
		case LHCS_RESPONSE:
			lwsl_debug("LHCS_RESPONSE: issuing response %d\n",
				   wsi->http.cgi->response_code);
			if (lws_add_http_header_status(wsi,
						   wsi->http.cgi->response_code,
						       &p, end))
				return 1;
			if (!wsi->http.cgi->explicitly_chunked &&
			    !wsi->http.cgi->content_length &&
				lws_add_http_header_by_token(wsi,
					WSI_TOKEN_HTTP_TRANSFER_ENCODING,
					(unsigned char *)"chunked", 7, &p, end))
				return 1;
			if (!(wsi->http2_substream))
				if (lws_add_http_header_by_token(wsi,
						WSI_TOKEN_CONNECTION,
						(unsigned char *)"close", 5,
						&p, end))
					return 1;
			n = lws_write(wsi, start, p - start,
				      LWS_WRITE_HTTP_HEADERS | LWS_WRITE_NO_FIN);

			/*
			 * so we have a bunch of http/1 style ascii headers
			 * starting from wsi->http.cgi->headers_buf through
			 * wsi->http.cgi->headers_pos.  These are OK for http/1
			 * connections, but they're no good for http/2 conns.
			 *
			 * Let's redo them at headers_pos forward using the
			 * correct coding for http/1 or http/2
			 */
			if (!wsi->http2_substream)
				goto post_hpack_recode;

			p = wsi->http.cgi->headers_start;
			wsi->http.cgi->headers_start =
					wsi->http.cgi->headers_pos;
			wsi->http.cgi->headers_dumped =
					wsi->http.cgi->headers_start;
			hrs = HR_NAME;
			name = buf;

			while (p < wsi->http.cgi->headers_start) {
				switch (hrs) {
				case HR_NAME:
					/*
					 * in http/2 upper-case header names
					 * are illegal.  So convert to lower-
					 * case.
					 */
					if (name - buf > 64)
						return -1;
					if (*p != ':') {
						if (*p >= 'A' && *p <= 'Z')
							*name++ = (*p++) +
								  ('a' - 'A');
						else
							*name++ = *p++;
					} else {
						p++;
						*name++ = '\0';
						value = name;
						hrs = HR_WHITESPACE;
					}
					break;
				case HR_WHITESPACE:
					if (*p == ' ') {
						p++;
						break;
					}
					hrs = HR_ARG;
					/* fallthru */
				case HR_ARG:
					if (name > end - 64)
						return -1;

					if (*p != '\x0a' && *p != '\x0d') {
						*name++ = *p++;
						break;
					}
					hrs = HR_CRLF;
					/* fallthru */
				case HR_CRLF:
					if ((*p != '\x0a' && *p != '\x0d') ||
					    p + 1 == wsi->http.cgi->headers_start) {
						*name = '\0';
						if ((strcmp((const char *)buf,
							    "transfer-encoding")
						)) {
							lwsl_debug("+ %s: %s\n",
								   buf, value);
							if (
					lws_add_http_header_by_name(wsi, buf,
					(unsigned char *)value, name - value,
					(unsigned char **)&wsi->http.cgi->headers_pos,
					(unsigned char *)wsi->http.cgi->headers_end))
								return 1;
							hrs = HR_NAME;
							name = buf;
							break;
						}
					}
					p++;
					break;
				}
			}
post_hpack_recode:
			/* finalize cached headers before dumping them */
			if (lws_finalize_http_header(wsi,
			      (unsigned char **)&wsi->http.cgi->headers_pos,
			      (unsigned char *)wsi->http.cgi->headers_end)) {

				lwsl_notice("finalize failed\n");
				return -1;
			}

			wsi->hdr_state = LHCS_DUMP_HEADERS;
			wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_HEADERS;
			lws_callback_on_writable(wsi);
			/* back to the loop for writeability again */
			return 0;

		case LHCS_DUMP_HEADERS:

			n = wsi->http.cgi->headers_pos -
			    wsi->http.cgi->headers_dumped;
			if (n > 512)
				n = 512;

			lwsl_debug("LHCS_DUMP_HEADERS: %d\n", n);

			cmd = LWS_WRITE_HTTP_HEADERS_CONTINUATION;
			if (wsi->http.cgi->headers_dumped + n !=
			    wsi->http.cgi->headers_pos) {
				lwsl_notice("adding no fin flag\n");
				cmd |= LWS_WRITE_NO_FIN;
			}

			m = lws_write(wsi,
				 (unsigned char *)wsi->http.cgi->headers_dumped,
				      n, cmd);
			if (m < 0) {
				lwsl_debug("%s: write says %d\n", __func__, m);
				return -1;
			}
			wsi->http.cgi->headers_dumped += n;
			if (wsi->http.cgi->headers_dumped ==
			    wsi->http.cgi->headers_pos) {
				wsi->hdr_state = LHCS_PAYLOAD;
				lws_free_set_NULL(wsi->http.cgi->headers_buf);
				lwsl_debug("freed cgi headers\n");
			} else {
				wsi->reason_bf |=
					LWS_CB_REASON_AUX_BF__CGI_HEADERS;
				lws_callback_on_writable(wsi);
			}

			/* writeability becomes uncertain now we wrote
			 * something, we must return to the event loop
			 */
			return 0;
		}

		if (!wsi->http.cgi->headers_buf) {
			/* if we don't already have a headers buf, cook one */
			n = 2048;
			if (wsi->http2_substream)
				n = 4096;
			wsi->http.cgi->headers_buf = lws_malloc(n + LWS_PRE,
							   "cgi hdr buf");
			if (!wsi->http.cgi->headers_buf) {
				lwsl_err("OOM\n");
				return -1;
			}

			lwsl_debug("allocated cgi hdrs\n");
			wsi->http.cgi->headers_start =
					wsi->http.cgi->headers_buf + LWS_PRE;
			wsi->http.cgi->headers_pos = wsi->http.cgi->headers_start;
			wsi->http.cgi->headers_dumped = wsi->http.cgi->headers_pos;
			wsi->http.cgi->headers_end =
					wsi->http.cgi->headers_buf + n - 1;

			for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
				wsi->http.cgi->match[n] = 0;
				wsi->http.cgi->lp = 0;
			}
		}

		n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
		if (n < 0)
			return -1;
		n = read(n, &c, 1);
		if (n < 0) {
			if (errno != EAGAIN) {
				lwsl_debug("%s: read says %d\n", __func__, n);
				return -1;
			}
			else
				n = 0;

			if (wsi->http.cgi->headers_pos >=
					wsi->http.cgi->headers_end - 4) {
				lwsl_notice("CGI hdrs > buf size\n");

				return -1;
			}
		}
		if (!n)
			goto agin;

		lwsl_debug("-- 0x%02X %c %d %d\n", (unsigned char)c, c,
			   wsi->http.cgi->match[1], wsi->hdr_state);
		if (!c)
			return -1;
		switch (wsi->hdr_state) {
		case LCHS_HEADER:
			hdr:
			for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++) {
				/*
				 * significant headers with
				 * numeric decimal payloads
				 */
				if (!significant_hdr[n][wsi->http.cgi->match[n]] &&
				    (c >= '0' && c <= '9') &&
				    wsi->http.cgi->lp < (int)sizeof(wsi->http.cgi->l) - 1) {
					wsi->http.cgi->l[wsi->http.cgi->lp++] = c;
					wsi->http.cgi->l[wsi->http.cgi->lp] = '\0';
					switch (n) {
					case SIGNIFICANT_HDR_CONTENT_LENGTH:
						wsi->http.cgi->content_length =
							atoll(wsi->http.cgi->l);
						break;
					case SIGNIFICANT_HDR_STATUS:
						wsi->http.cgi->response_code =
							atol(wsi->http.cgi->l);
						lwsl_debug("Status set to %d\n",
						   wsi->http.cgi->response_code);
						break;
					default:
						break;
					}
				}
				/* hits up to the NUL are sticky until next hdr */
				if (significant_hdr[n][wsi->http.cgi->match[n]]) {
					if (tolower(c) ==
					    significant_hdr[n][wsi->http.cgi->match[n]])
						wsi->http.cgi->match[n]++;
					else
						wsi->http.cgi->match[n] = 0;
				}
			}

			/* some cgi only send us \x0a for EOL */
			if (c == '\x0a') {
				wsi->hdr_state = LCHS_SINGLE_0A;
				*wsi->http.cgi->headers_pos++ = '\x0d';
			}
			*wsi->http.cgi->headers_pos++ = c;
			if (c == '\x0d')
				wsi->hdr_state = LCHS_LF1;

			if (wsi->hdr_state != LCHS_HEADER &&
			    !significant_hdr[SIGNIFICANT_HDR_TRANSFER_ENCODING]
				    [wsi->http.cgi->match[
					 SIGNIFICANT_HDR_TRANSFER_ENCODING]]) {
				lwsl_info("cgi produced chunked\n");
				wsi->http.cgi->explicitly_chunked = 1;
			}

			/* presence of Location: mandates 302 retcode */
			if (wsi->hdr_state != LCHS_HEADER &&
			    !significant_hdr[SIGNIFICANT_HDR_LOCATION][
			      wsi->http.cgi->match[SIGNIFICANT_HDR_LOCATION]]) {
				lwsl_debug("CGI: Location hdr seen\n");
				wsi->http.cgi->response_code = 302;
			}
			break;
		case LCHS_LF1:
			*wsi->http.cgi->headers_pos++ = c;
			if (c == '\x0a') {
				wsi->hdr_state = LCHS_CR2;
				break;
			}
			/* we got \r[^\n]... it's unreasonable */
			lwsl_debug("%s: funny CRLF 0x%02X\n", __func__,
				   (unsigned char)c);
			return -1;

		case LCHS_CR2:
			if (c == '\x0d') {
				/* drop the \x0d */
				wsi->hdr_state = LCHS_LF2;
				break;
			}
			wsi->hdr_state = LCHS_HEADER;
			for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
				wsi->http.cgi->match[n] = 0;
			wsi->http.cgi->lp = 0;
			goto hdr;

		case LCHS_LF2:
		case LCHS_SINGLE_0A:
			m = wsi->hdr_state;
			if (c == '\x0a') {
				lwsl_debug("Content-Length: %lld\n",
					(unsigned long long)
					wsi->http.cgi->content_length);
				wsi->hdr_state = LHCS_RESPONSE;
				/*
				 * drop the \0xa ... finalize
				 * will add it if needed (HTTP/1)
				 */
				break;
			}
			if (m == LCHS_LF2)
				/* we got \r\n\r[^\n]... unreasonable */
				return -1;
			/* we got \x0anext header, it's reasonable */
			*wsi->http.cgi->headers_pos++ = c;
			wsi->hdr_state = LCHS_HEADER;
			for (n = 0; n < SIGNIFICANT_HDR_COUNT; n++)
				wsi->http.cgi->match[n] = 0;
			wsi->http.cgi->lp = 0;
			break;
		case LHCS_PAYLOAD:
			break;
		}

agin:
		/* ran out of input, ended the hdrs, or filled up the hdrs buf */
		if (!n || wsi->hdr_state == LHCS_PAYLOAD)
			return 0;
	}

	/* payload processing */

	m = !wsi->http.cgi->implied_chunked && !wsi->http2_substream &&
	    !wsi->http.cgi->explicitly_chunked &&
	    !wsi->http.cgi->content_length;
	n = lws_get_socket_fd(wsi->http.cgi->stdwsi[LWS_STDOUT]);
	if (n < 0)
		return -1;
	if (m) {
		uint8_t term[LWS_PRE + 6];

		lwsl_info("%s: zero chunk\n", __func__);

		memcpy(term + LWS_PRE, (uint8_t *)"0\x0d\x0a\x0d\x0a", 5);

		if (lws_write(wsi, term + LWS_PRE, 5,
			      LWS_WRITE_HTTP_FINAL) != 5)
			return -1;

		wsi->http.cgi->cgi_transaction_over = 1;

		return 0;
	}

	n = read(n, start, sizeof(buf) - LWS_PRE);

	if (n < 0 && errno != EAGAIN) {
		lwsl_debug("%s: stdout read says %d\n", __func__, n);
		return -1;
	}
	if (n > 0) {
/*
		if (!wsi->http2_substream && m) {
			char chdr[LWS_HTTP_CHUNK_HDR_SIZE];
			m = lws_snprintf(chdr, LWS_HTTP_CHUNK_HDR_SIZE - 3,
					 "%X\x0d\x0a", n);
			memmove(start + m, start, n);
			memcpy(start, chdr, m);
			memcpy(start + m + n, "\x0d\x0a", 2);
			n += m + 2;
		}
		*/

#if defined(LWS_WITH_HTTP2)
		if (wsi->http2_substream) {
			struct lws *nwsi = lws_get_network_wsi(wsi);

			__lws_set_timeout(wsi,
				PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);

			if (!nwsi->immortal_substream_count)
				__lws_set_timeout(nwsi,
					PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE, 31);
		}
#endif

		cmd = LWS_WRITE_HTTP;
		if (wsi->http.cgi->content_length_seen + n ==
						wsi->http.cgi->content_length)
			cmd = LWS_WRITE_HTTP_FINAL;

		m = lws_write(wsi, (unsigned char *)start, n, cmd);
		//lwsl_notice("write %d\n", m);
		if (m < 0) {
			lwsl_debug("%s: stdout write says %d\n", __func__, m);
			return -1;
		}
		wsi->http.cgi->content_length_seen += n;
	} else {
		if (wsi->cgi_stdout_zero_length) {
			lwsl_debug("%s: stdout is POLLHUP'd\n", __func__);
			if (wsi->http2_substream)
				m = lws_write(wsi, (unsigned char *)start, 0,
					      LWS_WRITE_HTTP_FINAL);
			else
				return -1;
			return 1;
		}
		wsi->cgi_stdout_zero_length = 1;
	}
	return 0;
}
Ejemplo n.º 17
0
LWS_VISIBLE LWS_EXTERN int
lws_cgi(struct lws *wsi, const char * const *exec_array,
	int script_uri_path_len, int timeout_secs,
	const struct lws_protocol_vhost_options *mp_cgienv)
{
	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
	char *env_array[30], cgi_path[500], e[1024], *p = e,
	     *end = p + sizeof(e) - 1, tok[256], *t, *sum, *sumend;
	struct lws_cgi *cgi;
	int n, m = 0, i, uritok = -1, c;

	/*
	 * give the master wsi a cgi struct
	 */

	wsi->http.cgi = lws_zalloc(sizeof(*wsi->http.cgi), "new cgi");
	if (!wsi->http.cgi) {
		lwsl_err("%s: OOM\n", __func__);
		return -1;
	}

	wsi->http.cgi->response_code = HTTP_STATUS_OK;

	cgi = wsi->http.cgi;
	cgi->wsi = wsi; /* set cgi's owning wsi */
	sum = cgi->summary;
	sumend = sum + strlen(cgi->summary) - 1;

	for (n = 0; n < 3; n++) {
		cgi->pipe_fds[n][0] = -1;
		cgi->pipe_fds[n][1] = -1;
	}

	/* create pipes for [stdin|stdout] and [stderr] */

	for (n = 0; n < 3; n++)
		if (pipe(cgi->pipe_fds[n]) == -1)
			goto bail1;

	/* create cgi wsis for each stdin/out/err fd */

	for (n = 0; n < 3; n++) {
		cgi->stdwsi[n] = lws_create_basic_wsi(wsi->context, wsi->tsi);
		if (!cgi->stdwsi[n]) {
			lwsl_err("%s: unable to create cgi stdwsi\n", __func__);
			goto bail2;
		}
		cgi->stdwsi[n]->cgi_channel = n;
		lws_vhost_bind_wsi(wsi->vhost, cgi->stdwsi[n]);

		lwsl_debug("%s: cgi stdwsi %p: pipe idx %d -> fd %d / %d\n", __func__,
			   cgi->stdwsi[n], n, cgi->pipe_fds[n][!!(n == 0)],
			   cgi->pipe_fds[n][!(n == 0)]);

		/* read side is 0, stdin we want the write side, others read */
		cgi->stdwsi[n]->desc.sockfd = cgi->pipe_fds[n][!!(n == 0)];
		if (fcntl(cgi->pipe_fds[n][!!(n == 0)], F_SETFL,
		    O_NONBLOCK) < 0) {
			lwsl_err("%s: setting NONBLOCK failed\n", __func__);
			goto bail2;
		}
	}

	for (n = 0; n < 3; n++) {
		if (wsi->context->event_loop_ops->accept)
			if (wsi->context->event_loop_ops->accept(cgi->stdwsi[n]))
				goto bail3;

		if (__insert_wsi_socket_into_fds(wsi->context, cgi->stdwsi[n]))
			goto bail3;
		cgi->stdwsi[n]->parent = wsi;
		cgi->stdwsi[n]->sibling_list = wsi->child_list;
		wsi->child_list = cgi->stdwsi[n];
	}

	lws_change_pollfd(cgi->stdwsi[LWS_STDIN], LWS_POLLIN, LWS_POLLOUT);
	lws_change_pollfd(cgi->stdwsi[LWS_STDOUT], LWS_POLLOUT, LWS_POLLIN);
	lws_change_pollfd(cgi->stdwsi[LWS_STDERR], LWS_POLLOUT, LWS_POLLIN);

	lwsl_debug("%s: fds in %d, out %d, err %d\n", __func__,
		   cgi->stdwsi[LWS_STDIN]->desc.sockfd,
		   cgi->stdwsi[LWS_STDOUT]->desc.sockfd,
		   cgi->stdwsi[LWS_STDERR]->desc.sockfd);

	if (timeout_secs)
		lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, timeout_secs);

	/* the cgi stdout is always sending us http1.x header data first */
	wsi->hdr_state = LCHS_HEADER;

	/* add us to the pt list of active cgis */
	lwsl_debug("%s: adding cgi %p to list\n", __func__, wsi->http.cgi);
	cgi->cgi_list = pt->http.cgi_list;
	pt->http.cgi_list = cgi;

	sum += lws_snprintf(sum, sumend - sum, "%s ", exec_array[0]);

	if (0) {
		char *pct = lws_hdr_simple_ptr(wsi,
				WSI_TOKEN_HTTP_CONTENT_ENCODING);

		if (pct && !strcmp(pct, "gzip"))
			wsi->http.cgi->gzip_inflate = 1;
	}

	/* prepare his CGI env */

	n = 0;

	if (lws_is_ssl(wsi))
		env_array[n++] = "HTTPS=ON";
	if (wsi->http.ah) {
		static const unsigned char meths[] = {
			WSI_TOKEN_GET_URI,
			WSI_TOKEN_POST_URI,
			WSI_TOKEN_OPTIONS_URI,
			WSI_TOKEN_PUT_URI,
			WSI_TOKEN_PATCH_URI,
			WSI_TOKEN_DELETE_URI,
			WSI_TOKEN_CONNECT,
			WSI_TOKEN_HEAD_URI,
		#ifdef LWS_WITH_HTTP2
			WSI_TOKEN_HTTP_COLON_PATH,
		#endif
		};
		static const char * const meth_names[] = {
			"GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE",
			"CONNECT", "HEAD", ":path"
		};

		if (script_uri_path_len >= 0)
			for (m = 0; m < (int)LWS_ARRAY_SIZE(meths); m++)
				if (lws_hdr_total_length(wsi, meths[m]) >=
						script_uri_path_len) {
					uritok = meths[m];
					break;
				}

		if (script_uri_path_len < 0 && uritok < 0)
			goto bail3;
//		if (script_uri_path_len < 0)
//			uritok = 0;

		if (m >= 0) {
			env_array[n++] = p;
			if (m < 8) {
				p += lws_snprintf(p, end - p,
						  "REQUEST_METHOD=%s",
						  meth_names[m]);
				sum += lws_snprintf(sum, sumend - sum, "%s ",
						    meth_names[m]);
			} else {
				p += lws_snprintf(p, end - p,
						  "REQUEST_METHOD=%s",
			  lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD));
				sum += lws_snprintf(sum, sumend - sum, "%s ",
					lws_hdr_simple_ptr(wsi,
						  WSI_TOKEN_HTTP_COLON_METHOD));
			}
			p++;
		}

		if (uritok >= 0)
			sum += lws_snprintf(sum, sumend - sum, "%s ",
					    lws_hdr_simple_ptr(wsi, uritok));

		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "QUERY_STRING=");
		/* dump the individual URI Arg parameters */
		m = 0;
		while (script_uri_path_len >= 0) {
			i = lws_hdr_copy_fragment(wsi, tok, sizeof(tok),
					     WSI_TOKEN_HTTP_URI_ARGS, m);
			if (i < 0)
				break;
			t = tok;
			while (*t && *t != '=' && p < end - 4)
				*p++ = *t++;
			if (*t == '=')
				*p++ = *t++;
			i = urlencode(t, i- (t - tok), p, end - p);
			if (i > 0) {
				p += i;
				*p++ = '&';
			}
			m++;
		}
		if (m)
			p--;
		*p++ = '\0';

		if (uritok >= 0) {
			strcpy(cgi_path, "REQUEST_URI=");
			c = lws_hdr_copy(wsi, cgi_path + 12,
					 sizeof(cgi_path) - 12, uritok);
			if (c < 0)
				goto bail3;

			cgi_path[sizeof(cgi_path) - 1] = '\0';
			env_array[n++] = cgi_path;
		}

		sum += lws_snprintf(sum, sumend - sum, "%s", env_array[n - 1]);

		if (script_uri_path_len >= 0) {
			env_array[n++] = p;
			p += lws_snprintf(p, end - p, "PATH_INFO=%s",
				      cgi_path + 12 + script_uri_path_len);
			p++;
		}
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_REFERER=%s",
			      lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_REFERER));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_HOST=%s",
			      lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_COOKIE=");
		m = lws_hdr_copy(wsi, p, end - p, WSI_TOKEN_HTTP_COOKIE);
		if (m > 0)
			p += lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COOKIE);
		*p++ = '\0';
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_USER_AGENT=%s",
			    lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_USER_AGENT));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_CONTENT_ENCODING=%s",
		      lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_ENCODING));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_ACCEPT=%s",
			      lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "HTTP_ACCEPT_ENCODING=%s",
		      lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING));
		p++;
	}
	if (script_uri_path_len >= 0 &&
	    uritok == WSI_TOKEN_POST_URI) {
		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
			env_array[n++] = p;
			p += lws_snprintf(p, end - p, "CONTENT_TYPE=%s",
			  lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE));
			p++;
		}
		if (!wsi->http.cgi->gzip_inflate &&
		    lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
			env_array[n++] = p;
			p += lws_snprintf(p, end - p, "CONTENT_LENGTH=%s",
					  lws_hdr_simple_ptr(wsi,
					  WSI_TOKEN_HTTP_CONTENT_LENGTH));
			p++;
		}

		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH))
			wsi->http.cgi->post_in_expected =
				atoll(lws_hdr_simple_ptr(wsi,
						WSI_TOKEN_HTTP_CONTENT_LENGTH));
	}


	env_array[n++] = "PATH=/bin:/usr/bin:/usr/local/bin:/var/www/cgi-bin";

	env_array[n++] = p;
	p += lws_snprintf(p, end - p, "SCRIPT_PATH=%s", exec_array[0]) + 1;

	while (mp_cgienv) {
		env_array[n++] = p;
		p += lws_snprintf(p, end - p, "%s=%s", mp_cgienv->name,
			      mp_cgienv->value);
		if (!strcmp(mp_cgienv->name, "GIT_PROJECT_ROOT")) {
			wsi->http.cgi->implied_chunked = 1;
			wsi->http.cgi->explicitly_chunked = 1;
		}
		lwsl_info("   Applying mount-specific cgi env '%s'\n",
			   env_array[n - 1]);
		p++;
		mp_cgienv = mp_cgienv->next;
	}

	env_array[n++] = "SERVER_SOFTWARE=libwebsockets";
	env_array[n] = NULL;

#if 0
	for (m = 0; m < n; m++)
		lwsl_notice("    %s\n", env_array[m]);
#endif

	/*
	 * Actually having made the env, as a cgi we don't need the ah
	 * any more
	 */
	if (script_uri_path_len >= 0)
		lws_header_table_detach(wsi, 0);

	/* we are ready with the redirection pipes... run the thing */
#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
	cgi->pid = fork();
#else
	cgi->pid = vfork();
#endif
	if (cgi->pid < 0) {
		lwsl_err("fork failed, errno %d", errno);
		goto bail3;
	}

#if defined(__linux__)
	prctl(PR_SET_PDEATHSIG, SIGTERM);
#endif
	if (script_uri_path_len >= 0)
		/* stops non-daemonized main processess getting SIGINT
		 * from TTY */
		setpgrp();

	if (cgi->pid) {
		/* we are the parent process */
		wsi->context->count_cgi_spawned++;
		lwsl_info("%s: cgi %p spawned PID %d\n", __func__,
			   cgi, cgi->pid);

		/*
		 *  close:                stdin:r, stdout:w, stderr:w
		 * hide from other forks: stdin:w, stdout:r, stderr:r
		 */
		for (n = 0; n < 3; n++) {
			lws_plat_apply_FD_CLOEXEC(cgi->pipe_fds[n][!!(n == 0)]);
			close(cgi->pipe_fds[n][!(n == 0)]);
		}

		/* inform cgi owner of the child PID */
		n = user_callback_handle_rxflow(wsi->protocol->callback, wsi,
					    LWS_CALLBACK_CGI_PROCESS_ATTACH,
					    wsi->user_space, NULL, cgi->pid);
		(void)n;

		return 0;
	}

	/* somewhere we can at least read things and enter it */
	if (chdir("/tmp"))
		lwsl_notice("%s: Failed to chdir\n", __func__);

	/* We are the forked process, redirect and kill inherited things.
	 *
	 * Because of vfork(), we cannot do anything that changes pages in
	 * the parent environment.  Stuff that changes kernel state for the
	 * process is OK.  Stuff that happens after the execvpe() is OK.
	 */

	for (n = 0; n < 3; n++) {
		if (dup2(cgi->pipe_fds[n][!(n == 0)], n) < 0) {
			lwsl_err("%s: stdin dup2 failed\n", __func__);
			goto bail3;
		}
		close(cgi->pipe_fds[n][0]);
		close(cgi->pipe_fds[n][1]);
	}

#if !defined(LWS_HAVE_VFORK) || !defined(LWS_HAVE_EXECVPE)
	for (m = 0; m < n; m++) {
		p = strchr(env_array[m], '=');
		*p++ = '\0';
		setenv(env_array[m], p, 1);
	}
	execvp(exec_array[0], (char * const *)&exec_array[0]);
#else
	execvpe(exec_array[0], (char * const *)&exec_array[0], &env_array[0]);
#endif

	exit(1);

bail3:
	/* drop us from the pt cgi list */
	pt->http.cgi_list = cgi->cgi_list;

	while (--n >= 0)
		__remove_wsi_socket_from_fds(wsi->http.cgi->stdwsi[n]);
bail2:
	for (n = 0; n < 3; n++)
		if (wsi->http.cgi->stdwsi[n])
			__lws_free_wsi(cgi->stdwsi[n]);

bail1:
	for (n = 0; n < 3; n++) {
		if (cgi->pipe_fds[n][0] >= 0)
			close(cgi->pipe_fds[n][0]);
		if (cgi->pipe_fds[n][1] >= 0)
			close(cgi->pipe_fds[n][1]);
	}

	lws_free_set_NULL(wsi->http.cgi);

	lwsl_err("%s: failed\n", __func__);

	return -1;
}
Ejemplo n.º 18
0
void
lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth)
{
#ifdef LWS_WITH_IPV6
	char ads[INET6_ADDRSTRLEN];
#else
	char ads[INET_ADDRSTRLEN];
#endif
	char da[64];
	const char *pa, *me;
	struct tm *tmp;
	time_t t = time(NULL);
	int l = 256, m;

	if (!wsi->vhost)
		return;

	/* only worry about preparing it if we store it */
	if (wsi->vhost->log_fd == (int)LWS_INVALID_FILE)
		return;

	if (wsi->access_log_pending)
		lws_access_log(wsi);

	wsi->http.access_log.header_log = lws_malloc(l, "access log");
	if (wsi->http.access_log.header_log) {

		tmp = localtime(&t);
		if (tmp)
			strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp);
		else
			strcpy(da, "01/Jan/1970:00:00:00 +0000");

		pa = lws_get_peer_simple(wsi, ads, sizeof(ads));
		if (!pa)
			pa = "(unknown)";

		if (wsi->http2_substream)
			me = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_COLON_METHOD);
		else
			me = method_names[meth];
		if (!me)
			me = "(null)";

		lws_snprintf(wsi->http.access_log.header_log, l,
			 "%s - - [%s] \"%s %s %s\"",
			 pa, da, me, uri_ptr,
			 hver[wsi->http.request_version]);

		l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT);
		if (l) {
			wsi->http.access_log.user_agent = lws_malloc(l + 2, "access log");
			if (!wsi->http.access_log.user_agent) {
				lwsl_err("OOM getting user agent\n");
				lws_free_set_NULL(wsi->http.access_log.header_log);
				return;
			}

			lws_hdr_copy(wsi, wsi->http.access_log.user_agent,
					l + 1, WSI_TOKEN_HTTP_USER_AGENT);

			for (m = 0; m < l; m++)
				if (wsi->http.access_log.user_agent[m] == '\"')
					wsi->http.access_log.user_agent[m] = '\'';
		}
		l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_REFERER);
		if (l) {
			wsi->http.access_log.referrer = lws_malloc(l + 2, "referrer");
			if (!wsi->http.access_log.referrer) {
				lwsl_err("OOM getting user agent\n");
				lws_free_set_NULL(wsi->http.access_log.user_agent);
				lws_free_set_NULL(wsi->http.access_log.header_log);
				return;
			}
			lws_hdr_copy(wsi, wsi->http.access_log.referrer,
					l + 1, WSI_TOKEN_HTTP_REFERER);

			for (m = 0; m < l; m++)
				if (wsi->http.access_log.referrer[m] == '\"')
					wsi->http.access_log.referrer[m] = '\'';
		}
		wsi->access_log_pending = 1;
	}
}
Ejemplo n.º 19
0
int
lws_client_interpret_server_handshake(struct lws *wsi)
{
	int n, len, okay = 0, port = 0, ssl = 0;
	int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR;
	struct lws_context *context = wsi->context;
	const char *pc, *prot, *ads = NULL, *path, *cce = NULL;
	struct allocated_headers *ah = NULL;
	char *p, *q;
	char new_path[300];
#ifndef LWS_NO_EXTENSIONS
	struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
	char *sb = (char *)&pt->serv_buf[0];
	const struct lws_ext_options *opts;
	const struct lws_extension *ext;
	char ext_name[128];
	const char *c, *a;
	char ignore;
	int more = 1;
	void *v;
#endif
	if (wsi->u.hdr.stash)
		lws_free_set_NULL(wsi->u.hdr.stash);

	ah = wsi->u.hdr.ah;
	if (!wsi->do_ws) {
		/* we are being an http client...
		 */
		lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED);
		wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED;
		wsi->u.http.ah = ah;
		ah->http_response = 0;
	}

	/*
	 * well, what the server sent looked reasonable for syntax.
	 * Now let's confirm it sent all the necessary headers
	 *
	 * http (non-ws) client will expect something like this
	 *
	 * HTTP/1.0.200
	 * server:.libwebsockets
	 * content-type:.text/html
	 * content-length:.17703
	 * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000
	 *
	 *
	 *
	 */

	wsi->u.http.connection_type = HTTP_CONNECTION_KEEP_ALIVE;
	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP);
	if (wsi->do_ws && !p) {
		lwsl_info("no URI\n");
		cce = "HS: URI missing";
		goto bail3;
	}
	if (!p) {
		p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0);
		wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;
	}
	if (!p) {
		cce = "HS: URI missing";
		lwsl_info("no URI\n");
		goto bail3;
	}
	n = atoi(p);
	if (ah)
		ah->http_response = n;

	if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) {
		p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION);
		if (!p) {
			cce = "HS: Redirect code but no Location";
			goto bail3;
		}

		/* Relative reference absolute path */
		if (p[0] == '/')
		{
#ifdef LWS_OPENSSL_SUPPORT
			ssl = wsi->use_ssl;
#endif
			ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
			port = wsi->c_port;
			path = p + 1; /* +1 as lws_client_reset expects leading / to be omitted */
		}
		/* Absolute (Full) URI */
		else if (strchr(p, ':'))
		{
			if (lws_parse_uri(p, &prot, &ads, &port, &path)) {
				cce = "HS: URI did not parse";
				goto bail3;
			}

			if (!strcmp(prot, "wss") || !strcmp(prot, "https"))
				ssl = 1;
		}
		/* Relative reference relative path */
		else
		{
			/* This doesn't try to calculate an absolute path, that will be left to the server */
#ifdef LWS_OPENSSL_SUPPORT
			ssl = wsi->use_ssl;
#endif
			ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
			port = wsi->c_port;
			path = new_path + 1; /* +1 as lws_client_reset expects leading / to be omitted */
			strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path));
			new_path[sizeof(new_path) - 1] = '\0';
			q = strrchr(new_path, '/');
			if (q)
			{
				strncpy(q + 1, p, sizeof(new_path) - (q - new_path) - 1);
				new_path[sizeof(new_path) - 1] = '\0';
			}
			else
			{
				path = p;
			}
		}

#ifdef LWS_OPENSSL_SUPPORT
		if (wsi->use_ssl && !ssl) {
			cce = "HS: Redirect attempted SSL downgrade";
			goto bail3;
		}
#endif

		if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) {
			/* there are two ways to fail out with NULL return...
			 * simple, early problem where the wsi is intact, or
			 * we went through with the reconnect attempt and the
			 * wsi is already closed.  In the latter case, the wsi
			 * has beet set to NULL additionally.
			 */
			lwsl_err("Redirect failed\n");
			cce = "HS: Redirect failed";
			if (wsi)
				goto bail3;

			return 1;
		}
		return 0;
	}

	if (!wsi->do_ws) {
		if (n != 200 && n != 201 && n != 304 && n != 401) {
			lwsl_notice("Connection failed with code %d\n", n);
			cce = "HS: Server unrecognized response code";
			goto bail2;
		}

#ifdef LWS_WITH_HTTP_PROXY
		wsi->perform_rewrite = 0;
		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) {
			if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE),
				     "text/html", 9))
				wsi->perform_rewrite = 1;
		}
#endif

		/* allocate the per-connection user memory (if any) */
		if (lws_ensure_user_space(wsi)) {
			lwsl_err("Problem allocating wsi user mem\n");
			cce = "HS: OOM";
			goto bail2;
		}

		/* he may choose to send us stuff in chunked transfer-coding */
		wsi->chunked = 0;
		wsi->chunk_remaining = 0; /* ie, next thing is chunk size */
		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) {
			wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi,
					       WSI_TOKEN_HTTP_TRANSFER_ENCODING),
					"chunked");
			/* first thing is hex, after payload there is crlf */
			wsi->chunk_parser = ELCP_HEX;
		}

		if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
			wsi->u.http.content_length =
					atoll(lws_hdr_simple_ptr(wsi,
						WSI_TOKEN_HTTP_CONTENT_LENGTH));
			lwsl_notice("%s: incoming content length %llu\n", __func__,
					(unsigned long long)wsi->u.http.content_length);
			wsi->u.http.content_remain = wsi->u.http.content_length;
		} else /* can't do 1.1 without a content length or chunked */
			if (!wsi->chunked)
				wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE;

		/*
		 * we seem to be good to go, give client last chance to check
		 * headers and OK it
		 */
		if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
					    wsi->user_space, NULL, 0)) {

			cce = "HS: disallowed by client filter";
			goto bail2;
		}

		/* clear his proxy connection timeout */
		lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

		wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;

		/* call him back to inform him he is up */
		if (wsi->protocol->callback(wsi,
					    LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP,
					    wsi->user_space, NULL, 0)) {
			cce = "HS: disallowed at ESTABLISHED";
			goto bail3;
		}

		/* free up his parsing allocations */
		lws_header_table_detach(wsi, 0);

		lwsl_notice("%s: client connection up\n", __func__);

		return 0;
	}

	if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) {
		lwsl_info("no ACCEPT\n");
		cce = "HS: ACCEPT missing";
		goto bail3;
	}

	if (p && strncmp(p, "101", 3)) {
		lwsl_warn(
		       "lws_client_handshake: got bad HTTP response '%s'\n", p);
		cce = "HS: ws upgrade response not 101";
		goto bail3;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE);
	if (!p) {
		lwsl_info("no UPGRADE\n");
		cce = "HS: UPGRADE missing";
		goto bail3;
	}
	strtolower(p);
	if (strcmp(p, "websocket")) {
		lwsl_warn(
		      "lws_client_handshake: got bad Upgrade header '%s'\n", p);
		cce = "HS: Upgrade to something other than websocket";
		goto bail3;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION);
	if (!p) {
		lwsl_info("no Connection hdr\n");
		cce = "HS: CONNECTION missing";
		goto bail3;
	}
	strtolower(p);
	if (strcmp(p, "upgrade")) {
		lwsl_warn("lws_client_int_s_hs: bad header %s\n", p);
		cce = "HS: UPGRADE malformed";
		goto bail3;
	}

	pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS);
	if (!pc) {
		lwsl_parser("lws_client_int_s_hs: no protocol list\n");
	} else
		lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc);

	/*
	 * confirm the protocol the server wants to talk was in the list
	 * of protocols we offered
	 */

	len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL);
	if (!len) {
		lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n");
		/*
		 * no protocol name to work from,
		 * default to first protocol
		 */
		n = 0;
		wsi->protocol = &wsi->vhost->protocols[0];
		goto check_extensions;
	}

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL);
	len = strlen(p);

	while (pc && *pc && !okay) {
		if (!strncmp(pc, p, len) &&
		    (pc[len] == ',' || pc[len] == '\0')) {
			okay = 1;
			continue;
		}
		while (*pc && *pc++ != ',')
			;
		while (*pc && *pc == ' ')
			pc++;
	}

	if (!okay) {
		lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p);
		cce = "HS: PROTOCOL malformed";
		goto bail2;
	}

	/*
	 * identify the selected protocol struct and set it
	 */
	n = 0;
	wsi->protocol = NULL;
	while (wsi->vhost->protocols[n].callback && !wsi->protocol) {
		if (strcmp(p, wsi->vhost->protocols[n].name) == 0) {
			wsi->protocol = &wsi->vhost->protocols[n];
			break;
		}
		n++;
	}

	if (wsi->protocol == NULL) {
		lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p);
		cce = "HS: Cannot match protocol";
		goto bail2;
	}

check_extensions:
	/*
	 * stitch protocol choice into the vh protocol linked list
	 * We always insert ourselves at the start of the list
	 *
	 * X <-> B
	 * X <-> pAn <-> pB
	 */
	//lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n",
	//		__func__,
	//		wsi->vhost->same_vh_protocol_list[n],
	//		wsi->same_vh_protocol_prev);
	wsi->same_vh_protocol_prev = /* guy who points to us */
		&wsi->vhost->same_vh_protocol_list[n];
	wsi->same_vh_protocol_next = /* old first guy is our next */
			wsi->vhost->same_vh_protocol_list[n];
	/* we become the new first guy */
	wsi->vhost->same_vh_protocol_list[n] = wsi;

	if (wsi->same_vh_protocol_next)
		/* old first guy points back to us now */
		wsi->same_vh_protocol_next->same_vh_protocol_prev =
				&wsi->same_vh_protocol_next;

#ifndef LWS_NO_EXTENSIONS
	/* instantiate the accepted extensions */

	if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) {
		lwsl_ext("no client extensions allowed by server\n");
		goto check_accept;
	}

	/*
	 * break down the list of server accepted extensions
	 * and go through matching them or identifying bogons
	 */

	if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) {
		lwsl_warn("ext list from server failed to copy\n");
		cce = "HS: EXT: list too big";
		goto bail2;
	}

	c = sb;
	n = 0;
	ignore = 0;
	a = NULL;
	while (more) {

		if (*c && (*c != ',' && *c != '\t')) {
			if (*c == ';') {
				ignore = 1;
				if (!a)
					a = c + 1;
			}
			if (ignore || *c == ' ') {
				c++;
				continue;
			}

			ext_name[n] = *c++;
			if (n < sizeof(ext_name) - 1)
				n++;
			continue;
		}
		ext_name[n] = '\0';
		ignore = 0;
		if (!*c)
			more = 0;
		else {
			c++;
			if (!n)
				continue;
		}

		/* check we actually support it */

		lwsl_notice("checking client ext %s\n", ext_name);

		n = 0;
		ext = wsi->vhost->extensions;
		while (ext && ext->callback) {
			if (strcmp(ext_name, ext->name)) {
				ext++;
				continue;
			}

			n = 1;
			lwsl_notice("instantiating client ext %s\n", ext_name);

			/* instantiate the extension on this conn */

			wsi->active_extensions[wsi->count_act_ext] = ext;

			/* allow him to construct his ext instance */

			if (ext->callback(lws_get_context(wsi), ext, wsi,
				      LWS_EXT_CB_CLIENT_CONSTRUCT,
				      (void *)&wsi->act_ext_user[wsi->count_act_ext],
				      (void *)&opts, 0)) {
				lwsl_info(" ext %s failed construction\n", ext_name);
				ext++;
				continue;
			}

			/*
			 * allow the user code to override ext defaults if it
			 * wants to
			 */
			ext_name[0] = '\0';
			if (user_callback_handle_rxflow(wsi->protocol->callback,
					wsi, LWS_CALLBACK_WS_EXT_DEFAULTS,
					(char *)ext->name, ext_name,
					sizeof(ext_name))) {
				cce = "HS: EXT: failed setting defaults";
				goto bail2;
			}

			if (ext_name[0] &&
			    lws_ext_parse_options(ext, wsi, wsi->act_ext_user[
						  wsi->count_act_ext], opts, ext_name,
						  strlen(ext_name))) {
				lwsl_err("%s: unable to parse user defaults '%s'",
					 __func__, ext_name);
				cce = "HS: EXT: failed parsing defaults";
				goto bail2;
			}

			/*
			 * give the extension the server options
			 */
			if (a && lws_ext_parse_options(ext, wsi,
					wsi->act_ext_user[wsi->count_act_ext],
					opts, a, c - a)) {
				lwsl_err("%s: unable to parse remote def '%s'",
					 __func__, a);
				cce = "HS: EXT: failed parsing options";
				goto bail2;
			}

			if (ext->callback(lws_get_context(wsi), ext, wsi,
					LWS_EXT_CB_OPTION_CONFIRM,
				      wsi->act_ext_user[wsi->count_act_ext],
				      NULL, 0)) {
				lwsl_err("%s: ext %s rejects server options %s",
					 __func__, ext->name, a);
				cce = "HS: EXT: Rejects server options";
				goto bail2;
			}

			wsi->count_act_ext++;

			ext++;
		}

		if (n == 0) {
			lwsl_warn("Unknown ext '%s'!\n", ext_name);
			cce = "HS: EXT: unknown ext";
			goto bail2;
		}

		a = NULL;
		n = 0;
	}

check_accept:
#endif

	/*
	 * Confirm his accept token is the one we precomputed
	 */

	p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT);
	if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) {
		lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p,
				  wsi->u.hdr.ah->initial_handshake_hash_base64);
		cce = "HS: Accept hash wrong";
		goto bail2;
	}

	/* allocate the per-connection user memory (if any) */
	if (lws_ensure_user_space(wsi)) {
		lwsl_err("Problem allocating wsi user mem\n");
		cce = "HS: OOM";
		goto bail2;
	}

	/*
	 * we seem to be good to go, give client last chance to check
	 * headers and OK it
	 */
	if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH,
				    wsi->user_space, NULL, 0)) {
		cce = "HS: Rejected by filter cb";
		goto bail2;
	}

	/* clear his proxy connection timeout */
	lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);

	/* free up his parsing allocations */
	lws_header_table_detach(wsi, 0);

	lws_union_transition(wsi, LWSCM_WS_CLIENT);
	wsi->state = LWSS_ESTABLISHED;
	lws_restart_ws_ping_pong_timer(wsi);

	wsi->rxflow_change_to = LWS_RXFLOW_ALLOW;

	/*
	 * create the frame buffer for this connection according to the
	 * size mentioned in the protocol definition.  If 0 there, then
	 * use a big default for compatibility
	 */
	n = wsi->protocol->rx_buffer_size;
	if (!n)
		n = context->pt_serv_buf_size;
	n += LWS_PRE;
	wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */);
	if (!wsi->u.ws.rx_ubuf) {
		lwsl_err("Out of Mem allocating rx buffer %d\n", n);
		cce = "HS: OOM";
		goto bail2;
	}
       wsi->u.ws.rx_ubuf_alloc = n;
	lwsl_info("Allocating client RX buffer %d\n", n);

#if !defined(LWS_WITH_ESP32)
	if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n,
		       sizeof n)) {
		lwsl_warn("Failed to set SNDBUF to %d", n);
		cce = "HS: SO_SNDBUF failed";
		goto bail3;
	}
#endif

	lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name);

	/* call him back to inform him he is up */

	if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED,
				    wsi->user_space, NULL, 0)) {
		cce = "HS: Rejected at CLIENT_ESTABLISHED";
		goto bail3;
	}
#ifndef LWS_NO_EXTENSIONS
	/*
	 * inform all extensions, not just active ones since they
	 * already know
	 */
	ext = wsi->vhost->extensions;

	while (ext && ext->callback) {
		v = NULL;
		for (n = 0; n < wsi->count_act_ext; n++)
			if (wsi->active_extensions[n] == ext)
				v = wsi->act_ext_user[n];

		ext->callback(context, ext, wsi,
			  LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0);
		ext++;
	}
#endif

	return 0;

bail3:
	close_reason = LWS_CLOSE_STATUS_NOSTATUS;

bail2:
	if (wsi->protocol)
		wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR,
				wsi->user_space, (void *)cce,
				(unsigned int)strlen(cce));
	wsi->already_did_cce = 1;

	lwsl_info("closing connection due to bail2 connection error\n");

	/* closing will free up his parsing allocations */
	lws_close_free_wsi(wsi, close_reason);

	return 1;
}
Ejemplo n.º 20
0
struct lws *
lws_client_connect_via_info2(struct lws *wsi)
{
	struct client_info_stash *stash = wsi->u.hdr.stash;

	if (!stash)
		return wsi;

	/*
	 * we're not necessarily in a position to action these right away,
	 * stash them... we only need during connect phase so u.hdr is fine
	 */
	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
				  stash->address))
		goto bail1;

	/* these only need u.hdr lifetime as well */

	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path))
		goto bail1;

	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host))
		goto bail1;

	if (stash->origin[0])
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
					  stash->origin))
			goto bail1;
	/*
	 * this is a list of protocols we tell the server we're okay with
	 * stash it for later when we compare server response with it
	 */
	if (stash->protocol[0])
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
					  stash->protocol))
			goto bail1;
	if (stash->method[0])
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
					  stash->method))
			goto bail1;
	if (stash->iface[0])
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
					  stash->iface))
			goto bail1;

#if defined(LWS_WITH_SOCKS5)
	if (!wsi->vhost->socks_proxy_port)
		lws_free_set_NULL(wsi->u.hdr.stash);
#endif

	/*
	 * Check with each extension if it is able to route and proxy this
	 * connection for us.  For example, an extension like x-google-mux
	 * can handle this and then we don't need an actual socket for this
	 * connection.
	 */

	if (lws_ext_cb_all_exts(wsi->context, wsi,
				LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION,
				(void *)stash->address,
				wsi->c_port) > 0) {
		lwsl_client("lws_client_connect: ext handling conn\n");

		lws_set_timeout(wsi,
			PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE,
			        AWAITING_TIMEOUT);

		wsi->mode = LWSCM_WSCL_WAITING_EXTENSION_CONNECT;
		return wsi;
	}
	lwsl_client("lws_client_connect: direct conn\n");
	wsi->context->count_wsi_allocated++;

	return lws_client_connect_2(wsi);

bail1:
#if defined(LWS_WITH_SOCKS5)
	if (!wsi->vhost->socks_proxy_port)
		lws_free_set_NULL(wsi->u.hdr.stash);
#endif

	return NULL;
}
Ejemplo n.º 21
0
struct lws *
lws_http_client_connect_via_info2(struct lws *wsi)
{
	struct client_info_stash *stash = wsi->stash;

	if (!stash)
		return wsi;

	/*
	 * we're not necessarily in a position to action these right away,
	 * stash them... we only need during connect phase so into a temp
	 * allocated stash
	 */
	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS,
				  stash->address))
		goto bail1;

	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path))
		goto bail1;

	if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host))
		goto bail1;

	if (stash->origin)
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN,
					  stash->origin))
			goto bail1;
	/*
	 * this is a list of protocols we tell the server we're okay with
	 * stash it for later when we compare server response with it
	 */
	if (stash->protocol)
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
					  stash->protocol))
			goto bail1;
	if (stash->method)
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD,
					  stash->method))
			goto bail1;
	if (stash->iface)
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE,
					  stash->iface))
			goto bail1;
	if (stash->alpn)
		if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ALPN,
					  stash->alpn))
			goto bail1;

#if defined(LWS_WITH_SOCKS5)
	if (!wsi->vhost->socks_proxy_port)
		lws_client_stash_destroy(wsi);
#endif

	wsi->context->count_wsi_allocated++;

	return lws_client_connect_2(wsi);

bail1:
#if defined(LWS_WITH_SOCKS5)
	if (!wsi->vhost->socks_proxy_port)
		lws_free_set_NULL(wsi->stash);
#endif

	return NULL;
}
Ejemplo n.º 22
0
LWS_VISIBLE int
lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason,
			void *user, void *in, size_t len)
{
	struct lws_ssl_info *si;
#ifdef LWS_WITH_CGI
	struct lws_cgi_args *args;
#endif
#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY)
	char buf[8192];
	int n;
#endif
#if defined(LWS_WITH_HTTP_PROXY)
	unsigned char **p, *end;
	struct lws *parent;
#endif

	switch (reason) {
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
	case LWS_CALLBACK_HTTP:
#ifndef LWS_NO_SERVER
		if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL))
			return -1;

		if (lws_http_transaction_completed(wsi))
#endif
			return -1;
		break;
#if !defined(LWS_NO_SERVER)
	case LWS_CALLBACK_HTTP_BODY_COMPLETION:
	case LWS_CALLBACK_HTTP_FILE_COMPLETION:
		if (lws_http_transaction_completed(wsi))
			return -1;
		break;
#endif

	case LWS_CALLBACK_HTTP_WRITEABLE:
#ifdef LWS_WITH_CGI
		if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS |
				      LWS_CB_REASON_AUX_BF__CGI)) {
			n = lws_cgi_write_split_stdout_headers(wsi);
			if (n < 0) {
				lwsl_debug("AUX_BF__CGI forcing close\n");
				return -1;
			}
			if (!n)
				lws_rx_flow_control(
					wsi->http.cgi->stdwsi[LWS_STDOUT], 1);

			if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS)
				wsi->reason_bf &=
					~LWS_CB_REASON_AUX_BF__CGI_HEADERS;
			else
				wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI;

			if (wsi->http.cgi && wsi->http.cgi->cgi_transaction_over)
				return -1;
			break;
		}

		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) {
			if (!wsi->http2_substream) {
				memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5);
				lwsl_debug("writing chunk term and exiting\n");
				n = lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 5, LWS_WRITE_HTTP);
			} else
				n = lws_write(wsi, (unsigned char *)buf +
						   LWS_PRE, 0,
						   LWS_WRITE_HTTP_FINAL);

			/* always close after sending it */
			return -1;
		}
#endif
#if defined(LWS_WITH_HTTP_PROXY)

		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_HEADERS) {

			wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_HEADERS;

			lwsl_debug("%s: %p: issuing proxy headers\n",
				    __func__, wsi);
			n = lws_write(wsi, wsi->http.pending_return_headers +
					   LWS_PRE,
				      wsi->http.pending_return_headers_len,
				      LWS_WRITE_HTTP_HEADERS);

			lws_free_set_NULL(wsi->http.pending_return_headers);

			if (n < 0) {
				lwsl_err("%s: EST_CLIENT_HTTP: write failed\n",
					 __func__);
				return -1;
			}
			lws_callback_on_writable(wsi);
			break;
		}

		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) {
			char *px = buf + LWS_PRE;
			int lenx = sizeof(buf) - LWS_PRE - 32;

			/*
			 * our sink is writeable and our source has something
			 * to read.  So read a lump of source material of
			 * suitable size to send or what's available, whichever
			 * is the smaller.
			 */
			wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY;
			if (!lws_get_child(wsi))
				break;

			/* this causes LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ */
			if (lws_http_client_read(lws_get_child(wsi), &px,
						 &lenx) < 0) {
				lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY: "
					   "client closed\n", __func__);

				stream_close(wsi);

				return -1;
			}
			break;
		}

		if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY_TRANS_END) {
			lwsl_info("%s: LWS_CB_REASON_AUX_BF__PROXY_TRANS_END\n",
				   __func__);

			wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;

			if (stream_close(wsi))
				return -1;

			if (lws_http_transaction_completed(wsi))
				return -1;
		}
#endif
		break;

#if defined(LWS_WITH_HTTP_PROXY)
	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP:
		assert(lws_get_parent(wsi));
		if (!lws_get_parent(wsi))
			break;
		lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY;
		lws_callback_on_writable(lws_get_parent(wsi));
		break;

	case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: {
		char *out = buf + LWS_PRE;

		assert(lws_get_parent(wsi));

		if (wsi->http.proxy_parent_chunked) {

			if (len > sizeof(buf) - LWS_PRE - 16) {
				lwsl_err("oversize buf %d %d\n", (int)len,
						(int)sizeof(buf) - LWS_PRE - 16);
				return -1;
			}

			/*
			 * this only needs dealing with on http/1.1 to allow
			 * pipelining
			 */
			n = lws_snprintf(out, 14, "%X\x0d\x0a", (int)len);
			out += n;
			memcpy(out, in, len);
			out += len;
			*out++ = '\x0d';
			*out++ = '\x0a';

			n = lws_write(lws_get_parent(wsi),
				      (unsigned char *)buf + LWS_PRE,
				      len + n + 2, LWS_WRITE_HTTP);
		} else
			n = lws_write(lws_get_parent(wsi), (unsigned char *)in,
				      len, LWS_WRITE_HTTP);
		if (n < 0)
			return -1;
		break; }


	/* this handles the proxy case... */
	case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: {
		unsigned char *start, *p, *end;

		/*
		 * We want to proxy these headers, but we are being called
		 * at the point the onward client was established, which is
		 * unrelated to the state or writability of our proxy
		 * connection.
		 *
		 * Therefore produce the headers using the onward client ah
		 * while we have it, and stick them on the output buflist to be
		 * written on the proxy connection as soon as convenient.
		 */

		parent = lws_get_parent(wsi);

		if (!parent)
			return 0;

		start = p = (unsigned char *)buf + LWS_PRE;
		end = p + sizeof(buf) - LWS_PRE - 256;

		if (lws_add_http_header_status(lws_get_parent(wsi),
				lws_http_client_http_response(wsi), &p, end))
			return 1;

		/*
		 * copy these headers from the client connection to the parent
		 */

		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_CONTENT_LENGTH, &p, end);
		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_CONTENT_TYPE, &p, end);
		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_ETAG, &p, end);
		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, &p, end);
		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_CONTENT_ENCODING, &p, end);
		proxy_header(parent, wsi, end, 256,
				WSI_TOKEN_HTTP_CACHE_CONTROL, &p, end);

		if (!parent->http2_substream)
			if (lws_add_http_header_by_token(parent,
				WSI_TOKEN_CONNECTION, (unsigned char *)"close",
				5, &p, end))
			return -1;

		/*
		 * We proxy using h1 only atm, and strip any chunking so it
		 * can go back out on h2 just fine.
		 *
		 * However if we are actually going out on h1, we need to add
		 * our own chunking since we still don't know the size.
		 */

		if (!parent->http2_substream &&
		    !lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
			lwsl_debug("downstream parent chunked\n");
			if (lws_add_http_header_by_token(parent,
					WSI_TOKEN_HTTP_TRANSFER_ENCODING,
					(unsigned char *)"chunked", 7, &p, end))
				return -1;

			wsi->http.proxy_parent_chunked = 1;
		}

		if (lws_finalize_http_header(parent, &p, end))
			return 1;

		parent->http.pending_return_headers_len =
					lws_ptr_diff(p, start);
		parent->http.pending_return_headers =
			lws_malloc(parent->http.pending_return_headers_len +
				    LWS_PRE, "return proxy headers");
		if (!parent->http.pending_return_headers)
			return -1;

		memcpy(parent->http.pending_return_headers + LWS_PRE, start,
		       parent->http.pending_return_headers_len);

		parent->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY_HEADERS;

		lwsl_debug("%s: LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: "
			   "prepared headers\n", __func__);
		lws_callback_on_writable(parent);

		break; }

	case LWS_CALLBACK_COMPLETED_CLIENT_HTTP:
		lwsl_info("%s: COMPLETED_CLIENT_HTTP: %p (parent %p)\n",
					__func__, wsi, lws_get_parent(wsi));
		if (!lws_get_parent(wsi))
			break;
		lws_get_parent(wsi)->reason_bf |=
				LWS_CB_REASON_AUX_BF__PROXY_TRANS_END;
		lws_callback_on_writable(lws_get_parent(wsi));
		break;

	case LWS_CALLBACK_CLOSED_CLIENT_HTTP:
		if (!lws_get_parent(wsi))
			break;
		lwsl_err("%s: LWS_CALLBACK_CLOSED_CLIENT_HTTP\n", __func__);
		lws_set_timeout(lws_get_parent(wsi), LWS_TO_KILL_ASYNC,
				PENDING_TIMEOUT_KILLED_BY_PROXY_CLIENT_CLOSE);
		break;

	case LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER:
		parent = lws_get_parent(wsi);

		if (!parent)
			break;

		p = (unsigned char **)in;
		end = (*p) + len;

		/*
		 * copy these headers from the parent request to the client
		 * connection's request
		 */

		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HOST, p, end);
		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HTTP_ETAG, p, end);
		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HTTP_IF_MODIFIED_SINCE, p, end);
		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HTTP_ACCEPT_LANGUAGE, p, end);
		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HTTP_ACCEPT_ENCODING, p, end);
		proxy_header(wsi, parent, (unsigned char *)buf, sizeof(buf),
				WSI_TOKEN_HTTP_CACHE_CONTROL, p, end);

		buf[0] = '\0';
		lws_get_peer_simple(parent, buf, sizeof(buf));
		if (lws_add_http_header_by_token(wsi, WSI_TOKEN_X_FORWARDED_FOR,
				(unsigned char *)buf, (int)strlen(buf), p, end))
			return -1;

		break;

#endif

#ifdef LWS_WITH_CGI
	/* CGI IO events (POLLIN/OUT) appear here, our default policy is:
	 *
	 *  - POST data goes on subprocess stdin
	 *  - subprocess stdout goes on http via writeable callback
	 *  - subprocess stderr goes to the logs
	 */
	case LWS_CALLBACK_CGI:
		args = (struct lws_cgi_args *)in;
		switch (args->ch) { /* which of stdin/out/err ? */
		case LWS_STDIN:
			/* TBD stdin rx flow control */
			break;
		case LWS_STDOUT:
			/* quench POLLIN on STDOUT until MASTER got writeable */
			lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0);
			wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI;
			/* when writing to MASTER would not block */
			lws_callback_on_writable(wsi);
			break;
		case LWS_STDERR:
			n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]);
			if (n < 0)
				break;
			n = read(n, buf, sizeof(buf) - 2);
			if (n > 0) {
				if (buf[n - 1] != '\n')
					buf[n++] = '\n';
				buf[n] = '\0';
				lwsl_notice("CGI-stderr: %s\n", buf);
			}
			break;
		}
		break;

	case LWS_CALLBACK_CGI_TERMINATED:
		lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n",
				wsi->http.cgi->explicitly_chunked,
				(uint64_t)wsi->http.cgi->content_length);
		if (!wsi->http.cgi->explicitly_chunked &&
		    !wsi->http.cgi->content_length) {
			/* send terminating chunk */
			lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n");
			wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END;
			lws_callback_on_writable(wsi);
			lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3);
			break;
		}
		return -1;

	case LWS_CALLBACK_CGI_STDIN_DATA:  /* POST body for stdin */
		args = (struct lws_cgi_args *)in;
		args->data[args->len] = '\0';
		if (!args->stdwsi[LWS_STDIN])
			return -1;
		n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]);
		if (n < 0)
			return -1;

#if defined(LWS_WITH_ZLIB)
		if (wsi->http.cgi->gzip_inflate) {
			/* gzip handling */

			if (!wsi->http.cgi->gzip_init) {
				lwsl_info("inflating gzip\n");

				memset(&wsi->http.cgi->inflate, 0,
				       sizeof(wsi->http.cgi->inflate));

				if (inflateInit2(&wsi->http.cgi->inflate,
						 16 + 15) != Z_OK) {
					lwsl_err("%s: iniflateInit failed\n",
						 __func__);
					return -1;
				}

				wsi->http.cgi->gzip_init = 1;
			}

			wsi->http.cgi->inflate.next_in = args->data;
			wsi->http.cgi->inflate.avail_in = args->len;

			do {

				wsi->http.cgi->inflate.next_out =
						wsi->http.cgi->inflate_buf;
				wsi->http.cgi->inflate.avail_out =
					sizeof(wsi->http.cgi->inflate_buf);

				n = inflate(&wsi->http.cgi->inflate,
					    Z_SYNC_FLUSH);

				switch (n) {
				case Z_NEED_DICT:
				case Z_STREAM_ERROR:
				case Z_DATA_ERROR:
				case Z_MEM_ERROR:
					inflateEnd(&wsi->http.cgi->inflate);
					wsi->http.cgi->gzip_init = 0;
					lwsl_err("zlib error inflate %d\n", n);
					return -1;
				}

				if (wsi->http.cgi->inflate.avail_out !=
					   sizeof(wsi->http.cgi->inflate_buf)) {
					int written;

					written = write(args->stdwsi[LWS_STDIN]->desc.filefd,
						wsi->http.cgi->inflate_buf,
						sizeof(wsi->http.cgi->inflate_buf) -
						wsi->http.cgi->inflate.avail_out);

					if (written != (int)(
						sizeof(wsi->http.cgi->inflate_buf) -
						wsi->http.cgi->inflate.avail_out)) {
						lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
							"sent %d only %d went", n, args->len);
					}

					if (n == Z_STREAM_END) {
						lwsl_err("gzip inflate end\n");
						inflateEnd(&wsi->http.cgi->inflate);
						wsi->http.cgi->gzip_init = 0;
						break;
					}

				} else
					break;

				if (wsi->http.cgi->inflate.avail_out)
					break;

			} while (1);

			return args->len;
		}
#endif /* WITH_ZLIB */

		n = write(n, args->data, args->len);
//		lwsl_hexdump_notice(args->data, args->len);
		if (n < args->len)
			lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: "
				    "sent %d only %d went", n, args->len);

		if (wsi->http.cgi->post_in_expected && args->stdwsi[LWS_STDIN] &&
		    args->stdwsi[LWS_STDIN]->desc.filefd > 0) {
			wsi->http.cgi->post_in_expected -= n;
			if (!wsi->http.cgi->post_in_expected) {
				struct lws *siwsi = args->stdwsi[LWS_STDIN];

				lwsl_debug("%s: expected POST in end: "
					   "closing stdin wsi %p, fd %d\n",
					   __func__, siwsi, siwsi->desc.sockfd);

				__remove_wsi_socket_from_fds(siwsi);
				lwsi_set_state(siwsi, LRS_DEAD_SOCKET);
				siwsi->socket_is_permanently_unusable = 1;
				lws_remove_child_from_any_parent(siwsi);
				if (wsi->context->event_loop_ops->
							close_handle_manually) {
					wsi->context->event_loop_ops->
						close_handle_manually(siwsi);
					siwsi->told_event_loop_closed = 1;
				} else {
					compatible_close(siwsi->desc.sockfd);
					__lws_free_wsi(siwsi);
				}
				wsi->http.cgi->pipe_fds[LWS_STDIN][1] = -1;

				args->stdwsi[LWS_STDIN] = NULL;
			}
		}

		return n;
#endif /* WITH_CGI */
#endif /* ROLE_ H1 / H2 */
	case LWS_CALLBACK_SSL_INFO:
		si = in;

		(void)si;
		lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n",
			    si->where, si->ret);
		break;

#if LWS_MAX_SMP > 1
	case LWS_CALLBACK_GET_THREAD_ID:
		return (int)(unsigned long long)pthread_self();
#endif

	default:
		break;
	}

	return 0;
}
Ejemplo n.º 23
0
int
lws_http_action(struct lws *wsi)
{
	struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
	enum http_connection_type connection_type;
	enum http_version request_version;
	char content_length_str[32];
	const struct lws_http_mount *hm, *hit = NULL;
	unsigned int n, count = 0;
	char http_version_str[10];
	char http_conn_str[20];
	int http_version_len;
	char *uri_ptr = NULL;
	int uri_len = 0, best = 0;
	int meth = -1;

	static const unsigned char methods[] = {
		WSI_TOKEN_GET_URI,
		WSI_TOKEN_POST_URI,
		WSI_TOKEN_OPTIONS_URI,
		WSI_TOKEN_PUT_URI,
		WSI_TOKEN_PATCH_URI,
		WSI_TOKEN_DELETE_URI,
#ifdef LWS_USE_HTTP2
		WSI_TOKEN_HTTP_COLON_PATH,
#endif
	};
#if defined(_DEBUG) || defined(LWS_WITH_ACCESS_LOG)
	static const char * const method_names[] = {
		"GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE",
#ifdef LWS_USE_HTTP2
		":path",
#endif
	};
#endif

	/* it's not websocket.... shall we accept it as http? */

	for (n = 0; n < ARRAY_SIZE(methods); n++)
		if (lws_hdr_total_length(wsi, methods[n]))
			count++;
	if (!count) {
		lwsl_warn("Missing URI in HTTP request\n");
		goto bail_nuke_ah;
	}

	if (count != 1) {
		lwsl_warn("multiple methods?\n");
		goto bail_nuke_ah;
	}

	if (lws_ensure_user_space(wsi))
		goto bail_nuke_ah;

	for (n = 0; n < ARRAY_SIZE(methods); n++)
		if (lws_hdr_total_length(wsi, methods[n])) {
			uri_ptr = lws_hdr_simple_ptr(wsi, methods[n]);
			uri_len = lws_hdr_total_length(wsi, methods[n]);
			lwsl_info("Method: %s request for '%s'\n",
				  	method_names[n], uri_ptr);
			meth = n;
			break;
		}

	(void)meth;

	/* we insist on absolute paths */

	if (uri_ptr[0] != '/') {
		lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL);

		goto bail_nuke_ah;
	}

	/* HTTP header had a content length? */

	wsi->u.http.content_length = 0;
	if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) ||
		lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) ||
		lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI))
		wsi->u.http.content_length = 100 * 1024 * 1024;

	if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) {
		lws_hdr_copy(wsi, content_length_str,
			     sizeof(content_length_str) - 1,
			     WSI_TOKEN_HTTP_CONTENT_LENGTH);
		wsi->u.http.content_length = atoi(content_length_str);
	}

	if (wsi->http2_substream) {
		wsi->u.http.request_version = HTTP_VERSION_2;
	} else {
		/* http_version? Default to 1.0, override with token: */
		request_version = HTTP_VERSION_1_0;

		/* Works for single digit HTTP versions. : */
		http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP);
		if (http_version_len > 7) {
			lws_hdr_copy(wsi, http_version_str,
					sizeof(http_version_str) - 1, WSI_TOKEN_HTTP);
			if (http_version_str[5] == '1' && http_version_str[7] == '1')
				request_version = HTTP_VERSION_1_1;
		}
		wsi->u.http.request_version = request_version;

		/* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */
		if (request_version == HTTP_VERSION_1_1)
			connection_type = HTTP_CONNECTION_KEEP_ALIVE;
		else
			connection_type = HTTP_CONNECTION_CLOSE;

		/* Override default if http "Connection:" header: */
		if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) {
			lws_hdr_copy(wsi, http_conn_str, sizeof(http_conn_str) - 1,
				     WSI_TOKEN_CONNECTION);
			http_conn_str[sizeof(http_conn_str) - 1] = '\0';
			if (!strcasecmp(http_conn_str, "keep-alive"))
				connection_type = HTTP_CONNECTION_KEEP_ALIVE;
			else
				if (!strcasecmp(http_conn_str, "close"))
					connection_type = HTTP_CONNECTION_CLOSE;
		}
		wsi->u.http.connection_type = connection_type;
	}

	n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION,
				    wsi->user_space, uri_ptr, uri_len);
	if (n) {
		lwsl_info("LWS_CALLBACK_HTTP closing\n");

		return 1;
	}
	/*
	 * if there is content supposed to be coming,
	 * put a timeout on it having arrived
	 */
	lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT,
			wsi->context->timeout_secs);
#ifdef LWS_OPENSSL_SUPPORT
	if (wsi->redirect_to_https) {
		/*
		 * we accepted http:// only so we could redirect to
		 * https://, so issue the redirect.  Create the redirection
		 * URI from the host: header and ignore the path part
		 */
		unsigned char *start = pt->serv_buf + LWS_PRE, *p = start,
			      *end = p + 512;

		if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST))
			goto bail_nuke_ah;

		n = sprintf((char *)end, "https://%s/",
			    lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST));

		n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
				      end, n, &p, end);
		if ((int)n < 0)
			goto bail_nuke_ah;

		return lws_http_transaction_completed(wsi);
	}
#endif

#ifdef LWS_WITH_ACCESS_LOG
	/*
	 * Produce Apache-compatible log string for wsi, like this:
	 *
	 * 2.31.234.19 - - [27/Mar/2016:03:22:44 +0800]
	 * "GET /aep-screen.png HTTP/1.1"
	 * 200 152987 "https://libwebsockets.org/index.html"
	 * "Mozilla/5.0 (Macint... Chrome/49.0.2623.87 Safari/537.36"
	 *
	 */
	{
		static const char * const hver[] = {
			"http/1.0", "http/1.1", "http/2"
		};
#ifdef LWS_USE_IPV6
		char ads[INET6_ADDRSTRLEN];
#else
		char ads[INET_ADDRSTRLEN];
#endif
		char da[64];
		const char *pa, *me;
		struct tm *tmp;
		time_t t = time(NULL);
		int l = 256;

		if (wsi->access_log_pending)
			lws_access_log(wsi);

		wsi->access_log.header_log = lws_malloc(l);

		tmp = localtime(&t);
		if (tmp)
			strftime(da, sizeof(da), "%d/%b/%Y:%H:%M:%S %z", tmp);
		else
			strcpy(da, "01/Jan/1970:00:00:00 +0000");

		pa = lws_get_peer_simple(wsi, ads, sizeof(ads));
		if (!pa)
			pa = "(unknown)";

		if (meth >= 0)
			me = method_names[meth];
		else
			me = "unknown";

		snprintf(wsi->access_log.header_log, l,
			 "%s - - [%s] \"%s %s %s\"",
			 pa, da, me, uri_ptr,
			 hver[wsi->u.http.request_version]);

		l = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_USER_AGENT);
		if (l) {
			wsi->access_log.user_agent = lws_malloc(l + 2);
			lws_hdr_copy(wsi, wsi->access_log.user_agent,
				     l + 1, WSI_TOKEN_HTTP_USER_AGENT);
		}
		wsi->access_log_pending = 1;
	}
#endif

	/* can we serve it from the mount list? */

	hm = wsi->vhost->mount_list;
	while (hm) {
		if (uri_len >= hm->mountpoint_len &&
		    !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) &&
		    (uri_ptr[hm->mountpoint_len] == '\0' ||
		     uri_ptr[hm->mountpoint_len] == '/' ||
		     hm->mountpoint_len == 1)
		    ) {
			if (hm->origin_protocol == LWSMPRO_CALLBACK ||
			    ((hm->origin_protocol == LWSMPRO_CGI ||
			     lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI)) &&
			    hm->mountpoint_len > best)) {
				best = hm->mountpoint_len;
				hit = hm;
			}
		}
		hm = hm->mount_next;
	}
	if (hit) {
		char *s = uri_ptr + hit->mountpoint_len;

		lwsl_debug("*** hit %d %d %s\n", hit->mountpoint_len,
			   hit->origin_protocol , hit->origin);

		/*
		 * if we have a mountpoint like https://xxx.com/yyy
		 * there is an implied / at the end for our purposes since
		 * we can only mount on a "directory".
		 *
		 * But if we just go with that, the browser cannot understand
		 * that he is actually looking down one "directory level", so
		 * even though we give him /yyy/abc.html he acts like the
		 * current directory level is /.  So relative urls like "x.png"
		 * wrongly look outside the mountpoint.
		 *
		 * Therefore if we didn't come in on a url with an explicit
		 * / at the end, we must redirect to add it so the browser
		 * understands he is one "directory level" down.
		 */
		if ((hit->mountpoint_len > 1 ||
		     (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
		      hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
		    (*s != '/' ||
		     (hit->origin_protocol == LWSMPRO_REDIR_HTTP ||
		      hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) &&
		    (hit->origin_protocol != LWSMPRO_CGI && hit->origin_protocol != LWSMPRO_CALLBACK)) {
			unsigned char *start = pt->serv_buf + LWS_PRE,
					      *p = start, *end = p + 512;
			static const char *oprot[] = {
				"http://", "https://"
			};

			lwsl_debug("Doing 301 '%s' org %s\n", s, hit->origin);

			if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST))
				goto bail_nuke_ah;

			/* > at start indicates deal with by redirect */
			if (hit->origin_protocol & 4)
				n = snprintf((char *)end, 256, "%s%s",
					    oprot[hit->origin_protocol & 1],
					    hit->origin);
			else
				n = snprintf((char *)end, 256,
				    "https://%s/%s/",
				    lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST),
				    uri_ptr);

			n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY,
					      end, n, &p, end);
			if ((int)n < 0)
				goto bail_nuke_ah;

			return lws_http_transaction_completed(wsi);
		}

		/*
		 * A particular protocol callback is mounted here?
		 *
		 * For the duration of this http transaction, bind us to the
		 * associated protocol
		 */
		if (hit->origin_protocol == LWSMPRO_CALLBACK) {

			for (n = 0; n < wsi->vhost->count_protocols; n++)
				if (!strcmp(wsi->vhost->protocols[n].name,
					   hit->origin)) {

					if (wsi->protocol != &wsi->vhost->protocols[n])
						if (!wsi->user_space_externally_allocated)
							lws_free_set_NULL(wsi->user_space);
					wsi->protocol = &wsi->vhost->protocols[n];
					if (lws_ensure_user_space(wsi)) {
						lwsl_err("Unable to allocate user space\n");

						return 1;
					}
					break;
				}

			if (n == wsi->vhost->count_protocols) {
				n = -1;
				lwsl_err("Unable to find plugin '%s'\n",
					 hit->origin);
			}

			n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
						    wsi->user_space, uri_ptr, uri_len);

			goto after;
		}

		/* deferred cleanup and reset to protocols[0] */

		if (wsi->protocol != &wsi->vhost->protocols[0])
			if (!wsi->user_space_externally_allocated)
				lws_free_set_NULL(wsi->user_space);

		wsi->protocol = &wsi->vhost->protocols[0];

#ifdef LWS_WITH_CGI
		/* did we hit something with a cgi:// origin? */
		if (hit->origin_protocol == LWSMPRO_CGI) {
			const char *cmd[] = {
				NULL, /* replace with cgi path */
				NULL
			};
			unsigned char *p, *end, buffer[256];

			lwsl_debug("%s: cgi\n", __func__);
			cmd[0] = hit->origin;

			n = 5;
			if (hit->cgi_timeout)
				n = hit->cgi_timeout;

			n = lws_cgi(wsi, cmd, hit->mountpoint_len, n,
				    hit->cgienv);
			if (n) {
				lwsl_err("%s: cgi failed\n");
				return -1;
			}
			p = buffer + LWS_PRE;
			end = p + sizeof(buffer) - LWS_PRE;

			if (lws_add_http_header_status(wsi, 200, &p, end))
				return 1;
			if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION,
					(unsigned char *)"close", 5, &p, end))
				return 1;
			n = lws_write(wsi, buffer + LWS_PRE,
				      p - (buffer + LWS_PRE),
				      LWS_WRITE_HTTP_HEADERS);

			goto deal_body;
		}
#endif

		n = strlen(s);
		if (s[0] == '\0' || (n == 1 && s[n - 1] == '/'))
			s = (char *)hit->def;
		if (!s)
			s = "index.html";

		wsi->cache_secs = hit->cache_max_age;
		wsi->cache_reuse = hit->cache_reusable;
		wsi->cache_revalidate = hit->cache_revalidate;
		wsi->cache_intermediaries = hit->cache_intermediaries;

		n = lws_http_serve(wsi, s, hit->origin);
		if (n) {
			/*
			 * 	lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL);
			 */
			n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
					    wsi->user_space, uri_ptr, uri_len);
		}
	} else {
		/* deferred cleanup and reset to protocols[0] */

		if (wsi->protocol != &wsi->vhost->protocols[0])
			if (!wsi->user_space_externally_allocated)
				lws_free_set_NULL(wsi->user_space);
		wsi->protocol = &wsi->vhost->protocols[0];

		n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP,
				    wsi->user_space, uri_ptr, uri_len);
	}
after:
	if (n) {
		lwsl_info("LWS_CALLBACK_HTTP closing\n");

		return 1;
	}

#ifdef LWS_WITH_CGI
deal_body:
#endif
	/*
	 * If we're not issuing a file, check for content_length or
	 * HTTP keep-alive. No keep-alive header allocation for
	 * ISSUING_FILE, as this uses HTTP/1.0.
	 *
	 * In any case, return 0 and let lws_read decide how to
	 * proceed based on state
	 */
	if (wsi->state != LWSS_HTTP_ISSUING_FILE)
		/* Prepare to read body if we have a content length: */
		if (wsi->u.http.content_length > 0)
			wsi->state = LWSS_HTTP_BODY;

	return 0;

bail_nuke_ah:
	/* we're closing, losing some rx is OK */
	wsi->u.hdr.ah->rxpos = wsi->u.hdr.ah->rxlen;
	lws_header_table_detach(wsi, 1);

	return 1;
}