diff --git a/README-test-server b/README-test-server index 58a7123f..fa61c108 100644 --- a/README-test-server +++ b/README-test-server @@ -77,6 +77,11 @@ There are several other possible configure options minimize library footprint for embedded server-only case +--without-server Don't build the server part of the library nor the + test apps that need the server part. Useful to + minimize library footprint for embedded client-only + case + --without-daemonize Don't build daemonize.c / lws_daemonize --disable-debug Remove all debug logging below lwsl_warn in severity diff --git a/configure.ac b/configure.ac index 6e48e08c..1b99ab6f 100644 --- a/configure.ac +++ b/configure.ac @@ -82,6 +82,21 @@ CFLAGS="$CFLAGS -DLWS_NO_CLIENT" fi AM_CONDITIONAL(NO_CLIENT, test x$no_client = xyes) +# +# +# +AC_ARG_WITH(server, + [ --without-server dont build the client part of the library ], + [ no_server=yes + ]) + +if test "x$no_server" = "xyes" ; then +CFLAGS="$CFLAGS -DLWS_NO_SERVER" +fi +AM_CONDITIONAL(NO_SERVER, test x$no_server = xyes) + + + # # # diff --git a/lib/Makefile.am b/lib/Makefile.am index f7af4167..8b460bc2 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -1,8 +1,8 @@ lib_LTLIBRARIES=libwebsockets.la include_HEADERS=libwebsockets.h dist_libwebsockets_la_SOURCES=libwebsockets.c \ - handshake.c \ parsers.c \ + handshake.c \ libwebsockets.h \ base64-decode.c \ output.c \ @@ -23,6 +23,12 @@ dist_libwebsockets_la_SOURCES+= client.c \ client-handshake.c endif +if NO_SERVER +else +dist_libwebsockets_la_SOURCES+= server.c \ + server-handshake.c +endif + if USE_BUILTIN_GETIFADDRS dist_libwebsockets_la_SOURCES += getifaddrs.c endif diff --git a/lib/handshake.c b/lib/handshake.c index 2b8c304f..2b2765fd 100644 --- a/lib/handshake.c +++ b/lib/handshake.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 Andy Green + * 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 @@ -21,495 +21,6 @@ #include "private-libwebsockets.h" -#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } -#define LWS_CPYAPP_TOKEN(ptr, tok) { strcpy(p, wsi->utf8_token[tok].token); \ - p += wsi->utf8_token[tok].token_len; } - -static int -interpret_key(const char *key, unsigned long *result) -{ - char digits[20]; - int digit_pos = 0; - const char *p = key; - unsigned int spaces = 0; - unsigned long acc = 0; - int rem = 0; - - while (*p) { - if (!isdigit(*p)) { - p++; - continue; - } - if (digit_pos == sizeof(digits) - 1) - return -1; - digits[digit_pos++] = *p++; - } - digits[digit_pos] = '\0'; - if (!digit_pos) - return -2; - - while (*key) { - if (*key == ' ') - spaces++; - key++; - } - - if (!spaces) - return -3; - - p = &digits[0]; - while (*p) { - rem = (rem * 10) + ((*p++) - '0'); - acc = (acc * 10) + (rem / spaces); - rem -= (rem / spaces) * spaces; - } - - if (rem) { - lwsl_warn("nonzero handshake remainder\n"); - return -1; - } - - *result = acc; - - return 0; -} - - -static int -handshake_00(struct libwebsocket_context *context, struct libwebsocket *wsi) -{ - unsigned long key1, key2; - unsigned char sum[16]; - char *response; - char *p; - int n; - - /* 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 && - !libwebsocket_ensure_user_space(wsi)) - goto bail; - - /* create the response packet */ - - /* make a buffer big enough for everything */ - - response = (char *)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) { - lwsl_err("Out of memory for response buffer\n"); - goto bail; - } - - p = response; - LWS_CPYAPP(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" - "Upgrade: WebSocket\x0d\x0a" - "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 (wsi->ssl) { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: wss://"); - } else { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: ws://"); - } -#else - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: ws://"); -#endif - - LWS_CPYAPP_TOKEN(p, WSI_TOKEN_HOST); - LWS_CPYAPP_TOKEN(p, WSI_TOKEN_GET_URI); - - if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); - LWS_CPYAPP_TOKEN(p, WSI_TOKEN_PROTOCOL); - } - - LWS_CPYAPP(p, "\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] = (unsigned char)(key1 >> 24); - sum[1] = (unsigned char)(key1 >> 16); - sum[2] = (unsigned char)(key1 >> 8); - sum[3] = (unsigned char)(key1); - sum[4] = (unsigned char)(key2 >> 24); - sum[5] = (unsigned char)(key2 >> 16); - sum[6] = (unsigned char)(key2 >> 8); - sum[7] = (unsigned char)(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 */ - - lwsl_parser("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) { - lwsl_debug("handshake_00: ERROR writing to socket\n"); - 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->protocol->owning_server, - wsi, LWS_CALLBACK_ESTABLISHED, - wsi->user_space, NULL, 0); - - return 0; - -bail: - return -1; -} - -/* - * Perform the newer BASE64-encoded handshake scheme - */ - -static int -handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi) -{ - static const char *websocket_magic_guid_04 = - "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; - static const char *websocket_magic_guid_04_masking = - "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; - char accept_buf[MAX_WEBSOCKET_04_KEY_LEN + 37]; - char nonce_buf[256]; - char mask_summing_buf[256 + MAX_WEBSOCKET_04_KEY_LEN + 37]; - unsigned char hash[20]; - int n; - char *response; - char *p; - char *m = mask_summing_buf; - int nonce_len = 0; - int accept_len; - char *c; - char ext_name[128]; - struct libwebsocket_extension *ext; - int ext_count = 0; - int more = 1; - - if (!wsi->utf8_token[WSI_TOKEN_HOST].token_len || - !wsi->utf8_token[WSI_TOKEN_KEY].token_len) { - lwsl_parser("handshake_04 missing pieces\n"); - /* completed header processing, but missing some bits */ - goto bail; - } - - if (wsi->utf8_token[WSI_TOKEN_KEY].token_len >= - MAX_WEBSOCKET_04_KEY_LEN) { - lwsl_warn("Client sent handshake key longer " - "than max supported %d\n", MAX_WEBSOCKET_04_KEY_LEN); - goto bail; - } - - strcpy(accept_buf, wsi->utf8_token[WSI_TOKEN_KEY].token); - strcpy(accept_buf + wsi->utf8_token[WSI_TOKEN_KEY].token_len, - websocket_magic_guid_04); - - SHA1((unsigned char *)accept_buf, - wsi->utf8_token[WSI_TOKEN_KEY].token_len + - strlen(websocket_magic_guid_04), hash); - - accept_len = lws_b64_encode_string((char *)hash, 20, accept_buf, - sizeof accept_buf); - if (accept_len < 0) { - lwsl_warn("Base64 encoded hash too long\n"); - goto bail; - } - - /* allocate the per-connection user memory (if any) */ - if (wsi->protocol->per_session_data_size && - !libwebsocket_ensure_user_space(wsi)) - goto bail; - - /* create the response packet */ - - /* make a buffer big enough for everything */ - - response = (char *)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) { - lwsl_err("Out of memory for response buffer\n"); - goto bail; - } - - p = response; - LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" - "Upgrade: WebSocket\x0d\x0a" - "Connection: Upgrade\x0d\x0a" - "Sec-WebSocket-Accept: "); - strcpy(p, accept_buf); - p += accept_len; - - if (wsi->ietf_spec_revision == 4) { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Nonce: "); - - /* select the nonce */ - - n = libwebsockets_get_random(wsi->protocol->owning_server, - hash, 16); - if (n != 16) { - lwsl_err("Unable to read random device %s %d\n", - SYSTEM_RANDOM_FILEPATH, n); - if (wsi->user_space) - free(wsi->user_space); - goto bail; - } - - /* encode the nonce */ - - nonce_len = lws_b64_encode_string((const char *)hash, 16, - nonce_buf, sizeof nonce_buf); - if (nonce_len < 0) { - lwsl_err("Failed to base 64 encode the nonce\n"); - if (wsi->user_space) - free(wsi->user_space); - goto bail; - } - - /* apply the nonce */ - - strcpy(p, nonce_buf); - p += nonce_len; - } - - if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { - LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); - LWS_CPYAPP_TOKEN(p, WSI_TOKEN_PROTOCOL); - } - - /* - * Figure out which extensions the client has that we want to - * enable on this connection, and give him back the list - */ - - if (wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) { - - /* - * break down the list of client extensions - * and go through them - */ - - c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; - lwsl_parser("wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token = %s\n", - wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token); - wsi->count_active_extensions = 0; - 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 a client's extension against our support */ - - ext = wsi->protocol->owning_server->extensions; - - while (ext && ext->callback) { - - if (strcmp(ext_name, ext->name)) { - ext++; - continue; - } - - /* - * oh, we do support this one he - * asked for... but let's ask user - * code if it's OK to apply it on this - * particular connection + protocol - */ - - n = wsi->protocol->owning_server-> - protocols[0].callback( - wsi->protocol->owning_server, - wsi, - LWS_CALLBACK_CONFIRM_EXTENSION_OKAY, - wsi->user_space, ext_name, 0); - - /* - * zero return from callback means - * go ahead and allow the extension, - * it's what we get if the callback is - * unhandled - */ - - if (n) { - ext++; - continue; - } - - /* apply it */ - - if (ext_count) - *p++ = ','; - else - LWS_CPYAPP(p, - "\x0d\x0aSec-WebSocket-Extensions: "); - p += sprintf(p, "%s", ext_name); - ext_count++; - - /* 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"); - free(response); - goto bail; - } - 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_CONSTRUCT, - wsi->active_extensions_user[ - wsi->count_active_extensions], NULL, 0); - - wsi->count_active_extensions++; - lwsl_parser("wsi->count_active_extensions <- %d\n", - wsi->count_active_extensions); - - ext++; - } - - n = 0; - } - } - - /* end of response packet */ - - LWS_CPYAPP(p, "\x0d\x0a\x0d\x0a"); - - if (wsi->ietf_spec_revision == 4) { - - /* - * precompute the masking key the client will use from the SHA1 - * hash of ( base 64 client key we were sent, concatenated with - * the bse 64 nonce we sent, concatenated with a magic constant - * guid specified by the 04 standard ) - * - * We store the hash in the connection's wsi ready to use with - * undoing the masking the client has done on framed data it - * sends (we send our data to the client in clear). - */ - - strcpy(mask_summing_buf, wsi->utf8_token[WSI_TOKEN_KEY].token); - m += wsi->utf8_token[WSI_TOKEN_KEY].token_len; - strcpy(m, nonce_buf); - m += nonce_len; - strcpy(m, websocket_magic_guid_04_masking); - m += strlen(websocket_magic_guid_04_masking); - - SHA1((unsigned char *)mask_summing_buf, m - mask_summing_buf, - wsi->masking_key_04); - } - - if (!lws_any_extension_handled(context, wsi, - LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX, - response, p - response)) { - - /* okay send the handshake response accepting the connection */ - - lwsl_parser("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) { - lwsl_debug("handshake_0405: ERROR writing to socket\n"); - 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; - wsi->rx_packet_length = 0; - - /* notify user code that we're ready to roll */ - - if (wsi->protocol->callback) - wsi->protocol->callback(wsi->protocol->owning_server, - 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 @@ -580,8 +91,12 @@ libwebsocket_read(struct libwebsocket_context *context, break; } #endif +#ifndef LWS_NO_SERVER /* LWS_CONNMODE_WS_SERVING */ + extern int handshake_00(struct libwebsocket_context *context, struct libwebsocket *wsi); + extern int handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi); + for (n = 0; n < len; n++) libwebsocket_parse(wsi, *buf++); @@ -702,7 +217,7 @@ libwebsocket_read(struct libwebsocket_context *context, lwsl_parser("accepted v%02d connection\n", wsi->ietf_spec_revision); - +#endif break; case WSI_STATE_AWAITING_CLOSE_ACK: @@ -719,11 +234,12 @@ libwebsocket_read(struct libwebsocket_context *context, break; } #endif +#ifndef LWS_NO_SERVER /* LWS_CONNMODE_WS_SERVING */ if (libwebsocket_interpret_incoming_packet(wsi, buf, len) < 0) goto bail; - +#endif break; default: lwsl_err("libwebsocket_read: Unhandled state\n"); diff --git a/lib/libwebsockets.c b/lib/libwebsockets.c index 99ce9e84..000861c1 100644 --- a/lib/libwebsockets.c +++ b/lib/libwebsockets.c @@ -128,49 +128,6 @@ do_ext: return 0; } -#ifdef LWS_OPENSSL_SUPPORT -static void -libwebsockets_decode_ssl_error(void) -{ - char buf[256]; - u_long err; - - while ((err = ERR_get_error()) != 0) { - ERR_error_string_n(err, buf, sizeof(buf)); - lwsl_err("*** %s\n", buf); - } -} -#endif - - -static int -interface_to_sa(const char *ifname, struct sockaddr_in *addr, size_t addrlen) -{ - int rc = -1; -#ifdef WIN32 - /* TODO */ -#else - struct ifaddrs *ifr; - struct ifaddrs *ifc; - struct sockaddr_in *sin; - - getifaddrs(&ifr); - for (ifc = ifr; ifc != NULL; ifc = ifc->ifa_next) { - if (strcmp(ifc->ifa_name, ifname)) - continue; - if (ifc->ifa_addr == NULL) - continue; - sin = (struct sockaddr_in *)ifc->ifa_addr; - if (sin->sin_family != AF_INET) - continue; - memcpy(addr, sin, addrlen); - rc = 0; - } - - freeifaddrs(ifr); -#endif - return rc; -} void libwebsocket_close_and_free_session(struct libwebsocket_context *context, @@ -367,10 +324,10 @@ just_kill_connection: for (n = 0; n < WSI_TOKEN_COUNT; n++) if (wsi->utf8_token[n].token) free(wsi->utf8_token[n].token); - +#ifndef LWS_NO_CLIENT if (wsi->c_address) free(wsi->c_address); - +#endif if (wsi->rxflow_buffer) free(wsi->rxflow_buffer); @@ -703,54 +660,6 @@ libwebsocket_service_timeout_check(struct libwebsocket_context *context, } } -struct libwebsocket * -libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) -{ - struct libwebsocket *new_wsi; - int n; - - new_wsi = (struct libwebsocket *)malloc(sizeof(struct libwebsocket)); - if (new_wsi == NULL) { - lwsl_err("Out of memory for new connection\n"); - return NULL; - } - - memset(new_wsi, 0, sizeof(struct libwebsocket)); - new_wsi->count_active_extensions = 0; - new_wsi->pending_timeout = NO_PENDING_TIMEOUT; - - /* intialize the instance struct */ - - new_wsi->state = WSI_STATE_HTTP; - new_wsi->name_buffer_pos = 0; - new_wsi->mode = LWS_CONNMODE_HTTP_SERVING; - - for (n = 0; n < WSI_TOKEN_COUNT; n++) { - new_wsi->utf8_token[n].token = NULL; - new_wsi->utf8_token[n].token_len = 0; - } - - /* - * these can only be set once the protocol is known - * we set an unestablished connection's protocol pointer - * to the start of the supported list, so it can look - * for matching ones during the handshake - */ - new_wsi->protocol = context->protocols; - new_wsi->user_space = NULL; - - /* - * 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 - */ - new_wsi->ietf_spec_revision = 0; - - return new_wsi; -} - - /** * libwebsocket_service_fd() - Service polled socket with something waiting * @context: Websocket context @@ -766,22 +675,19 @@ int libwebsocket_service_fd(struct libwebsocket_context *context, struct pollfd *pollfd) { + struct libwebsocket *wsi; unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; - struct libwebsocket *wsi; - struct libwebsocket *new_wsi; int n; int m; - ssize_t len; - int accept_fd; - unsigned int clilen; - struct sockaddr_in cli_addr; struct timeval tv; int more = 1; struct lws_tokens eff_buf; - int opt = 1; #ifndef LWS_NO_CLIENT extern int lws_client_socket_service(struct libwebsocket_context *context, struct libwebsocket *wsi, struct pollfd *pollfd); +#endif +#ifndef LWS_NO_SERVER + extern int lws_server_socket_service(struct libwebsocket_context *context, struct libwebsocket *wsi, struct pollfd *pollfd); #endif /* * you can call us with pollfd = NULL to just allow the once-per-second @@ -856,278 +762,13 @@ libwebsocket_service_fd(struct libwebsocket_context *context, switch (wsi->mode) { +#ifndef LWS_NO_SERVER case LWS_CONNMODE_HTTP_SERVING: - - /* handle http headers coming in */ - - /* any incoming data ready? */ - - if (pollfd->revents & POLLIN) { - - #ifdef LWS_OPENSSL_SUPPORT - if (wsi->ssl) - len = SSL_read(wsi->ssl, buf, sizeof buf); - else - #endif - len = recv(pollfd->fd, buf, sizeof buf, 0); - - if (len < 0) { - lwsl_debug("Socket read returned %d\n", len); - if (errno != EINTR && errno != EAGAIN) - libwebsocket_close_and_free_session(context, - wsi, LWS_CLOSE_STATUS_NOSTATUS); - return 0; - } - if (!len) { - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - return 0; - } - - n = libwebsocket_read(context, wsi, buf, len); - if (n < 0) - /* we closed wsi */ - return 0; - } - - /* this handles POLLOUT for http serving fragments */ - - if (!(pollfd->revents & POLLOUT)) - break; - - /* one shot */ - pollfd->events &= ~POLLOUT; - - if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE) - break; - - if (libwebsockets_serve_http_file_fragment(context, wsi) < 0) - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NOSTATUS); - else - if (wsi->state == WSI_STATE_HTTP && wsi->protocol->callback) - if (user_callback_handle_rxflow(wsi->protocol->callback, context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space, - wsi->filepath, wsi->filepos)) - libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); - break; - case LWS_CONNMODE_SERVER_LISTENER: - - /* pollin means a client has connected to us then */ - - if (!(pollfd->revents & POLLIN)) - break; - - /* listen socket got an unencrypted connection... */ - - clilen = sizeof(cli_addr); - accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, - &clilen); - if (accept_fd < 0) { - lwsl_warn("ERROR on accept: %s\n", strerror(errno)); - break; - } - - /* Disable Nagle */ - opt = 1; - setsockopt(accept_fd, IPPROTO_TCP, TCP_NODELAY, - (const void *)&opt, sizeof(opt)); - - /* - * look at who we connected to and give user code a chance - * to reject based on client IP. There's no protocol selected - * yet so we issue this to protocols[0] - */ - - if ((context->protocols[0].callback)(context, wsi, - LWS_CALLBACK_FILTER_NETWORK_CONNECTION, - (void *)(long)accept_fd, NULL, 0)) { - lwsl_debug("Callback denied network connection\n"); - compatible_close(accept_fd); - break; - } - - new_wsi = libwebsocket_create_new_server_wsi(context); - if (new_wsi == NULL) { - compatible_close(accept_fd); - break; - } - - new_wsi->sock = accept_fd; - - -#ifdef LWS_OPENSSL_SUPPORT - new_wsi->ssl = NULL; - - if (context->use_ssl) { - - new_wsi->ssl = SSL_new(context->ssl_ctx); - if (new_wsi->ssl == NULL) { - lwsl_err("SSL_new failed: %s\n", - ERR_error_string(SSL_get_error( - new_wsi->ssl, 0), NULL)); - libwebsockets_decode_ssl_error(); - free(new_wsi); - compatible_close(accept_fd); - break; - } - - SSL_set_ex_data(new_wsi->ssl, - openssl_websocket_private_data_index, context); - - SSL_set_fd(new_wsi->ssl, accept_fd); - - n = SSL_accept(new_wsi->ssl); - if (n != 1) { - /* - * browsers seem to probe with various - * ssl params which fail then retry - * and succeed - */ - lwsl_debug("SSL_accept failed skt %u: %s\n", - pollfd->fd, - ERR_error_string(SSL_get_error( - new_wsi->ssl, n), NULL)); - SSL_free( - new_wsi->ssl); - free(new_wsi); - compatible_close(accept_fd); - break; - } - - lwsl_debug("accepted new SSL conn " - "port %u on fd=%d SSL ver %s\n", - ntohs(cli_addr.sin_port), accept_fd, - SSL_get_version(new_wsi->ssl)); - - } else -#endif - lwsl_debug("accepted new conn port %u on fd=%d\n", - ntohs(cli_addr.sin_port), accept_fd); - - insert_wsi_socket_into_fds(context, new_wsi); - break; - case LWS_CONNMODE_BROADCAST_PROXY_LISTENER: - - /* as we are listening, POLLIN means accept() is needed */ - - if (!(pollfd->revents & POLLIN)) - break; - - /* listen socket got an unencrypted connection... */ - - clilen = sizeof(cli_addr); - accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, - &clilen); - if (accept_fd < 0) { - lwsl_warn("ERROR on accept %d\n", accept_fd); - return 0; - } - - /* create a dummy wsi for the connection and add it */ - - new_wsi = (struct libwebsocket *)malloc(sizeof(struct libwebsocket)); - if (new_wsi == NULL) { - lwsl_err("Out of mem\n"); - goto bail_prox_listener; - } - memset(new_wsi, 0, sizeof (struct libwebsocket)); - new_wsi->sock = accept_fd; - new_wsi->mode = LWS_CONNMODE_BROADCAST_PROXY; - new_wsi->state = WSI_STATE_ESTABLISHED; - new_wsi->count_active_extensions = 0; - /* note which protocol we are proxying */ - new_wsi->protocol_index_for_broadcast_proxy = - wsi->protocol_index_for_broadcast_proxy; - - insert_wsi_socket_into_fds(context, new_wsi); - break; - -bail_prox_listener: - compatible_close(accept_fd); - break; - case LWS_CONNMODE_BROADCAST_PROXY: - - /* handle session socket closed */ - - if (pollfd->revents & (POLLERR | POLLHUP)) { - - lwsl_debug("Session Socket %p (fd=%d) dead\n", - (void *)wsi, pollfd->fd); - - libwebsocket_close_and_free_session(context, wsi, - LWS_CLOSE_STATUS_NORMAL); - return 0; - } - - /* - * either extension code with stuff to spill, or the user code, - * requested a callback when it was OK to write - */ - - if (pollfd->revents & POLLOUT) - if (lws_handle_POLLOUT_event(context, wsi, - pollfd) < 0) { - libwebsocket_close_and_free_session( - context, wsi, LWS_CLOSE_STATUS_NORMAL); - return 0; - } - - /* any incoming data ready? */ - - if (!(pollfd->revents & POLLIN)) - break; - - /* get the issued broadcast payload from the socket */ - - len = read(pollfd->fd, buf + LWS_SEND_BUFFER_PRE_PADDING, - MAX_BROADCAST_PAYLOAD); - if (len < 0) { - lwsl_err("Error reading broadcast payload\n"); - break; - } - - /* broadcast it to all guys with this protocol index */ - - for (n = 0; n < context->fds_count; n++) { - - new_wsi = context->lws_lookup[context->fds[n].fd]; - if (new_wsi == NULL) - continue; - - /* only to clients we are serving to */ - - if (new_wsi->mode != LWS_CONNMODE_WS_SERVING) - continue; - - /* - * never broadcast to non-established - * connection - */ - - if (new_wsi->state != WSI_STATE_ESTABLISHED) - continue; - - /* - * only broadcast to connections using - * the requested protocol - */ - - if (new_wsi->protocol->protocol_index != - wsi->protocol_index_for_broadcast_proxy) - continue; - - /* broadcast it to this connection */ - - user_callback_handle_rxflow(new_wsi->protocol->callback, context, new_wsi, - LWS_CALLBACK_BROADCAST, - new_wsi->user_space, - buf + LWS_SEND_BUFFER_PRE_PADDING, len); - } - break; - + return lws_server_socket_service(context, wsi, pollfd); +#endif case LWS_CONNMODE_WS_SERVING: case LWS_CONNMODE_WS_CLIENT: @@ -1535,7 +1176,13 @@ libwebsocket_get_socket_fd(struct libwebsocket *wsi) return wsi->sock; } - +#ifdef LWS_NO_SERVER +int +_libwebsocket_rx_flow_control(struct libwebsocket *wsi) +{ + return 0; +} +#else int _libwebsocket_rx_flow_control(struct libwebsocket *wsi) { @@ -1579,6 +1226,7 @@ _libwebsocket_rx_flow_control(struct libwebsocket *wsi) return 1; } +#endif /** * libwebsocket_rx_flow_control() - Enable and disable socket servicing for @@ -1736,17 +1384,13 @@ libwebsocket_create_context(int port, const char *interf, { int n; int m; - int sockfd = 0; int fd; struct sockaddr_in serv_addr, cli_addr; int opt = 1; struct libwebsocket_context *context = NULL; unsigned int slen; char *p; - char hostname[1024] = ""; -// struct hostent *he; struct libwebsocket *wsi; - struct sockaddr sa; #ifdef LWS_OPENSSL_SUPPORT SSL_METHOD *method; @@ -1848,11 +1492,12 @@ libwebsocket_create_context(int port, const char *interf, openssl_websocket_private_data_index = 0; #endif - if (options & LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME) { + strcpy(context->canonical_hostname, "unknown"); - strcpy(context->canonical_hostname, "unknown"); - - } else { +#ifndef LWS_NO_SERVER + if (!(options & LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) { + struct sockaddr sa; + char hostname[1024] = ""; /* find canonical hostname */ @@ -1880,9 +1525,9 @@ libwebsocket_create_context(int port, const char *interf, strncpy(context->canonical_hostname, hostname, sizeof context->canonical_hostname - 1); - // lwsl_debug("context->canonical_hostname = %s\n", - // context->canonical_hostname); + lwsl_info(" canonical_hostname = %s\n", context->canonical_hostname); } +#endif /* split the proxy ads:port if given */ @@ -1901,11 +1546,12 @@ libwebsocket_create_context(int port, const char *interf, *p = '\0'; context->http_proxy_port = atoi(p + 1); - lwsl_debug("Using proxy %s:%u\n", + lwsl_info(" Proxy %s:%u\n", context->http_proxy_address, context->http_proxy_port); } +#ifndef LWS_NO_SERVER if (port) { #ifdef LWS_OPENSSL_SUPPORT @@ -1926,6 +1572,7 @@ libwebsocket_create_context(int port, const char *interf, "serving unencrypted\n"); #endif } +#endif /* ignore SIGPIPE */ #ifdef WIN32 @@ -1970,6 +1617,8 @@ libwebsocket_create_context(int port, const char *interf, SSL_CTX_set_options(context->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); SSL_CTX_set_cipher_list(context->ssl_ctx, CIPHERS_LIST_STRING); +#ifndef LWS_NO_CLIENT + /* client context */ if (port == CONTEXT_PORT_NO_LISTEN) { @@ -2021,6 +1670,7 @@ libwebsocket_create_context(int port, const char *interf, LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS, context->ssl_client_ctx, NULL, 0); } +#endif /* as a server, are we requiring clients to identify themselves? */ @@ -2078,9 +1728,12 @@ libwebsocket_create_context(int port, const char *interf, if (lws_b64_selftest()) return NULL; +#ifndef LWS_NO_SERVER /* set up our external listening socket we serve on */ if (port) { + extern int interface_to_sa(const char *ifname, struct sockaddr_in *addr, size_t addrlen); + int sockfd; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) { @@ -2135,6 +1788,7 @@ libwebsocket_create_context(int port, const char *interf, listen(sockfd, LWS_SOMAXCONN); lwsl_info(" Listening on port %d\n", port); } +#endif /* * drop any root privs for this process diff --git a/lib/parsers.c b/lib/parsers.c index a1e4a486..b2d40f2f 100644 --- a/lib/parsers.c +++ b/lib/parsers.c @@ -1,7 +1,7 @@ /* * libwebsockets - small server side websockets and web server implementation * - * Copyright (C) 2010 Andy Green + * 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 @@ -25,33 +25,6 @@ #include #endif -static const struct lws_tokens lws_tokens[WSI_TOKEN_COUNT] = { - - /* win32 can't do C99 */ - -/* [WSI_TOKEN_GET_URI] = */{ "GET ", 4 }, -/* [WSI_TOKEN_HOST] = */{ "Host:", 5 }, -/* [WSI_TOKEN_CONNECTION] = */{ "Connection:", 11 }, -/* [WSI_TOKEN_KEY1] = */{ "Sec-WebSocket-Key1:", 19 }, -/* [WSI_TOKEN_KEY2] = */{ "Sec-WebSocket-Key2:", 19 }, -/* [WSI_TOKEN_PROTOCOL] = */{ "Sec-WebSocket-Protocol:", 23 }, -/* [WSI_TOKEN_UPGRADE] = */{ "Upgrade:", 8 }, -/* [WSI_TOKEN_ORIGIN] = */{ "Origin:", 7 }, -/* [WSI_TOKEN_DRAFT] = */{ "Sec-WebSocket-Draft:", 20 }, -/* [WSI_TOKEN_CHALLENGE] = */{ "\x0d\x0a", 2 }, - -/* [WSI_TOKEN_KEY] = */{ "Sec-WebSocket-Key:", 18 }, -/* [WSI_TOKEN_VERSION] = */{ "Sec-WebSocket-Version:", 22 }, -/* [WSI_TOKEN_SWORIGIN]= */{ "Sec-WebSocket-Origin:", 21 }, - -/* [WSI_TOKEN_EXTENSIONS] = */{ "Sec-WebSocket-Extensions:", 25 }, - -/* [WSI_TOKEN_ACCEPT] = */{ "Sec-WebSocket-Accept:", 21 }, -/* [WSI_TOKEN_NONCE] = */{ "Sec-WebSocket-Nonce:", 20 }, -/* [WSI_TOKEN_HTTP] = */{ "HTTP/1.1 ", 9 }, -/* [WSI_TOKEN_MUXURL] = */{ "", -1 }, - -}; unsigned char lextable[] = { /* pos 0: state 0 */ @@ -626,7 +599,6 @@ int lws_frame_is_binary(struct libwebsocket *wsi) return wsi->frame_is_binary; } - int libwebsocket_rx_sm(struct libwebsocket *wsi, unsigned char c) { @@ -1191,8 +1163,6 @@ illegal_ctl_length: } - - int libwebsocket_interpret_incoming_packet(struct libwebsocket *wsi, unsigned char *buf, size_t len) { diff --git a/lib/private-libwebsockets.h b/lib/private-libwebsockets.h index 8c52c816..4f6400df 100644 --- a/lib/private-libwebsockets.h +++ b/lib/private-libwebsockets.h @@ -378,9 +378,11 @@ struct libwebsocket { /* 07 specific */ char this_frame_masked; + enum connection_mode mode; + +#ifndef LWS_NO_CLIENT /* client support */ char initial_handshake_hash_base64[30]; - enum connection_mode mode; char *c_path; char *c_host; char *c_origin; @@ -389,7 +391,7 @@ struct libwebsocket { char *c_address; int c_port; - +#endif #ifdef LWS_OPENSSL_SUPPORT SSL *ssl; diff --git a/lib/server-handshake.c b/lib/server-handshake.c new file mode 100644 index 00000000..b91769ec --- /dev/null +++ b/lib/server-handshake.c @@ -0,0 +1,510 @@ +/* + * 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" + +#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } +#define LWS_CPYAPP_TOKEN(ptr, tok) { strcpy(p, wsi->utf8_token[tok].token); \ + p += wsi->utf8_token[tok].token_len; } + +static int +interpret_key(const char *key, unsigned long *result) +{ + char digits[20]; + int digit_pos = 0; + const char *p = key; + unsigned int spaces = 0; + unsigned long acc = 0; + int rem = 0; + + while (*p) { + if (!isdigit(*p)) { + p++; + continue; + } + if (digit_pos == sizeof(digits) - 1) + return -1; + digits[digit_pos++] = *p++; + } + digits[digit_pos] = '\0'; + if (!digit_pos) + return -2; + + while (*key) { + if (*key == ' ') + spaces++; + key++; + } + + if (!spaces) + return -3; + + p = &digits[0]; + while (*p) { + rem = (rem * 10) + ((*p++) - '0'); + acc = (acc * 10) + (rem / spaces); + rem -= (rem / spaces) * spaces; + } + + if (rem) { + lwsl_warn("nonzero handshake remainder\n"); + return -1; + } + + *result = acc; + + return 0; +} + + +int handshake_00(struct libwebsocket_context *context, struct libwebsocket *wsi) +{ + unsigned long key1, key2; + unsigned char sum[16]; + char *response; + char *p; + int n; + + /* 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 && + !libwebsocket_ensure_user_space(wsi)) + goto bail; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = (char *)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) { + lwsl_err("Out of memory for response buffer\n"); + goto bail; + } + + p = response; + LWS_CPYAPP(p, "HTTP/1.1 101 WebSocket Protocol Handshake\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a" + "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 (wsi->ssl) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: wss://"); + } else { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: ws://"); + } +#else + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Location: ws://"); +#endif + + LWS_CPYAPP_TOKEN(p, WSI_TOKEN_HOST); + LWS_CPYAPP_TOKEN(p, WSI_TOKEN_GET_URI); + + if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); + LWS_CPYAPP_TOKEN(p, WSI_TOKEN_PROTOCOL); + } + + LWS_CPYAPP(p, "\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] = (unsigned char)(key1 >> 24); + sum[1] = (unsigned char)(key1 >> 16); + sum[2] = (unsigned char)(key1 >> 8); + sum[3] = (unsigned char)(key1); + sum[4] = (unsigned char)(key2 >> 24); + sum[5] = (unsigned char)(key2 >> 16); + sum[6] = (unsigned char)(key2 >> 8); + sum[7] = (unsigned char)(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 */ + + lwsl_parser("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) { + lwsl_debug("handshake_00: ERROR writing to socket\n"); + 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->protocol->owning_server, + wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, NULL, 0); + + return 0; + +bail: + return -1; +} + +/* + * Perform the newer BASE64-encoded handshake scheme + */ + +int +handshake_0405(struct libwebsocket_context *context, struct libwebsocket *wsi) +{ + static const char *websocket_magic_guid_04 = + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; + static const char *websocket_magic_guid_04_masking = + "61AC5F19-FBBA-4540-B96F-6561F1AB40A8"; + char accept_buf[MAX_WEBSOCKET_04_KEY_LEN + 37]; + char nonce_buf[256]; + char mask_summing_buf[256 + MAX_WEBSOCKET_04_KEY_LEN + 37]; + unsigned char hash[20]; + int n; + char *response; + char *p; + char *m = mask_summing_buf; + int nonce_len = 0; + int accept_len; + char *c; + char ext_name[128]; + struct libwebsocket_extension *ext; + int ext_count = 0; + int more = 1; + + if (!wsi->utf8_token[WSI_TOKEN_HOST].token_len || + !wsi->utf8_token[WSI_TOKEN_KEY].token_len) { + lwsl_parser("handshake_04 missing pieces\n"); + /* completed header processing, but missing some bits */ + goto bail; + } + + if (wsi->utf8_token[WSI_TOKEN_KEY].token_len >= + MAX_WEBSOCKET_04_KEY_LEN) { + lwsl_warn("Client sent handshake key longer " + "than max supported %d\n", MAX_WEBSOCKET_04_KEY_LEN); + goto bail; + } + + strcpy(accept_buf, wsi->utf8_token[WSI_TOKEN_KEY].token); + strcpy(accept_buf + wsi->utf8_token[WSI_TOKEN_KEY].token_len, + websocket_magic_guid_04); + + SHA1((unsigned char *)accept_buf, + wsi->utf8_token[WSI_TOKEN_KEY].token_len + + strlen(websocket_magic_guid_04), hash); + + accept_len = lws_b64_encode_string((char *)hash, 20, accept_buf, + sizeof accept_buf); + if (accept_len < 0) { + lwsl_warn("Base64 encoded hash too long\n"); + goto bail; + } + + /* allocate the per-connection user memory (if any) */ + if (wsi->protocol->per_session_data_size && + !libwebsocket_ensure_user_space(wsi)) + goto bail; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = (char *)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) { + lwsl_err("Out of memory for response buffer\n"); + goto bail; + } + + p = response; + LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Accept: "); + strcpy(p, accept_buf); + p += accept_len; + + if (wsi->ietf_spec_revision == 4) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Nonce: "); + + /* select the nonce */ + + n = libwebsockets_get_random(wsi->protocol->owning_server, + hash, 16); + if (n != 16) { + lwsl_err("Unable to read random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, n); + if (wsi->user_space) + free(wsi->user_space); + goto bail; + } + + /* encode the nonce */ + + nonce_len = lws_b64_encode_string((const char *)hash, 16, + nonce_buf, sizeof nonce_buf); + if (nonce_len < 0) { + lwsl_err("Failed to base 64 encode the nonce\n"); + if (wsi->user_space) + free(wsi->user_space); + goto bail; + } + + /* apply the nonce */ + + strcpy(p, nonce_buf); + p += nonce_len; + } + + if (wsi->utf8_token[WSI_TOKEN_PROTOCOL].token) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); + LWS_CPYAPP_TOKEN(p, WSI_TOKEN_PROTOCOL); + } + + /* + * Figure out which extensions the client has that we want to + * enable on this connection, and give him back the list + */ + + if (wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token_len) { + + /* + * break down the list of client extensions + * and go through them + */ + + c = wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token; + lwsl_parser("wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token = %s\n", + wsi->utf8_token[WSI_TOKEN_EXTENSIONS].token); + wsi->count_active_extensions = 0; + 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 a client's extension against our support */ + + ext = wsi->protocol->owning_server->extensions; + + while (ext && ext->callback) { + + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + /* + * oh, we do support this one he + * asked for... but let's ask user + * code if it's OK to apply it on this + * particular connection + protocol + */ + + n = wsi->protocol->owning_server-> + protocols[0].callback( + wsi->protocol->owning_server, + wsi, + LWS_CALLBACK_CONFIRM_EXTENSION_OKAY, + wsi->user_space, ext_name, 0); + + /* + * zero return from callback means + * go ahead and allow the extension, + * it's what we get if the callback is + * unhandled + */ + + if (n) { + ext++; + continue; + } + + /* apply it */ + + if (ext_count) + *p++ = ','; + else + LWS_CPYAPP(p, + "\x0d\x0aSec-WebSocket-Extensions: "); + p += sprintf(p, "%s", ext_name); + ext_count++; + + /* 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"); + free(response); + goto bail; + } + 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_CONSTRUCT, + wsi->active_extensions_user[ + wsi->count_active_extensions], NULL, 0); + + wsi->count_active_extensions++; + lwsl_parser("wsi->count_active_extensions <- %d\n", + wsi->count_active_extensions); + + ext++; + } + + n = 0; + } + } + + /* end of response packet */ + + LWS_CPYAPP(p, "\x0d\x0a\x0d\x0a"); + + if (wsi->ietf_spec_revision == 4) { + + /* + * precompute the masking key the client will use from the SHA1 + * hash of ( base 64 client key we were sent, concatenated with + * the bse 64 nonce we sent, concatenated with a magic constant + * guid specified by the 04 standard ) + * + * We store the hash in the connection's wsi ready to use with + * undoing the masking the client has done on framed data it + * sends (we send our data to the client in clear). + */ + + strcpy(mask_summing_buf, wsi->utf8_token[WSI_TOKEN_KEY].token); + m += wsi->utf8_token[WSI_TOKEN_KEY].token_len; + strcpy(m, nonce_buf); + m += nonce_len; + strcpy(m, websocket_magic_guid_04_masking); + m += strlen(websocket_magic_guid_04_masking); + + SHA1((unsigned char *)mask_summing_buf, m - mask_summing_buf, + wsi->masking_key_04); + } + + if (!lws_any_extension_handled(context, wsi, + LWS_EXT_CALLBACK_HANDSHAKE_REPLY_TX, + response, p - response)) { + + /* okay send the handshake response accepting the connection */ + + lwsl_parser("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) { + lwsl_debug("handshake_0405: ERROR writing to socket\n"); + 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; + wsi->rx_packet_length = 0; + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + wsi->protocol->callback(wsi->protocol->owning_server, + wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, NULL, 0); + + return 0; + + +bail: + return -1; +} + diff --git a/lib/server.c b/lib/server.c new file mode 100644 index 00000000..17f83aeb --- /dev/null +++ b/lib/server.c @@ -0,0 +1,421 @@ +/* + * 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; + +static void +libwebsockets_decode_ssl_error(void) +{ + char buf[256]; + u_long err; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, buf, sizeof(buf)); + lwsl_err("*** %s\n", buf); + } +} +#endif + +int +interface_to_sa(const char *ifname, struct sockaddr_in *addr, size_t addrlen) +{ + int rc = -1; +#ifdef WIN32 + /* TODO */ +#else + struct ifaddrs *ifr; + struct ifaddrs *ifc; + struct sockaddr_in *sin; + + getifaddrs(&ifr); + for (ifc = ifr; ifc != NULL; ifc = ifc->ifa_next) { + if (strcmp(ifc->ifa_name, ifname)) + continue; + if (ifc->ifa_addr == NULL) + continue; + sin = (struct sockaddr_in *)ifc->ifa_addr; + if (sin->sin_family != AF_INET) + continue; + memcpy(addr, sin, addrlen); + rc = 0; + } + + freeifaddrs(ifr); +#endif + return rc; +} + +struct libwebsocket * +libwebsocket_create_new_server_wsi(struct libwebsocket_context *context) +{ + struct libwebsocket *new_wsi; + int n; + + new_wsi = (struct libwebsocket *)malloc(sizeof(struct libwebsocket)); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + memset(new_wsi, 0, sizeof(struct libwebsocket)); + new_wsi->count_active_extensions = 0; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + + /* intialize the instance struct */ + + new_wsi->state = WSI_STATE_HTTP; + new_wsi->name_buffer_pos = 0; + new_wsi->mode = LWS_CONNMODE_HTTP_SERVING; + + for (n = 0; n < WSI_TOKEN_COUNT; n++) { + new_wsi->utf8_token[n].token = NULL; + new_wsi->utf8_token[n].token_len = 0; + } + + /* + * these can only be set once the protocol is known + * we set an unestablished connection's protocol pointer + * to the start of the supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = context->protocols; + new_wsi->user_space = NULL; + + /* + * 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 + */ + new_wsi->ietf_spec_revision = 0; + + return new_wsi; +} + +int lws_server_socket_service(struct libwebsocket_context *context, + struct libwebsocket *wsi, struct pollfd *pollfd) +{ + unsigned char buf[LWS_SEND_BUFFER_PRE_PADDING + 1 + + MAX_BROADCAST_PAYLOAD + LWS_SEND_BUFFER_POST_PADDING]; + struct libwebsocket *new_wsi; + int accept_fd; + unsigned int clilen; + struct sockaddr_in cli_addr; + int n; + int opt = 1; + ssize_t len; + + switch (wsi->mode) { + + case LWS_CONNMODE_HTTP_SERVING: + + /* handle http headers coming in */ + + /* any incoming data ready? */ + + if (pollfd->revents & POLLIN) { + + #ifdef LWS_OPENSSL_SUPPORT + if (wsi->ssl) + len = SSL_read(wsi->ssl, buf, sizeof buf); + else + #endif + len = recv(pollfd->fd, buf, sizeof buf, 0); + + if (len < 0) { + lwsl_debug("Socket read returned %d\n", len); + if (errno != EINTR && errno != EAGAIN) + libwebsocket_close_and_free_session(context, + wsi, LWS_CLOSE_STATUS_NOSTATUS); + return 0; + } + if (!len) { + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + return 0; + } + + n = libwebsocket_read(context, wsi, buf, len); + if (n < 0) + /* we closed wsi */ + return 0; + } + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & POLLOUT)) + break; + + /* one shot */ + pollfd->events &= ~POLLOUT; + + if (wsi->state != WSI_STATE_HTTP_ISSUING_FILE) + break; + + if (libwebsockets_serve_http_file_fragment(context, wsi) < 0) + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NOSTATUS); + else + if (wsi->state == WSI_STATE_HTTP && wsi->protocol->callback) + if (user_callback_handle_rxflow(wsi->protocol->callback, context, wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, wsi->user_space, + wsi->filepath, wsi->filepos)) + libwebsocket_close_and_free_session(context, wsi, LWS_CLOSE_STATUS_NOSTATUS); + break; + + case LWS_CONNMODE_SERVER_LISTENER: + + /* pollin means a client has connected to us then */ + + if (!(pollfd->revents & POLLIN)) + break; + + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, + &clilen); + if (accept_fd < 0) { + lwsl_warn("ERROR on accept: %s\n", strerror(errno)); + break; + } + + /* Disable Nagle */ + opt = 1; + setsockopt(accept_fd, IPPROTO_TCP, TCP_NODELAY, + (const void *)&opt, sizeof(opt)); + + /* + * look at who we connected to and give user code a chance + * to reject based on client IP. There's no protocol selected + * yet so we issue this to protocols[0] + */ + + if ((context->protocols[0].callback)(context, wsi, + LWS_CALLBACK_FILTER_NETWORK_CONNECTION, + (void *)(long)accept_fd, NULL, 0)) { + lwsl_debug("Callback denied network connection\n"); + compatible_close(accept_fd); + break; + } + + new_wsi = libwebsocket_create_new_server_wsi(context); + if (new_wsi == NULL) { + compatible_close(accept_fd); + break; + } + + new_wsi->sock = accept_fd; + + +#ifdef LWS_OPENSSL_SUPPORT + new_wsi->ssl = NULL; + + if (context->use_ssl) { + + new_wsi->ssl = SSL_new(context->ssl_ctx); + if (new_wsi->ssl == NULL) { + lwsl_err("SSL_new failed: %s\n", + ERR_error_string(SSL_get_error( + new_wsi->ssl, 0), NULL)); + libwebsockets_decode_ssl_error(); + free(new_wsi); + compatible_close(accept_fd); + break; + } + + SSL_set_ex_data(new_wsi->ssl, + openssl_websocket_private_data_index, context); + + SSL_set_fd(new_wsi->ssl, accept_fd); + + n = SSL_accept(new_wsi->ssl); + if (n != 1) { + /* + * browsers seem to probe with various + * ssl params which fail then retry + * and succeed + */ + lwsl_debug("SSL_accept failed skt %u: %s\n", + pollfd->fd, + ERR_error_string(SSL_get_error( + new_wsi->ssl, n), NULL)); + SSL_free( + new_wsi->ssl); + free(new_wsi); + compatible_close(accept_fd); + break; + } + + lwsl_debug("accepted new SSL conn " + "port %u on fd=%d SSL ver %s\n", + ntohs(cli_addr.sin_port), accept_fd, + SSL_get_version(new_wsi->ssl)); + + } else +#endif + lwsl_debug("accepted new conn port %u on fd=%d\n", + ntohs(cli_addr.sin_port), accept_fd); + + insert_wsi_socket_into_fds(context, new_wsi); + break; + + case LWS_CONNMODE_BROADCAST_PROXY_LISTENER: + + /* as we are listening, POLLIN means accept() is needed */ + + if (!(pollfd->revents & POLLIN)) + break; + + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, + &clilen); + if (accept_fd < 0) { + lwsl_warn("ERROR on accept %d\n", accept_fd); + return 0; + } + + /* create a dummy wsi for the connection and add it */ + + new_wsi = (struct libwebsocket *)malloc(sizeof(struct libwebsocket)); + if (new_wsi == NULL) { + lwsl_err("Out of mem\n"); + goto bail_prox_listener; + } + memset(new_wsi, 0, sizeof (struct libwebsocket)); + new_wsi->sock = accept_fd; + new_wsi->mode = LWS_CONNMODE_BROADCAST_PROXY; + new_wsi->state = WSI_STATE_ESTABLISHED; + new_wsi->count_active_extensions = 0; + /* note which protocol we are proxying */ + new_wsi->protocol_index_for_broadcast_proxy = + wsi->protocol_index_for_broadcast_proxy; + + insert_wsi_socket_into_fds(context, new_wsi); + break; + +bail_prox_listener: + compatible_close(accept_fd); + break; + + case LWS_CONNMODE_BROADCAST_PROXY: + + /* handle session socket closed */ + + if (pollfd->revents & (POLLERR | POLLHUP)) { + + lwsl_debug("Session Socket %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + libwebsocket_close_and_free_session(context, wsi, + LWS_CLOSE_STATUS_NORMAL); + return 0; + } + + /* + * either extension code with stuff to spill, or the user code, + * requested a callback when it was OK to write + */ + + if (pollfd->revents & POLLOUT) + if (lws_handle_POLLOUT_event(context, wsi, + pollfd) < 0) { + libwebsocket_close_and_free_session( + context, wsi, LWS_CLOSE_STATUS_NORMAL); + return 0; + } + + /* any incoming data ready? */ + + if (!(pollfd->revents & POLLIN)) + break; + + /* get the issued broadcast payload from the socket */ + + len = read(pollfd->fd, buf + LWS_SEND_BUFFER_PRE_PADDING, + MAX_BROADCAST_PAYLOAD); + if (len < 0) { + lwsl_err("Error reading broadcast payload\n"); + break; + } + + /* broadcast it to all guys with this protocol index */ + + for (n = 0; n < context->fds_count; n++) { + + new_wsi = context->lws_lookup[context->fds[n].fd]; + if (new_wsi == NULL) + continue; + + /* only to clients we are serving to */ + + if (new_wsi->mode != LWS_CONNMODE_WS_SERVING) + continue; + + /* + * never broadcast to non-established + * connection + */ + + if (new_wsi->state != WSI_STATE_ESTABLISHED) + continue; + + /* + * only broadcast to connections using + * the requested protocol + */ + + if (new_wsi->protocol->protocol_index != + wsi->protocol_index_for_broadcast_proxy) + continue; + + /* broadcast it to this connection */ + + user_callback_handle_rxflow(new_wsi->protocol->callback, context, new_wsi, + LWS_CALLBACK_BROADCAST, + new_wsi->user_space, + buf + LWS_SEND_BUFFER_PRE_PADDING, len); + } + break; + default: + break; + } + return 0; +} diff --git a/test-server/Makefile.am b/test-server/Makefile.am index de0a2a21..4dda7709 100644 --- a/test-server/Makefile.am +++ b/test-server/Makefile.am @@ -1,60 +1,99 @@ -bin_PROGRAMS=libwebsockets-test-server libwebsockets-test-server-extpoll +bin_PROGRAMS= if NO_CLIENT else -bin_PROGRAMS+=libwebsockets-test-client libwebsockets-test-fraggle +bin_PROGRAMS+= libwebsockets-test-client +if NO_SERVER +else +bin_PROGRAMS+= libwebsockets-test-fraggle +endif endif +if NO_SERVER +else +bin_PROGRAMS+=libwebsockets-test-server libwebsockets-test-server-extpoll +endif + +if NO_SERVER +else libwebsockets_test_server_SOURCES=test-server.c libwebsockets_test_server_CFLAGS= libwebsockets_test_server_LDADD=-L../lib -lwebsockets -lz libwebsockets_test_server_extpoll_SOURCES=test-server.c libwebsockets_test_server_extpoll_CFLAGS=$(AM_CFLAGS) -DEXTERNAL_POLL libwebsockets_test_server_extpoll_LDADD=-L../lib -lwebsockets -lz +endif if NO_CLIENT else libwebsockets_test_client_SOURCES=test-client.c libwebsockets_test_client_CFLAGS= libwebsockets_test_client_LDADD=-L../lib -lwebsockets -lz +if NO_SERVER +else libwebsockets_test_fraggle_SOURCES=test-fraggle.c libwebsockets_test_fraggle_CFLAGS= libwebsockets_test_fraggle_LDADD=-L../lib -lwebsockets -lz endif +endif -if MINGW +if MINGW +if NO_SERVER +else libwebsockets_test_server_CFLAGS+= -w -I../win32port/win32helpers libwebsockets_test_server_extpoll_CFLAGS+= -w -I../win32port/win32helpers +endif if NO_CLIENT else libwebsockets_test_client_CFLAGS+= -w -I../win32port/win32helpers +if NO_SERVER +else libwebsockets_test_fraggle_CFLAGS+= -w -I../win32port/win32helpers endif +endif +if NO_SERVER +else libwebsockets_test_server_LDADD+= -lm -luser32 -ladvapi32 -lkernel32 -lgcc -lws2_32 -lz libwebsockets_test_server_extpoll_LDADD+= -lm -luser32 -ladvapi32 -lkernel32 -lgcc -lws2_32 -lz +endif if NO_CLIENT else libwebsockets_test_client_LDADD+= -lm -luser32 -ladvapi32 -lkernel32 -lgcc -lws2_32 -lz +if NO_SERVER +else libwebsockets_test_fraggle_LDADD+= -lm -luser32 -ladvapi32 -lkernel32 -lgcc -lws2_32 -lz endif +endif +else +if NO_SERVER else libwebsockets_test_server_CFLAGS+= -Werror libwebsockets_test_server_extpoll_CFLAGS+= -Werror +endif if NO_CLIENT else libwebsockets_test_client_CFLAGS+= -Werror +if NO_SERVER +else libwebsockets_test_fraggle_CFLAGS+= -Werror endif +endif endif +if NO_SERVER +else libwebsockets_test_server_CFLAGS+= -Wall -std=gnu99 -pedantic -DINSTALL_DATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" libwebsockets_test_server_extpoll_CFLAGS+= -Wall -std=gnu99 -pedantic -DINSTALL_DATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" +endif if NO_CLIENT else libwebsockets_test_client_CFLAGS+= -Wall -std=gnu99 -pedantic -DINSTALL_DATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" +if NO_SERVER +else libwebsockets_test_fraggle_CFLAGS+= -Wall -std=gnu99 -pedantic -DINSTALL_DATADIR=\"@datadir@\" -DLWS_OPENSSL_CLIENT_CERTS=\"@clientcertdir@\" endif +endif if NOPING else