introduce-new-04-handshake.patch
authorAndy Green <andy@warmcat.com>
Tue, 18 Jan 2011 17:14:03 +0000 (17:14 +0000)
committerAndy Green <andy@warmcat.com>
Tue, 18 Jan 2011 17:14:03 +0000 (17:14 +0000)
Signed-off-by: Andy Green <andy@warmcat.com>
lib/handshake.c
lib/libwebsockets.c
lib/parsers.c
lib/private-libwebsockets.h

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