1
0
Fork 0
mirror of https://github.com/warmcat/libwebsockets.git synced 2025-03-16 00:00:07 +01:00
libwebsockets/lib/roles/http/client/client-handshake.c
Andy Green 392dfe186b LWS_ERRNO: audit uses for case logging may destroy errno
On some platforms, it's possible that logging flow may reset errno.  In the case where
we try to log errno on those platforms and afterwards try to query it, we will get a
nasty surprise that the logged errno is destroyed by the time we come to test it.

In the two cases of this in the tree at the moment, sample errno into a temp and
log and test the temp.

Thanks to Sakthi Ramabadran for finding this.
2019-12-14 23:55:28 +00:00

1415 lines
36 KiB
C

/*
* libwebsockets - small server side websockets and web server implementation
*
* Copyright (C) 2010 - 2019 Andy Green <andy@warmcat.com>
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to
* deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
* sell copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
* IN THE SOFTWARE.
*/
#include "private-lib-core.h"
#if !defined(LWS_WITH_SYS_ASYNC_DNS)
static int
lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result)
{
struct addrinfo hints;
memset(&hints, 0, sizeof(hints));
*result = NULL;
hints.ai_socktype = SOCK_STREAM;
#ifdef LWS_WITH_IPV6
if (wsi->ipv6) {
#if !defined(__ANDROID__)
hints.ai_family = AF_UNSPEC;
hints.ai_flags = AI_V4MAPPED;
#endif
} else
#endif
{
hints.ai_family = PF_UNSPEC;
}
return getaddrinfo(ads, NULL, &hints, result);
}
#endif
struct lws *
lws_client_connect_4_established(struct lws *wsi, struct lws *wsi_piggyback,
ssize_t plen)
{
#if defined(LWS_CLIENT_HTTP_PROXYING)
struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi];
#endif
const char *meth = NULL;
struct lws_pollfd pfd;
const char *cce = "";
int n, m, rawish = 0;
if (wsi->stash)
meth = wsi->stash->cis[CIS_METHOD];
else
meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
if (meth && !strcmp(meth, "RAW"))
rawish = 1;
if (wsi_piggyback)
goto send_hs;
#if defined(LWS_CLIENT_HTTP_PROXYING)
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
/* we are connected to server, or proxy */
/* http proxy */
if (wsi->vhost->http.http_proxy_port) {
const char *cpa;
if (wsi->stash)
cpa = wsi->stash->cis[CIS_ADDRESS];
else
cpa = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
lwsl_info("%s: going via proxy\n", __func__);
plen = lws_snprintf((char *)pt->serv_buf, 256,
"CONNECT %s:%u HTTP/1.1\x0d\x0a"
"Host: %s:%u\x0d\x0a"
"User-agent: lws\x0d\x0a", cpa, wsi->ocport,
cpa, wsi->ocport);
if (wsi->vhost->proxy_basic_auth_token[0])
plen += lws_snprintf((char *)pt->serv_buf + plen, 256,
"Proxy-authorization: basic %s\x0d\x0a",
wsi->vhost->proxy_basic_auth_token);
plen += lws_snprintf((char *)pt->serv_buf + plen, 5, "\x0d\x0a");
/* lwsl_hexdump_notice(pt->serv_buf, plen); */
/*
* OK from now on we talk via the proxy, so connect to that
*/
if (wsi->stash)
wsi->stash->cis[CIS_ADDRESS] =
wsi->vhost->http.http_proxy_address;
else
if (lws_hdr_simple_create(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS,
wsi->vhost->http.http_proxy_address))
goto failed;
wsi->c_port = wsi->vhost->http.http_proxy_port;
n = send(wsi->desc.sockfd, (char *)pt->serv_buf, (int)plen,
MSG_NOSIGNAL);
if (n < 0) {
lwsl_debug("ERROR writing to proxy socket\n");
cce = "proxy write failed";
goto failed;
}
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE,
AWAITING_TIMEOUT);
lwsi_set_state(wsi, LRS_WAITING_PROXY_REPLY);
return wsi;
}
#endif
#endif
#if defined(LWS_WITH_SOCKS5)
/* socks proxy */
else if (wsi->vhost->socks_proxy_port) {
n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen,
MSG_NOSIGNAL);
if (n < 0) {
lwsl_debug("ERROR writing socks greeting\n");
cce = "socks write failed";
goto failed;
}
lws_set_timeout(wsi,
PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY,
AWAITING_TIMEOUT);
lwsi_set_state(wsi, LRS_WAITING_SOCKS_GREETING_REPLY);
return wsi;
}
#endif
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
send_hs:
if (wsi_piggyback &&
!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue)) {
/*
* We are pipelining on an already-established connection...
* we can skip tls establishment.
*/
lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE2);
/*
* we can't send our headers directly, because they have to
* be sent when the parent is writeable. The parent will check
* for anybody on his client transaction queue that is in
* LRS_H1C_ISSUE_HANDSHAKE2, and let them write.
*
* If we are trying to do this too early, before the master
* connection has written his own headers, then it will just
* wait in the queue until it's possible to send them.
*/
lws_callback_on_writable(wsi_piggyback);
#if defined(LWS_WITH_DETAILED_LATENCY)
wsi->detlat.earliest_write_req =
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
#endif
lwsl_info("%s: wsi %p: waiting to send hdrs (par state 0x%x)\n",
__func__, wsi, lwsi_state(wsi_piggyback));
} else {
lwsl_info("%s: wsi %p: %s %s client created own conn (raw %d) vh %s\n",
__func__, wsi, wsi->role_ops->name,
wsi->protocol->name, rawish, wsi->vhost->name);
/* we are making our own connection */
if (!rawish)
lwsi_set_state(wsi, LRS_H1C_ISSUE_HANDSHAKE);
else {
/* for a method = "RAW" connection, this makes us
* established */
/* clear his established timeout */
lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0);
m = wsi->role_ops->adoption_cb[0];
if (m) {
n = user_callback_handle_rxflow(
wsi->protocol->callback, wsi,
m, wsi->user_space, NULL, 0);
if (n < 0) {
lwsl_info("LWS_CALLBACK_RAW_PROXY_CLI_ADOPT failed\n");
goto failed;
}
}
/* service.c pollout processing wants this */
wsi->hdr_parsing_completed = 1;
lwsl_info("%s: setting ESTABLISHED\n", __func__);
lwsi_set_state(wsi, LRS_ESTABLISHED);
return wsi;
}
/*
* provoke service to issue the handshake directly.
*
* we need to do it this way because in the proxy case, this is
* the next state and executed only if and when we get a good
* proxy response inside the state machine... but notice in
* SSL case this may not have sent anything yet with 0 return,
* and won't until many retries from main loop. To stop that
* becoming endless, cover with a timeout.
*/
lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE,
AWAITING_TIMEOUT);
assert(lws_socket_is_valid(wsi->desc.sockfd));
pfd.fd = wsi->desc.sockfd;
pfd.events = LWS_POLLIN;
pfd.revents = LWS_POLLIN;
n = lws_service_fd(wsi->context, &pfd);
if (n < 0) {
cce = "first service failed";
goto failed;
}
if (n) /* returns 1 on failure after closing wsi */
return NULL;
}
#endif
return wsi;
failed:
lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
return NULL;
}
struct lws *
lws_client_connect_3_connect(struct lws *wsi, const char *ads,
const struct addrinfo *result, int n, void *opaque)
{
#if defined(LWS_WITH_UNIX_SOCK)
struct sockaddr_un sau;
#endif
#ifdef LWS_WITH_IPV6
char ipv6only = lws_check_opt(wsi->vhost->options,
LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY |
LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE);
#endif
const struct sockaddr *psa = NULL;
const char *cce = "", *iface;
ssize_t plen = 0;
lws_sockaddr46 sa46;
char ni[48];
int m;
#if defined(LWS_WITH_IPV6) && defined(__ANDROID__)
ipv6only = 0;
#endif
/*
* async dns calls back here for everybody who cares when it gets a
* result... but if we are piggybacking, we do not want to connect
* ourselves
*/
if (!lws_dll2_is_detached(&wsi->dll2_cli_txn_queue))
return wsi;
#if 0
if (!ads && !result) {
cce = "dns resolution failed";
if (!wsi->oom4)
goto oom4;
else
goto failed;
}
#endif
/*
* We can check using getsockopt if our connect actually completed
*/
if (lwsi_state(wsi) == LRS_WAITING_CONNECT &&
lws_socket_is_valid(wsi->desc.sockfd)) {
socklen_t sl = sizeof(int);
int e = 0;
/*
* this resets SO_ERROR after reading it. If there's an error
* condition the connect definitively failed.
*/
if (!getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR,
&e, &sl)) {
if (!e) {
lwsl_info("%s: getsockopt check: conn OK\n",
__func__);
goto conn_good;
}
lwsl_debug("%s: getsockopt fd %d says err %d\n", __func__,
wsi->desc.sockfd, e);
}
lwsl_debug("%s: getsockopt check: conn fail: errno %d\n",
__func__, LWS_ERRNO);
goto try_next_result_fds;
}
#if defined(LWS_WITH_UNIX_SOCK)
if (ads && *ads == '+') {
ads++;
memset(&sa46, 0, sizeof(sa46));
memset(&sau, 0, sizeof(sau));
sau.sun_family = AF_UNIX;
strncpy(sau.sun_path, ads, sizeof(sau.sun_path));
sau.sun_path[sizeof(sau.sun_path) - 1] = '\0';
lwsl_info("%s: Unix skt: %s\n", __func__, ads);
if (sau.sun_path[0] == '@')
sau.sun_path[0] = '\0';
goto ads_known;
}
#endif
#if defined(LWS_WITH_SYS_ASYNC_DNS)
if (n == LADNS_RET_FAILED) {
lwsl_notice("%s: adns failed %s\n", __func__, ads);
goto oom4;
}
#endif
if (!wsi->dns_results) {
wsi->dns_results_next = wsi->dns_results = result;
if (result)
lwsl_debug("%s: result %p result->ai_next %p\n",
__func__, result, result->ai_next);
}
#if defined(LWS_WITH_DETAILED_LATENCY)
if (lwsi_state(wsi) == LRS_WAITING_DNS &&
wsi->context->detailed_latency_cb) {
wsi->detlat.type = LDLT_NAME_RESOLUTION;
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
lws_now_usecs() -
wsi->detlat.earliest_write_req_pre_write;
wsi->detlat.latencies[LAT_DUR_USERCB] = 0;
lws_det_lat_cb(wsi->context, &wsi->detlat);
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
}
#endif
#if defined(LWS_CLIENT_HTTP_PROXYING) && \
(defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2))
/* Decide what it is we need to connect to:
*
* Priority 1: connect to http proxy */
if (wsi->vhost->http.http_proxy_port) {
ads = wsi->vhost->http.http_proxy_address;
wsi->c_port = wsi->vhost->http.http_proxy_port;
#else
if (0) {
#endif
#if defined(LWS_WITH_SOCKS5)
/* Priority 2: Connect to SOCK5 Proxy */
} else if (wsi->vhost->socks_proxy_port) {
if (socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen)) {
cce = "socks msg too large";
goto oom4;
}
lwsl_client("Sending SOCKS Greeting\n");
ads = wsi->vhost->socks_proxy_address;
wsi->c_port = wsi->vhost->socks_proxy_port;
#endif
}
memset(&sa46, 0, sizeof(sa46));
if (n || !wsi->dns_results) {
/* lws_getaddrinfo46 failed, there is no usable result */
lwsl_notice("%s: lws_getaddrinfo46 failed %d\n",
__func__, n);
cce = "ipv6 lws_getaddrinfo46 failed";
goto oom4;
}
/*
* Let's try connecting to each of the results in turn until one works
* or we run out of results
*/
next_result:
psa = (const struct sockaddr *)&sa46;
n = sizeof(sa46);
memset(&sa46, 0, sizeof(sa46));
switch (wsi->dns_results_next->ai_family) {
case AF_INET:
#if defined(LWS_WITH_IPV6)
if (ipv6only) {
sa46.sa4.sin_family = AF_INET6;
/* map IPv4 to IPv6 */
memset((char *)&sa46.sa6.sin6_addr, 0,
sizeof(sa46.sa6.sin6_addr));
sa46.sa6.sin6_addr.s6_addr[10] = 0xff;
sa46.sa6.sin6_addr.s6_addr[11] = 0xff;
memcpy(&sa46.sa6.sin6_addr.s6_addr[12],
&((struct sockaddr_in *)
wsi->dns_results_next->ai_addr)->sin_addr,
sizeof(struct in_addr));
sa46.sa6.sin6_port = htons(wsi->c_port);
ni[0] = '\0';
lws_write_numeric_address(sa46.sa6.sin6_addr.s6_addr,
16, ni, sizeof(ni));
lwsl_info("%s: %s ipv4->ipv6 %s\n", __func__, ads, ni);
break;
}
#endif
sa46.sa4.sin_family = AF_INET;
sa46.sa4.sin_addr.s_addr =
((struct sockaddr_in *)wsi->dns_results_next->ai_addr)->
sin_addr.s_addr;
memset(&sa46.sa4.sin_zero, 0, sizeof(sa46.sa4.sin_zero));
sa46.sa4.sin_port = htons(wsi->c_port);
n = sizeof(struct sockaddr_in);
lws_write_numeric_address((uint8_t *)&sa46.sa4.sin_addr.s_addr,
4, ni, sizeof(ni));
lwsl_info("%s: %s ipv4 %s\n", __func__, ads, ni);
break;
case AF_INET6:
#if defined(LWS_WITH_IPV6)
if (!wsi->ipv6)
goto try_next_result;
sa46.sa4.sin_family = AF_INET6;
memcpy(&sa46.sa6.sin6_addr,
&((struct sockaddr_in6 *)wsi->dns_results_next->ai_addr)->
sin6_addr, sizeof(struct in6_addr));
sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)
wsi->dns_results_next->ai_addr)->sin6_scope_id;
sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)
wsi->dns_results_next->ai_addr)->sin6_flowinfo;
sa46.sa6.sin6_port = htons(wsi->c_port);
lws_write_numeric_address((uint8_t *)&sa46.sa6.sin6_addr,
16, ni, sizeof(ni));
lwsl_info("%s: %s ipv6 %s\n", __func__, ads, ni);
#else
goto try_next_result; /* ipv4 only can't use this */
#endif
break;
}
#if defined(LWS_WITH_UNIX_SOCK)
ads_known:
#endif
/* now we decided on ipv4 or ipv6, set the port and create socket*/
if (!lws_socket_is_valid(wsi->desc.sockfd)) {
if (wsi->context->event_loop_ops->check_client_connect_ok &&
wsi->context->event_loop_ops->check_client_connect_ok(wsi)) {
cce = "waiting for event loop watcher to close";
goto oom4;
}
#if defined(LWS_WITH_UNIX_SOCK)
if (wsi->unix_skt)
wsi->desc.sockfd = socket(AF_UNIX, SOCK_STREAM, 0);
else
#endif
wsi->desc.sockfd = socket(sa46.sa4.sin_family,
SOCK_STREAM, 0);
if (!lws_socket_is_valid(wsi->desc.sockfd)) {
lwsl_warn("Unable to open socket\n");
goto try_next_result;
}
if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd,
#if defined(LWS_WITH_UNIX_SOCK)
wsi->unix_skt)) {
#else
0)) {
#endif
lwsl_err("Failed to set wsi socket options\n");
goto try_next_result_closesock;
}
lwsl_debug("%s: %p: WAITING_CONNECT\n", __func__, wsi);
lwsi_set_state(wsi, LRS_WAITING_CONNECT);
#if !defined(LWS_AMAZON_RTOS)
if (wsi->context->event_loop_ops->accept)
if (wsi->context->event_loop_ops->accept(wsi))
goto try_next_result_closesock;
#endif
if (__insert_wsi_socket_into_fds(wsi->context, wsi))
goto try_next_result_closesock;
if (lws_change_pollfd(wsi, 0, LWS_POLLIN))
goto try_next_result_fds;
/*
* Past here, we can't simply free the structs as error
* handling as oom4 does.
*
* We can run the whole close flow, or unpick the fds inclusion
* and anything else we have done.
*/
wsi->oom4 = 1;
if (!wsi->protocol)
wsi->protocol = &wsi->vhost->protocols[0];
lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE,
AWAITING_TIMEOUT);
if (wsi->stash)
iface = wsi->stash->cis[CIS_IFACE];
else
iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
if (iface && *iface) {
n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0,
iface, wsi->ipv6);
if (n < 0)
goto try_next_result_fds;
}
}
#if defined(LWS_WITH_UNIX_SOCK)
if (wsi->unix_skt) {
psa = (const struct sockaddr *)&sau;
n = sizeof(sau);
} else
#endif
if (!psa) /* coverity */
goto try_next_result_fds;
/*
* The actual connection attempt
*/
#if defined(LWS_WITH_DETAILED_LATENCY)
wsi->detlat.earliest_write_req =
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
#endif
m = connect(wsi->desc.sockfd, (const struct sockaddr *)psa, n);
if (m == -1) {
int errno_copy = LWS_ERRNO;
lwsl_debug("%s: connect says errno: %d\n", __func__, errno_copy);
if (errno_copy != LWS_EALREADY &&
errno_copy != LWS_EINPROGRESS &&
errno_copy != LWS_EWOULDBLOCK
#ifdef _WIN32
&& errno_copy != WSAEINVAL
#endif
) {
#if defined(_DEBUG)
char nads[48];
lws_sa46_write_numeric_address(&sa46, nads, sizeof(nads));
lwsl_info("%s: Connect failed: %s port %d\n",
__func__, nads, wsi->c_port);
#endif
goto try_next_result_fds;
}
if (lws_plat_check_connection_error(wsi))
goto try_next_result_fds;
/*
* must do specifically a POLLOUT poll to hear
* about the connect completion
*/
if (lws_change_pollfd(wsi, 0, LWS_POLLOUT))
goto try_next_result_fds;
return wsi;
}
conn_good:
lwsl_debug("%s: Connection started\n", __func__);
/* the tcp connection has happend */
#if defined(LWS_WITH_DETAILED_LATENCY)
if (wsi->context->detailed_latency_cb) {
wsi->detlat.type = LDLT_CONNECTION;
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
lws_now_usecs() -
wsi->detlat.earliest_write_req_pre_write;
wsi->detlat.latencies[LAT_DUR_USERCB] = 0;
lws_det_lat_cb(wsi->context, &wsi->detlat);
wsi->detlat.earliest_write_req =
wsi->detlat.earliest_write_req_pre_write =
lws_now_usecs();
}
#endif
lws_addrinfo_clean(wsi);
if (wsi->protocol)
wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE,
wsi->user_space, NULL, 0);
return lws_client_connect_4_established(wsi, NULL, plen);
oom4:
if (lwsi_role_client(wsi) && wsi->protocol /* && lwsi_state_est(wsi) */)
lws_inform_client_conn_fail(wsi,(void *)cce, strlen(cce));
/* take care that we might be inserted in fds already */
if (wsi->position_in_fds_table != LWS_NO_FDS_POS)
goto failed1;
/*
* We can't be an active client connection any more, if we thought
* that was what we were going to be doing. It should be if we are
* failing by oom4 path, we are still called by
* lws_client_connect_via_info() and will be returning NULL to that,
* so nobody else should have had a chance to queue on us.
*/
{
struct lws_vhost *vhost = wsi->vhost;
lws_vhost_lock(vhost);
__lws_free_wsi(wsi);
lws_vhost_unlock(vhost);
}
return NULL;
try_next_result_fds:
wsi->oom4 = 0;
__remove_wsi_socket_from_fds(wsi);
try_next_result_closesock:
compatible_close(wsi->desc.sockfd);
wsi->desc.sockfd = LWS_SOCK_INVALID;
try_next_result:
if (wsi->dns_results_next) {
wsi->dns_results_next = wsi->dns_results_next->ai_next;
if (wsi->dns_results_next)
goto next_result;
}
lws_addrinfo_clean(wsi);
cce = "Unable to connect";
//failed:
lws_inform_client_conn_fail(wsi, (void *)cce, strlen(cce));
failed1:
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
return NULL;
}
struct lws *
lws_client_connect_2_dnsreq(struct lws *wsi)
{
const char *meth = NULL, *ads;
struct addrinfo *result = NULL;
#if defined(LWS_WITH_IPV6)
struct sockaddr_in addr;
const char *iface;
#endif
int n, port = 0;
struct lws *w;
if (lwsi_state(wsi) == LRS_WAITING_DNS) {
lwsl_notice("%s: LRS_WAITING_DNS\n", __func__);
return wsi;
}
if (wsi->stash)
meth = wsi->stash->cis[CIS_METHOD];
else
meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD);
/* we only pipeline connections that said it was okay */
if (!wsi->client_pipeline) {
lwsl_debug("%s: new conn on no pipeline flag\n", __func__);
goto solo;
}
/* only pipeline things we associate with being a stream */
if (meth && strcmp(meth, "RAW") && strcmp(meth, "GET") &&
strcmp(meth, "POST"))
goto solo;
/* consult active connections to find out disposition */
switch (lws_vhost_active_conns(wsi, &w)) {
case ACTIVE_CONNS_SOLO:
break;
case ACTIVE_CONNS_MUXED:
return wsi;
case ACTIVE_CONNS_QUEUED:
return lws_client_connect_4_established(wsi, w, 0);
}
solo:
wsi->addrinfo_idx = 0;
/*
* clients who will create their own fresh connection keep a copy of
* the hostname they originally connected to, in case other connections
* want to use it too
*/
if (!wsi->cli_hostname_copy) {
if (wsi->stash && wsi->stash->cis[CIS_HOST])
wsi->cli_hostname_copy =
lws_strdup(wsi->stash->cis[CIS_HOST]);
else {
char *pa = lws_hdr_simple_ptr(wsi,
_WSI_TOKEN_CLIENT_PEER_ADDRESS);
if (pa)
wsi->cli_hostname_copy = lws_strdup(pa);
}
}
/*
* If we made our own connection, and we're doing a method that can take
* a pipeline, we are an "active client connection".
*
* Add ourselves to the vhost list of those so that others can
* piggyback on our transaction queue
*/
if (meth && (!strcmp(meth, "RAW") || !strcmp(meth, "GET") ||
!strcmp(meth, "POST")) &&
lws_dll2_is_detached(&wsi->dll2_cli_txn_queue) &&
lws_dll2_is_detached(&wsi->dll_cli_active_conns)) {
lws_vhost_lock(wsi->vhost);
/* caution... we will have to unpick this on oom4 path */
lws_dll2_add_head(&wsi->dll_cli_active_conns,
&wsi->vhost->dll_cli_active_conns_owner);
lws_vhost_unlock(wsi->vhost);
}
/*
* unix socket destination?
*/
if (wsi->stash)
ads = wsi->stash->cis[CIS_ADDRESS];
else
ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS);
#if defined(LWS_WITH_UNIX_SOCK)
if (*ads == '+') {
wsi->unix_skt = 1;
n = 0;
goto next_step;
}
#endif
/*
* start off allowing ipv6 on connection if vhost allows it
*/
wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost);
#ifdef LWS_WITH_IPV6
if (wsi->stash)
iface = wsi->stash->cis[CIS_IFACE];
else
iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE);
if (wsi->ipv6 && iface &&
inet_pton(AF_INET, iface, &addr.sin_addr) == 1) {
lwsl_notice("%s: client connection forced to IPv4\n", __func__);
wsi->ipv6 = 0;
}
#endif
#if defined(LWS_WITH_DETAILED_LATENCY)
if (lwsi_state(wsi) == LRS_WAITING_DNS &&
wsi->context->detailed_latency_cb) {
wsi->detlat.type = LDLT_NAME_RESOLUTION;
wsi->detlat.latencies[LAT_DUR_PROXY_CLIENT_REQ_TO_WRITE] =
lws_now_usecs() -
wsi->detlat.earliest_write_req_pre_write;
wsi->detlat.latencies[LAT_DUR_USERCB] = 0;
lws_det_lat_cb(wsi->context, &wsi->detlat);
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
}
#endif
#if defined(LWS_CLIENT_HTTP_PROXYING) && \
(defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2))
/* Decide what it is we need to connect to:
*
* Priority 1: connect to http proxy */
if (wsi->vhost->http.http_proxy_port) {
ads = wsi->vhost->http.http_proxy_address;
port = wsi->vhost->http.http_proxy_port;
#else
if (0) {
#endif
#if defined(LWS_WITH_SOCKS5)
/* Priority 2: Connect to SOCK5 Proxy */
} else if (wsi->vhost->socks_proxy_port) {
lwsl_client("Sending SOCKS Greeting\n");
ads = wsi->vhost->socks_proxy_address;
port = wsi->vhost->socks_proxy_port;
#endif
} else {
/* Priority 3: Connect directly */
/* ads already set */
port = wsi->c_port;
}
/*
* prepare the actual connection
* to whatever we decided to connect to
*/
lwsi_set_state(wsi, LRS_WAITING_DNS);
lwsl_info("%s: %p: lookup %s:%u\n", __func__, wsi, ads, port);
(void)port;
#if defined(LWS_WITH_DETAILED_LATENCY)
wsi->detlat.earliest_write_req_pre_write = lws_now_usecs();
#endif
#if !defined(LWS_WITH_SYS_ASYNC_DNS)
if (wsi->dns_results)
n = 0;
else
n = lws_getaddrinfo46(wsi, ads, &result);
#else
lwsi_set_state(wsi, LRS_WAITING_DNS);
/* this is either FAILED, CONTINUING, or already called connect_4 */
n = lws_async_dns_query(wsi->context, wsi->tsi, ads, LWS_ADNS_RECORD_A,
lws_client_connect_3_connect, wsi, NULL);
if (n == LADNS_RET_FAILED_WSI_CLOSED)
return NULL;
if (n == LADNS_RET_FAILED)
goto failed1;
return wsi;
#endif
#if defined(LWS_WITH_UNIX_SOCK)
next_step:
#endif
return lws_client_connect_3_connect(wsi, ads, result, n, NULL);
#if defined(LWS_WITH_SYS_ASYNC_DNS)
failed1:
lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS, "client_connect2");
return NULL;
#endif
}
#if defined(LWS_ROLE_H1) || defined(LWS_ROLE_H2)
static uint8_t hnames2[] = {
_WSI_TOKEN_CLIENT_ORIGIN,
_WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
_WSI_TOKEN_CLIENT_METHOD,
_WSI_TOKEN_CLIENT_IFACE,
_WSI_TOKEN_CLIENT_ALPN
};
/**
* lws_client_reset() - retarget a connected wsi to start over with a new
* connection (ie, redirect)
* this only works if still in HTTP, ie, not upgraded yet
* wsi: connection to reset
* address: network address of the new server
* port: port to connect to
* path: uri path to connect to on the new server
* host: host header to send to the new server
*/
struct lws *
lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port,
const char *path, const char *host)
{
char *stash, *p;
struct lws *wsi;
size_t size = 0;
int n;
if (!pwsi)
return NULL;
wsi = *pwsi;
lwsl_debug("%s: wsi %p: redir %d: %s\n", __func__, wsi, wsi->redirects,
address);
if (wsi->redirects == 3) {
lwsl_err("%s: Too many redirects\n", __func__);
return NULL;
}
wsi->redirects++;
/*
* goal is to close our role part, close the sockfd, detach the ah
* but leave our wsi extant and still bound to whatever vhost it was
*/
for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++)
size += lws_hdr_total_length(wsi, hnames2[n]) + 1;
if ((int)size < lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI) + 1)
size = lws_hdr_total_length(wsi, _WSI_TOKEN_CLIENT_URI) + 1;
/*
* The incoming address and host can be from inside the existing ah
* we are going to detach and reattch
*/
size += strlen(path) + 1 + strlen(address) + 1 + strlen(host) + 1 + 1;
p = stash = lws_malloc(size, __func__);
if (!stash)
return NULL;
/*
* _WSI_TOKEN_CLIENT_ORIGIN,
* _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
* _WSI_TOKEN_CLIENT_METHOD,
* _WSI_TOKEN_CLIENT_IFACE,
* _WSI_TOKEN_CLIENT_ALPN
* address
* host
* path
*/
for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++)
if (lws_hdr_total_length(wsi, hnames2[n])) {
memcpy(p, lws_hdr_simple_ptr(wsi, hnames2[n]),
lws_hdr_total_length(wsi, hnames2[n]) + 1);
p += lws_hdr_total_length(wsi, hnames2[n]) + 1;
} else
*p++ = '\0';
memcpy(p, address, strlen(address) + 1);
address = p;
p += strlen(address) + 1;
memcpy(p, host, strlen(host) + 1);
host = p;
p += strlen(host) + 1;
memcpy(p, path, strlen(path) + 1);
path = p;
if (!port) {
lwsl_info("%s: forcing port 443\n", __func__);
port = 443;
ssl = 1;
}
lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d, pifds %d\n",
address, port, path, ssl, wsi->position_in_fds_table);
__remove_wsi_socket_from_fds(wsi);
__lws_reset_wsi(wsi); /* detaches ah here */
wsi->client_pipeline = 1;
/* close the connection by hand */
#if defined(LWS_WITH_TLS)
lws_ssl_close(wsi);
#endif
if (wsi->role_ops && wsi->role_ops->close_kill_connection)
wsi->role_ops->close_kill_connection(wsi, 1);
if (wsi->context->event_loop_ops->close_handle_manually)
wsi->context->event_loop_ops->close_handle_manually(wsi);
else
if (wsi->desc.sockfd != LWS_SOCK_INVALID)
compatible_close(wsi->desc.sockfd);
#if defined(LWS_WITH_TLS)
if (!ssl)
wsi->tls.use_ssl &= LCCSCF_USE_SSL;
else
wsi->tls.use_ssl |= LCCSCF_USE_SSL;
#else
if (ssl) {
lwsl_err("%s: not configured for ssl\n", __func__);
goto bail;
}
#endif
if (wsi->protocol && wsi->role_ops && wsi->protocol_bind_balance) {
wsi->protocol->callback(wsi,
wsi->role_ops->protocol_unbind_cb[
!!lwsi_role_server(wsi)],
wsi->user_space, (void *)__func__, 0);
wsi->protocol_bind_balance = 0;
}
wsi->desc.sockfd = LWS_SOCK_INVALID;
lws_role_transition(wsi, LWSIFR_CLIENT, LRS_UNCONNECTED, &role_ops_h1);
// wsi->protocol = NULL;
if (wsi->protocol)
lws_bind_protocol(wsi, wsi->protocol, "client_reset");
wsi->pending_timeout = NO_PENDING_TIMEOUT;
wsi->c_port = port;
wsi->hdr_parsing_completed = 0;
if (lws_header_table_attach(wsi, 0)) {
lwsl_err("%s: failed to get ah\n", __func__);
goto bail;
}
//_lws_header_table_reset(wsi->http.ah);
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address))
goto bail;
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host))
goto bail;
/*
* _WSI_TOKEN_CLIENT_ORIGIN,
* _WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
* _WSI_TOKEN_CLIENT_METHOD,
* _WSI_TOKEN_CLIENT_IFACE,
* _WSI_TOKEN_CLIENT_ALPN
* address
* host
* path
*/
p = stash;
for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames2); n++) {
if (lws_hdr_simple_create(wsi, hnames2[n], p))
goto bail;
p += lws_hdr_total_length(wsi, hnames2[n]) + 1;
}
stash[0] = '/';
memmove(&stash[1], path, size - 1 < strlen(path) + 1 ? size - 1 : strlen(path) + 1);
if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash))
goto bail;
lws_free_set_NULL(stash);
#if defined(LWS_WITH_HTTP2)
if (wsi->client_h2_substream)
wsi->h2.END_STREAM = wsi->h2.END_HEADERS = 0;
#endif
*pwsi = lws_client_connect_2_dnsreq(wsi);
return *pwsi;
bail:
lws_free_set_NULL(stash);
return NULL;
}
#if defined(LWS_WITH_HTTP_PROXY) && defined(LWS_WITH_HUBBUB)
hubbub_error
html_parser_cb(const hubbub_token *token, void *pw)
{
struct lws_rewrite *r = (struct lws_rewrite *)pw;
char buf[1024], *start = buf + LWS_PRE, *p = start,
*end = &buf[sizeof(buf) - 1], dotstar[128];
size_t i;
switch (token->type) {
case HUBBUB_TOKEN_DOCTYPE:
lws_strnncpy(dotstar, token->data.doctype.name.ptr,
token->data.doctype.name.len, sizeof(dotstar));
p += lws_snprintf(p, end - p, "<!DOCTYPE %s %s ",
dotstar, token->data.doctype.force_quirks ?
"(force-quirks) " : "");
if (token->data.doctype.public_missing)
lwsl_debug("\tpublic: missing\n");
else {
lws_strnncpy(dotstar, token->data.doctype.public_id.ptr,
token->data.doctype.public_id.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, "PUBLIC \"%s\"\n",
dotstar);
}
if (token->data.doctype.system_missing)
lwsl_debug("\tsystem: missing\n");
else {
lws_strnncpy(dotstar, token->data.doctype.system_id.ptr,
token->data.doctype.system_id.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, " \"%s\">\n", dotstar);
}
break;
case HUBBUB_TOKEN_START_TAG:
lws_strnncpy(dotstar, token->data.tag.name.ptr,
token->data.tag.name.len, sizeof(dotstar));
p += lws_snprintf(p, end - p, "<%s", dotstar);
/* (token->data.tag.self_closing) ?
"(self-closing) " : "",
(token->data.tag.n_attributes > 0) ?
"attributes:" : "");
*/
for (i = 0; i < token->data.tag.n_attributes; i++) {
if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) ||
!hstrcmp(&token->data.tag.attributes[i].name, "action", 6) ||
!hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) {
const char *pp = (const char *)token->data.tag.attributes[i].value.ptr;
int plen = (int) token->data.tag.attributes[i].value.len;
if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) {
if (!hstrcmp(&token->data.tag.attributes[i].value,
r->from, r->from_len)) {
pp += r->from_len;
plen -= r->from_len;
}
lws_strnncpy(dotstar,
token->data.tag.attributes[i].name.ptr,
token->data.tag.attributes[i].name.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, " %s=\"%s",
dotstar, r->to);
lws_strnncpy(dotstar, pp, plen, sizeof(dotstar));
p += lws_snprintf(p, end - p, " /%s\"", dotstar);
continue;
}
}
lws_strnncpy(dotstar,
token->data.tag.attributes[i].name.ptr,
token->data.tag.attributes[i].name.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, " %s=\"", dotstar);
lws_strnncpy(dotstar,
token->data.tag.attributes[i].value.ptr,
token->data.tag.attributes[i].value.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, "%s\"", dotstar);
}
p += lws_snprintf(p, end - p, ">");
break;
case HUBBUB_TOKEN_END_TAG:
lws_strnncpy(dotstar, token->data.tag.name.ptr,
token->data.tag.name.len, sizeof(dotstar));
p += lws_snprintf(p, end - p, "</%s", dotstar);
/*
(token->data.tag.self_closing) ?
"(self-closing) " : "",
(token->data.tag.n_attributes > 0) ?
"attributes:" : "");
*/
for (i = 0; i < token->data.tag.n_attributes; i++) {
lws_strnncpy(dotstar,
token->data.tag.attributes[i].name.ptr,
token->data.tag.attributes[i].name.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, " %s='", dotstar);
lws_strnncpy(dotstar,
token->data.tag.attributes[i].value.ptr,
token->data.tag.attributes[i].value.len,
sizeof(dotstar));
p += lws_snprintf(p, end - p, "%s'\n", dotstar);
}
p += lws_snprintf(p, end - p, ">");
break;
case HUBBUB_TOKEN_COMMENT:
lws_strnncpy(dotstar, token->data.comment.ptr,
token->data.comment.len, sizeof(dotstar));
p += lws_snprintf(p, end - p, "<!-- %s -->\n", dotstar);
break;
case HUBBUB_TOKEN_CHARACTER:
if (token->data.character.len == 1) {
if (*token->data.character.ptr == '<') {
p += lws_snprintf(p, end - p, "&lt;");
break;
}
if (*token->data.character.ptr == '>') {
p += lws_snprintf(p, end - p, "&gt;");
break;
}
if (*token->data.character.ptr == '&') {
p += lws_snprintf(p, end - p, "&amp;");
break;
}
}
lws_strnncpy(dotstar, token->data.character.ptr,
token->data.character.len, sizeof(dotstar));
p += lws_snprintf(p, end - p, "%s", dotstar);
break;
case HUBBUB_TOKEN_EOF:
p += lws_snprintf(p, end - p, "\n");
break;
}
if (r->wsi->protocol_bind_balance &&
user_callback_handle_rxflow(r->wsi->protocol->callback,
r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ,
r->wsi->user_space, start, p - start))
return -1;
return HUBBUB_OK;
}
#endif
#endif
static const uint8_t hnames[] = {
_WSI_TOKEN_CLIENT_PEER_ADDRESS,
_WSI_TOKEN_CLIENT_URI,
_WSI_TOKEN_CLIENT_HOST,
_WSI_TOKEN_CLIENT_ORIGIN,
_WSI_TOKEN_CLIENT_SENT_PROTOCOLS,
_WSI_TOKEN_CLIENT_METHOD,
_WSI_TOKEN_CLIENT_IFACE,
_WSI_TOKEN_CLIENT_ALPN
};
struct lws *
lws_http_client_connect_via_info2(struct lws *wsi)
{
struct client_info_stash *stash = wsi->stash;
int n;
lwsl_debug("%s: %p (stash %p)\n", __func__, wsi, stash);
if (!stash)
return wsi;
wsi->opaque_user_data = wsi->stash->opaque_user_data;
if (stash->cis[CIS_METHOD] && !strcmp(stash->cis[CIS_METHOD], "RAW"))
goto no_ah;
/*
* 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
*/
for (n = 0; n < (int)LWS_ARRAY_SIZE(hnames); n++)
if (hnames[n] && stash->cis[n]) {
if (lws_hdr_simple_create(wsi, hnames[n], stash->cis[n]))
goto bail1;
}
#if defined(LWS_WITH_SOCKS5)
if (!wsi->vhost->socks_proxy_port)
lws_free_set_NULL(wsi->stash);
#endif
no_ah:
wsi->context->count_wsi_allocated++;
return lws_client_connect_2_dnsreq(wsi);
bail1:
#if defined(LWS_WITH_SOCKS5)
if (!wsi->vhost->socks_proxy_port)
lws_free_set_NULL(wsi->stash);
#endif
return NULL;
}
#if defined(LWS_WITH_SOCKS5)
int
socks_generate_msg(struct lws *wsi, enum socks_msg_type type, ssize_t *msg_len)
{
struct lws_context *context = wsi->context;
struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi];
uint8_t *p = pt->serv_buf, *end = &p[context->pt_serv_buf_size];
ssize_t n, passwd_len;
short net_num;
char *cp;
switch (type) {
case SOCKS_MSG_GREETING:
if (lws_ptr_diff(end, p) < 4)
return 1;
/* socks version, version 5 only */
*p++ = SOCKS_VERSION_5;
/* number of methods */
*p++ = 2;
/* username password method */
*p++ = SOCKS_AUTH_USERNAME_PASSWORD;
/* no authentication method */
*p++ = SOCKS_AUTH_NO_AUTH;
break;
case SOCKS_MSG_USERNAME_PASSWORD:
n = strlen(wsi->vhost->socks_user);
passwd_len = strlen(wsi->vhost->socks_password);
if (n > 254 || passwd_len > 254)
return 1;
if (lws_ptr_diff(end, p) < 3 + n + passwd_len)
return 1;
/* the subnegotiation version */
*p++ = SOCKS_SUBNEGOTIATION_VERSION_1;
/* length of the user name */
*p++ = n;
/* user name */
memcpy(p, wsi->vhost->socks_user, n);
p += n;
/* length of the password */
*p++ = passwd_len;
/* password */
memcpy(p, wsi->vhost->socks_password, passwd_len);
p += passwd_len;
break;
case SOCKS_MSG_CONNECT:
n = strlen(wsi->stash->cis[CIS_ADDRESS]);
if (n > 254 || lws_ptr_diff(end, p) < 5 + n + 2)
return 1;
cp = (char *)&net_num;
/* socks version */
*p++ = SOCKS_VERSION_5;
/* socks command */
*p++ = SOCKS_COMMAND_CONNECT;
/* reserved */
*p++ = 0;
/* address type */
*p++ = SOCKS_ATYP_DOMAINNAME;
/* length of ---> */
*p++ = n;
/* the address we tell SOCKS proxy to connect to */
memcpy(p, wsi->stash->cis[CIS_ADDRESS], n);
p += n;
net_num = htons(wsi->c_port);
/* the port we tell SOCKS proxy to connect to */
*p++ = cp[0];
*p++ = cp[1];
break;
default:
return 1;
}
*msg_len = lws_ptr_diff(p, pt->serv_buf);
return 0;
}
#endif