diff --git a/lib/client-handshake.c b/lib/client-handshake.c index c6f8ba3e..56c4e943 100644 --- a/lib/client-handshake.c +++ b/lib/client-handshake.c @@ -2,20 +2,6 @@ #include -/* - * In-place str to lower case - */ - -void -strtolower(char *s) -{ - while (*s) { - *s = tolower(*s); - s++; - } -} - - /** * libwebsocket_client_connect() - Connect to another websocket server * @this: Websocket context @@ -48,25 +34,12 @@ libwebsocket_client_connect(struct libwebsocket_context *this, { struct hostent *server_hostent; struct sockaddr_in server_addr; - char buf[150]; - char key_b64[150]; - char hash[20]; + char pkt[512]; struct pollfd pfd; - static const char magic_websocket_guid[] = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - static const char magic_websocket_04_masking_guid[] = - "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; - char pkt[1024]; - char *p = &pkt[0]; - const char *pc; - int len; - int okay = 0; struct libwebsocket *wsi; int n; int plen = 0; -#ifdef LWS_OPENSSL_SUPPORT - char ssl_err_buf[512]; -#else +#ifndef LWS_OPENSSL_SUPPORT if (ssl_connection) { fprintf(stderr, "libwebsockets not configured for ssl\n"); return NULL; @@ -74,10 +47,8 @@ libwebsocket_client_connect(struct libwebsocket_context *this, #endif wsi = malloc(sizeof(struct libwebsocket)); - if (wsi == NULL) { - fprintf(stderr, "Out of memory allocing new connection\n"); - return NULL; - } + if (wsi == NULL) + goto bail1; memset(wsi, 0, sizeof *wsi); @@ -93,6 +64,32 @@ libwebsocket_client_connect(struct libwebsocket_context *this, wsi->pings_vs_pongs = 0; wsi->protocol = NULL; wsi->pending_timeout = NO_PENDING_TIMEOUT; +#ifdef LWS_OPENSSL_SUPPORT + wsi->use_ssl = ssl_connection; +#endif + + /* copy parameters over so state machine has access */ + + wsi->c_path = malloc(strlen(path) + 1); + if (wsi->c_path == NULL) + goto bail1; + strcpy(wsi->c_path, path); + wsi->c_host = malloc(strlen(host) + 1); + if (wsi->c_host == NULL) + goto oom1; + strcpy(wsi->c_host, host); + wsi->c_origin = malloc(strlen(origin) + 1); + if (wsi->c_origin == NULL) + goto oom2; + strcpy(wsi->c_origin, origin); + if (protocol) { + wsi->c_protocol = malloc(strlen(protocol) + 1); + if (wsi->c_protocol == NULL) + goto oom3; + strcpy(wsi->c_protocol, protocol); + } else + wsi->c_protocol = NULL; + /* set up appropriate masking */ @@ -109,7 +106,7 @@ libwebsocket_client_connect(struct libwebsocket_context *this, fprintf(stderr, "Client ietf version %d not supported\n", wsi->ietf_spec_revision); - return NULL; + goto oom4; } /* force no mask if he asks for that though */ @@ -145,18 +142,16 @@ libwebsocket_client_connect(struct libwebsocket_context *this, server_hostent = gethostbyname(address); if (server_hostent == NULL) { fprintf(stderr, "Unable to get host name from %s\n", address); - goto bail1; + goto oom4; } wsi->sock = socket(AF_INET, SOCK_STREAM, 0); if (wsi->sock < 0) { fprintf(stderr, "Unable to open socket\n"); - goto bail1; + goto oom4; } - insert_wsi(this, wsi); - server_addr.sin_family = AF_INET; server_addr.sin_port = htons(port); server_addr.sin_addr = *((struct in_addr *)server_hostent->h_addr); @@ -165,9 +160,24 @@ libwebsocket_client_connect(struct libwebsocket_context *this, if (connect(wsi->sock, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1) { fprintf(stderr, "Connect failed\n"); - goto bail1; + goto oom4; } + /* into fd -> wsi hashtable */ + + insert_wsi(this, wsi); + + /* into internal poll list */ + + this->fds[this->fds_count].fd = wsi->sock; + this->fds[this->fds_count].revents = 0; + this->fds[this->fds_count++].events = POLLIN; + + /* external POLL support via protocol 0 */ + this->protocols[0].callback(this, wsi, + LWS_CALLBACK_ADD_POLL_FD, + (void *)(long)wsi->sock, NULL, POLLIN); + /* we are connected to server, or proxy */ if (this->http_proxy_port) { @@ -179,349 +189,41 @@ libwebsocket_client_connect(struct libwebsocket_context *this, goto bail1; } - pfd.fd = wsi->sock; - pfd.events = POLLIN; - pfd.revents = 0; + libwebsocket_set_timeout(wsi, + PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, 5); - n = poll(&pfd, 1, 5000); - if (n <= 0) { - close(wsi->sock); - fprintf(stderr, "libwebsocket_client_handshake " - "timeout on proxy response"); - goto bail1; - } + wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY; - n = recv(wsi->sock, pkt, sizeof pkt, 0); - if (n < 0) { - close(wsi->sock); - fprintf(stderr, "ERROR reading from proxy socket\n"); - goto bail1; - } - - pkt[13] = '\0'; - if (strcmp(pkt, "HTTP/1.0 200 ") != 0) { - close(wsi->sock); - fprintf(stderr, "ERROR from proxy: %s\n", pkt); - goto bail1; - } - - /* we can just start sending to proxy */ + return wsi; } -#ifdef LWS_OPENSSL_SUPPORT - if (ssl_connection) { - - wsi->ssl = SSL_new(this->ssl_client_ctx); - wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE); - SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); - - if (SSL_connect(wsi->ssl) <= 0) { - fprintf(stderr, "SSL connect error %s\n", - ERR_error_string(ERR_get_error(), ssl_err_buf)); - goto bail1; - } - - n = SSL_get_verify_result(wsi->ssl); - if (n != X509_V_OK) { - if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || - ssl_connection != 2) { - - fprintf(stderr, "server's cert didn't " - "look good %d\n", n); - goto bail2; - } - } - } else { - wsi->ssl = NULL; -#endif - - -#ifdef LWS_OPENSSL_SUPPORT - } -#endif - /* - * create the random key + * 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 */ - n = read(this->fd_random, hash, 16); - if (n != 16) { - fprintf(stderr, "Unable to read from random device %s\n", - SYSTEM_RANDOM_FILEPATH); - goto bail2; - } - - 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, "GET %s HTTP/1.1\x0d\x0a", path); - p += sprintf(p, "Host: %s\x0d\x0a", host); - p += sprintf(p, "Upgrade: websocket\x0d\x0a"); - p += sprintf(p, "Connection: Upgrade\x0d\x0aSec-WebSocket-Key: "); - strcpy(p, key_b64); - p += strlen(key_b64); - p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", origin); - if (protocol != NULL) - p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", protocol); - p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a", - wsi->ietf_spec_revision); - - - /* prepare the expected server accept response */ - - strcpy(buf, key_b64); - strcpy(&buf[strlen(buf)], magic_websocket_guid); - - SHA1((unsigned char *)buf, strlen(buf), (unsigned char *)hash); - - lws_b64_encode_string(hash, 20, wsi->initial_handshake_hash_base64, - sizeof wsi->initial_handshake_hash_base64); - - /* send our request to the server */ - -#ifdef LWS_OPENSSL_SUPPORT - if (ssl_connection) - n = SSL_write(wsi->ssl, pkt, p - pkt); - else -#endif - n = send(wsi->sock, pkt, p - pkt, 0); - - if (n < 0) { - fprintf(stderr, "ERROR writing to client socket\n"); - goto bail2; - } - - wsi->parser_state = WSI_TOKEN_NAME_PART; - + wsi->mode = LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE; pfd.fd = wsi->sock; - pfd.events = POLLIN; - pfd.revents = 0; + pfd.revents = POLLIN; + libwebsocket_service_fd(this, &pfd); - n = poll(&pfd, 1, 5000); - if (n < 0) { - fprintf(stderr, "libwebsocket_client_handshake socket error " - "while waiting for handshake response"); - goto bail2; - } - if (n == 0) { - fprintf(stderr, "libwebsocket_client_handshake timeout " - "while waiting for handshake response"); - goto bail2; - } - - /* 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 - */ - -#ifdef LWS_OPENSSL_SUPPORT - if (ssl_connection) - len = SSL_read(wsi->ssl, pkt, sizeof pkt); - else -#endif - len = recv(wsi->sock, pkt, sizeof pkt, 0); - - if (len < 0) { - fprintf(stderr, "libwebsocket_client_handshake read error\n"); - goto bail2; - } - - p = pkt; - for (n = 0; n < len; n++) - libwebsocket_parse(wsi, *p++); - - if (wsi->parser_state != WSI_PARSING_COMPLETE) { - fprintf(stderr, "libwebsocket_client_handshake server response" - " failed parsing\n"); - goto bail2; - } - - /* - * well, what the server sent looked reasonable for syntax. - * Now let's confirm it sent all the necessary headers - */ - - if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || - !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || - !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || - !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len || - !wsi->utf8_token[WSI_TOKEN_NONCE].token_len || - (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && - protocol != NULL)) { - fprintf(stderr, "libwebsocket_client_handshake " - "missing required header(s)\n"); - pkt[len] = '\0'; - fprintf(stderr, "%s", pkt); - goto bail2; - } - - /* - * Everything seems to be there, now take a closer look at what is in - * each header - */ - - strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, - "101 switching protocols")) { - fprintf(stderr, "libwebsocket_client_handshake server sent bad" - " HTTP response '%s'\n", - wsi->utf8_token[WSI_TOKEN_HTTP].token); - goto bail2; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, "websocket")) { - fprintf(stderr, "libwebsocket_client_handshake server sent bad" - " Upgrade header '%s'\n", - wsi->utf8_token[WSI_TOKEN_UPGRADE].token); - goto bail2; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token); - if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, "upgrade")) { - fprintf(stderr, "libwebsocket_client_handshake server sent bad" - " Connection hdr '%s'\n", - wsi->utf8_token[WSI_TOKEN_CONNECTION].token); - goto bail2; - } - /* - * confirm the protocol the server wants to talk was in the list of - * protocols we offered - */ - - if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) { - - /* no protocol name to work from, default to first protocol */ - wsi->protocol = &this->protocols[0]; - - goto check_accept; - } - - pc = protocol; - while (*pc && !okay) { - if ((!strncmp(pc, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) && - (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' || - pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) { - okay = 1; - continue; - } - while (*pc && *pc != ',') - pc++; - while (*pc && *pc != ' ') - pc++; - } - if (!okay) { - fprintf(stderr, "libwebsocket_client_handshake server " - "sent bad protocol '%s'\n", - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); - goto bail2; - } - - /* - * identify the selected protocol struct and set it - */ - n = 0; - wsi->protocol = NULL; - while (this->protocols[n].callback) { - if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - this->protocols[n].name) == 0) - wsi->protocol = &this->protocols[n]; - n++; - } - - if (wsi->protocol == NULL) { - fprintf(stderr, "libwebsocket_client_handshake server " - "requested protocol '%s', which we " - "said we supported but we don't!\n", - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); - goto bail2; - } - -check_accept: - /* - * Confirm his accept token is the same as the one we precomputed - */ - - if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, - wsi->initial_handshake_hash_base64)) { - fprintf(stderr, "libwebsocket_client_handshake server sent " - "bad ACCEPT '%s' vs computed '%s'\n", - wsi->utf8_token[WSI_TOKEN_ACCEPT].token, - wsi->initial_handshake_hash_base64); - goto bail2; - } - - /* - * Calculate the masking key to use when sending data to server - */ - - strcpy(buf, key_b64); - p = buf + strlen(key_b64); - strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token); - p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len; - strcpy(p, magic_websocket_04_masking_guid); - SHA1((unsigned char *)buf, strlen(buf), wsi->masking_key_04); - - /* allocate the per-connection user memory (if any) */ - - if (wsi->protocol->per_session_data_size) { - wsi->user_space = malloc( - wsi->protocol->per_session_data_size); - if (wsi->user_space == NULL) { - fprintf(stderr, "Out of memory for " - "conn user space\n"); - goto bail2; - } - } else - wsi->user_space = NULL; - - /* okay he is good to go */ - - this->fds[this->fds_count].fd = wsi->sock; - this->fds[this->fds_count].revents = 0; - this->fds[this->fds_count++].events = POLLIN; - - /* external POLL support via protocol 0 */ - this->protocols[0].callback(this, wsi, - LWS_CALLBACK_ADD_POLL_FD, - (void *)(long)wsi->sock, NULL, POLLIN); - - - wsi->state = WSI_STATE_ESTABLISHED; - wsi->mode = LWS_CONNMODE_WS_CLIENT; - - fprintf(stderr, "handshake OK for protocol %s\n", wsi->protocol->name); - - /* call him back to inform him he is up */ - - wsi->protocol->callback(this, wsi, - LWS_CALLBACK_CLIENT_ESTABLISHED, - wsi->user_space, - NULL, 0); return wsi; +oom4: + if (wsi->c_protocol) + free(wsi->c_protocol); + +oom3: + free(wsi->c_origin); + +oom2: + free(wsi->c_host); + +oom1: + free(wsi->c_path); -bail2: - libwebsocket_close_and_free_session(this, wsi); bail1: free(wsi); diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index a35193de..0e648179 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -21,6 +21,19 @@ #include "private-libwebsockets.h" +/* + * In-place str to lower case + */ + +static void +strtolower(char *s) +{ + while (*s) { + *s = tolower(*s); + s++; + } +} + /* file descriptor hash management */ struct libwebsocket * @@ -285,7 +298,18 @@ libwebsocket_service_fd(struct libwebsocket_context *this, unsigned int clilen; struct sockaddr_in cli_addr; struct timeval tv; - + static const char magic_websocket_guid[] = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + static const char magic_websocket_04_masking_guid[] = + "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; + char hash[20]; + char pkt[1024]; + char *p = &pkt[0]; + const char *pc; + int okay = 0; +#ifdef LWS_OPENSSL_SUPPORT + char ssl_err_buf[512]; +#endif /* * you can call us with pollfd = NULL to just allow the once-per-second * global timeout checks; if less than a second since the last check @@ -601,6 +625,397 @@ libwebsocket_service_fd(struct libwebsocket_context *this, } break; + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & (POLLERR | POLLHUP)) { + + fprintf(stderr, "Proxy connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + libwebsocket_close_and_free_session(this, wsi); + return 1; + } + + n = recv(wsi->sock, pkt, sizeof pkt, 0); + if (n < 0) { + libwebsocket_close_and_free_session(this, wsi); + fprintf(stderr, "ERROR reading from proxy socket\n"); + return 1; + } + + pkt[13] = '\0'; + if (strcmp(pkt, "HTTP/1.0 200 ") != 0) { + libwebsocket_close_and_free_session(this, wsi); + fprintf(stderr, "ERROR from proxy: %s\n", pkt); + return 1; + } + + /* clear his proxy connection timeout */ + + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* fallthru */ + + case LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE: + + #ifdef LWS_OPENSSL_SUPPORT + if (wsi->use_ssl) { + + wsi->ssl = SSL_new(this->ssl_client_ctx); + wsi->client_bio = BIO_new_socket(wsi->sock, BIO_NOCLOSE); + SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); + + if (SSL_connect(wsi->ssl) <= 0) { + fprintf(stderr, "SSL connect error %s\n", + ERR_error_string(ERR_get_error(), ssl_err_buf)); + libwebsocket_close_and_free_session(this, wsi); + return 1; + } + + n = SSL_get_verify_result(wsi->ssl); + if (n != X509_V_OK) { + if (n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + wsi->use_ssl != 2) { + + fprintf(stderr, "server's cert didn't " + "look good %d\n", n); + libwebsocket_close_and_free_session(this, wsi); + return 1; + } + } + } else { + wsi->ssl = NULL; + #endif + + + #ifdef LWS_OPENSSL_SUPPORT + } + #endif + + /* + * create the random key + */ + + n = read(this->fd_random, hash, 16); + if (n != 16) { + fprintf(stderr, "Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + free(wsi->c_path); + free(wsi->c_host); + free(wsi->c_origin); + if (wsi->c_protocol) + free(wsi->c_protocol); + libwebsocket_close_and_free_session(this, wsi); + return 1; + } + + lws_b64_encode_string(hash, 16, wsi->key_b64, + sizeof wsi->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, "GET %s HTTP/1.1\x0d\x0a", wsi->c_path); + p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host); + p += sprintf(p, "Upgrade: websocket\x0d\x0a"); + p += sprintf(p, "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, wsi->key_b64); + p += strlen(wsi->key_b64); + p += sprintf(p, "\x0d\x0aSec-WebSocket-Origin: %s\x0d\x0a", + wsi->c_origin); + if (wsi->c_protocol != NULL) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + wsi->c_protocol); + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a\x0d\x0a", + wsi->ietf_spec_revision); + + /* done with these now */ + + free(wsi->c_path); + free(wsi->c_host); + free(wsi->c_origin); + + /* prepare the expected server accept response */ + + strcpy((char *)buf, wsi->key_b64); + strcpy((char *)&buf[strlen((char *)buf)], magic_websocket_guid); + + SHA1(buf, strlen((char *)buf), (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, + wsi->initial_handshake_hash_base64, + sizeof wsi->initial_handshake_hash_base64); + + /* send our request to the server */ + + #ifdef LWS_OPENSSL_SUPPORT + if (wsi->use_ssl) + n = SSL_write(wsi->ssl, pkt, p - pkt); + else + #endif + n = send(wsi->sock, pkt, p - pkt, 0); + + if (n < 0) { + fprintf(stderr, "ERROR writing to client socket\n"); + libwebsocket_close_and_free_session(this, wsi); + return 1; + } + + wsi->parser_state = WSI_TOKEN_NAME_PART; + wsi->mode = LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY; + libwebsocket_set_timeout(wsi, + PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, 5); + + break; + + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + + /* handle server hung up on us */ + + if (pollfd->revents & (POLLERR | POLLHUP)) { + + fprintf(stderr, "Server connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto bail3; + } + + + /* 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 + */ + + #ifdef LWS_OPENSSL_SUPPORT + if (wsi->use_ssl) + len = SSL_read(wsi->ssl, pkt, sizeof pkt); + else + #endif + len = recv(wsi->sock, pkt, sizeof pkt, 0); + + if (len < 0) { + fprintf(stderr, + "libwebsocket_client_handshake read error\n"); + goto bail3; + } + + p = pkt; + for (n = 0; n < len; n++) + libwebsocket_parse(wsi, *p++); + + if (wsi->parser_state != WSI_PARSING_COMPLETE) { + fprintf(stderr, "libwebsocket_client_handshake " + "server response ailed parsing\n"); + goto bail3; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + */ + + if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || + !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || + !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || + !wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len || + !wsi->utf8_token[WSI_TOKEN_NONCE].token_len || + (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + wsi->c_protocol != NULL)) { + fprintf(stderr, "libwebsocket_client_handshake " + "missing required header(s)\n"); + pkt[len] = '\0'; + fprintf(stderr, "%s", pkt); + goto bail3; + } + + /* + * Everything seems to be there, now take a closer look at what + * is in each header + */ + + strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, + "101 switching protocols")) { + fprintf(stderr, "libwebsocket_client_handshake " + "server sent bad HTTP response '%s'\n", + wsi->utf8_token[WSI_TOKEN_HTTP].token); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_UPGRADE].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_UPGRADE].token, + "websocket")) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad Upgrade header '%s'\n", + wsi->utf8_token[WSI_TOKEN_UPGRADE].token); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_CONNECTION].token); + if (strcmp(wsi->utf8_token[WSI_TOKEN_CONNECTION].token, + "upgrade")) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad Connection hdr '%s'\n", + wsi->utf8_token[WSI_TOKEN_CONNECTION].token); + goto bail3; + } + + + pc = wsi->c_protocol; + + /* + * confirm the protocol the server wants to talk was in the list + * of protocols we offered + */ + + if (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len) { + + /* + * no protocol name to work from, + * default to first protocol + */ + wsi->protocol = &this->protocols[0]; + + free(wsi->c_protocol); + + goto check_accept; + } + + while (*pc && !okay) { + if ((!strncmp(pc, + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len)) && + (pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == ',' || + pc[wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len] == '\0')) { + okay = 1; + continue; + } + while (*pc && *pc != ',') + pc++; + while (*pc && *pc != ' ') + pc++; + } + + /* done with him now */ + + if (wsi->c_protocol) + free(wsi->c_protocol); + + + if (!okay) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad protocol '%s'\n", + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + goto bail2; + } + + /* + * identify the selected protocol struct and set it + */ + n = 0; + wsi->protocol = NULL; + while (this->protocols[n].callback) { + if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + this->protocols[n].name) == 0) + wsi->protocol = &this->protocols[n]; + n++; + } + + if (wsi->protocol == NULL) { + fprintf(stderr, "libwebsocket_client_handshake server " + "requested protocol '%s', which we " + "said we supported but we don't!\n", + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + goto bail2; + } + + check_accept: + /* + * Confirm his accept token is the one we precomputed + */ + + if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, + wsi->initial_handshake_hash_base64)) { + fprintf(stderr, "libwebsocket_client_handshake server " + "sent bad ACCEPT '%s' vs computed '%s'\n", + wsi->utf8_token[WSI_TOKEN_ACCEPT].token, + wsi->initial_handshake_hash_base64); + goto bail2; + } + + /* + * Calculate the masking key to use when sending data to server + */ + + strcpy((char *)buf, wsi->key_b64); + p = (char *)buf + strlen(wsi->key_b64); + strcpy(p, wsi->utf8_token[WSI_TOKEN_NONCE].token); + p += wsi->utf8_token[WSI_TOKEN_NONCE].token_len; + strcpy(p, magic_websocket_04_masking_guid); + SHA1(buf, strlen((char *)buf), wsi->masking_key_04); + + /* allocate the per-connection user memory (if any) */ + + if (wsi->protocol->per_session_data_size) { + wsi->user_space = malloc( + wsi->protocol->per_session_data_size); + if (wsi->user_space == NULL) { + fprintf(stderr, "Out of memory for " + "conn user space\n"); + goto bail2; + } + } else + wsi->user_space = NULL; + + /* clear his proxy connection timeout */ + + libwebsocket_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* mark him as being alive */ + + wsi->state = WSI_STATE_ESTABLISHED; + wsi->mode = LWS_CONNMODE_WS_CLIENT; + + fprintf(stderr, "handshake OK for protocol %s\n", + wsi->protocol->name); + + /* call him back to inform him he is up */ + + wsi->protocol->callback(this, wsi, + LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, + NULL, 0); + + break; + +bail3: + if (wsi->c_protocol) + free(wsi->c_protocol); + +bail2: + libwebsocket_close_and_free_session(this, wsi); + return 1; + + case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: @@ -829,6 +1244,28 @@ libwebsocket_callback_on_writable_all_protocol( return 0; } +/** + * libwebsocket_set_timeout() - marks the wsi as subject to a timeout + * + * You will not need this unless you are doing something special + * + * @wsi: Websocket connection instance + * @reason: timeout reason + * @secs: how many seconds + */ + +void +libwebsocket_set_timeout(struct libwebsocket *wsi, + enum pending_timeout reason, int secs) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + wsi->pending_timeout_limit = tv.tv_sec + secs; + wsi->pending_timeout = reason; +} + /** * libwebsocket_get_socket_fd() - returns the socket file descriptor diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index d5885413..5fbbcef4 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -106,7 +106,7 @@ enum lws_connection_states { WSI_STATE_DEAD_SOCKET, WSI_STATE_ESTABLISHED, WSI_STATE_CLIENT_UNCONNECTED, - WSI_STATE_RETURNED_CLOSE_ALREADY + WSI_STATE_RETURNED_CLOSE_ALREADY, }; enum lws_rx_parse_state { @@ -141,6 +141,11 @@ enum connection_mode { LWS_CONNMODE_WS_SERVING, LWS_CONNMODE_WS_CLIENT, + /* transient modes */ + LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY, + LWS_CONNMODE_WS_CLIENT_ISSUE_HANDSHAKE, + LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY, + /* special internal types */ LWS_CONNMODE_SERVER_LISTENER, LWS_CONNMODE_BROADCAST_PROXY_LISTENER, @@ -182,7 +187,9 @@ struct libwebsocket_context { enum pending_timeout { NO_PENDING_TIMEOUT = 0, + PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, + PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, PENDING_TIMEOUT_AWAITING_PING, }; @@ -217,6 +224,7 @@ struct libwebsocket { /* 04 protocol specific */ + char key_b64[150]; unsigned char masking_key_04[20]; unsigned char frame_masking_nonce_04[4]; unsigned char frame_mask_04[20]; @@ -232,10 +240,15 @@ struct libwebsocket { /* client support */ char initial_handshake_hash_base64[30]; enum connection_mode mode; + char *c_path; + char *c_host; + char *c_origin; + char *c_protocol; #ifdef LWS_OPENSSL_SUPPORT SSL *ssl; BIO *client_bio; + int use_ssl; #endif void *user_space; @@ -282,6 +295,10 @@ insert_wsi(struct libwebsocket_context *this, struct libwebsocket *wsi); extern int delete_from_fd(struct libwebsocket_context *this, int fd); +extern void +libwebsocket_set_timeout(struct libwebsocket *wsi, + enum pending_timeout reason, int secs); + #ifndef LWS_OPENSSL_SUPPORT unsigned char * diff --git a/libwebsockets-api-doc.html b/libwebsockets-api-doc.html index feb6638e..e351ab87 100644 --- a/libwebsockets-api-doc.html +++ b/libwebsockets-api-doc.html @@ -141,6 +141,27 @@ nothing is pending, or as soon as it services whatever was pending.
Protocol whose connections will get callbacks
+

libwebsocket_set_timeout - marks the wsi as subject to a timeout

+void +libwebsocket_set_timeout +(struct libwebsocket * wsi, +enum pending_timeout reason, +int secs) +

Arguments

+
+
wsi +
Websocket connection instance +
reason +
timeout reason +
secs +
how many seconds +
+

Description

+
+

+You will not need this unless you are doing something special +

+

libwebsocket_get_socket_fd - returns the socket file descriptor

int libwebsocket_get_socket_fd