diff options
Diffstat (limited to 'thirdparty/lws/client')
-rw-r--r-- | thirdparty/lws/client/client-handshake.c | 1051 | ||||
-rw-r--r-- | thirdparty/lws/client/client-parser.c | 598 | ||||
-rw-r--r-- | thirdparty/lws/client/client.c | 1296 | ||||
-rw-r--r-- | thirdparty/lws/client/ssl-client.c | 625 |
4 files changed, 3570 insertions, 0 deletions
diff --git a/thirdparty/lws/client/client-handshake.c b/thirdparty/lws/client/client-handshake.c new file mode 100644 index 0000000000..c2720d9283 --- /dev/null +++ b/thirdparty/lws/client/client-handshake.c @@ -0,0 +1,1051 @@ +#include "private-libwebsockets.h" + +static int +lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) +{ + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + *result = NULL; + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + +#if !defined(__ANDROID__) + hints.ai_family = AF_INET6; + hints.ai_flags = AI_V4MAPPED; +#endif + } else +#endif + { + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + } + + return getaddrinfo(ads, NULL, &hints, result); +} + +struct lws * +lws_client_connect_2(struct lws *wsi) +{ + sockaddr46 sa46; + struct addrinfo *result; + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollfd pfd; + const char *cce = "", *iface; + int n, port; + ssize_t plen = 0; + const char *ads; +#ifdef LWS_WITH_IPV6 + char ipv6only = lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); + +#if defined(__ANDROID__) + ipv6only = 0; +#endif +#endif + + lwsl_client("%s\n", __func__); + + if (!wsi->u.hdr.ah) { + cce = "ah was NULL at cc2"; + lwsl_err("%s\n", cce); + goto oom4; + } + + /* + * start off allowing ipv6 on connection if vhost allows it + */ + wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); + + /* Decide what it is we need to connect to: + * + * Priority 1: connect to http proxy */ + + if (wsi->vhost->http_proxy_port) { + plen = sprintf((char *)pt->serv_buf, + "CONNECT %s:%u HTTP/1.0\x0d\x0a" + "User-agent: libwebsockets\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), + wsi->c_port); + + if (wsi->vhost->proxy_basic_auth_token[0]) + plen += sprintf((char *)pt->serv_buf + plen, + "Proxy-authorization: basic %s\x0d\x0a", + wsi->vhost->proxy_basic_auth_token); + + plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); + ads = wsi->vhost->http_proxy_address; + port = wsi->vhost->http_proxy_port; + +#if defined(LWS_WITH_SOCKS5) + + /* Priority 2: Connect to SOCK5 Proxy */ + + } else if (wsi->vhost->socks_proxy_port) { + socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); + 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 = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + } + + /* + * prepare the actual connection + * to whatever we decided to connect to + */ + + lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads); + + n = lws_getaddrinfo46(wsi, ads, &result); + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + + if (n) { + /* 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; + } + + memset(&sa46, 0, sizeof(sa46)); + + sa46.sa6.sin6_family = AF_INET6; + switch (result->ai_family) { + case AF_INET: + if (ipv6only) + break; + /* map IPv4 to IPv6 */ + bzero((char *)&sa46.sa6.sin6_addr, + 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 *)result->ai_addr)->sin_addr, + sizeof(struct in_addr)); + lwsl_notice("uplevelling AF_INET to AF_INET6\n"); + break; + + case AF_INET6: + memcpy(&sa46.sa6.sin6_addr, + &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id; + sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo; + break; + default: + lwsl_err("Unknown address family\n"); + freeaddrinfo(result); + cce = "unknown address family"; + goto oom4; + } + } else +#endif /* use ipv6 */ + + /* use ipv4 */ + { + void *p = NULL; + + if (!n) { + struct addrinfo *res = result; + + /* pick the first AF_INET (IPv4) result */ + + while (!p && res) { + switch (res->ai_family) { + case AF_INET: + p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + break; + } + + res = res->ai_next; + } +#if defined(LWS_FALLBACK_GETHOSTBYNAME) + } else if (n == EAI_SYSTEM) { + struct hostent *host; + + lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); + host = gethostbyname(ads); + if (host) { + p = host->h_addr; + } else { + lwsl_err("gethostbyname failed\n"); + cce = "gethostbyname (ipv4) failed"; + goto oom4; + } +#endif + } else { + lwsl_err("getaddrinfo failed\n"); + cce = "getaddrinfo failed"; + goto oom4; + } + + if (!p) { + if (result) + freeaddrinfo(result); + lwsl_err("Couldn't identify address\n"); + cce = "unable to lookup address"; + goto oom4; + } + + sa46.sa4.sin_family = AF_INET; + sa46.sa4.sin_addr = *((struct in_addr *)p); + bzero(&sa46.sa4.sin_zero, 8); + } + + if (result) + freeaddrinfo(result); + + /* now we decided on ipv4 or ipv6, set the port */ + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + +#if defined(LWS_WITH_LIBUV) + if (LWS_LIBUV_ENABLED(context)) + if (lws_libuv_check_watcher_active(wsi)) { + lwsl_warn("Waiting for libuv watcher to close\n"); + cce = "waiting for libuv watcher to close"; + goto oom4; + } +#endif + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) + wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + lwsl_warn("Unable to open socket\n"); + cce = "unable to open socket"; + goto oom4; + } + + if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { + lwsl_err("Failed to set wsi socket options\n"); + compatible_close(wsi->desc.sockfd); + cce = "set socket opts failed"; + goto oom4; + } + + wsi->mode = LWSCM_WSCL_WAITING_CONNECT; + + lws_libev_accept(wsi, wsi->desc); + lws_libuv_accept(wsi, wsi->desc); + lws_libevent_accept(wsi, wsi->desc); + + if (insert_wsi_socket_into_fds(context, wsi)) { + compatible_close(wsi->desc.sockfd); + cce = "insert wsi failed"; + goto oom4; + } + + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + /* + * past here, we can't simply free the structs as error + * handling as oom4 does. We have to run the whole close flow. + */ + + if (!wsi->protocol) + wsi->protocol = &wsi->vhost->protocols[0]; + + wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, + wsi->user_space, NULL, 0); + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, + AWAITING_TIMEOUT); + + iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + + if (iface) { + n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); + if (n < 0) { + cce = "unable to bind socket"; + goto failed; + } + } + } + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + sa46.sa6.sin6_port = htons(port); + n = sizeof(struct sockaddr_in6); + } else +#endif + { + sa46.sa4.sin_port = htons(port); + n = sizeof(struct sockaddr); + } + + if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || + LWS_ERRNO == LWS_EISCONN) { + if (LWS_ERRNO == LWS_EALREADY || + LWS_ERRNO == LWS_EINPROGRESS || + LWS_ERRNO == LWS_EWOULDBLOCK +#ifdef _WIN32 + || LWS_ERRNO == WSAEINVAL +#endif + ) { + lwsl_client("nonblocking connect retry (errno = %d)\n", + LWS_ERRNO); + + if (lws_plat_check_connection_error(wsi)) { + cce = "socket connect failed"; + goto failed; + } + + /* + * must do specifically a POLLOUT poll to hear + * about the connect completion + */ + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + cce = "POLLOUT set failed"; + goto failed; + } + + return wsi; + } + + if (LWS_ERRNO != LWS_EISCONN) { + lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); + cce = "connect failed"; + goto failed; + } + } + + lwsl_client("connected\n"); + + /* we are connected to server, or proxy */ + + /* http proxy */ + if (wsi->vhost->http_proxy_port) { + + /* + * OK from now on we talk via the proxy, so connect to that + * + * (will overwrite existing pointer, + * leaving old string/frag there but unreferenced) + */ + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->http_proxy_address)) + goto failed; + wsi->c_port = wsi->vhost->http_proxy_port; + + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, 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); + + wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; + + return wsi; + } +#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); + + wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY; + + return wsi; + } +#endif + + /* + * 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 some + * 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); + + wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; + pfd.fd = wsi->desc.sockfd; + pfd.events = LWS_POLLIN; + pfd.revents = LWS_POLLIN; + + n = lws_service_fd(context, &pfd); + if (n < 0) { + cce = "first service failed"; + goto failed; + } + if (n) /* returns 1 on failure after closing wsi */ + return NULL; + + return wsi; + +oom4: + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + + if (wsi->mode == LWSCM_HTTP_CLIENT || + wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED || + wsi->mode == LWSCM_WSCL_WAITING_CONNECT) { + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; + } + /* take care that we might be inserted in fds already */ + if (wsi->position_in_fds_table != -1) + goto failed1; + lws_remove_from_timeout_list(wsi); + lws_header_table_detach(wsi, 0); + lws_free(wsi); + + return NULL; + +failed: + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; +failed1: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return NULL; +} + +/** + * 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 + */ +LWS_VISIBLE struct lws * +lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, + const char *path, const char *host) +{ + char origin[300] = "", protocol[300] = "", method[32] = "", iface[16] = "", *p; + struct lws *wsi = *pwsi; + + if (wsi->redirects == 3) { + lwsl_err("%s: Too many redirects\n", __func__); + return NULL; + } + wsi->redirects++; + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); + if (p) + strncpy(origin, p, sizeof(origin) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + if (p) + strncpy(protocol, p, sizeof(protocol) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (p) + strncpy(method, p, sizeof(method) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + if (p) + strncpy(method, p, sizeof(iface) - 1); + + lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n", + address, port, path, ssl); + + /* close the connection by hand */ + +#ifdef LWS_OPENSSL_SUPPORT + lws_ssl_close(wsi); +#endif + +#ifdef LWS_WITH_LIBUV + if (LWS_LIBUV_ENABLED(wsi->context)) { + lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi); + /* + * libuv has to do his own close handle processing asynchronously + * but once it starts we can do everything else synchronously, + * including trash wsi->desc.sockfd since it took a copy. + * + * When it completes it will call compatible_close() + */ + lws_libuv_closehandle_manually(wsi); + } else +#else + compatible_close(wsi->desc.sockfd); +#endif + + remove_wsi_socket_from_fds(wsi); + +#ifdef LWS_OPENSSL_SUPPORT + wsi->use_ssl = ssl; +#else + if (ssl) { + lwsl_err("%s: not configured for ssl\n", __func__); + return NULL; + } +#endif + + wsi->desc.sockfd = LWS_SOCK_INVALID; + wsi->state = LWSS_CLIENT_UNCONNECTED; + wsi->protocol = NULL; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->c_port = port; + wsi->hdr_parsing_completed = 0; + _lws_header_table_reset(wsi->u.hdr.ah); + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address)) + return NULL; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host)) + return NULL; + + if (origin[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, + origin)) + return NULL; + if (protocol[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + protocol)) + return NULL; + if (method[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, + method)) + return NULL; + + if (iface[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, + iface)) + return NULL; + + origin[0] = '/'; + strncpy(&origin[1], path, sizeof(origin) - 2); + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, origin)) + return NULL; + + *pwsi = lws_client_connect_2(wsi); + + return *pwsi; +} + +#ifdef LWS_WITH_HTTP_PROXY +static 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]; + size_t i; + + switch (token->type) { + case HUBBUB_TOKEN_DOCTYPE: + + p += lws_snprintf(p, end - p, "<!DOCTYPE %.*s %s ", + (int) token->data.doctype.name.len, + token->data.doctype.name.ptr, + token->data.doctype.force_quirks ? + "(force-quirks) " : ""); + + if (token->data.doctype.public_missing) + lwsl_debug("\tpublic: missing\n"); + else + p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n", + (int) token->data.doctype.public_id.len, + token->data.doctype.public_id.ptr); + + if (token->data.doctype.system_missing) + lwsl_debug("\tsystem: missing\n"); + else + p += lws_snprintf(p, end - p, " \"%.*s\">\n", + (int) token->data.doctype.system_id.len, + token->data.doctype.system_id.ptr); + + break; + case HUBBUB_TOKEN_START_TAG: + p += lws_snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len, + token->data.tag.name.ptr); + +/* (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; + } + p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + r->to, plen, pp); + continue; + } + } + + p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_END_TAG: + p += lws_snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len, + token->data.tag.name.ptr); +/* + (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + p += lws_snprintf(p, end - p, " %.*s='%.*s'\n", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_COMMENT: + p += lws_snprintf(p, end - p, "<!-- %.*s -->\n", + (int) token->data.comment.len, + token->data.comment.ptr); + break; + case HUBBUB_TOKEN_CHARACTER: + if (token->data.character.len == 1) { + if (*token->data.character.ptr == '<') { + p += lws_snprintf(p, end - p, "<"); + break; + } + if (*token->data.character.ptr == '>') { + p += lws_snprintf(p, end - p, ">"); + break; + } + if (*token->data.character.ptr == '&') { + p += lws_snprintf(p, end - p, "&"); + break; + } + } + + p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len, + token->data.character.ptr); + break; + case HUBBUB_TOKEN_EOF: + p += lws_snprintf(p, end - p, "\n"); + break; + } + + if (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 + +LWS_VISIBLE struct lws * +lws_client_connect_via_info(struct lws_client_connect_info *i) +{ + struct lws *wsi; + int v = SPEC_LATEST_SUPPORTED; + const struct lws_protocols *p; + + if (i->context->requested_kill) + return NULL; + + if (!i->context->protocol_init_done) + lws_protocol_init(i->context); + + wsi = lws_zalloc(sizeof(struct lws), "client wsi"); + if (wsi == NULL) + goto bail; + + wsi->context = i->context; + /* assert the mode and union status (hdr) clearly */ + lws_union_transition(wsi, LWSCM_HTTP_CLIENT); + wsi->desc.sockfd = LWS_SOCK_INVALID; + + /* 1) fill up the wsi with stuff from the connect_info as far as it + * can go. It's because not only is our connection async, we might + * not even be able to get ahold of an ah at this point. + */ + + /* -1 means just use latest supported */ + if (i->ietf_version_or_minus_one != -1 && i->ietf_version_or_minus_one) + v = i->ietf_version_or_minus_one; + + wsi->ietf_spec_revision = v; + wsi->user_space = NULL; + wsi->state = LWSS_CLIENT_UNCONNECTED; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->position_in_fds_table = -1; + wsi->c_port = i->port; + wsi->vhost = i->vhost; + if (!wsi->vhost) + wsi->vhost = i->context->vhost_list; + + wsi->protocol = &wsi->vhost->protocols[0]; + + /* for http[s] connection, allow protocol selection by name */ + + if (i->method && i->vhost && i->protocol) { + p = lws_vhost_name_to_protocol(i->vhost, i->protocol); + if (p) + wsi->protocol = p; + } + + if (wsi && !wsi->user_space && i->userdata) { + wsi->user_space_externally_allocated = 1; + wsi->user_space = i->userdata; + } else + /* if we stay in http, we can assign the user space now, + * otherwise do it after the protocol negotiated + */ + if (i->method) + if (lws_ensure_user_space(wsi)) + goto bail; + +#ifdef LWS_OPENSSL_SUPPORT + wsi->use_ssl = i->ssl_connection; +#else + if (i->ssl_connection) { + lwsl_err("libwebsockets not configured for ssl\n"); + goto bail; + } +#endif + + /* 2) stash the things from connect_info that we can't process without + * an ah. Because if no ah, we will go on the ah waiting list and + * process those things later (after the connect_info and maybe the + * things pointed to have gone out of scope. + */ + + wsi->u.hdr.stash = lws_malloc(sizeof(*wsi->u.hdr.stash), "client stash"); + if (!wsi->u.hdr.stash) { + lwsl_err("%s: OOM\n", __func__); + goto bail; + } + + wsi->u.hdr.stash->origin[0] = '\0'; + wsi->u.hdr.stash->protocol[0] = '\0'; + wsi->u.hdr.stash->method[0] = '\0'; + wsi->u.hdr.stash->iface[0] = '\0'; + + strncpy(wsi->u.hdr.stash->address, i->address, + sizeof(wsi->u.hdr.stash->address) - 1); + strncpy(wsi->u.hdr.stash->path, i->path, + sizeof(wsi->u.hdr.stash->path) - 1); + strncpy(wsi->u.hdr.stash->host, i->host, + sizeof(wsi->u.hdr.stash->host) - 1); + if (i->origin) + strncpy(wsi->u.hdr.stash->origin, i->origin, + sizeof(wsi->u.hdr.stash->origin) - 1); + if (i->protocol) + strncpy(wsi->u.hdr.stash->protocol, i->protocol, + sizeof(wsi->u.hdr.stash->protocol) - 1); + if (i->method) + strncpy(wsi->u.hdr.stash->method, i->method, + sizeof(wsi->u.hdr.stash->method) - 1); + if (i->iface) + strncpy(wsi->u.hdr.stash->iface, i->iface, + sizeof(wsi->u.hdr.stash->iface) - 1); + + wsi->u.hdr.stash->address[sizeof(wsi->u.hdr.stash->address) - 1] = '\0'; + wsi->u.hdr.stash->path[sizeof(wsi->u.hdr.stash->path) - 1] = '\0'; + wsi->u.hdr.stash->host[sizeof(wsi->u.hdr.stash->host) - 1] = '\0'; + wsi->u.hdr.stash->origin[sizeof(wsi->u.hdr.stash->origin) - 1] = '\0'; + wsi->u.hdr.stash->protocol[sizeof(wsi->u.hdr.stash->protocol) - 1] = '\0'; + wsi->u.hdr.stash->method[sizeof(wsi->u.hdr.stash->method) - 1] = '\0'; + wsi->u.hdr.stash->iface[sizeof(wsi->u.hdr.stash->iface) - 1] = '\0'; + + if (i->pwsi) + *i->pwsi = wsi; + + /* if we went on the waiting list, no probs just return the wsi + * when we get the ah, now or later, he will call + * lws_client_connect_via_info2() below. + */ + if (lws_header_table_attach(wsi, 0) < 0) { + /* + * if we failed here, the connection is already closed + * and freed. + */ + goto bail1; + } + + if (i->parent_wsi) { + lwsl_info("%s: created child %p of parent %p\n", __func__, + wsi, i->parent_wsi); + wsi->parent = i->parent_wsi; + wsi->sibling_list = i->parent_wsi->child_list; + i->parent_wsi->child_list = wsi; + } +#ifdef LWS_WITH_HTTP_PROXY + if (i->uri_replace_to) + wsi->rw = lws_rewrite_create(wsi, html_parser_cb, + i->uri_replace_from, + i->uri_replace_to); +#endif + + return wsi; + +bail: + lws_free(wsi); + +bail1: + if (i->pwsi) + *i->pwsi = NULL; + + return NULL; +} + +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; +} + +LWS_VISIBLE struct lws * +lws_client_connect_extended(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one, + void *userdata) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = userdata; + + return lws_client_connect_via_info(&i); +} + +LWS_VISIBLE struct lws * +lws_client_connect(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = NULL; + + return lws_client_connect_via_info(&i); +} + +#if defined(LWS_WITH_SOCKS5) +void 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]; + ssize_t len = 0, n, passwd_len; + short net_num; + char *p; + + switch (type) { + case SOCKS_MSG_GREETING: + /* socks version, version 5 only */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* number of methods */ + pt->serv_buf[len++] = 2; + /* username password method */ + pt->serv_buf[len++] = SOCKS_AUTH_USERNAME_PASSWORD; + /* no authentication method */ + pt->serv_buf[len++] = SOCKS_AUTH_NO_AUTH; + break; + + case SOCKS_MSG_USERNAME_PASSWORD: + n = strlen(wsi->vhost->socks_user); + passwd_len = strlen(wsi->vhost->socks_password); + + /* the subnegotiation version */ + pt->serv_buf[len++] = SOCKS_SUBNEGOTIATION_VERSION_1; + /* length of the user name */ + pt->serv_buf[len++] = n; + /* user name */ + strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user, + context->pt_serv_buf_size - len); + len += n; + /* length of the password */ + pt->serv_buf[len++] = passwd_len; + /* password */ + strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password, + context->pt_serv_buf_size - len); + len += passwd_len; + break; + + case SOCKS_MSG_CONNECT: + p = (char*)&net_num; + + /* socks version */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* socks command */ + pt->serv_buf[len++] = SOCKS_COMMAND_CONNECT; + /* reserved */ + pt->serv_buf[len++] = 0; + /* address type */ + pt->serv_buf[len++] = SOCKS_ATYP_DOMAINNAME; + /* skip length, we fill it in at the end */ + n = len++; + + /* the address we tell SOCKS proxy to connect to */ + strncpy((char *)&(pt->serv_buf[len]), wsi->u.hdr.stash->address, + context->pt_serv_buf_size - len); + len += strlen(wsi->u.hdr.stash->address); + net_num = htons(wsi->c_port); + + /* the port we tell SOCKS proxy to connect to */ + pt->serv_buf[len++] = p[0]; + pt->serv_buf[len++] = p[1]; + + /* the length of the address, excluding port */ + pt->serv_buf[n] = strlen(wsi->u.hdr.stash->address); + break; + + default: + return; + } + + *msg_len = len; +} +#endif diff --git a/thirdparty/lws/client/client-parser.c b/thirdparty/lws/client/client-parser.c new file mode 100644 index 0000000000..0e42dac362 --- /dev/null +++ b/thirdparty/lws/client/client-parser.c @@ -0,0 +1,598 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * parsers.c: lws_rx_sm() needs to be roughly kept in + * sync with changes here, esp related to ext draining + */ + +int lws_client_rx_sm(struct lws *wsi, unsigned char c) +{ + int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; + int handled, n, m, rx_draining_ext = 0; + unsigned short close_code; + struct lws_tokens eff_buf; + unsigned char *pp; + + if (wsi->u.ws.rx_draining_ext) { + assert(!c); + eff_buf.token = NULL; + eff_buf.token_len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); + rx_draining_ext = 1; + lwsl_debug("%s: doing draining flow\n", __func__); + + goto drain_extension; + } + + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + /* control frames (PING) may interrupt checkable sequences */ + wsi->u.ws.defeat_check_utf8 = 0; + + switch (wsi->ietf_spec_revision) { + case 13: + wsi->u.ws.opcode = c & 0xf; + /* revisit if an extension wants them... */ + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.continuation_possible = 1; + wsi->u.ws.check_utf8 = lws_check_opt( + wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8); + wsi->u.ws.utf8 = 0; + break; + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.continuation_possible = 1; + break; + case LWSWSOPC_CONTINUATION: + if (!wsi->u.ws.continuation_possible) { + lwsl_info("disordered continuation\n"); + return -1; + } + break; + case LWSWSOPC_CLOSE: + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.utf8 = 0; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 0xb: + case 0xc: + case 0xd: + case 0xe: + case 0xf: + lwsl_info("illegal opcode\n"); + return -1; + default: + wsi->u.ws.defeat_check_utf8 = 1; + break; + } + wsi->u.ws.rsv = (c & 0x70); + /* revisit if an extension wants them... */ + if ( +#ifndef LWS_NO_EXTENSIONS + !wsi->count_act_ext && +#endif + wsi->u.ws.rsv) { + lwsl_info("illegal rsv bits set\n"); + return -1; + } + wsi->u.ws.final = !!((c >> 7) & 1); + lwsl_ext("%s: This RX frame Final %d\n", __func__, + wsi->u.ws.final); + + if (wsi->u.ws.owed_a_fin && + (wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME || + wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) { + lwsl_info("hey you owed us a FIN\n"); + return -1; + } + if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) { + wsi->u.ws.continuation_possible = 0; + wsi->u.ws.owed_a_fin = 0; + } + + if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) { + lwsl_info("control msg can't be fragmented\n"); + return -1; + } + if (!wsi->u.ws.final) + wsi->u.ws.owed_a_fin = 1; + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.frame_is_binary = wsi->u.ws.opcode == + LWSWSOPC_BINARY_FRAME; + break; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + + default: + lwsl_err("unknown spec version %02d\n", + wsi->ietf_spec_revision); + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN: + + wsi->u.ws.this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->u.ws.rx_packet_length = c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (c) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->u.ws.rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->u.ws.rx_packet_length |= c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + lwsl_warn("b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } +#if defined __LP64__ + wsi->u.ws.rx_packet_length = ((size_t)c) << 56; +#else + wsi->u.ws.rx_packet_length = 0; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->u.ws.rx_packet_length |= (size_t)c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_1: + wsi->u.ws.mask[0] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_2: + wsi->u.ws.mask[1] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_3: + wsi->u.ws.mask[2] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_4: + wsi->u.ws.mask[3] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + + assert(wsi->u.ws.rx_ubuf); + + if (wsi->u.ws.rx_draining_ext) + goto drain_extension; + + if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce) + c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3]; + + wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c; + + if (--wsi->u.ws.rx_packet_length == 0) { + /* spill because we have the whole frame */ + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + + /* + * if there's no protocol max frame size given, we are + * supposed to default to context->pt_serv_buf_size + */ + if (!wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) + break; + + if (wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) + break; + + /* spill because we filled our rx buffer */ +spill: + + handled = 0; + + /* + * is this frame a control packet we should take care of at this + * layer? If so service it and hide it from the user callback + */ + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_CLOSE: + pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE]; + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8) && + wsi->u.ws.rx_ubuf_head > 2 && + lws_check_utf8(&wsi->u.ws.utf8, pp + 2, + wsi->u.ws.rx_ubuf_head - 2)) + goto utf8_fail; + + /* is this an acknowledgement of our close? */ + if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + lwsl_parser("seen server's close ack\n"); + return -1; + } + + lwsl_parser("client sees server close len = %d\n", + wsi->u.ws.rx_ubuf_head); + if (wsi->u.ws.rx_ubuf_head >= 2) { + close_code = (pp[0] << 8) | pp[1]; + if (close_code < 1000 || + close_code == 1004 || + close_code == 1005 || + close_code == 1006 || + close_code == 1012 || + close_code == 1013 || + close_code == 1014 || + close_code == 1015 || + (close_code >= 1016 && close_code < 3000) + ) { + pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff; + pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff; + } + } + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, pp, + wsi->u.ws.rx_ubuf_head)) + return -1; + + if (lws_partial_buffered(wsi)) + /* + * if we're in the middle of something, + * we can't do a normal close response and + * have to just close our end. + */ + wsi->socket_is_permanently_unusable = 1; + else + /* + * parrot the close packet payload back + * we do not care about how it went, we are closing + * immediately afterwards + */ + lws_write(wsi, (unsigned char *) + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head, + LWS_WRITE_CLOSE); + wsi->state = LWSS_RETURNED_CLOSE_ALREADY; + /* close the connection */ + return -1; + + case LWSWSOPC_PING: + lwsl_info("received %d byte ping, sending pong\n", + wsi->u.ws.rx_ubuf_head); + + /* he set a close reason on this guy, ignore PING */ + if (wsi->u.ws.close_in_ping_buffer_len) + goto ping_drop; + + if (wsi->u.ws.ping_pending_flag) { + /* + * there is already a pending ping payload + * we should just log and drop + */ + lwsl_parser("DROP PING since one pending\n"); + goto ping_drop; + } + + /* control packets can only be < 128 bytes long */ + if (wsi->u.ws.rx_ubuf_head > 128 - 3) { + lwsl_parser("DROP PING payload too large\n"); + goto ping_drop; + } + + /* stash the pong payload */ + memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; + wsi->u.ws.ping_pending_flag = 1; + + /* get it sent as soon as possible */ + lws_callback_on_writable(wsi); +ping_drop: + wsi->u.ws.rx_ubuf_head = 0; + handled = 1; + break; + + case LWSWSOPC_PONG: + lwsl_info("client receied pong\n"); + lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + if (wsi->pending_timeout == + PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { + lwsl_info("%p: received expected PONG\n", wsi); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + } + + /* issue it */ + callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; + break; + + case LWSWSOPC_CONTINUATION: + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + break; + + default: + + lwsl_parser("Reserved opc 0x%2X\n", wsi->u.ws.opcode); + + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (lws_ext_cb_active(wsi, + LWS_EXT_CB_EXTENDED_PAYLOAD_RX, + &eff_buf, 0) <= 0) { + /* not handled or failed */ + lwsl_ext("Unhandled ext opc 0x%x\n", + wsi->u.ws.opcode); + wsi->u.ws.rx_ubuf_head = 0; + + return 0; + } + handled = 1; + break; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using lws_write + */ + if (handled) + goto already_done; + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) + goto already_done; + +drain_extension: + lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); + lwsl_ext("Ext RX returned %d\n", n); + if (n < 0) { + wsi->socket_is_permanently_unusable = 1; + return -1; + } + + lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len); + + if (rx_draining_ext && !eff_buf.token_len) { + lwsl_debug(" --- ending drain on 0 read result\n"); + goto already_done; + } + + if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) { + if (lws_check_utf8(&wsi->u.ws.utf8, + (unsigned char *)eff_buf.token, + eff_buf.token_len)) + goto utf8_fail; + + /* we are ending partway through utf-8 character? */ + if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final && + wsi->u.ws.utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); +utf8_fail: + lwsl_info("utf8 error\n"); + return -1; + } + } + + if (eff_buf.token_len < 0 && + callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG) + goto already_done; + + if (!eff_buf.token) + goto already_done; + + eff_buf.token[eff_buf.token_len] = '\0'; + + if (!wsi->protocol->callback) + goto already_done; + + if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG) + lwsl_info("Client doing pong callback\n"); + + if (n && eff_buf.token_len) + /* extension had more... main loop will come back + * we want callback to be done with this set, if so, + * because lws_is_final() hides it was final until the + * last chunk + */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state == LWSS_AWAITING_CLOSE_ACK) + goto already_done; + + m = wsi->protocol->callback(wsi, + (enum lws_callback_reasons)callback_action, + wsi->user_space, eff_buf.token, eff_buf.token_len); + + /* if user code wants to close, let caller know */ + if (m) + return 1; + +already_done: + wsi->u.ws.rx_ubuf_head = 0; + break; + default: + lwsl_err("client rx illegal state\n"); + return 1; + } + + return 0; + +illegal_ctl_length: + lwsl_warn("Control frame asking for extended length is illegal\n"); + + /* kill the connection */ + return -1; +} + + diff --git a/thirdparty/lws/client/client.c b/thirdparty/lws/client/client.c new file mode 100644 index 0000000000..20450aa923 --- /dev/null +++ b/thirdparty/lws/client/client.c @@ -0,0 +1,1296 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +int +lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m; + + switch (wsi->mode) { + case LWSCM_WSCL_WAITING_PROXY_REPLY: + case LWSCM_WSCL_ISSUE_HANDSHAKE: + case LWSCM_WSCL_WAITING_SERVER_REPLY: + case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: + case LWSCM_WS_CLIENT: + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + lwsl_debug("%s: caching %ld\n", __func__, (long)len); + lws_rxflow_cache(wsi, *buf, 0, len); + return 0; + } + if (wsi->u.ws.rx_draining_ext) { +#if !defined(LWS_NO_CLIENT) + if (wsi->mode == LWSCM_WS_CLIENT) + m = lws_client_rx_sm(wsi, 0); + else +#endif + m = lws_rx_sm(wsi, 0); + if (m < 0) + return -1; + continue; + } + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; + + if (lws_client_rx_sm(wsi, *(*buf)++)) { + lwsl_debug("client_rx_sm exited\n"); + return -1; + } + len--; + } + lwsl_debug("%s: finished with %ld\n", __func__, (long)len); + return 0; + default: + break; + } + + return 0; +} + +LWS_VISIBLE LWS_EXTERN void +lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) +{ + wsi->client_http_body_pending = !!something_left_to_send; +} + +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; + ssize_t 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: + case LWSCM_WSCL_WAITING_SOCKS_AUTH_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); + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("SOCKS read EAGAIN, retrying\n"); + return 0; + } + lwsl_err("ERROR reading from SOCKS socket\n"); + goto bail3; + } + + switch (wsi->mode) { + + case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5) + goto socks_reply_fail; + + if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { + lwsl_client("SOCKS greeting reply: No Auth Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; + goto socks_send; + } + + if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { + lwsl_client("SOCKS greeting reply: User/Pw Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; + goto socks_send; + } + goto socks_reply_fail; + + case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: + if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || + pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) + goto socks_reply_fail; + + lwsl_client("SOCKS password OK, sending connect\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; +socks_send: + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to socks proxy\n"); + goto bail3; + } + + lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); + wsi->mode = conn_mode; + break; + +socks_reply_fail: + lwsl_notice("socks reply: v%d, err %d\n", + pt->serv_buf[0], pt->serv_buf[1]); + goto bail3; + + case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5 || + pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) + goto socks_reply_fail; + + lwsl_client("socks connect OK\n"); + + /* 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_handshake; + } + break; +#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); + + goto bail3; + } + + 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; + } + lwsl_err("ERROR reading from proxy socket\n"); + goto bail3; + } + + pt->serv_buf[13] = '\0'; + if (strcmp(sb, "HTTP/1.0 200 ") && + strcmp(sb, "HTTP/1.1 200 ")) { + lwsl_err("ERROR proxy: %s\n", sb); + goto bail3; + } + + /* 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_handshake: +#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 hanging up on us... + * but if there is POLLIN waiting, handle that first + */ + if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == + 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; +} + +/* + * In-place str to lower case + */ + +static void +strtolower(char *s) +{ + while (*s) { +#ifdef LWS_PLAT_OPTEE + int tolower_optee(int c); + *s = tolower_optee((int)*s); +#else + *s = tolower((int)*s); +#endif + s++; + } +} + +int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi) +{ + lwsl_debug("%s: wsi %p\n", __func__, wsi); + /* if we can't go back to accept new headers, drop the connection */ + if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + lwsl_info("%s: %p: close connection\n", __func__, wsi); + return 1; + } + + /* we don't support chained client connections yet */ + return 1; +#if 0 + /* otherwise set ourselves up ready to go again */ + wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; + wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED; + wsi->u.http.rx_content_length = 0; + wsi->hdr_parsing_completed = 0; + + /* He asked for it to stay alive indefinitely */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* + * As client, nothing new is going to come until we ask for it + * we can drop the ah, if any + */ + if (wsi->u.hdr.ah) { + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + } + + /* If we're (re)starting on headers, need other implied init */ + wsi->u.hdr.ues = URIES_IDLE; + + lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); + + return 0; +#endif +} + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_http_client_http_response(struct lws *wsi) +{ + if (!wsi->u.http.ah) + return 0; + + return wsi->u.http.ah->http_response; +} + +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) { + +#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.rx_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.rx_content_length); + wsi->u.http.rx_content_remain = wsi->u.http.rx_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 */, "client frame buffer"); + 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; +} + + +char * +lws_generate_client_handshake(struct lws *wsi, char *pkt) +{ + char buf[128], hash[20], key_b64[40], *p = pkt; + struct lws_context *context = wsi->context; + const char *meth; + int n; +#ifndef LWS_NO_EXTENSIONS + const struct lws_extension *ext; + int ext_count = 0; +#endif + const char *pp = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (!meth) { + meth = "GET"; + wsi->do_ws = 1; + } else { + wsi->do_ws = 0; + } + + if (!strcmp(meth, "RAW")) { + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_notice("client transition to raw\n"); + + if (pp) { + const struct lws_protocols *pr; + + pr = lws_vhost_name_to_protocol(wsi->vhost, pp); + + if (!pr) { + lwsl_err("protocol %s not enabled on vhost\n", + pp); + return NULL; + } + + lws_bind_protocol(wsi, pr); + } + + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + return NULL; + + lws_header_table_force_to_detachable_state(wsi); + lws_union_transition(wsi, LWSCM_RAW); + lws_header_table_detach(wsi, 1); + + return NULL; + } + + if (wsi->do_ws) { + /* + * create the random key + */ + n = lws_get_random(context, hash, 16); + if (n != 16) { + lwsl_err("Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + return NULL; + } + + lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); + } + + /* + * 04 example client handshake + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + */ + + p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + + p += sprintf(p, "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); + + p += sprintf(p, "Host: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { + if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) + p += sprintf(p, "Origin: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); + else + p += sprintf(p, "Origin: http://%s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); + } + + if (wsi->do_ws) { + p += sprintf(p, "Upgrade: websocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, key_b64); + p += strlen(key_b64); + p += sprintf(p, "\x0d\x0a"); + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); + + /* tell the server what extensions we could support */ + +#ifndef LWS_NO_EXTENSIONS + ext = wsi->vhost->extensions; + while (ext && ext->callback) { + n = lws_ext_cb_all_exts(context, wsi, + LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, + (char *)ext->name, 0); + if (n) { /* an extension vetos us */ + lwsl_ext("ext %s vetoed\n", (char *)ext->name); + ext++; + continue; + } + n = wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, + wsi->user_space, (char *)ext->name, 0); + + /* + * zero return from callback means + * go ahead and allow the extension, + * it's what we get if the callback is + * unhandled + */ + + if (n) { + ext++; + continue; + } + + /* apply it */ + + if (ext_count) + *p++ = ','; + else + p += sprintf(p, "Sec-WebSocket-Extensions: "); + p += sprintf(p, "%s", ext->client_offer); + ext_count++; + + ext++; + } + if (ext_count) + p += sprintf(p, "\x0d\x0a"); +#endif + + if (wsi->ietf_spec_revision) + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", + wsi->ietf_spec_revision); + + /* prepare the expected server accept response */ + + key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ + n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); + + lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, + wsi->u.hdr.ah->initial_handshake_hash_base64, + sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); + } + + /* give userland a chance to append, eg, cookies */ + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12)) + return NULL; + + p += sprintf(p, "\x0d\x0a"); + + return p; +} + diff --git a/thirdparty/lws/client/ssl-client.c b/thirdparty/lws/client/ssl-client.c new file mode 100644 index 0000000000..b69fd2da30 --- /dev/null +++ b/thirdparty/lws/client/ssl-client.c @@ -0,0 +1,625 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +extern int openssl_websocket_private_data_index, + openssl_SSL_CTX_private_data_index; + +extern void +lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); + +extern int lws_ssl_get_error(struct lws *wsi, int n); + +#if defined(USE_WOLFSSL) +#else + +static int +OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ +#if defined(LWS_WITH_MBEDTLS) + lwsl_notice("%s\n", __func__); + + return 0; +#else + SSL *ssl; + int n; + struct lws *wsi; + + /* keep old behaviour accepting self-signed server certs */ + if (!preverify_ok) { + int err = X509_STORE_CTX_get_error(x509_ctx); + + if (err != X509_V_OK) { + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); + + if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && + wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED) { + lwsl_notice("accepting self-signed certificate (verify_callback)\n"); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + return 1; // ok + } else if ((err == X509_V_ERR_CERT_NOT_YET_VALID || + err == X509_V_ERR_CERT_HAS_EXPIRED) && + wsi->use_ssl & LCCSCF_ALLOW_EXPIRED) { + if (err == X509_V_ERR_CERT_NOT_YET_VALID) + lwsl_notice("accepting not yet valid certificate (verify_callback)\n"); + else if (err == X509_V_ERR_CERT_HAS_EXPIRED) + lwsl_notice("accepting expired certificate (verify_callback)\n"); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + return 1; // ok + } + } + } + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); + + n = lws_get_context_protocol(wsi->context, 0).callback(wsi, + LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION, + x509_ctx, ssl, preverify_ok); + + /* keep old behaviour if something wrong with server certs */ + /* if ssl error is overruled in callback and cert is ok, + * X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and + * return value is 0 from callback */ + if (!preverify_ok) { + int err = X509_STORE_CTX_get_error(x509_ctx); + + if (err != X509_V_OK) { /* cert validation error was not handled in callback */ + int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + const char* msg = X509_verify_cert_error_string(err); + lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;depth=%d)\n", msg, preverify_ok, err, depth); + return preverify_ok; // not ok + } + } + /* convert callback return code from 0 = OK to verify callback return value 1 = OK */ + return !n; +#endif +} +#endif + +int +lws_ssl_client_bio_create(struct lws *wsi) +{ + char hostname[128], *p; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_HOST) <= 0) { + lwsl_err("%s: Unable to get hostname\n", __func__); + + return -1; + } + + /* + * remove any :port part on the hostname... necessary for network + * connection but typical certificates do not contain it + */ + p = hostname; + while (*p) { + if (*p == ':') { + *p = '\0'; + break; + } + p++; + } + + wsi->ssl = SSL_new(wsi->vhost->ssl_client_ctx); + if (!wsi->ssl) { + lwsl_err("SSL_new failed: %s\n", + ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); + lws_ssl_elaborate_error(); + return -1; + } + +#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) + if (wsi->vhost->ssl_info_event_mask) + SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); +#endif + +#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host + X509_VERIFY_PARAM *param; + (void)param; + + if (!(wsi->use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) { + param = SSL_get0_param(wsi->ssl); + /* Enable automatic hostname checks */ + X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + X509_VERIFY_PARAM_set1_host(param, hostname, 0); + } + +#endif + +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) +#ifndef USE_OLD_CYASSL + /* OpenSSL_client_verify_callback will be called @ SSL_connect() */ + SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); +#endif +#endif + +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +#endif + /* + * use server name indication (SNI), if supported, + * when establishing connection + */ +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL +#ifdef CYASSL_SNI_HOST_NAME + CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname)); +#endif +#else +#ifdef WOLFSSL_SNI_HOST_NAME + wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname)); +#endif +#endif +#else +#if defined(LWS_WITH_MBEDTLS) + if (wsi->vhost->x509_client_CA) + SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); + else + SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback); + +#else +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_set_tlsext_host_name(wsi->ssl, hostname); +#endif +#endif +#endif + +#ifdef USE_WOLFSSL + /* + * wolfSSL/CyaSSL does certificate verification differently + * from OpenSSL. + * If we should ignore the certificate, we need to set + * this before SSL_new and SSL_connect is called. + * Otherwise the connect will simply fail with error code -155 + */ +#ifdef USE_OLD_CYASSL + if (wsi->use_ssl == 2) + CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); +#else + if (wsi->use_ssl == 2) + wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); +#endif +#endif /* USE_WOLFSSL */ + +#if !defined(LWS_WITH_MBEDTLS) + wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE); + SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); +#else + SSL_set_fd(wsi->ssl, wsi->desc.sockfd); +#endif + +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL + CyaSSL_set_using_nonblock(wsi->ssl, 1); +#else + wolfSSL_set_using_nonblock(wsi->ssl, 1); +#endif +#else +#if !defined(LWS_WITH_MBEDTLS) + BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ +#endif +#endif + +#if !defined(LWS_WITH_MBEDTLS) + SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, + wsi); +#endif + + return 0; +} + +#if defined(LWS_WITH_MBEDTLS) +int ERR_get_error(void) +{ + return 0; +} +#endif + +int +lws_ssl_client_connect1(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + int n = 0; + + lws_latency_pre(context, wsi); + + n = SSL_connect(wsi->ssl); + + lws_latency(context, wsi, + "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); + + if (n < 0) { + n = lws_ssl_get_error(wsi, n); + + if (n == SSL_ERROR_WANT_READ) + goto some_wait; + + if (n == SSL_ERROR_WANT_WRITE) { + /* + * wants us to retry connect due to + * state of the underlying ssl layer... + * but since it may be stalled on + * blocked write, no incoming data may + * arrive to trigger the retry. + * Force (possibly many times if the SSL + * state persists in returning the + * condition code, but other sockets + * are getting serviced inbetweentimes) + * us to get called back when writable. + */ + lwsl_info("%s: WANT_WRITE... retrying\n", __func__); + lws_callback_on_writable(wsi); +some_wait: + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + { + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + + lwsl_err("ssl hs1 error, X509_V_ERR = %d: %s\n", + n, ERR_error_string(n, sb)); + lws_ssl_elaborate_error(); + } + + n = -1; + } + + if (n <= 0) { + /* + * retry if new data comes until we + * run into the connection timeout or win + */ + + unsigned long error = ERR_get_error(); + + if (error != SSL_ERROR_NONE) { + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + lwsl_err("SSL connect error %lu: %s\n", + error, ERR_error_string(error, sb)); + return -1; + } + + return 0; + } + + return 1; +} + +int +lws_ssl_client_connect2(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + int n = 0; + + if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { + lws_latency_pre(context, wsi); + n = SSL_connect(wsi->ssl); + lwsl_debug("%s: SSL_connect says %d\n", __func__, n); + + lws_latency(context, wsi, + "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); + + if (n < 0) { + n = lws_ssl_get_error(wsi, n); + + if (n == SSL_ERROR_WANT_READ) { + lwsl_info("SSL_connect WANT_READ... retrying\n"); + + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + if (n == SSL_ERROR_WANT_WRITE) { + /* + * wants us to retry connect due to + * state of the underlying ssl layer... + * but since it may be stalled on + * blocked write, no incoming data may + * arrive to trigger the retry. + * Force (possibly many times if the SSL + * state persists in returning the + * condition code, but other sockets + * are getting serviced inbetweentimes) + * us to get called back when writable. + */ + lwsl_info("SSL_connect WANT_WRITE... retrying\n"); + lws_callback_on_writable(wsi); + + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + n = -1; + } + + if (n <= 0) { + /* + * retry if new data comes until we + * run into the connection timeout or win + */ + unsigned long error = ERR_get_error(); + if (error != SSL_ERROR_NONE) { + lwsl_err("SSL connect error %lu: %s\n", + error, ERR_error_string(error, sb)); + return -1; + } + } + } + +#if defined(LWS_WITH_MBEDTLS) + { + X509 *peer = SSL_get_peer_certificate(wsi->ssl); + + if (!peer) { + lwsl_notice("peer did not provide cert\n"); + + return -1; + } + lwsl_notice("peer provided cert\n"); + } +#endif + +#ifndef USE_WOLFSSL + /* + * See comment above about wolfSSL certificate + * verification + */ + lws_latency_pre(context, wsi); + n = SSL_get_verify_result(wsi->ssl); + lws_latency(context, wsi, + "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); + + lwsl_debug("get_verify says %d\n", n); + + if (n != X509_V_OK) { + if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && + (wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED)) { + lwsl_notice("accepting self-signed certificate\n"); + } else if ((n == X509_V_ERR_CERT_NOT_YET_VALID || + n == X509_V_ERR_CERT_HAS_EXPIRED) && + (wsi->use_ssl & LCCSCF_ALLOW_EXPIRED)) { + lwsl_notice("accepting expired certificate\n"); + } else if (n == X509_V_ERR_CERT_NOT_YET_VALID) { + lwsl_notice("Cert is from the future... " + "probably our clock... accepting...\n"); + } else { + lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n", + n, ERR_error_string(n, sb)); + lws_ssl_elaborate_error(); + return -1; + } + } + +#endif /* USE_WOLFSSL */ + + return 1; +} + + +int lws_context_init_client_ssl(struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + SSL_METHOD *method = NULL; + struct lws wsi; + unsigned long error; + const char *ca_filepath = info->ssl_ca_filepath; +#if !defined(LWS_WITH_MBEDTLS) + const char *cipher_list = info->ssl_cipher_list; + const char *private_key_filepath = info->ssl_private_key_filepath; + const char *cert_filepath = info->ssl_cert_filepath; + int n; + + if (vhost->options & LWS_SERVER_OPTION_ONLY_RAW) + return 0; + + /* + * for backwards-compatibility default to using ssl_... members, but + * if the newer client-specific ones are given, use those + */ + if (info->client_ssl_cipher_list) + cipher_list = info->client_ssl_cipher_list; + if (info->client_ssl_cert_filepath) + cert_filepath = info->client_ssl_cert_filepath; + if (info->client_ssl_private_key_filepath) + private_key_filepath = info->client_ssl_private_key_filepath; +#endif + if (info->client_ssl_ca_filepath) + ca_filepath = info->client_ssl_ca_filepath; + + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + return 0; + + if (vhost->ssl_client_ctx) + return 0; + + if (info->provided_client_ssl_ctx) { + /* use the provided OpenSSL context if given one */ + vhost->ssl_client_ctx = info->provided_client_ssl_ctx; + /* nothing for lib to delete */ + vhost->user_supplied_ssl_ctx = 1; + + return 0; + } + + /* basic openssl init already happened in context init */ + + /* choose the most recent spin of the api */ +#if defined(LWS_HAVE_TLS_CLIENT_METHOD) + method = (SSL_METHOD *)TLS_client_method(); +#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD) + method = (SSL_METHOD *)TLSv1_2_client_method(); +#else + method = (SSL_METHOD *)SSLv23_client_method(); +#endif + 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; + } + /* create context */ + vhost->ssl_client_ctx = SSL_CTX_new(method); + if (!vhost->ssl_client_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; + } + + lwsl_notice("created client ssl context for %s\n", vhost->name); + +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION); +#endif + +#if defined(LWS_WITH_MBEDTLS) + if (ca_filepath) { + lws_filepos_t len; + uint8_t *buf; + /* + * prototype this here, the shim does not export it in the + * header, and we need to use the shim unchanged for ESP32 case + */ + X509 *d2i_X509(X509 **cert, const unsigned char *buffer, long len); + + if (alloc_file(vhost->context, ca_filepath, &buf, &len)) { + lwsl_err("Load CA cert file %s failed\n", ca_filepath); + return 1; + } + + vhost->x509_client_CA = d2i_X509(NULL, buf, len); + free(buf); + if (!vhost->x509_client_CA) { + lwsl_err("client CA: x509 parse failed\n"); + return 1; + } + + SSL_CTX_add_client_CA(vhost->ssl_client_ctx, + vhost->x509_client_CA); + + lwsl_notice("client loaded CA for verification %s\n", ca_filepath); + } +#else + SSL_CTX_set_options(vhost->ssl_client_ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (cipher_list) + SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list); + +#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS)) + /* loads OS default CA certs */ + SSL_CTX_set_default_verify_paths(vhost->ssl_client_ctx); +#endif + + /* openssl init for cert verification (for client sockets) */ + if (!ca_filepath) { + if (!SSL_CTX_load_verify_locations( + vhost->ssl_client_ctx, NULL, LWS_OPENSSL_CLIENT_CERTS)) + lwsl_err("Unable to load SSL Client certs from %s " + "(set by LWS_OPENSSL_CLIENT_CERTS) -- " + "client ssl isn't going to work\n", + LWS_OPENSSL_CLIENT_CERTS); + } else + if (!SSL_CTX_load_verify_locations( + vhost->ssl_client_ctx, ca_filepath, NULL)) { + lwsl_err( + "Unable to load SSL Client certs " + "file from %s -- client ssl isn't " + "going to work\n", info->client_ssl_ca_filepath); + lws_ssl_elaborate_error(); + } + else + lwsl_info("loaded ssl_ca_filepath\n"); + + /* + * callback allowing user code to load extra verification certs + * helping the client to verify server identity + */ + + /* support for client-side certificate authentication */ + if (cert_filepath) { + lwsl_notice("%s: doing cert filepath\n", __func__); + n = SSL_CTX_use_certificate_chain_file(vhost->ssl_client_ctx, + cert_filepath); + if (n < 1) { + lwsl_err("problem %d getting cert '%s'\n", n, + cert_filepath); + lws_ssl_elaborate_error(); + return 1; + } + lwsl_notice("Loaded client cert %s\n", cert_filepath); + } + if (private_key_filepath) { + lwsl_notice("%s: doing private key filepath\n", __func__); + lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info); + /* set the private key from KeyFile */ + if (SSL_CTX_use_PrivateKey_file(vhost->ssl_client_ctx, + private_key_filepath, SSL_FILETYPE_PEM) != 1) { + lwsl_err("use_PrivateKey_file '%s'\n", + private_key_filepath); + lws_ssl_elaborate_error(); + return 1; + } + lwsl_notice("Loaded client cert private key %s\n", + private_key_filepath); + + /* verify private key */ + if (!SSL_CTX_check_private_key(vhost->ssl_client_ctx)) { + lwsl_err("Private SSL key doesn't match cert\n"); + return 1; + } + } +#endif + /* + * give him a fake wsi with context set, so he can use + * lws_get_context() in the callback + */ + memset(&wsi, 0, sizeof(wsi)); + wsi.vhost = vhost; + wsi.context = vhost->context; + + vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS, + vhost->ssl_client_ctx, NULL, 0); + + return 0; +} |