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