diff --git a/lib/handshake.c b/lib/handshake.c index 206156c1..4c6ef58b 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -70,6 +70,309 @@ interpret_key(const char *key, unsigned long *result) return 0; } + +static int +handshake_76(struct libwebsocket *wsi) +{ + unsigned long key1, key2; + unsigned char sum[16]; + char *response; + char *p; + int n; + + /* Websocket 76? - confirm we have all the necessary pieces */ + + if (!wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len || + !wsi->utf8_token[WSI_TOKEN_HOST].token_len || + !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY1].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY2].token_len) + /* completed header processing, but missing some bits */ + goto bail; + + /* 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 bail; + } + } else + wsi->user_space = NULL; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = malloc(256 + + wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len + + wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len + + wsi->utf8_token[WSI_TOKEN_HOST].token_len + + wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len + + wsi->utf8_token[WSI_TOKEN_GET_URI].token_len + + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); + if (!response) { + fprintf(stderr, "Out of memory for response buffer\n"); + goto bail; + } + + p = response; + strcpy(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a"); + p += strlen("HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a"); + strcpy(p, "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Origin: "); + p += strlen("Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Origin: "); + strcpy(p, wsi->utf8_token[WSI_TOKEN_ORIGIN].token); + p += wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len; +#ifdef LWS_OPENSSL_SUPPORT + if (use_ssl) { + strcpy(p, "\x0d\x0aSec-WebSocket-Location: wss://"); + p += strlen("\x0d\x0aSec-WebSocket-Location: wss://"); + } else { +#endif + strcpy(p, "\x0d\x0aSec-WebSocket-Location: ws://"); + p += strlen("\x0d\x0aSec-WebSocket-Location: ws://"); +#ifdef LWS_OPENSSL_SUPPORT + } +#endif + strcpy(p, wsi->utf8_token[WSI_TOKEN_HOST].token); + p += wsi->utf8_token[WSI_TOKEN_HOST].token_len; + strcpy(p, wsi->utf8_token[WSI_TOKEN_GET_URI].token); + p += wsi->utf8_token[WSI_TOKEN_GET_URI].token_len; + + if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { + strcpy(p, "\x0d\x0aSec-WebSocket-Protocol: "); + p += strlen("\x0d\x0aSec-WebSocket-Protocol: "); + strcpy(p, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + p += wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len; + } + + strcpy(p, "\x0d\x0a\x0d\x0a"); + p += strlen("\x0d\x0a\x0d\x0a"); + + /* convert the two keys into 32-bit integers */ + + if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY1].token, &key1)) + goto bail; + if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY2].token, &key2)) + goto bail; + + /* lay them out in network byte order (MSB first */ + + sum[0] = key1 >> 24; + sum[1] = key1 >> 16; + sum[2] = key1 >> 8; + sum[3] = key1; + sum[4] = key2 >> 24; + sum[5] = key2 >> 16; + sum[6] = key2 >> 8; + sum[7] = key2; + + /* follow them with the challenge token we were sent */ + + memcpy(&sum[8], wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 8); + + /* + * compute the md5sum of that 16-byte series and use as our + * payload after our headers + */ + + MD5(sum, 16, (unsigned char *)p); + p += 16; + + /* it's complete: go ahead and send it */ + + debug("issuing response packet %d len\n", (int)(p - response)); +#ifdef DEBUG + fwrite(response, 1, p - response, stderr); +#endif + n = libwebsocket_write(wsi, (unsigned char *)response, + p - response, LWS_WRITE_HTTP); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + goto bail; + } + + /* alright clean up and set ourselves into established state */ + + free(response); + wsi->state = WSI_STATE_ESTABLISHED; + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, NULL, 0); + + return 0; + +bail: + return -1; +} + +/* + * Perform the newer BASE64-encoded handshake scheme + */ + +static int +handshake_04(struct libwebsocket *wsi) +{ + static const char *websocket_magic_guid_04 = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + char buf[MAX_WEBSOCKET_04_KEY_LEN + 37]; + unsigned char hash[20]; + int n; + char *response; + char *p; + int fd; + + if (!wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len || + !wsi->utf8_token[WSI_TOKEN_HOST].token_len || + !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY].token_len) + /* completed header processing, but missing some bits */ + goto bail; + + if (wsi->utf8_token[WSI_TOKEN_KEY].token_len >= + MAX_WEBSOCKET_04_KEY_LEN) { + fprintf(stderr, "Client sent handshake key longer " + "than max supported %d\n", MAX_WEBSOCKET_04_KEY_LEN); + goto bail; + } + + strcpy(buf, wsi->utf8_token[WSI_TOKEN_KEY].token); + strcpy(buf + wsi->utf8_token[WSI_TOKEN_KEY].token_len, + websocket_magic_guid_04); + + SHA1((unsigned char *)buf, wsi->utf8_token[WSI_TOKEN_KEY].token_len + + strlen(websocket_magic_guid_04), hash); + + n = lws_b64_encode_string((char *)hash, buf, sizeof buf); + if (n < 0) { + fprintf(stderr, "Base64 encoded hash too long\n"); + goto bail; + } + + /* 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 bail; + } + } else + wsi->user_space = NULL; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = malloc(256 + + wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len + + wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len + + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); + if (!response) { + fprintf(stderr, "Out of memory for response buffer\n"); + goto bail; + } + + p = response; + strcpy(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a"); + p += strlen("HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a"); + strcpy(p, "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Accept: "); + p += strlen("Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Accept: "); + strcpy(p, buf); + p += n; + + /* select the nonce */ + + fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); + if (fd < 1) { + fprintf(stderr, "Unable to open random device %s\n", + SYSTEM_RANDOM_FILEPATH); + free(wsi->user_space); + goto bail; + } + n = read(fd, hash, 16); + if (n != 16) { + fprintf(stderr, "Unable to read from random device %s\n", + SYSTEM_RANDOM_FILEPATH); + free(wsi->user_space); + goto bail; + } + close(fd); + + /* encode the nonce */ + + n = lws_b64_encode_string((const char *)hash, buf, sizeof buf); + if (n < 0) { + fprintf(stderr, "Failed to base 64 encode the nonce\n"); + free(wsi->user_space); + goto bail; + } + + /* apply the nonce */ + + strcpy(p, buf); + p += n; + + if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { + strcpy(p, "\x0d\x0aSec-WebSocket-Protocol: "); + p += strlen("\x0d\x0aSec-WebSocket-Protocol: "); + strcpy(p, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); + p += wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len; + } + + /* end of response packet */ + + strcpy(p, "\x0d\x0a\x0d\x0a"); + p += strlen("\x0d\x0a\x0d\x0a"); + + debug("issuing response packet %d len\n", (int)(p - response)); +#ifdef DEBUG + fwrite(response, 1, p - response, stderr); +#endif + n = libwebsocket_write(wsi, (unsigned char *)response, + p - response, LWS_WRITE_HTTP); + if (n < 0) { + fprintf(stderr, "ERROR writing to socket"); + goto bail; + } + + /* alright clean up and set ourselves into established state */ + + free(response); + wsi->state = WSI_STATE_ESTABLISHED; + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, NULL, 0); + + return 0; + + +bail: + return -1; +} + + /* * -04 of the protocol (actually the 80th version) has a radically different * handshake. The 04 spec gives the following idea @@ -83,7 +386,7 @@ interpret_key(const char *key, unsigned long *result) * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== * Sec-WebSocket-Origin: http://example.com * Sec-WebSocket-Protocol: chat, superchat - * Sec-WebSocket-Version: 4 + * Sec-WebSocket-Version: 4 * * The handshake from the server looks as follows: * @@ -106,10 +409,6 @@ int libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) { size_t n; - char *p; - unsigned long key1, key2; - unsigned char sum[16]; - char *response; switch (wsi->state) { case WSI_STATE_HTTP: @@ -140,34 +439,11 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) return 0; } - /* Websocket - confirm we have all the necessary pieces */ - - if (!wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len || - !wsi->utf8_token[WSI_TOKEN_HOST].token_len || - !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || - !wsi->utf8_token[WSI_TOKEN_KEY1].token_len || - !wsi->utf8_token[WSI_TOKEN_KEY2].token_len) - /* completed header processing, but missing some bits */ - goto bail; - - /* are we happy about the draft version client side wants? */ - - if (wsi->utf8_token[WSI_TOKEN_DRAFT].token) { - wsi->ietf_spec_revision = - atoi(wsi->utf8_token[WSI_TOKEN_DRAFT].token); - switch (wsi->ietf_spec_revision) { - case 76: - case 3: - break; - default: - fprintf(stderr, "Rejecting handshake on seeing " - "unsupported draft request %d\n", - wsi->ietf_spec_revision); - goto bail; - } - } - - /* Make sure user side is happy about protocol */ + /* + * It's websocket + * + * Make sure user side is happy about protocol + */ while (wsi->protocol->callback) { @@ -196,126 +472,35 @@ libwebsocket_read(struct libwebsocket *wsi, unsigned char * buf, size_t len) goto bail; } - /* 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 bail; - } - } else - wsi->user_space = NULL; - - /* create the response packet */ - - /* make a buffer big enough for everything */ - - response = malloc(256 + - wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len + - wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len + - wsi->utf8_token[WSI_TOKEN_HOST].token_len + - wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len + - wsi->utf8_token[WSI_TOKEN_GET_URI].token_len + - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); - if (!response) { - fprintf(stderr, "Out of memory for response buffer\n"); - goto bail; - } - - p = response; - strcpy(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" - "Upgrade: WebSocket\x0d\x0a"); - p += strlen("HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" - "Upgrade: WebSocket\x0d\x0a"); - strcpy(p, "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Origin: "); - p += strlen("Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Origin: "); - strcpy(p, wsi->utf8_token[WSI_TOKEN_ORIGIN].token); - p += wsi->utf8_token[WSI_TOKEN_ORIGIN].token_len; -#ifdef LWS_OPENSSL_SUPPORT - if (use_ssl) { - strcpy(p, "\x0d\x0aSec-WebSocket-Location: wss://"); - p += strlen("\x0d\x0aSec-WebSocket-Location: wss://"); - } else { -#endif - strcpy(p, "\x0d\x0aSec-WebSocket-Location: ws://"); - p += strlen("\x0d\x0aSec-WebSocket-Location: ws://"); -#ifdef LWS_OPENSSL_SUPPORT - } -#endif - strcpy(p, wsi->utf8_token[WSI_TOKEN_HOST].token); - p += wsi->utf8_token[WSI_TOKEN_HOST].token_len; - strcpy(p, wsi->utf8_token[WSI_TOKEN_GET_URI].token); - p += wsi->utf8_token[WSI_TOKEN_GET_URI].token_len; - - if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { - strcpy(p, "\x0d\x0aSec-WebSocket-Protocol: "); - p += strlen("\x0d\x0aSec-WebSocket-Protocol: "); - strcpy(p, wsi->utf8_token[WSI_TOKEN_PROTOCOL].token); - p += wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len; - } - - strcpy(p, "\x0d\x0a\x0d\x0a"); - p += strlen("\x0d\x0a\x0d\x0a"); - - /* convert the two keys into 32-bit integers */ - - if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY1].token, &key1)) - goto bail; - if (interpret_key(wsi->utf8_token[WSI_TOKEN_KEY2].token, &key2)) - goto bail; - - /* lay them out in network byte order (MSB first */ - - sum[0] = key1 >> 24; - sum[1] = key1 >> 16; - sum[2] = key1 >> 8; - sum[3] = key1; - sum[4] = key2 >> 24; - sum[5] = key2 >> 16; - sum[6] = key2 >> 8; - sum[7] = key2; - - /* follow them with the challenge token we were sent */ - - memcpy(&sum[8], wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 8); - /* - * compute the md5sum of that 16-byte series and use as our - * payload after our headers + * find out which spec version the client is using + * if this header is not given, we default to 00 (aka 76) */ - MD5(sum, 16, (unsigned char *)p); - p += 16; + if (wsi->utf8_token[WSI_TOKEN_VERSION].token_len) + wsi->ietf_spec_revision = + atoi(wsi->utf8_token[WSI_TOKEN_VERSION].token); - /* it's complete: go ahead and send it */ + /* + * Websocket 04+? + * confirm we have all the necessary pieces + */ - debug("issuing response packet %d len\n", (int)(p - response)); -#ifdef DEBUG - fwrite(response, 1, p - response, stderr); -#endif - n = libwebsocket_write(wsi, (unsigned char *)response, - p - response, LWS_WRITE_HTTP); - if (n < 0) { - fprintf(stderr, "ERROR writing to socket"); + switch (wsi->ietf_spec_revision) { + case 0: /* applies to 76 and 00 */ + if (handshake_76(wsi)) + goto bail; + break; + case 4: /* 04 */ + if (handshake_04(wsi)) + goto bail; + break; + default: + fprintf(stderr, "Unknown client spec version %d\n", + wsi->ietf_spec_revision); goto bail; } - /* alright clean up and set ourselves into established state */ - - free(response); - wsi->state = WSI_STATE_ESTABLISHED; - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - - /* notify user code that we're ready to roll */ - - if (wsi->protocol->callback) - wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, - wsi->user_space, NULL, 0); break; case WSI_STATE_ESTABLISHED: diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6917d1e4..742afa7a 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -603,12 +603,12 @@ int libwebsocket_create_server(int port, this->wsi[this->fds_count]->user_space = NULL; /* - * Default protocol is 76 + * Default protocol is 76 / 00 * After 76, there's a header specified to inform which * draft the client wants, when that's seen we modify * the individual connection's spec revision accordingly */ - this->wsi[this->fds_count]->ietf_spec_revision = 76; + this->wsi[this->fds_count]->ietf_spec_revision = 0; fill_in_fds: diff --git a/lib/parsers.c b/lib/parsers.c index 64725828..f153790d 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -203,11 +203,11 @@ static int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) switch (wsi->ietf_spec_revision) { /* Firefox 4.0b6 likes this as of 30 Oct */ - case 76: + case 0: if (c == 0xff) wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF; break; - case 0: + case 4: break; } diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 2d02cc2c..8001a06a 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -46,6 +46,7 @@ #endif #include +#include #include "libwebsockets.h" /* #define DEBUG */ @@ -73,6 +74,9 @@ extern int use_ssl; #define MAX_BROADCAST_PAYLOAD 1024 #define LWS_MAX_PROTOCOLS 10 +#define MAX_WEBSOCKET_04_KEY_LEN 128 +#define SYSTEM_RANDOM_FILEPATH "/dev/random" + enum lws_connection_states { WSI_STATE_HTTP, WSI_STATE_HTTP_HEADERS,