diff --git a/lib/Makefile.am b/lib/Makefile.am index b1eb4344..ad4592bb 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -3,6 +3,8 @@ include_HEADERS=libwebsockets.h dist_libwebsockets_la_SOURCES=libwebsockets.c \ handshake.c \ parsers.c \ + client.c \ + client-parser.c \ libwebsockets.h \ base64-decode.c \ client-handshake.c \ diff --git a/lib/client-parser.c b/lib/client-parser.c new file mode 100644 index 00000000..72e91b29 --- /dev/null +++ b/lib/client-parser.c @@ -0,0 +1,517 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2013 Andy Green + * + * 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" + +#ifdef WIN32 +#include +#endif + +int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c) +{ + int n; + unsigned char buf[20 + 4]; + int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; + int handled; + struct lws_tokens eff_buf; + int m; + + lwsl_parser(" CRX: %02X %d\n", c, wsi->lws_rx_parse_state); + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + + switch (wsi->ietf_spec_revision) { + /* Firefox 4.0b6 likes this as of 30 Oct */ + case 0: + if (c == 0xff) + wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF; + if (c == 0) { + wsi->lws_rx_parse_state = + LWS_RXPS_EAT_UNTIL_76_FF; + wsi->rx_user_buffer_head = 0; + } + break; + case 4: + case 5: + case 6: + case 7: + case 8: + case 13: + /* + * 04 logical framing from the spec (all this is masked when + * incoming and has to be unmasked) + * + * We ignore the possibility of extension data because we don't + * negotiate any extensions at the moment. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|R| Payload len | Extended payload length | + * |I|S|S|S| (4) |S| (7) | (16/63) | + * |N|V|V|V| |V| | (if payload len==126/127) | + * | |1|2|3| |4| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | | Extension data | + * +-------------------------------+ - - - - - - - - - - - - - - - + + * : : + * +---------------------------------------------------------------+ + * : Application data : + * +---------------------------------------------------------------+ + * + * We pass payload through to userland as soon as we get it, ignoring + * FIN. It's up to userland to buffer it up if it wants to see a + * whole unfragmented block of the original size (which may be up to + * 2^63 long!) + * + * Notice in v7 RSV4 is set to indicate 32-bit frame key is coming in + * after length, unlike extension data which is now deprecated, this + * does not impact the payload length calculation. + */ + + /* + * 04 spec defines the opcode like this: (1, 2, and 3 are + * "control frame" opcodes which may not be fragmented or + * have size larger than 126) + * + * frame-opcode = + * %x0 ; continuation frame + * / %x1 ; connection close + * / %x2 ; ping + * / %x3 ; pong + * / %x4 ; text frame + * / %x5 ; binary frame + * / %x6-F ; reserved + * + * FIN (b7) + */ + + if (wsi->ietf_spec_revision < 7) + switch (c & 0xf) { + case LWS_WS_OPCODE_04__CONTINUATION: + wsi->opcode = + LWS_WS_OPCODE_07__CONTINUATION; + break; + case LWS_WS_OPCODE_04__CLOSE: + wsi->opcode = LWS_WS_OPCODE_07__CLOSE; + break; + case LWS_WS_OPCODE_04__PING: + wsi->opcode = LWS_WS_OPCODE_07__PING; + break; + case LWS_WS_OPCODE_04__PONG: + wsi->opcode = LWS_WS_OPCODE_07__PONG; + break; + case LWS_WS_OPCODE_04__TEXT_FRAME: + wsi->opcode = + LWS_WS_OPCODE_07__TEXT_FRAME; + break; + case LWS_WS_OPCODE_04__BINARY_FRAME: + wsi->opcode = + LWS_WS_OPCODE_07__BINARY_FRAME; + break; + default: + lwsl_warn("reserved opcodes not " + "usable pre v7 protocol\n"); + return -1; + } + else + wsi->opcode = c & 0xf; + wsi->rsv = (c & 0x70); + wsi->final = !!((c >> 7) & 1); + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + + default: + lwsl_err("client_rx_sm doesn't know how " + "to handle spec version %02d\n", + wsi->ietf_spec_revision); + break; + } + break; + + + case LWS_RXPS_04_FRAME_HDR_LEN: + + if ((c & 0x80) && wsi->ietf_spec_revision < 7) { + lwsl_warn("Frame has extensions set illegally 4\n"); + /* kill the connection */ + return -1; + } + + wsi->this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->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->opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->rx_packet_length = c; + if (wsi->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->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->rx_packet_length |= c; + if (wsi->this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->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->rx_packet_length = ((size_t)c) << 56; +#else + wsi->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->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->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->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->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->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->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->rx_packet_length |= (size_t)c; + if (wsi->this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->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->frame_masking_nonce_04[0] = c; + if (c) + wsi->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->frame_masking_nonce_04[1] = c; + if (c) + wsi->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->frame_masking_nonce_04[2] = c; + if (c) + wsi->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->frame_masking_nonce_04[3] = c; + if (c) + wsi->all_zero_nonce = 0; + + if (wsi->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_EAT_UNTIL_76_FF: + if (c == 0xff) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto issue; + } + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + (wsi->rx_user_buffer_head++)] = c; + + if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) + break; +issue: + if (wsi->protocol->callback) + wsi->protocol->callback(wsi->protocol->owning_server, + wsi, + LWS_CALLBACK_CLIENT_RECEIVE, + wsi->user_space, + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head); + wsi->rx_user_buffer_head = 0; + break; + case LWS_RXPS_SEEN_76_FF: + if (c) + break; + + lwsl_parser("Seen that client is requesting " + "a v76 close, sending ack\n"); + buf[0] = 0xff; + buf[1] = 0; + n = libwebsocket_write(wsi, buf, 2, LWS_WRITE_HTTP); + if (n < 0) { + lwsl_warn("LWS_RXPS_SEEN_76_FF: ERROR writing to socket\n"); + return -1; + } + lwsl_parser(" v76 close ack sent, server closing skt\n"); + /* returning < 0 will get it closed in parent */ + return -1; + + case LWS_RXPS_PULLING_76_LENGTH: + break; + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + if ((!wsi->this_frame_masked) || wsi->all_zero_nonce) + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + (wsi->rx_user_buffer_head++)] = c; + else + wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + + (wsi->rx_user_buffer_head++)] = + wsi->xor_mask(wsi, c); + + if (--wsi->rx_packet_length == 0) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) + break; +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->opcode) { + case LWS_WS_OPCODE_07__CLOSE: + /* is this an acknowledgement of our close? */ + if (wsi->state == WSI_STATE_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 packet len = %d\n", wsi->rx_user_buffer_head); + /* parrot the close packet payload back */ + n = libwebsocket_write(wsi, (unsigned char *) + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head, LWS_WRITE_CLOSE); + lwsl_parser("client writing close ack returned %d\n", n); + wsi->state = WSI_STATE_RETURNED_CLOSE_ALREADY; + /* close the connection */ + return -1; + + case LWS_WS_OPCODE_07__PING: + /* parrot the ping packet payload back as a pong*/ + n = libwebsocket_write(wsi, (unsigned char *) + &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], + wsi->rx_user_buffer_head, LWS_WRITE_PONG); + handled = 1; + break; + + case LWS_WS_OPCODE_07__PONG: + /* keep the statistics... */ + wsi->pings_vs_pongs--; + + /* issue it */ + callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; + break; + + case LWS_WS_OPCODE_07__CONTINUATION: + case LWS_WS_OPCODE_07__TEXT_FRAME: + case LWS_WS_OPCODE_07__BINARY_FRAME: + break; + + default: + + lwsl_parser("Reserved opcode 0x%2X\n", wsi->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->rx_user_buffer[ + LWS_SEND_BUFFER_PRE_PADDING]; + eff_buf.token_len = wsi->rx_user_buffer_head; + + for (n = 0; n < wsi->count_active_extensions; n++) { + m = wsi->active_extensions[n]->callback( + wsi->protocol->owning_server, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX, + wsi->active_extensions_user[n], + &eff_buf, 0); + if (m) + handled = 1; + } + + if (!handled) { + lwsl_ext("Unhandled extended opcode " + "0x%x - ignoring frame\n", wsi->opcode); + wsi->rx_user_buffer_head = 0; + + return 0; + } + + 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 libwebsocket_write + */ + if (handled) + goto already_done; + + eff_buf.token = &wsi->rx_user_buffer[ + LWS_SEND_BUFFER_PRE_PADDING]; + eff_buf.token_len = wsi->rx_user_buffer_head; + + for (n = 0; n < wsi->count_active_extensions; n++) { + m = wsi->active_extensions[n]->callback( + wsi->protocol->owning_server, + wsi->active_extensions[n], wsi, + LWS_EXT_CALLBACK_PAYLOAD_RX, + wsi->active_extensions_user[n], + &eff_buf, 0); + if (m < 0) { + lwsl_ext( + "Extension '%s' failed to handle payload!\n", + wsi->active_extensions[n]->name); + return -1; + } + } + + if (eff_buf.token_len > 0) { + eff_buf.token[eff_buf.token_len] = '\0'; + + if (wsi->protocol->callback) + wsi->protocol->callback( + wsi->protocol->owning_server, + wsi, + (enum libwebsocket_callback_reasons)callback_action, + wsi->user_space, + eff_buf.token, + eff_buf.token_len); + } +already_done: + wsi->rx_user_buffer_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/lib/client.c b/lib/client.c new file mode 100644 index 00000000..1fa35b04 --- /dev/null +++ b/lib/client.c @@ -0,0 +1,1015 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2013 Andy Green + * + * 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" + +#ifdef WIN32 +#include +#include +#else +#ifdef LWS_BUILTIN_GETIFADDRS +#include +#else +#include +#endif +#include +#include +#include +#endif + +#ifdef LWS_OPENSSL_SUPPORT +extern int openssl_websocket_private_data_index; +#endif + +int lws_client_socket_service(struct libwebsocket_context *context, struct libwebsocket *wsi, struct pollfd *pollfd) +{ + int n; + char pkt[1024]; + char *p = &pkt[0]; + int len; + char c; +#ifdef LWS_OPENSSL_SUPPORT + char ssl_err_buf[512]; +#endif + + switch (wsi->mode) { + + case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & (POLLERR | POLLHUP)) { + + lwsl_warn("Proxy connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 1; + } + + n = recv(wsi->sock, pkt, sizeof pkt, 0); + if (n < 0) { + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + lwsl_err("ERROR reading from proxy socket\n"); + return 1; + } + + pkt[13] = '\0'; + if (strcmp(pkt, "HTTP/1.0 200 ") != 0) { + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + lwsl_err("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: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + */ + + #ifdef LWS_OPENSSL_SUPPORT + + /* + * take care of our libwebsocket_callback_on_writable + * happening at a time when there's no real connection yet + */ + + pollfd->events &= ~POLLOUT; + + /* external POLL support via protocol 0 */ + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLEAR_MODE_POLL_FD, + (void *)(long)wsi->sock, NULL, POLLOUT); + + /* we can retry this... so just cook the SSL BIO the first time */ + + if (wsi->use_ssl && !wsi->ssl) { + + wsi->ssl = SSL_new(context->ssl_client_ctx); + wsi->client_bio = BIO_new_socket(wsi->sock, + BIO_NOCLOSE); + SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); + + SSL_set_ex_data(wsi->ssl, + openssl_websocket_private_data_index, + context); + } + + if (wsi->use_ssl) { + n = SSL_connect(wsi->ssl); + + if (n < 0) { + n = SSL_get_error(wsi->ssl, n); + + if (n == SSL_ERROR_WANT_READ || + 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 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 -> SSL_ERROR_WANT_... retrying\n"); + libwebsocket_callback_on_writable(context, wsi); + + return 0; /* no error */ + } + n = -1; + } + + if (n <= 0) { + /* + * retry if new data comes until we + * run into the connection timeout or win + */ + + lwsl_err("SSL connect error %s\n", + ERR_error_string(ERR_get_error(), + ssl_err_buf)); + return 0; + } + + n = SSL_get_verify_result(wsi->ssl); + if ((n != X509_V_OK) && ( + n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + wsi->use_ssl != 2)) { + + lwsl_err("server's cert didn't " + "look good %d\n", n); + libwebsocket_close_and_free_session(context, + wsi, LWS_CLOSE_STATUS_NOSTATUS); + return 1; + } + } else + wsi->ssl = NULL; + #endif + + p = libwebsockets_generate_client_handshake(context, wsi, p); + if (p == NULL) + return 1; + + /* 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) { + lwsl_debug("ERROR writing to client socket\n"); + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + 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, AWAITING_TIMEOUT); + + break; + + case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: + + /* handle server hung up on us */ + + if (pollfd->revents & (POLLERR | POLLHUP)) { + + lwsl_debug("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 + */ + + /* + * 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->parser_state != WSI_PARSING_COMPLETE && len > 0) { +#ifdef LWS_OPENSSL_SUPPORT + if (wsi->use_ssl) + len = SSL_read(wsi->ssl, &c, 1); + else +#endif + len = recv(wsi->sock, &c, 1, 0); + + libwebsocket_parse(wsi, c); + } + + /* + * 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->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(context, wsi); + +bail3: + if (wsi->c_protocol) + free(wsi->c_protocol); + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 1; + + case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT: + lwsl_ext("LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT\n"); + break; + + case LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD: + lwsl_ext("LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD\n"); + break; + default: + break; + } + + return 0; +} + + +/* + * In-place str to lower case + */ + +static void +strtolower(char *s) +{ + while (*s) { + *s = tolower(*s); + s++; + } +} + +int +lws_client_interpret_server_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi) +{ + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + + MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; + char pkt[1024]; + char *p = &pkt[0]; + const char *pc; + const char *c; + int more = 1; + int okay = 0; + char ext_name[128]; + struct libwebsocket_extension *ext; + void *v; + int len = 0; + int n; + static const char magic_websocket_04_masking_guid[] = + "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; + + /* + * 00 / 76 --> + * + * HTTP/1.1 101 WebSocket Protocol Handshake + * Upgrade: WebSocket + * Connection: Upgrade + * Sec-WebSocket-Origin: http://127.0.0.1 + * Sec-WebSocket-Location: ws://127.0.0.1:9999/socket.io/websocket + * + * xxxxxxxxxxxxxxxx + */ + + if (wsi->ietf_spec_revision == 0) { + if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || + !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || + !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || + !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || + (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + wsi->c_protocol != NULL)) { + lwsl_parser("libwebsocket_client_handshake " + "missing required header(s)\n"); + pkt[len] = '\0'; + lwsl_parser("%s", pkt); + goto bail3; + } + + strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); + if (strncmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, "101", 3)) { + lwsl_warn("libwebsocket_client_handshake " + "server sent bad HTTP response '%s'\n", + wsi->utf8_token[WSI_TOKEN_HTTP].token); + goto bail3; + } + + if (wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len < 16) { + lwsl_parser("libwebsocket_client_handshake " + "challenge reply too short %d\n", + wsi->utf8_token[ + WSI_TOKEN_CHALLENGE].token_len); + pkt[len] = '\0'; + lwsl_parser("%s", pkt); + goto bail3; + + } + + goto select_protocol; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + */ +#if 0 + lwsl_parser("WSI_TOKEN_HTTP: %d\n", + wsi->utf8_token[WSI_TOKEN_HTTP].token_len); + lwsl_parser("WSI_TOKEN_UPGRADE: %d\n", + wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len); + lwsl_parser("WSI_TOKEN_CONNECTION: %d\n", + wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len); + lwsl_parser("WSI_TOKEN_ACCEPT: %d\n", + wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len); + lwsl_parser("WSI_TOKEN_NONCE: %d\n", + wsi->utf8_token[WSI_TOKEN_NONCE].token_len); + lwsl_parser("WSI_TOKEN_PROTOCOL: %d\n", + wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); +#endif + 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->ietf_spec_revision == 4) || + (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && + wsi->c_protocol != NULL)) { + lwsl_parser("libwebsocket_client_handshake " + "missing required header(s)\n"); + pkt[len] = '\0'; + lwsl_parser("%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 (strncmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, "101", 3)) { + lwsl_warn("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")) { + lwsl_warn("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")) { + lwsl_warn("libwebsocket_client_handshake server " + "sent bad Connection hdr '%s'\n", + wsi->utf8_token[WSI_TOKEN_CONNECTION].token); + goto bail3; + } + +select_protocol: + pc = wsi->c_protocol; + if (pc == NULL) + lwsl_parser("lws_client_interpret_server_handshake: " + "NULL c_protocol\n"); + else + lwsl_parser("lws_client_interpret_server_handshake: " + "cPprotocol='%s'\n", pc); + + /* + * 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) { + + lwsl_warn("lws_client_interpret_server_handshake " + "WSI_TOKEN_PROTOCOL is null\n"); + /* + * no protocol name to work from, + * default to first protocol + */ + wsi->protocol = &context->protocols[0]; + wsi->c_callback = wsi->protocol->callback; + free(wsi->c_protocol); + + goto check_extensions; + } + + 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) { + lwsl_err("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 (context->protocols[n].callback && !wsi->protocol) { /* Stop after finding first one?? */ + if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, + context->protocols[n].name) == 0) { + wsi->protocol = &context->protocols[n]; + wsi->c_callback = wsi->protocol->callback; + } + n++; + } + + if (wsi->protocol == NULL) { + lwsl_err("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_extensions: + + /* instantiate the accepted extensions */ + + if (!wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) { + lwsl_ext("no client extenstions allowed by server\n"); + goto check_accept; + } + + /* + * break down the list of server accepted extensions + * and go through matching them or identifying bogons + */ + + c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; + n = 0; + while (more) { + + if (*c && (*c != ',' && *c != ' ' && *c != '\t')) { + ext_name[n] = *c++; + if (n < sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + /* check we actually support it */ + + lwsl_ext("checking client ext %s\n", ext_name); + + n = 0; + ext = wsi->protocol->owning_server->extensions; + while (ext && ext->callback) { + + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + n = 1; + + lwsl_ext("instantiating client ext %s\n", ext_name); + + /* instantiate the extension on this conn */ + + wsi->active_extensions_user[ + wsi->count_active_extensions] = + malloc(ext->per_session_data_size); + if (wsi->active_extensions_user[ + wsi->count_active_extensions] == NULL) { + lwsl_err("Out of mem\n"); + goto bail2; + } + memset(wsi->active_extensions_user[ + wsi->count_active_extensions], 0, + ext->per_session_data_size); + wsi->active_extensions[ + wsi->count_active_extensions] = ext; + + /* allow him to construct his context */ + + ext->callback(wsi->protocol->owning_server, + ext, wsi, + LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, + wsi->active_extensions_user[ + wsi->count_active_extensions], + NULL, 0); + + wsi->count_active_extensions++; + + ext++; + } + + if (n == 0) { + lwsl_warn("Server said we should use" + "an unknown extension '%s'!\n", ext_name); + goto bail2; + } + + n = 0; + } + + +check_accept: + + if (wsi->ietf_spec_revision == 0) { + + if (memcmp(wsi->initial_handshake_hash_base64, + wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 16)) { + lwsl_warn("libwebsocket_client_handshake " + "failed 00 challenge compare\n"); + pkt[len] = '\0'; + lwsl_warn("%s", pkt); + goto bail2; + } + + goto accept_ok; + } + + /* + * Confirm his accept token is the one we precomputed + */ + + if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, + wsi->initial_handshake_hash_base64)) { + lwsl_warn("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; + } + + if (wsi->ietf_spec_revision == 4) { + /* + * Calculate the 04 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); + } +accept_ok: + + /* allocate the per-connection user memory (if any) */ + if (wsi->protocol->per_session_data_size && + !libwebsocket_ensure_user_space(wsi)) + goto bail2; + + /* 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; + + lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); + + /* call him back to inform him he is up */ + + wsi->protocol->callback(context, wsi, + LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, NULL, 0); + + /* + * inform all extensions, not just active ones since they + * already know + */ + + ext = context->extensions; + + while (ext && ext->callback) { + v = NULL; + for (n = 0; n < wsi->count_active_extensions; n++) + if (wsi->active_extensions[n] == ext) + v = wsi->active_extensions_user[n]; + + ext->callback(context, ext, wsi, + LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0); + ext++; + } + + return 0; + +bail3: + if (wsi->c_protocol) + free(wsi->c_protocol); + +bail2: + if (wsi->c_callback) wsi->c_callback(context, wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, + NULL, 0); + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); // But this should be LWS_CLOSE_STATUS_PROTOCOL_ERR + + return 1; +} + +void libwebsockets_00_spaceout(char *key, int spaces, int seed) +{ + char *p; + + key++; + while (spaces--) { + if (*key && (seed & 1)) + key++; + seed >>= 1; + + p = key + strlen(key); + while (p >= key) { + p[1] = p[0]; + p--; + } + *key++ = ' '; + } +} + +void libwebsockets_00_spam(char *key, int count, int seed) +{ + char *p; + + key++; + while (count--) { + + if (*key && (seed & 1)) + key++; + seed >>= 1; + + p = key + strlen(key); + while (p >= key) { + p[1] = p[0]; + p--; + } + *key++ = 0x21 + ((seed & 0xffff) % 15); + /* 4 would use it up too fast.. not like it matters */ + seed >>= 1; + } +} + +char * +libwebsockets_generate_client_handshake(struct libwebsocket_context *context, + struct libwebsocket *wsi, char *pkt) +{ + char hash[20]; + char *p = pkt; + int n; + struct libwebsocket_extension *ext; + struct libwebsocket_extension *ext1; + int ext_count = 0; + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + + MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; + static const char magic_websocket_guid[] = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + + /* + * create the random key + */ + + n = libwebsockets_get_random(context, hash, 16); + if (n != 16) { + lwsl_err("Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + free(wsi->c_path); + free(wsi->c_host); + if (wsi->c_origin) + free(wsi->c_origin); + if (wsi->c_protocol) + free(wsi->c_protocol); + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return NULL; + } + + lws_b64_encode_string(hash, 16, wsi->key_b64, + sizeof wsi->key_b64); + + /* + * 00 example client handshake + * + * GET /socket.io/websocket HTTP/1.1 + * Upgrade: WebSocket + * Connection: Upgrade + * Host: 127.0.0.1:9999 + * Origin: http://127.0.0.1 + * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ + * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 + * Cookie: socketio=websocket + * + * (Á®Ä0¶†≥ + * + * 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, "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); + + if (wsi->ietf_spec_revision == 0) { + unsigned char spaces_1, spaces_2; + unsigned int max_1, max_2; + unsigned int num_1, num_2; + unsigned long product_1, product_2; + char key_1[40]; + char key_2[40]; + unsigned int seed; + unsigned int count; + char challenge[16]; + + libwebsockets_get_random(context, &spaces_1, sizeof(char)); + libwebsockets_get_random(context, &spaces_2, sizeof(char)); + + spaces_1 = (spaces_1 % 12) + 1; + spaces_2 = (spaces_2 % 12) + 1; + + max_1 = 4294967295 / spaces_1; + max_2 = 4294967295 / spaces_2; + + libwebsockets_get_random(context, &num_1, sizeof(int)); + libwebsockets_get_random(context, &num_2, sizeof(int)); + + num_1 = (num_1 % max_1); + num_2 = (num_2 % max_2); + + challenge[0] = num_1 >> 24; + challenge[1] = num_1 >> 16; + challenge[2] = num_1 >> 8; + challenge[3] = num_1; + challenge[4] = num_2 >> 24; + challenge[5] = num_2 >> 16; + challenge[6] = num_2 >> 8; + challenge[7] = num_2; + + product_1 = num_1 * spaces_1; + product_2 = num_2 * spaces_2; + + sprintf(key_1, "%lu", product_1); + sprintf(key_2, "%lu", product_2); + + libwebsockets_get_random(context, &seed, sizeof(int)); + libwebsockets_get_random(context, &count, sizeof(int)); + + libwebsockets_00_spam(key_1, (count % 12) + 1, seed); + + libwebsockets_get_random(context, &seed, sizeof(int)); + libwebsockets_get_random(context, &count, sizeof(int)); + + libwebsockets_00_spam(key_2, (count % 12) + 1, seed); + + libwebsockets_get_random(context, &seed, sizeof(int)); + + libwebsockets_00_spaceout(key_1, spaces_1, seed); + libwebsockets_00_spaceout(key_2, spaces_2, seed >> 16); + + p += sprintf(p, "Upgrade: WebSocket\x0d\x0a" + "Connection: Upgrade\x0d\x0aHost: %s\x0d\x0a", + wsi->c_host); + if (wsi->c_origin) + p += sprintf(p, "Origin: %s\x0d\x0a", wsi->c_origin); + + if (wsi->c_protocol) + p += sprintf(p, "Sec-WebSocket-Protocol: %s" + "\x0d\x0a", wsi->c_protocol); + + p += sprintf(p, "Sec-WebSocket-Key1: %s\x0d\x0a", key_1); + p += sprintf(p, "Sec-WebSocket-Key2: %s\x0d\x0a", key_2); + + /* give userland a chance to append, eg, cookies */ + + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + NULL, &p, (pkt + sizeof(pkt)) - p - 12); + + p += sprintf(p, "\x0d\x0a"); + + if (libwebsockets_get_random(context, p, 8) != 8) + return NULL; + memcpy(&challenge[8], p, 8); + p += 8; + + /* precompute what we want to see from the server */ + + MD5((unsigned char *)challenge, 16, + (unsigned char *)wsi->initial_handshake_hash_base64); + + goto issue_hdr; + } + + p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host); + p += sprintf(p, "Upgrade: websocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, wsi->key_b64); + p += strlen(wsi->key_b64); + p += sprintf(p, "\x0d\x0a"); + if (wsi->c_origin) { + if (wsi->ietf_spec_revision == 13) { + p += sprintf(p, "Origin: %s\x0d\x0a", + wsi->c_origin); + } + else { + p += sprintf(p, "Sec-WebSocket-Origin: %s\x0d\x0a", + wsi->c_origin); + } + } + if (wsi->c_protocol) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + wsi->c_protocol); + + /* tell the server what extensions we could support */ + + p += sprintf(p, "Sec-WebSocket-Extensions: "); + + ext = context->extensions; + while (ext && ext->callback) { + + n = 0; + ext1 = context->extensions; + + while (ext1 && ext1->callback) { + n |= ext1->callback(context, ext1, wsi, + LWS_EXT_CALLBACK_CHECK_OK_TO_PROPOSE_EXTENSION, + NULL, (char *)ext->name, 0); + + ext1++; + } + + if (n) { /* an extension vetos us */ + lwsl_ext("ext %s vetoed\n", (char *)ext->name); + ext++; + continue; + } + + n = context->protocols[0].callback(context, 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++ = ','; + p += sprintf(p, "%s", ext->name); + ext_count++; + + ext++; + } + + p += sprintf(p, "\x0d\x0a"); + + if (wsi->ietf_spec_revision) + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", + wsi->ietf_spec_revision); + + /* give userland a chance to append, eg, cookies */ + + context->protocols[0].callback(context, wsi, + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + NULL, &p, (pkt + sizeof(pkt)) - p - 12); + + p += sprintf(p, "\x0d\x0a"); + + /* 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); + +issue_hdr: + +#if 0 + puts(pkt); +#endif + + /* done with these now */ + + free(wsi->c_path); + free(wsi->c_host); + if (wsi->c_origin) + free(wsi->c_origin); + + return p; +} + diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 6d9f3110..63d286a8 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -61,19 +61,6 @@ static const char *log_level_names[] = { "CLIENT", }; -/* - * In-place str to lower case - */ - -static void -strtolower(char *s) -{ - while (*s) { - *s = tolower(*s); - s++; - } -} - /* file descriptor hash management */ struct libwebsocket * @@ -530,47 +517,6 @@ libwebsockets_SHA1(const unsigned char *d, size_t n, unsigned char *md) return SHA1(d, n, md); } -void libwebsockets_00_spaceout(char *key, int spaces, int seed) -{ - char *p; - - key++; - while (spaces--) { - if (*key && (seed & 1)) - key++; - seed >>= 1; - - p = key + strlen(key); - while (p >= key) { - p[1] = p[0]; - p--; - } - *key++ = ' '; - } -} - -void libwebsockets_00_spam(char *key, int count, int seed) -{ - char *p; - - key++; - while (count--) { - - if (*key && (seed & 1)) - key++; - seed >>= 1; - - p = key + strlen(key); - while (p >= key) { - p[1] = p[0]; - p--; - } - *key++ = 0x21 + ((seed & 0xffff) % 15); - /* 4 would use it up too fast.. not like it matters */ - seed >>= 1; - } -} - int lws_send_pipe_choked(struct libwebsocket *wsi) { struct pollfd fds; @@ -797,678 +743,6 @@ libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) return new_wsi; } -char * -libwebsockets_generate_client_handshake(struct libwebsocket_context *context, - struct libwebsocket *wsi, char *pkt) -{ - char hash[20]; - char *p = pkt; - int n; - struct libwebsocket_extension *ext; - struct libwebsocket_extension *ext1; - int ext_count = 0; - unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + - MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; - static const char magic_websocket_guid[] = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - - /* - * create the random key - */ - - n = libwebsockets_get_random(context, hash, 16); - if (n != 16) { - lwsl_err("Unable to read from random dev %s\n", - SYSTEM_RANDOM_FILEPATH); - free(wsi->c_path); - free(wsi->c_host); - if (wsi->c_origin) - free(wsi->c_origin); - if (wsi->c_protocol) - free(wsi->c_protocol); - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - return NULL; - } - - lws_b64_encode_string(hash, 16, wsi->key_b64, - sizeof wsi->key_b64); - - /* - * 00 example client handshake - * - * GET /socket.io/websocket HTTP/1.1 - * Upgrade: WebSocket - * Connection: Upgrade - * Host: 127.0.0.1:9999 - * Origin: http://127.0.0.1 - * Sec-WebSocket-Key1: 1 0 2#0W 9 89 7 92 ^ - * Sec-WebSocket-Key2: 7 7Y 4328 B2v[8(z1 - * Cookie: socketio=websocket - * - * (Á®Ä0¶†≥ - * - * 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, "Pragma: no-cache\x0d\x0a" - "Cache-Control: no-cache\x0d\x0a"); - - if (wsi->ietf_spec_revision == 0) { - unsigned char spaces_1, spaces_2; - unsigned int max_1, max_2; - unsigned int num_1, num_2; - unsigned long product_1, product_2; - char key_1[40]; - char key_2[40]; - unsigned int seed; - unsigned int count; - char challenge[16]; - - libwebsockets_get_random(context, &spaces_1, sizeof(char)); - libwebsockets_get_random(context, &spaces_2, sizeof(char)); - - spaces_1 = (spaces_1 % 12) + 1; - spaces_2 = (spaces_2 % 12) + 1; - - max_1 = 4294967295 / spaces_1; - max_2 = 4294967295 / spaces_2; - - libwebsockets_get_random(context, &num_1, sizeof(int)); - libwebsockets_get_random(context, &num_2, sizeof(int)); - - num_1 = (num_1 % max_1); - num_2 = (num_2 % max_2); - - challenge[0] = num_1 >> 24; - challenge[1] = num_1 >> 16; - challenge[2] = num_1 >> 8; - challenge[3] = num_1; - challenge[4] = num_2 >> 24; - challenge[5] = num_2 >> 16; - challenge[6] = num_2 >> 8; - challenge[7] = num_2; - - product_1 = num_1 * spaces_1; - product_2 = num_2 * spaces_2; - - sprintf(key_1, "%lu", product_1); - sprintf(key_2, "%lu", product_2); - - libwebsockets_get_random(context, &seed, sizeof(int)); - libwebsockets_get_random(context, &count, sizeof(int)); - - libwebsockets_00_spam(key_1, (count % 12) + 1, seed); - - libwebsockets_get_random(context, &seed, sizeof(int)); - libwebsockets_get_random(context, &count, sizeof(int)); - - libwebsockets_00_spam(key_2, (count % 12) + 1, seed); - - libwebsockets_get_random(context, &seed, sizeof(int)); - - libwebsockets_00_spaceout(key_1, spaces_1, seed); - libwebsockets_00_spaceout(key_2, spaces_2, seed >> 16); - - p += sprintf(p, "Upgrade: WebSocket\x0d\x0a" - "Connection: Upgrade\x0d\x0aHost: %s\x0d\x0a", - wsi->c_host); - if (wsi->c_origin) - p += sprintf(p, "Origin: %s\x0d\x0a", wsi->c_origin); - - if (wsi->c_protocol) - p += sprintf(p, "Sec-WebSocket-Protocol: %s" - "\x0d\x0a", wsi->c_protocol); - - p += sprintf(p, "Sec-WebSocket-Key1: %s\x0d\x0a", key_1); - p += sprintf(p, "Sec-WebSocket-Key2: %s\x0d\x0a", key_2); - - /* give userland a chance to append, eg, cookies */ - - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, - NULL, &p, (pkt + sizeof(pkt)) - p - 12); - - p += sprintf(p, "\x0d\x0a"); - - if (libwebsockets_get_random(context, p, 8) != 8) - return NULL; - memcpy(&challenge[8], p, 8); - p += 8; - - /* precompute what we want to see from the server */ - - MD5((unsigned char *)challenge, 16, - (unsigned char *)wsi->initial_handshake_hash_base64); - - goto issue_hdr; - } - - p += sprintf(p, "Host: %s\x0d\x0a", wsi->c_host); - p += sprintf(p, "Upgrade: websocket\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Key: "); - strcpy(p, wsi->key_b64); - p += strlen(wsi->key_b64); - p += sprintf(p, "\x0d\x0a"); - if (wsi->c_origin) { - if (wsi->ietf_spec_revision == 13) { - p += sprintf(p, "Origin: %s\x0d\x0a", - wsi->c_origin); - } - else { - p += sprintf(p, "Sec-WebSocket-Origin: %s\x0d\x0a", - wsi->c_origin); - } - } - if (wsi->c_protocol) - p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", - wsi->c_protocol); - - /* tell the server what extensions we could support */ - - p += sprintf(p, "Sec-WebSocket-Extensions: "); - - ext = context->extensions; - while (ext && ext->callback) { - - n = 0; - ext1 = context->extensions; - - while (ext1 && ext1->callback) { - n |= ext1->callback(context, ext1, wsi, - LWS_EXT_CALLBACK_CHECK_OK_TO_PROPOSE_EXTENSION, - NULL, (char *)ext->name, 0); - - ext1++; - } - - if (n) { /* an extension vetos us */ - lwsl_ext("ext %s vetoed\n", (char *)ext->name); - ext++; - continue; - } - - n = context->protocols[0].callback(context, 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++ = ','; - p += sprintf(p, "%s", ext->name); - ext_count++; - - ext++; - } - - p += sprintf(p, "\x0d\x0a"); - - if (wsi->ietf_spec_revision) - p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", - wsi->ietf_spec_revision); - - /* give userland a chance to append, eg, cookies */ - - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, - NULL, &p, (pkt + sizeof(pkt)) - p - 12); - - p += sprintf(p, "\x0d\x0a"); - - /* 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); - -issue_hdr: - -#if 0 - puts(pkt); -#endif - - /* done with these now */ - - free(wsi->c_path); - free(wsi->c_host); - if (wsi->c_origin) - free(wsi->c_origin); - - return p; -} - -int -lws_client_interpret_server_handshake(struct libwebsocket_context *context, - struct libwebsocket *wsi) -{ - unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + - MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; - char pkt[1024]; - char *p = &pkt[0]; - const char *pc; - const char *c; - int more = 1; - int okay = 0; - char ext_name[128]; - struct libwebsocket_extension *ext; - void *v; - int len = 0; - int n; - static const char magic_websocket_04_masking_guid[] = - "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; - - /* - * 00 / 76 --> - * - * HTTP/1.1 101 WebSocket Protocol Handshake - * Upgrade: WebSocket - * Connection: Upgrade - * Sec-WebSocket-Origin: http://127.0.0.1 - * Sec-WebSocket-Location: ws://127.0.0.1:9999/socket.io/websocket - * - * xxxxxxxxxxxxxxxx - */ - - if (wsi->ietf_spec_revision == 0) { - if (!wsi->utf8_token[WSI_TOKEN_HTTP].token_len || - !wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len || - !wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len || - !wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len || - (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && - wsi->c_protocol != NULL)) { - lwsl_parser("libwebsocket_client_handshake " - "missing required header(s)\n"); - pkt[len] = '\0'; - lwsl_parser("%s", pkt); - goto bail3; - } - - strtolower(wsi->utf8_token[WSI_TOKEN_HTTP].token); - if (strncmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, "101", 3)) { - lwsl_warn("libwebsocket_client_handshake " - "server sent bad HTTP response '%s'\n", - wsi->utf8_token[WSI_TOKEN_HTTP].token); - goto bail3; - } - - if (wsi->utf8_token[WSI_TOKEN_CHALLENGE].token_len < 16) { - lwsl_parser("libwebsocket_client_handshake " - "challenge reply too short %d\n", - wsi->utf8_token[ - WSI_TOKEN_CHALLENGE].token_len); - pkt[len] = '\0'; - lwsl_parser("%s", pkt); - goto bail3; - - } - - goto select_protocol; - } - - /* - * well, what the server sent looked reasonable for syntax. - * Now let's confirm it sent all the necessary headers - */ -#if 0 - lwsl_parser("WSI_TOKEN_HTTP: %d\n", - wsi->utf8_token[WSI_TOKEN_HTTP].token_len); - lwsl_parser("WSI_TOKEN_UPGRADE: %d\n", - wsi->utf8_token[WSI_TOKEN_UPGRADE].token_len); - lwsl_parser("WSI_TOKEN_CONNECTION: %d\n", - wsi->utf8_token[WSI_TOKEN_CONNECTION].token_len); - lwsl_parser("WSI_TOKEN_ACCEPT: %d\n", - wsi->utf8_token[WSI_TOKEN_ACCEPT].token_len); - lwsl_parser("WSI_TOKEN_NONCE: %d\n", - wsi->utf8_token[WSI_TOKEN_NONCE].token_len); - lwsl_parser("WSI_TOKEN_PROTOCOL: %d\n", - wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len); -#endif - 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->ietf_spec_revision == 4) || - (!wsi->utf8_token[WSI_TOKEN_PROTOCOL].token_len && - wsi->c_protocol != NULL)) { - lwsl_parser("libwebsocket_client_handshake " - "missing required header(s)\n"); - pkt[len] = '\0'; - lwsl_parser("%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 (strncmp(wsi->utf8_token[WSI_TOKEN_HTTP].token, "101", 3)) { - lwsl_warn("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")) { - lwsl_warn("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")) { - lwsl_warn("libwebsocket_client_handshake server " - "sent bad Connection hdr '%s'\n", - wsi->utf8_token[WSI_TOKEN_CONNECTION].token); - goto bail3; - } - -select_protocol: - pc = wsi->c_protocol; - if (pc == NULL) - lwsl_parser("lws_client_interpret_server_handshake: " - "NULL c_protocol\n"); - else - lwsl_parser("lws_client_interpret_server_handshake: " - "cPprotocol='%s'\n", pc); - - /* - * 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) { - - lwsl_warn("lws_client_interpret_server_handshake " - "WSI_TOKEN_PROTOCOL is null\n"); - /* - * no protocol name to work from, - * default to first protocol - */ - wsi->protocol = &context->protocols[0]; - wsi->c_callback = wsi->protocol->callback; - free(wsi->c_protocol); - - goto check_extensions; - } - - 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) { - lwsl_err("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 (context->protocols[n].callback && !wsi->protocol) { /* Stop after finding first one?? */ - if (strcmp(wsi->utf8_token[WSI_TOKEN_PROTOCOL].token, - context->protocols[n].name) == 0) { - wsi->protocol = &context->protocols[n]; - wsi->c_callback = wsi->protocol->callback; - } - n++; - } - - if (wsi->protocol == NULL) { - lwsl_err("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_extensions: - - /* instantiate the accepted extensions */ - - if (!wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) { - lwsl_ext("no client extenstions allowed by server\n"); - goto check_accept; - } - - /* - * break down the list of server accepted extensions - * and go through matching them or identifying bogons - */ - - c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; - n = 0; - while (more) { - - if (*c && (*c != ',' && *c != ' ' && *c != '\t')) { - ext_name[n] = *c++; - if (n < sizeof(ext_name) - 1) - n++; - continue; - } - ext_name[n] = '\0'; - if (!*c) - more = 0; - else { - c++; - if (!n) - continue; - } - - /* check we actually support it */ - - lwsl_ext("checking client ext %s\n", ext_name); - - n = 0; - ext = wsi->protocol->owning_server->extensions; - while (ext && ext->callback) { - - if (strcmp(ext_name, ext->name)) { - ext++; - continue; - } - - n = 1; - - lwsl_ext("instantiating client ext %s\n", ext_name); - - /* instantiate the extension on this conn */ - - wsi->active_extensions_user[ - wsi->count_active_extensions] = - malloc(ext->per_session_data_size); - if (wsi->active_extensions_user[ - wsi->count_active_extensions] == NULL) { - lwsl_err("Out of mem\n"); - goto bail2; - } - memset(wsi->active_extensions_user[ - wsi->count_active_extensions], 0, - ext->per_session_data_size); - wsi->active_extensions[ - wsi->count_active_extensions] = ext; - - /* allow him to construct his context */ - - ext->callback(wsi->protocol->owning_server, - ext, wsi, - LWS_EXT_CALLBACK_CLIENT_CONSTRUCT, - wsi->active_extensions_user[ - wsi->count_active_extensions], - NULL, 0); - - wsi->count_active_extensions++; - - ext++; - } - - if (n == 0) { - lwsl_warn("Server said we should use" - "an unknown extension '%s'!\n", ext_name); - goto bail2; - } - - n = 0; - } - - -check_accept: - - if (wsi->ietf_spec_revision == 0) { - - if (memcmp(wsi->initial_handshake_hash_base64, - wsi->utf8_token[WSI_TOKEN_CHALLENGE].token, 16)) { - lwsl_warn("libwebsocket_client_handshake " - "failed 00 challenge compare\n"); - pkt[len] = '\0'; - lwsl_warn("%s", pkt); - goto bail2; - } - - goto accept_ok; - } - - /* - * Confirm his accept token is the one we precomputed - */ - - if (strcmp(wsi->utf8_token[WSI_TOKEN_ACCEPT].token, - wsi->initial_handshake_hash_base64)) { - lwsl_warn("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; - } - - if (wsi->ietf_spec_revision == 4) { - /* - * Calculate the 04 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); - } -accept_ok: - - /* allocate the per-connection user memory (if any) */ - if (wsi->protocol->per_session_data_size && - !libwebsocket_ensure_user_space(wsi)) - goto bail2; - - /* 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; - - lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); - - /* call him back to inform him he is up */ - - wsi->protocol->callback(context, wsi, - LWS_CALLBACK_CLIENT_ESTABLISHED, - wsi->user_space, NULL, 0); - - /* - * inform all extensions, not just active ones since they - * already know - */ - - ext = context->extensions; - - while (ext && ext->callback) { - v = NULL; - for (n = 0; n < wsi->count_active_extensions; n++) - if (wsi->active_extensions[n] == ext) - v = wsi->active_extensions_user[n]; - - ext->callback(context, ext, wsi, - LWS_EXT_CALLBACK_ANY_WSI_ESTABLISHED, v, NULL, 0); - ext++; - } - - return 0; - -bail3: - if (wsi->c_protocol) - free(wsi->c_protocol); - -bail2: - if (wsi->c_callback) wsi->c_callback(context, wsi, - LWS_CALLBACK_CLIENT_CONNECTION_ERROR, - wsi->user_space, - NULL, 0); - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); // But this should be LWS_CLOSE_STATUS_PROTOCOL_ERR - - return 1; -} - - /** * libwebsocket_service_fd() - Service polled socket with something waiting @@ -1496,16 +770,11 @@ libwebsocket_service_fd(struct libwebsocket_context *context, unsigned int clilen; struct sockaddr_in cli_addr; struct timeval tv; - char pkt[1024]; - char *p = &pkt[0]; int more = 1; struct lws_tokens eff_buf; int opt = 1; - char c; + extern int lws_client_socket_service(struct libwebsocket_context *context, struct libwebsocket *wsi, struct pollfd *pollfd); -#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 @@ -1889,235 +1158,6 @@ bail_prox_listener: } break; - case LWS_CONNMODE_WS_CLIENT_WAITING_PROXY_REPLY: - - /* handle proxy hung up on us */ - - if (pollfd->revents & (POLLERR | POLLHUP)) { - - lwsl_warn("Proxy connection %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - return 1; - } - - n = recv(wsi->sock, pkt, sizeof pkt, 0); - if (n < 0) { - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - lwsl_err("ERROR reading from proxy socket\n"); - return 1; - } - - pkt[13] = '\0'; - if (strcmp(pkt, "HTTP/1.0 200 ") != 0) { - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - lwsl_err("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: - - /* - * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE - * timeout protection set in client-handshake.c - */ - - #ifdef LWS_OPENSSL_SUPPORT - - /* - * take care of our libwebsocket_callback_on_writable - * happening at a time when there's no real connection yet - */ - - pollfd->events &= ~POLLOUT; - - /* external POLL support via protocol 0 */ - context->protocols[0].callback(context, wsi, - LWS_CALLBACK_CLEAR_MODE_POLL_FD, - (void *)(long)wsi->sock, NULL, POLLOUT); - - /* we can retry this... so just cook the SSL BIO the first time */ - - if (wsi->use_ssl && !wsi->ssl) { - - wsi->ssl = SSL_new(context->ssl_client_ctx); - wsi->client_bio = BIO_new_socket(wsi->sock, - BIO_NOCLOSE); - SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); - - SSL_set_ex_data(wsi->ssl, - openssl_websocket_private_data_index, - context); - } - - if (wsi->use_ssl) { - n = SSL_connect(wsi->ssl); - - if (n < 0) { - n = SSL_get_error(wsi->ssl, n); - - if (n == SSL_ERROR_WANT_READ || - 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 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 -> SSL_ERROR_WANT_... retrying\n"); - libwebsocket_callback_on_writable(context, wsi); - - return 0; /* no error */ - } - n = -1; - } - - if (n <= 0) { - /* - * retry if new data comes until we - * run into the connection timeout or win - */ - - lwsl_err("SSL connect error %s\n", - ERR_error_string(ERR_get_error(), - ssl_err_buf)); - return 0; - } - - n = SSL_get_verify_result(wsi->ssl); - if ((n != X509_V_OK) && ( - n != X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || - wsi->use_ssl != 2)) { - - lwsl_err("server's cert didn't " - "look good %d\n", n); - libwebsocket_close_and_free_session(context, - wsi, LWS_CLOSE_STATUS_NOSTATUS); - return 1; - } - } else - wsi->ssl = NULL; - #endif - - p = libwebsockets_generate_client_handshake(context, wsi, p); - if (p == NULL) - return 1; - - /* 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) { - lwsl_debug("ERROR writing to client socket\n"); - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - 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, AWAITING_TIMEOUT); - - break; - - case LWS_CONNMODE_WS_CLIENT_WAITING_SERVER_REPLY: - - /* handle server hung up on us */ - - if (pollfd->revents & (POLLERR | POLLHUP)) { - - lwsl_debug("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 - */ - - /* - * 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->parser_state != WSI_PARSING_COMPLETE && len > 0) { -#ifdef LWS_OPENSSL_SUPPORT - if (wsi->use_ssl) - len = SSL_read(wsi->ssl, &c, 1); - else -#endif - len = recv(wsi->sock, &c, 1, 0); - - libwebsocket_parse(wsi, c); - } - - /* - * 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->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(context, wsi); - -bail3: - if (wsi->c_protocol) - free(wsi->c_protocol); - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - return 1; - - case LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT: - lwsl_ext("LWS_CONNMODE_WS_CLIENT_WAITING_EXTENSION_CONNECT\n"); - break; - - case LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD: - lwsl_ext("LWS_CONNMODE_WS_CLIENT_PENDING_CANDIDATE_CHILD\n"); - break; - case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: @@ -2231,6 +1271,9 @@ read_pending: goto read_pending; #endif break; + + default: + return lws_client_socket_service(context, wsi, pollfd); } return 0; diff --git a/lib/parsers.c b/lib/parsers.c index 3d2139cc..4c608e50 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -886,495 +886,6 @@ illegal_ctl_length: } -int libwebsocket_client_rx_sm(struct libwebsocket *wsi, unsigned char c) -{ - int n; - unsigned char buf[20 + 4]; - int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; - int handled; - struct lws_tokens eff_buf; - int m; - - lwsl_parser(" CRX: %02X %d\n", c, wsi->lws_rx_parse_state); - - switch (wsi->lws_rx_parse_state) { - case LWS_RXPS_NEW: - - switch (wsi->ietf_spec_revision) { - /* Firefox 4.0b6 likes this as of 30 Oct */ - case 0: - if (c == 0xff) - wsi->lws_rx_parse_state = LWS_RXPS_SEEN_76_FF; - if (c == 0) { - wsi->lws_rx_parse_state = - LWS_RXPS_EAT_UNTIL_76_FF; - wsi->rx_user_buffer_head = 0; - } - break; - case 4: - case 5: - case 6: - case 7: - case 8: - case 13: - /* - * 04 logical framing from the spec (all this is masked when - * incoming and has to be unmasked) - * - * We ignore the possibility of extension data because we don't - * negotiate any extensions at the moment. - * - * 0 1 2 3 - * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 - * +-+-+-+-+-------+-+-------------+-------------------------------+ - * |F|R|R|R| opcode|R| Payload len | Extended payload length | - * |I|S|S|S| (4) |S| (7) | (16/63) | - * |N|V|V|V| |V| | (if payload len==126/127) | - * | |1|2|3| |4| | | - * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + - * | Extended payload length continued, if payload len == 127 | - * + - - - - - - - - - - - - - - - +-------------------------------+ - * | | Extension data | - * +-------------------------------+ - - - - - - - - - - - - - - - + - * : : - * +---------------------------------------------------------------+ - * : Application data : - * +---------------------------------------------------------------+ - * - * We pass payload through to userland as soon as we get it, ignoring - * FIN. It's up to userland to buffer it up if it wants to see a - * whole unfragmented block of the original size (which may be up to - * 2^63 long!) - * - * Notice in v7 RSV4 is set to indicate 32-bit frame key is coming in - * after length, unlike extension data which is now deprecated, this - * does not impact the payload length calculation. - */ - - /* - * 04 spec defines the opcode like this: (1, 2, and 3 are - * "control frame" opcodes which may not be fragmented or - * have size larger than 126) - * - * frame-opcode = - * %x0 ; continuation frame - * / %x1 ; connection close - * / %x2 ; ping - * / %x3 ; pong - * / %x4 ; text frame - * / %x5 ; binary frame - * / %x6-F ; reserved - * - * FIN (b7) - */ - - if (wsi->ietf_spec_revision < 7) - switch (c & 0xf) { - case LWS_WS_OPCODE_04__CONTINUATION: - wsi->opcode = - LWS_WS_OPCODE_07__CONTINUATION; - break; - case LWS_WS_OPCODE_04__CLOSE: - wsi->opcode = LWS_WS_OPCODE_07__CLOSE; - break; - case LWS_WS_OPCODE_04__PING: - wsi->opcode = LWS_WS_OPCODE_07__PING; - break; - case LWS_WS_OPCODE_04__PONG: - wsi->opcode = LWS_WS_OPCODE_07__PONG; - break; - case LWS_WS_OPCODE_04__TEXT_FRAME: - wsi->opcode = - LWS_WS_OPCODE_07__TEXT_FRAME; - break; - case LWS_WS_OPCODE_04__BINARY_FRAME: - wsi->opcode = - LWS_WS_OPCODE_07__BINARY_FRAME; - break; - default: - lwsl_warn("reserved opcodes not " - "usable pre v7 protocol\n"); - return -1; - } - else - wsi->opcode = c & 0xf; - wsi->rsv = (c & 0x70); - wsi->final = !!((c >> 7) & 1); - - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; - break; - - default: - lwsl_err("client_rx_sm doesn't know how " - "to handle spec version %02d\n", - wsi->ietf_spec_revision); - break; - } - break; - - - case LWS_RXPS_04_FRAME_HDR_LEN: - - if ((c & 0x80) && wsi->ietf_spec_revision < 7) { - lwsl_warn("Frame has extensions set illegally 4\n"); - /* kill the connection */ - return -1; - } - - wsi->this_frame_masked = !!(c & 0x80); - - switch (c & 0x7f) { - case 126: - /* control frames are not allowed to have big lengths */ - if (wsi->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->opcode & 8) - goto illegal_ctl_length; - wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; - break; - default: - wsi->rx_packet_length = c; - if (wsi->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->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->rx_packet_length |= c; - if (wsi->this_frame_masked) - wsi->lws_rx_parse_state = - LWS_RXPS_07_COLLECT_FRAME_KEY_1; - else { - if (wsi->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->rx_packet_length = ((size_t)c) << 56; -#else - wsi->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->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->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->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->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->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->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->rx_packet_length |= (size_t)c; - if (wsi->this_frame_masked) - wsi->lws_rx_parse_state = - LWS_RXPS_07_COLLECT_FRAME_KEY_1; - else { - if (wsi->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->frame_masking_nonce_04[0] = c; - if (c) - wsi->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->frame_masking_nonce_04[1] = c; - if (c) - wsi->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->frame_masking_nonce_04[2] = c; - if (c) - wsi->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->frame_masking_nonce_04[3] = c; - if (c) - wsi->all_zero_nonce = 0; - - if (wsi->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_EAT_UNTIL_76_FF: - if (c == 0xff) { - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - goto issue; - } - wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + - (wsi->rx_user_buffer_head++)] = c; - - if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) - break; -issue: - if (wsi->protocol->callback) - wsi->protocol->callback(wsi->protocol->owning_server, - wsi, - LWS_CALLBACK_CLIENT_RECEIVE, - wsi->user_space, - &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], - wsi->rx_user_buffer_head); - wsi->rx_user_buffer_head = 0; - break; - case LWS_RXPS_SEEN_76_FF: - if (c) - break; - - lwsl_parser("Seen that client is requesting " - "a v76 close, sending ack\n"); - buf[0] = 0xff; - buf[1] = 0; - n = libwebsocket_write(wsi, buf, 2, LWS_WRITE_HTTP); - if (n < 0) { - lwsl_warn("LWS_RXPS_SEEN_76_FF: ERROR writing to socket\n"); - return -1; - } - lwsl_parser(" v76 close ack sent, server closing skt\n"); - /* returning < 0 will get it closed in parent */ - return -1; - - case LWS_RXPS_PULLING_76_LENGTH: - break; - - case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: - if ((!wsi->this_frame_masked) || wsi->all_zero_nonce) - wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + - (wsi->rx_user_buffer_head++)] = c; - else - wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING + - (wsi->rx_user_buffer_head++)] = - wsi->xor_mask(wsi, c); - - if (--wsi->rx_packet_length == 0) { - wsi->lws_rx_parse_state = LWS_RXPS_NEW; - goto spill; - } - if (wsi->rx_user_buffer_head != MAX_USER_RX_BUFFER) - break; -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->opcode) { - case LWS_WS_OPCODE_07__CLOSE: - /* is this an acknowledgement of our close? */ - if (wsi->state == WSI_STATE_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 packet len = %d\n", wsi->rx_user_buffer_head); - /* parrot the close packet payload back */ - n = libwebsocket_write(wsi, (unsigned char *) - &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], - wsi->rx_user_buffer_head, LWS_WRITE_CLOSE); - lwsl_parser("client writing close ack returned %d\n", n); - wsi->state = WSI_STATE_RETURNED_CLOSE_ALREADY; - /* close the connection */ - return -1; - - case LWS_WS_OPCODE_07__PING: - /* parrot the ping packet payload back as a pong*/ - n = libwebsocket_write(wsi, (unsigned char *) - &wsi->rx_user_buffer[LWS_SEND_BUFFER_PRE_PADDING], - wsi->rx_user_buffer_head, LWS_WRITE_PONG); - handled = 1; - break; - - case LWS_WS_OPCODE_07__PONG: - /* keep the statistics... */ - wsi->pings_vs_pongs--; - - /* issue it */ - callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; - break; - - case LWS_WS_OPCODE_07__CONTINUATION: - case LWS_WS_OPCODE_07__TEXT_FRAME: - case LWS_WS_OPCODE_07__BINARY_FRAME: - break; - - default: - - lwsl_parser("Reserved opcode 0x%2X\n", wsi->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->rx_user_buffer[ - LWS_SEND_BUFFER_PRE_PADDING]; - eff_buf.token_len = wsi->rx_user_buffer_head; - - for (n = 0; n < wsi->count_active_extensions; n++) { - m = wsi->active_extensions[n]->callback( - wsi->protocol->owning_server, - wsi->active_extensions[n], wsi, - LWS_EXT_CALLBACK_EXTENDED_PAYLOAD_RX, - wsi->active_extensions_user[n], - &eff_buf, 0); - if (m) - handled = 1; - } - - if (!handled) { - lwsl_ext("Unhandled extended opcode " - "0x%x - ignoring frame\n", wsi->opcode); - wsi->rx_user_buffer_head = 0; - - return 0; - } - - 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 libwebsocket_write - */ - if (handled) - goto already_done; - - eff_buf.token = &wsi->rx_user_buffer[ - LWS_SEND_BUFFER_PRE_PADDING]; - eff_buf.token_len = wsi->rx_user_buffer_head; - - for (n = 0; n < wsi->count_active_extensions; n++) { - m = wsi->active_extensions[n]->callback( - wsi->protocol->owning_server, - wsi->active_extensions[n], wsi, - LWS_EXT_CALLBACK_PAYLOAD_RX, - wsi->active_extensions_user[n], - &eff_buf, 0); - if (m < 0) { - lwsl_ext( - "Extension '%s' failed to handle payload!\n", - wsi->active_extensions[n]->name); - return -1; - } - } - - if (eff_buf.token_len > 0) { - eff_buf.token[eff_buf.token_len] = '\0'; - - if (wsi->protocol->callback) - wsi->protocol->callback( - wsi->protocol->owning_server, - wsi, - (enum libwebsocket_callback_reasons)callback_action, - wsi->user_space, - eff_buf.token, - eff_buf.token_len); - } -already_done: - wsi->rx_user_buffer_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; - -} - int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi,