From 545b6c80decbd526712899796588a68d915a5b2e Mon Sep 17 00:00:00 2001 From: Joel Martin Date: Tue, 25 Oct 2011 17:02:04 -0500 Subject: [PATCH] Update kumina with HyBi-10+ support. Pull 63aa9ce07 from https://github.com/kumina/wsproxy --- README.md | 6 +- other/kumina.c | 472 ++++++++++++++++++++++++++++--------------------- 2 files changed, 278 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 83a1b3d..954074b 100644 --- a/README.md +++ b/README.md @@ -98,7 +98,7 @@ implementations: yes yes no - no + via inetd yes Daemon @@ -106,7 +106,7 @@ implementations: yes no no - no + via inetd yes SSL wss @@ -178,7 +178,7 @@ implementations: no no no - no + yes yes diff --git a/other/kumina.c b/other/kumina.c index 2a02e8a..54b2476 100644 --- a/other/kumina.c +++ b/other/kumina.c @@ -32,20 +32,33 @@ #include #include #include +#include #include #include #include #include #include +#include -static pid_t other; +#define MAXOFRAME UINT16_MAX +#define HYBI10_ACCEPTHDRLEN 29 + +#ifdef DEBUG +#define DPRINTF(fmt, ...) fprintf(stderr, fmt "\n", ## __VA_ARGS__) +#else +#define DPRINTF(fmt, ...) +#endif + +static pid_t other = -1; +static int hybi10 = 0; static void die(int exitcode) { - kill(other, SIGTERM); + if (other != -1) + kill(other, SIGTERM); exit(exitcode); } @@ -53,11 +66,11 @@ static void usage(void) { - fprintf(stderr, "usage: kumina minport maxport\n"); + fprintf(stderr, "usage: wsproxy minport maxport\n"); exit(1); } -static int +static unsigned char pgetc(FILE *fp) { int ret; @@ -68,113 +81,8 @@ pgetc(FILE *fp) return (ret); } -#if 0 /* UTF-8 */ - static void -pputc(FILE *fp, unsigned char ch) -{ - int ret; - - ret = fputc(ch, fp); - if (ret == EOF) - die(0); -} - -static void -decode(FILE *in, int outfd) -{ - FILE *out; - int ch; - unsigned char och; - - out = fdopen(outfd, "w"); - if (out == NULL) { - perror("fdopen"); - die(1); - } - - for (;;) { - /* Frame header. */ - ch = pgetc(in); - if (ch != 0x00) { - fprintf(stderr, "malformed frame header received\n"); - die(1); - } - - for (;;) { - /* Frame trailer. */ - ch = pgetc(in); - if (ch == EOF) - die(0); - if (ch == 0xff) { - fflush(out); - break; - } - - /* UTF-8 character, only allowing points 0 to 255. */ - if (ch < 0x80) - och = ch; - else if ((ch & 0xf3) == 0xc0) { - och = ch << 6; - ch = pgetc(in); - if ((ch & 0xc0) != 0x80) - goto malformed; - och |= ch & 0x3f; - } else - goto malformed; - pputc(out, och); - } - } - -malformed: - fprintf(stderr, "malformed UTF-8 sequence received\n"); - die(1); -} - -static int -encode(int in, int out) -{ - unsigned char inbuf[512]; - unsigned char outbuf[sizeof inbuf * 2 + 2]; - unsigned char *op; - ssize_t len, i; - - for (;;) { - len = read(in, inbuf, sizeof inbuf); - if (len == -1) { - perror("read"); - die(1); - } else if (len == 0) - die(0); - - op = outbuf; - /* Frame header. */ - *op++ = 0x00; - for (i = 0; i < len; i++) { - /* Encode data as UTF-8. */ - if (inbuf[i] < 0x80) - *op++ = inbuf[i]; - else { - *op++ = 0xc0 | (inbuf[i] >> 6); - *op++ = 0x80 | (inbuf[i] & 0x3f); - } - } - /* Frame trailer. */ - *op++ = 0xff; - assert(op <= outbuf + sizeof outbuf); - len = write(out, outbuf, op - outbuf); - if (len == -1) { - perror("write"); - die(1); - } else if (len != op - outbuf) - die(0); - } -} - -#else /* base64 */ - -static void -putb64(FILE *out, char *inb, size_t *inblen) +putb64(FILE *out, const char *inb, size_t *inblen) { char inbuf[5] = { 0 }; unsigned char outbuf[3]; @@ -187,7 +95,7 @@ putb64(FILE *out, char *inb, size_t *inblen) memcpy(inbuf, inb, *inblen); outbuflen = b64_pton(inbuf, outbuf, sizeof outbuf); if (outbuflen <= 0) { - fprintf(stderr, "invalid Base64 data\n"); + DPRINTF("invalid Base64 data"); die(1); } if (fwrite(outbuf, outbuflen, 1, out) != 1) { @@ -197,11 +105,178 @@ putb64(FILE *out, char *inb, size_t *inblen) *inblen = 0; } +/* + * Support for HyBi10. + */ + static void -decode(FILE *in, int outfd) +hybi10_calcaccepthdr(const char *key, char *out) +{ + SHA_CTX c; + unsigned char hash[SHA_DIGEST_LENGTH]; + int r; + + SHA1_Init(&c); + SHA1_Update(&c, key, strlen(key)); + SHA1_Update(&c, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", 36); + SHA1_Final(hash, &c); + + r = b64_ntop(hash, sizeof hash, out, HYBI10_ACCEPTHDRLEN); + assert(r == HYBI10_ACCEPTHDRLEN - 1); +} + +static uint64_t +hybi10_getlength(FILE *in) +{ + uint64_t len; + int lenlen; + unsigned char ch; + + ch = pgetc(in); + if (!(ch & 0x80)) { + DPRINTF("mask bit not set"); + die(1); + } + ch &= ~0x80; + + /* Two or eight bytes of input length? */ + switch (ch) { + case 126: + lenlen = 2; + break; + case 127: + lenlen = 8; + break; + default: + /* Small packet, length encoded directly. */ + return (ch); + } + + len = 0; + while (lenlen-- > 0) + len = len << 8 | pgetc(in); + return (len); +} + +static void +hybi10_getmasks(FILE *in, unsigned char masks[4]) +{ + int i; + + for (i = 0; i < 4; i++) + masks[i] = pgetc(in); +} + +static void +hybi10_decode(FILE *in, int outfd) { FILE *out; - int ch; + unsigned char ch, masks[4]; + char inb[4]; + size_t inblen = 0; + uint64_t i, framelen; + + out = fdopen(outfd, "w"); + if (out == NULL) { + perror("fdopen"); + die(1); + } + + for (;;) { + /* Frame header. */ + ch = pgetc(in); + if (ch != 0x81) { + DPRINTF("unsupported packet received: %#hhx", ch); + die(1); + } + + /* Payload length. */ + framelen = hybi10_getlength(in); + hybi10_getmasks(in, masks); + for (i = 0; i < framelen; i++) { + ch = pgetc(in) ^ masks[i % 4]; + if (!((ch >= 'A' && ch <= 'Z') || + (ch >= 'a' && ch <= 'z') || + (ch >= '0' && ch <= '9') || + ch == '+' || ch == '/' || ch == '=')) { + DPRINTF("non-Base64 character received"); + die(1); + } + + /* Base64 character. */ + inb[inblen++] = ch; + if (inblen == sizeof inb) + putb64(out, inb, &inblen); + } + + /* Frame trailer. */ + putb64(out, inb, &inblen); + if (fflush(out) == -1) { + perror("fflush"); + die(1); + } + } +} + +static void +hybi10_encode(int in, int out) +{ + unsigned char inbuf[MAXOFRAME / 4 * 3]; + char outbuf[MAXOFRAME + 5]; /* Four-byte header + nul. */ + ssize_t len, wlen; + + for (;;) { + len = read(in, inbuf, sizeof inbuf); + if (len == -1) { + perror("read"); + die(1); + } else if (len == 0) + die(0); + + /* Encode data as Base64. */ + len = b64_ntop(inbuf, len, outbuf + 4, sizeof outbuf - 4); + assert(len > 0 && len <= MAXOFRAME); + /* Frame header. */ + outbuf[0] = 0x81; + outbuf[1] = 126; + outbuf[2] = len >> 8; + outbuf[3] = len; + len += 4; + + wlen = write(out, outbuf, len); + if (wlen == -1) { + perror("write"); + die(1); + } else if (wlen != len) + die(0); + } +} + +/* + * Support for Hixie 76. + */ + +static void +hixie76_calcresponse(uint32_t key1, uint32_t key2, const char *key3, char *out) +{ + MD5_CTX c; + char in[16] = { + key1 >> 24, key1 >> 16, key1 >> 8, key1, + key2 >> 24, key2 >> 16, key2 >> 8, key2, + key3[0], key3[1], key3[2], key3[3], + key3[4], key3[5], key3[6], key3[7] + }; + + MD5_Init(&c); + MD5_Update(&c, (void *)in, sizeof in); + MD5_Final((void *)out, &c); +} + +static void +hixie76_decode(FILE *in, int outfd) +{ + FILE *out; + unsigned char ch; char inb[4]; size_t inblen = 0; @@ -215,32 +290,16 @@ decode(FILE *in, int outfd) /* Frame header. */ ch = pgetc(in); if (ch != 0x00) { - fprintf(stderr, "malformed frame header received\n"); + DPRINTF("unsupported packet received: %#hhx", ch); die(1); } - for (;;) { - ch = pgetc(in); - if (ch == EOF) { - putb64(out, inb, &inblen); - die(0); - } - /* Frame trailer. */ - if (ch == 0xff) { - putb64(out, inb, &inblen); - if (fflush(out) == -1) { - perror("fflush"); - die(1); - } - break; - } - + while ((ch = pgetc(in)) != 0xff) { if (!((ch >= 'A' && ch <= 'Z') || (ch >= 'a' && ch <= 'z') || (ch >= '0' && ch <= '9') || ch == '+' || ch == '/' || ch == '=')) { - fprintf(stderr, - "non-Base64 character received\n"); + DPRINTF("non-Base64 character received"); die(1); } @@ -249,14 +308,21 @@ decode(FILE *in, int outfd) if (inblen == sizeof inb) putb64(out, inb, &inblen); } + + /* Frame trailer. */ + putb64(out, inb, &inblen); + if (fflush(out) == -1) { + perror("fflush"); + die(1); + } } } -static int -encode(int in, int out) +static void +hixie76_encode(int in, int out) { - unsigned char inbuf[512]; - char outbuf[sizeof inbuf * 2 + 2]; + unsigned char inbuf[MAXOFRAME / 4 * 3]; + char outbuf[MAXOFRAME + 2]; ssize_t len, wlen; for (;;) { @@ -270,22 +336,20 @@ encode(int in, int out) /* Frame header. */ outbuf[0] = 0x00; /* Encode data as Base64. */ - len = b64_ntop(inbuf, len, outbuf + 1, sizeof outbuf - 1) + 1; - assert(len >= 1); - /* Frame footer. */ - outbuf[len++] = 0xff; + len = b64_ntop(inbuf, len, outbuf + 1, sizeof outbuf - 1); + assert(len > 0 && len <= MAXOFRAME); + /* Frame trailer. */ + outbuf[len + 1] = 0xff; - wlen = write(out, outbuf, len); + wlen = write(out, outbuf, len + 2); if (wlen == -1) { perror("write"); die(1); - } else if (wlen != len) + } else if (wlen != len + 2) die(0); } } -#endif - static char * do_strndup(const char *str, size_t n) { @@ -331,44 +395,19 @@ parsehdrkey(const char *key) return (spaces == 0 ? 0 : sum / spaces); } -static void -calcresponse(uint32_t key1, uint32_t key2, const char *key3, char *out) -{ - MD5_CTX c; - char in[16]; - - in[0] = key1 >> 24; - in[1] = key1 >> 16; - in[2] = key1 >> 8; - in[3] = key1; - in[4] = key2 >> 24; - in[5] = key2 >> 16; - in[6] = key2 >> 8; - in[7] = key2; - memcpy(in + 8, key3, 8); - - MD5_Init(&c); - MD5_Update(&c, (void *)in, sizeof in); - MD5_Final((void *)out, &c); -} - static void eat_flash_magic(void) { static const char flash_magic[] = ""; - ssize_t i; - int ch; + size_t i; + char ch; for (i = 0; i < sizeof flash_magic - 1; i++) { - ch = getchar(); - if (ch == EOF) { - perror("getc"); - exit(1); - } + ch = pgetc(stdin); /* Not a Flash applet. Roll back. */ if (ch != flash_magic[i]) { ungetc(ch, stdin); - while (--i >= 0) + while (i-- > 0) ungetc(flash_magic[i], stdin); return; } @@ -388,7 +427,8 @@ main(int argc, char *argv[]) struct sockaddr_in sa_in; struct sockaddr_in6 sa_in6; } sa; - char line[512], key3[8], response[16], *host = NULL, *origin = NULL; + char line[512], key3[8], *host = NULL, + *origin = NULL, *key = NULL, *protocol; unsigned long minport, maxport, port; uint32_t key1 = 0, key2 = 0; socklen_t salen; @@ -423,31 +463,48 @@ main(int argc, char *argv[]) return (1); } if (strncmp(line, "GET /", 5) != 0) { - fprintf(stderr, "malformed HTTP header received\n"); + DPRINTF("malformed HTTP header received"); return (1); } - if (strncmp(line, "GET /kumina-monitoring/ ", 25) == 0) + if (strncmp(line, "GET /wsproxy-monitoring/ ", 25) == 0) { monitoring = 1; - port = strtoul(line + 5, NULL, 10); - if (!monitoring && (port < minport || port > maxport)) { - fprintf(stderr, "port not allowed\n"); - return (1); + port = 0; /* Keep compiler happy. */ + } else if (minport == maxport) { + /* Simply ignore URL and connect to a single host. */ + port = minport; + } else { + /* Multiplexing mode. Use port number in URL. */ + port = strtoul(line + 5, NULL, 10); + if (port < minport || port > maxport) { + DPRINTF("port not allowed"); + return (1); + } } /* Parse HTTP headers. */ do { if (fgets(line, sizeof line, stdin) == NULL) { - fprintf(stderr, "partial HTTP header received\n"); + DPRINTF("partial HTTP header received"); return (1); } if (strncasecmp(line, "Host: ", 6) == 0) { host = parsestring(line + 6); } else if (strncasecmp(line, "Origin: ", 8) == 0) { origin = parsestring(line + 8); + } else if (strncasecmp(line, "Sec-WebSocket-Key: ", 19) == 0) { + hybi10 = 1; + key = parsestring(line + 19); } else if (strncasecmp(line, "Sec-WebSocket-Key1: ", 20) == 0) { key1 = parsehdrkey(line + 20); } else if (strncasecmp(line, "Sec-WebSocket-Key2: ", 20) == 0) { key2 = parsehdrkey(line + 20); + } else if (strncasecmp(line, "Sec-WebSocket-Protocol: ", + 24) == 0) { + protocol = parsestring(line + 24); + if (strcmp(protocol, "base64") != 0) { + DPRINTF("Unsupported protocol: %s", protocol); + return (1); + } } } while (strcmp(line, "\n") != 0 && strcmp(line, "\r\n") != 0); @@ -461,10 +518,11 @@ main(int argc, char *argv[]) } /* Eight byte payload. */ - if (fread(key3, sizeof key3, 1, stdin) != 1) { - fprintf(stderr, "key data missing\n"); - return (1); - } + if (!hybi10) + if (fread(key3, sizeof key3, 1, stdin) != 1) { + DPRINTF("key data missing"); + return (1); + } /* Use our own address. Fall back to 127.0.0.1 on failure. */ salen = sizeof sa; @@ -483,7 +541,7 @@ main(int argc, char *argv[]) break; default: /* Unknown protocol. */ - fprintf(stderr, "unsupported network protocol\n"); + DPRINTF("unsupported network protocol"); return (1); } s = socket(sa.sa.sa_family, SOCK_STREAM, 0); @@ -496,15 +554,29 @@ main(int argc, char *argv[]) return (1); } - /* Send HTTP response. */ - calcresponse(key1, key2, key3, response); - printf("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" - "Upgrade: WebSocket\r\n" - "Connection: Upgrade\r\n" - "Sec-WebSocket-Origin: %s\r\n" - "Sec-WebSocket-Location: ws://%s/%lu\r\n" - "Sec-WebSocket-Protocol: base64\r\n\r\n", origin, host, port); - fwrite(response, sizeof response, 1, stdout); + /* Send HTTP response, based on protocol version. */ + if (hybi10) { + char accepthdr[HYBI10_ACCEPTHDRLEN]; + + hybi10_calcaccepthdr(key, accepthdr); + printf("HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n" + "Sec-WebSocket-Protocol: base64\r\n\r\n", accepthdr); + } else { + char response[MD5_DIGEST_LENGTH]; + + hixie76_calcresponse(key1, key2, key3, response); + printf("HTTP/1.1 101 WebSocket Protocol Handshake\r\n" + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Origin: %s\r\n" + "Sec-WebSocket-Location: ws://%s/%lu\r\n" + "Sec-WebSocket-Protocol: base64\r\n\r\n", + origin, host, port); + fwrite(response, sizeof response, 1, stdout); + } fflush(stdout); /* Spawn child process for bi-directional pipe. */ @@ -514,10 +586,16 @@ main(int argc, char *argv[]) return (1); } else if (pid == 0) { other = getppid(); - decode(stdin, s); + if (hybi10) + hybi10_decode(stdin, s); + else + hixie76_decode(stdin, s); } else { other = pid; - encode(s, STDOUT_FILENO); + if (hybi10) + hybi10_encode(s, STDOUT_FILENO); + else + hixie76_encode(s, STDOUT_FILENO); } assert(0); }