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);
}