diff --git a/Makefile b/Makefile index 911c33a0c6f5..7649adb82a06 100644 --- a/Makefile +++ b/Makefile @@ -365,7 +365,8 @@ PKGLIBEXEC_PROGRAMS = \ lightningd/lightning_gossipd \ lightningd/lightning_hsmd \ lightningd/lightning_onchaind \ - lightningd/lightning_openingd + lightningd/lightning_openingd \ + lightningd/lightning_websocketd # Don't delete these intermediaries. .PRECIOUS: $(ALL_GEN_HEADERS) $(ALL_GEN_SOURCES) diff --git a/connectd/Makefile b/connectd/Makefile index 397558128e53..60cdf59d1fe2 100644 --- a/connectd/Makefile +++ b/connectd/Makefile @@ -14,10 +14,17 @@ CONNECTD_SRC := $(CONNECTD_HEADERS:.h=.c) connectd/connectd.c CONNECTD_OBJS := $(CONNECTD_SRC:.c=.o) $(CONNECTD_OBJS): $(CONNECTD_HEADERS) +WEBSOCKETD_HEADERS := connectd/sha1.h +WEBSOCKETD_SRC := $(WEBSOCKETD_HEADERS:.h=.c) connectd/websocketd.c + +WEBSOCKETD_OBJS := $(WEBSOCKETD_SRC:.c=.o) +$(WEBSOCKETD_OBJS): $(WEBSOCKETD_HEADERS) + # Make sure these depend on everything. -ALL_C_SOURCES += $(CONNECTD_SRC) -ALL_C_HEADERS += $(CONNECTD_HEADERS) +ALL_C_SOURCES += $(CONNECTD_SRC) $(WEBSOCKETD_SRC) +ALL_C_HEADERS += $(CONNECTD_HEADERS) $(WEBSOCKETD_HEADERS) ALL_PROGRAMS += lightningd/lightning_connectd +ALL_PROGRAMS += lightningd/lightning_websocketd # Here's what lightningd depends on LIGHTNINGD_CONTROL_HEADERS += connectd/connectd_wiregen.h @@ -68,4 +75,6 @@ CONNECTD_COMMON_OBJS := \ lightningd/lightning_connectd: $(CONNECTD_OBJS) $(CONNECTD_COMMON_OBJS) $(BITCOIN_OBJS) $(WIRE_OBJS) $(HSMD_CLIENT_OBJS) +lightningd/lightning_websocketd: $(WEBSOCKETD_OBJS) common/setup.o common/utils.o + include connectd/test/Makefile diff --git a/connectd/sha1.c b/connectd/sha1.c new file mode 100644 index 000000000000..d9e4951978a6 --- /dev/null +++ b/connectd/sha1.c @@ -0,0 +1,190 @@ +/* hex variants removed -- RR */ +#include + +/******************************************************************************* + * Teeny SHA-1 + * + * The below sha1digest() calculates a SHA-1 hash value for a + * specified data buffer and generates a hex representation of the + * result. This implementation is a re-forming of the SHA-1 code at + * https://github.com/jinqiangshou/EncryptionLibrary. + * + * Copyright (c) 2017 CTrabant + * + * License: MIT, see included LICENSE file for details. + * + * To use the sha1digest() function either copy it into an existing + * project source code file or include this file in a project and put + * the declaration (example below) in the sources files where needed. + ******************************************************************************/ + +#include + +/* Declaration: +extern int sha1digest(uint8_t *digest, const uint8_t *data, size_t databytes); +*/ + +/******************************************************************************* + * sha1digest: https://github.com/CTrabant/teeny-sha1 + * + * Calculate the SHA-1 value for supplied data buffer and generate a + * text representation in hexadecimal. + * + * Based on https://github.com/jinqiangshou/EncryptionLibrary, credit + * goes to @jinqiangshou, all new bugs are mine. + * + * @input: + * data -- data to be hashed + * databytes -- bytes in data buffer to be hashed + * + * @output: + * digest -- the result, MUST be at least 20 bytes + * + * @return: 0 on success and non-zero on error. + ******************************************************************************/ +int +sha1digest(uint8_t *digest, const uint8_t *data, size_t databytes) +{ +#define SHA1ROTATELEFT(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits)))) + + uint32_t W[80]; + uint32_t H[] = {0x67452301, + 0xEFCDAB89, + 0x98BADCFE, + 0x10325476, + 0xC3D2E1F0}; + uint32_t a; + uint32_t b; + uint32_t c; + uint32_t d; + uint32_t e; + uint32_t f = 0; + uint32_t k = 0; + + uint32_t idx; + uint32_t lidx; + uint32_t widx; + uint32_t didx = 0; + + int32_t wcount; + uint32_t temp; + uint64_t databits = ((uint64_t)databytes) * 8; + uint32_t loopcount = (databytes + 8) / 64 + 1; + uint32_t tailbytes = 64 * loopcount - databytes; + uint8_t datatail[128] = {0}; + + if (!digest) + return -1; + + if (!data) + return -1; + + /* Pre-processing of data tail (includes padding to fill out 512-bit chunk): + Add bit '1' to end of message (big-endian) + Add 64-bit message length in bits at very end (big-endian) */ + datatail[0] = 0x80; + datatail[tailbytes - 8] = (uint8_t) (databits >> 56 & 0xFF); + datatail[tailbytes - 7] = (uint8_t) (databits >> 48 & 0xFF); + datatail[tailbytes - 6] = (uint8_t) (databits >> 40 & 0xFF); + datatail[tailbytes - 5] = (uint8_t) (databits >> 32 & 0xFF); + datatail[tailbytes - 4] = (uint8_t) (databits >> 24 & 0xFF); + datatail[tailbytes - 3] = (uint8_t) (databits >> 16 & 0xFF); + datatail[tailbytes - 2] = (uint8_t) (databits >> 8 & 0xFF); + datatail[tailbytes - 1] = (uint8_t) (databits >> 0 & 0xFF); + + /* Process each 512-bit chunk */ + for (lidx = 0; lidx < loopcount; lidx++) + { + /* Compute all elements in W */ + memset (W, 0, 80 * sizeof (uint32_t)); + + /* Break 512-bit chunk into sixteen 32-bit, big endian words */ + for (widx = 0; widx <= 15; widx++) + { + wcount = 24; + + /* Copy byte-per byte from specified buffer */ + while (didx < databytes && wcount >= 0) + { + W[widx] += (((uint32_t)data[didx]) << wcount); + didx++; + wcount -= 8; + } + /* Fill out W with padding as needed */ + while (wcount >= 0) + { + W[widx] += (((uint32_t)datatail[didx - databytes]) << wcount); + didx++; + wcount -= 8; + } + } + + /* Extend the sixteen 32-bit words into eighty 32-bit words, with potential optimization from: + "Improving the Performance of the Secure Hash Algorithm (SHA-1)" by Max Locktyukhin */ + for (widx = 16; widx <= 31; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 3] ^ W[widx - 8] ^ W[widx - 14] ^ W[widx - 16]), 1); + } + for (widx = 32; widx <= 79; widx++) + { + W[widx] = SHA1ROTATELEFT ((W[widx - 6] ^ W[widx - 16] ^ W[widx - 28] ^ W[widx - 32]), 2); + } + + /* Main loop */ + a = H[0]; + b = H[1]; + c = H[2]; + d = H[3]; + e = H[4]; + + for (idx = 0; idx <= 79; idx++) + { + if (idx <= 19) + { + f = (b & c) | ((~b) & d); + k = 0x5A827999; + } + else if (idx >= 20 && idx <= 39) + { + f = b ^ c ^ d; + k = 0x6ED9EBA1; + } + else if (idx >= 40 && idx <= 59) + { + f = (b & c) | (b & d) | (c & d); + k = 0x8F1BBCDC; + } + else if (idx >= 60 && idx <= 79) + { + f = b ^ c ^ d; + k = 0xCA62C1D6; + } + temp = SHA1ROTATELEFT (a, 5) + f + e + k + W[idx]; + e = d; + d = c; + c = SHA1ROTATELEFT (b, 30); + b = a; + a = temp; + } + + H[0] += a; + H[1] += b; + H[2] += c; + H[3] += d; + H[4] += e; + } + + /* Store binary digest in supplied buffer */ + if (digest) + { + for (idx = 0; idx < 5; idx++) + { + digest[idx * 4 + 0] = (uint8_t) (H[idx] >> 24); + digest[idx * 4 + 1] = (uint8_t) (H[idx] >> 16); + digest[idx * 4 + 2] = (uint8_t) (H[idx] >> 8); + digest[idx * 4 + 3] = (uint8_t) (H[idx]); + } + } + + return 0; +} /* End of sha1digest() */ diff --git a/connectd/sha1.h b/connectd/sha1.h new file mode 100644 index 000000000000..45e8ba5bc12c --- /dev/null +++ b/connectd/sha1.h @@ -0,0 +1,9 @@ +#ifndef LIGHTNING_CONNECTD_SHA1_H +#define LIGHTNING_CONNECTD_SHA1_H +#include "config.h" +#include +#include + +extern int sha1digest(uint8_t *digest, const uint8_t *data, size_t databytes); + +#endif /* LIGHTNING_CONNECTD_SHA1_H */ diff --git a/connectd/test/Makefile b/connectd/test/Makefile index fe92b237655c..c25428f51ed1 100644 --- a/connectd/test/Makefile +++ b/connectd/test/Makefile @@ -17,7 +17,7 @@ ALL_TEST_PROGRAMS += $(CONNECTD_TEST_PROGRAMS) $(CONNECTD_TEST_PROGRAMS): $(CONNECTD_TEST_COMMON_OBJS) $(BITCOIN_OBJS) # Test objects depend on ../ src and headers. -$(CONNECTD_TEST_OBJS): $(CONNECTD_HEADERS) $(CONNECTD_SRC) +$(CONNECTD_TEST_OBJS): $(CONNECTD_HEADERS) $(CONNECTD_SRC) $(WEBSOCKETD_HEADERS) $(WEBSOCKETD_SRC) check-units: $(CONNECTD_TEST_PROGRAMS:%=unittest/%) diff --git a/connectd/test/run-websocket.c b/connectd/test/run-websocket.c new file mode 100644 index 000000000000..2f58db53bc1e --- /dev/null +++ b/connectd/test/run-websocket.c @@ -0,0 +1,181 @@ +#include "config.h" +#include +#include +#include +#include +#include + +/* We don't want to actually do io! */ +#define write my_write +#define read my_read +#define write_all my_write_all +#define read_all my_read_all + +static char *my_rbuf, *my_wbuf; +static size_t my_rbuf_off; + +static ssize_t my_read(int fd, void *buf, size_t count) +{ + if (strlen(my_rbuf + my_rbuf_off) < count) + count = strlen(my_rbuf + my_rbuf_off); + memcpy(buf, my_rbuf + my_rbuf_off, count); + my_rbuf_off += count; + return count; +} +static bool my_read_all(int fd, void *buf, size_t count) +{ + my_read(fd, buf, count); + return true; +} + +static ssize_t my_write(int fd, const void *buf, size_t count) +{ + size_t buflen = tal_bytelen(my_wbuf); + tal_resize(&my_wbuf, buflen + count); + memcpy(my_wbuf + buflen, buf, count); + return count; +} +static bool my_write_all(int fd, const void *buf, size_t count) +{ + my_write(fd, buf, count); + return true; +} + +int websocket_main(int argc, char *argv[]); + +#define main websocket_main + #include "../websocketd.c" + #include "../sha1.c" +#undef main + +/* AUTOGENERATED MOCKS START */ +/* Generated stub for amount_asset_is_main */ +bool amount_asset_is_main(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_is_main called!\n"); abort(); } +/* Generated stub for amount_asset_to_sat */ +struct amount_sat amount_asset_to_sat(struct amount_asset *asset UNNEEDED) +{ fprintf(stderr, "amount_asset_to_sat called!\n"); abort(); } +/* Generated stub for amount_sat */ +struct amount_sat amount_sat(u64 satoshis UNNEEDED) +{ fprintf(stderr, "amount_sat called!\n"); abort(); } +/* Generated stub for amount_sat_add */ + bool amount_sat_add(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_add called!\n"); abort(); } +/* Generated stub for amount_sat_eq */ +bool amount_sat_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_eq called!\n"); abort(); } +/* Generated stub for amount_sat_greater_eq */ +bool amount_sat_greater_eq(struct amount_sat a UNNEEDED, struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_greater_eq called!\n"); abort(); } +/* Generated stub for amount_sat_sub */ + bool amount_sat_sub(struct amount_sat *val UNNEEDED, + struct amount_sat a UNNEEDED, + struct amount_sat b UNNEEDED) +{ fprintf(stderr, "amount_sat_sub called!\n"); abort(); } +/* Generated stub for amount_sat_to_asset */ +struct amount_asset amount_sat_to_asset(struct amount_sat *sat UNNEEDED, const u8 *asset UNNEEDED) +{ fprintf(stderr, "amount_sat_to_asset called!\n"); abort(); } +/* Generated stub for amount_tx_fee */ +struct amount_sat amount_tx_fee(u32 fee_per_kw UNNEEDED, size_t weight UNNEEDED) +{ fprintf(stderr, "amount_tx_fee called!\n"); abort(); } +/* Generated stub for fromwire */ +const u8 *fromwire(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, void *copy UNNEEDED, size_t n UNNEEDED) +{ fprintf(stderr, "fromwire called!\n"); abort(); } +/* Generated stub for fromwire_amount_sat */ +struct amount_sat fromwire_amount_sat(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_amount_sat called!\n"); abort(); } +/* Generated stub for fromwire_bool */ +bool fromwire_bool(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_bool called!\n"); abort(); } +/* Generated stub for fromwire_fail */ +void *fromwire_fail(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_fail called!\n"); abort(); } +/* Generated stub for fromwire_secp256k1_ecdsa_signature */ +void fromwire_secp256k1_ecdsa_signature(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, + secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "fromwire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for fromwire_sha256 */ +void fromwire_sha256(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "fromwire_sha256 called!\n"); abort(); } +/* Generated stub for fromwire_tal_arrn */ +u8 *fromwire_tal_arrn(const tal_t *ctx UNNEEDED, + const u8 **cursor UNNEEDED, size_t *max UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_tal_arrn called!\n"); abort(); } +/* Generated stub for fromwire_u16 */ +u16 fromwire_u16(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u16 called!\n"); abort(); } +/* Generated stub for fromwire_u32 */ +u32 fromwire_u32(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u32 called!\n"); abort(); } +/* Generated stub for fromwire_u64 */ +u64 fromwire_u64(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u64 called!\n"); abort(); } +/* Generated stub for fromwire_u8 */ +u8 fromwire_u8(const u8 **cursor UNNEEDED, size_t *max UNNEEDED) +{ fprintf(stderr, "fromwire_u8 called!\n"); abort(); } +/* Generated stub for fromwire_u8_array */ +void fromwire_u8_array(const u8 **cursor UNNEEDED, size_t *max UNNEEDED, u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "fromwire_u8_array called!\n"); abort(); } +/* Generated stub for towire */ +void towire(u8 **pptr UNNEEDED, const void *data UNNEEDED, size_t len UNNEEDED) +{ fprintf(stderr, "towire called!\n"); abort(); } +/* Generated stub for towire_amount_sat */ +void towire_amount_sat(u8 **pptr UNNEEDED, const struct amount_sat sat UNNEEDED) +{ fprintf(stderr, "towire_amount_sat called!\n"); abort(); } +/* Generated stub for towire_bool */ +void towire_bool(u8 **pptr UNNEEDED, bool v UNNEEDED) +{ fprintf(stderr, "towire_bool called!\n"); abort(); } +/* Generated stub for towire_secp256k1_ecdsa_signature */ +void towire_secp256k1_ecdsa_signature(u8 **pptr UNNEEDED, + const secp256k1_ecdsa_signature *signature UNNEEDED) +{ fprintf(stderr, "towire_secp256k1_ecdsa_signature called!\n"); abort(); } +/* Generated stub for towire_sha256 */ +void towire_sha256(u8 **pptr UNNEEDED, const struct sha256 *sha256 UNNEEDED) +{ fprintf(stderr, "towire_sha256 called!\n"); abort(); } +/* Generated stub for towire_u16 */ +void towire_u16(u8 **pptr UNNEEDED, u16 v UNNEEDED) +{ fprintf(stderr, "towire_u16 called!\n"); abort(); } +/* Generated stub for towire_u32 */ +void towire_u32(u8 **pptr UNNEEDED, u32 v UNNEEDED) +{ fprintf(stderr, "towire_u32 called!\n"); abort(); } +/* Generated stub for towire_u64 */ +void towire_u64(u8 **pptr UNNEEDED, u64 v UNNEEDED) +{ fprintf(stderr, "towire_u64 called!\n"); abort(); } +/* Generated stub for towire_u8 */ +void towire_u8(u8 **pptr UNNEEDED, u8 v UNNEEDED) +{ fprintf(stderr, "towire_u8 called!\n"); abort(); } +/* Generated stub for towire_u8_array */ +void towire_u8_array(u8 **pptr UNNEEDED, const u8 *arr UNNEEDED, size_t num UNNEEDED) +{ fprintf(stderr, "towire_u8_array called!\n"); abort(); } +/* AUTOGENERATED MOCKS END */ + +int main(int argc, char *argv[]) +{ + const char *hdr; + + common_setup(argv[0]); + + hdr = "GET /chat HTTP/1.1\r\n" + "Host: server.example.com\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n" + "Origin: http://example.com\r\n" + "Sec-WebSocket-Protocol: chat, superchat\r\n" + "Sec-WebSocket-Version: 13\r\n\r\n"; + + my_rbuf = tal_strdup(tmpctx, hdr); + my_wbuf = tal_arr(tmpctx, char, 0); + + http_upgrade(STDIN_FILENO, ""); + assert(streq(tal_strndup(tmpctx, my_wbuf, tal_bytelen(my_wbuf)), + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n" + "\r\n")); + + common_shutdown(); +} diff --git a/connectd/websocketd.c b/connectd/websocketd.c new file mode 100644 index 000000000000..97cd4fedae19 --- /dev/null +++ b/connectd/websocketd.c @@ -0,0 +1,352 @@ +/* A simple standalone websocket <-> binary proxy. + * See https://datatracker.ietf.org/doc/html/rfc6455 + */ +#include "config.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + 0 1 2 3 + 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + +-+-+-+-+-------+-+-------------+-------------------------------+ + |F|R|R|R| opcode|M| Payload len | Extended payload length | + |I|S|S|S| (4) |A| (7) | (16/64) | + |N|V|V|V| |S| | (if payload len==126/127) | + | |1|2|3| |K| | | + +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + | Extended payload length continued, if payload len == 127 | + + - - - - - - - - - - - - - - - +-------------------------------+ + | |Masking-key, if MASK set to 1 | + +-------------------------------+-------------------------------+ + | Masking-key (continued) | Payload Data | + +-------------------------------- - - - - - - - - - - - - - - - + + : Payload Data continued ... : + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + | Payload Data continued ... | + +---------------------------------------------------------------+ +*/ + +/* RFC-6455: + + A |Sec-WebSocket-Accept| header field. The value of this header field + is constructed by concatenating /key/, defined above in step 4 in + Section 4.2.2, with the string "258EAFA5- + E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this + concatenated value to obtain a 20-byte value and base64- encoding (see + Section 4 of [RFC4648]) this 20-byte hash. + +... + + NOTE: As an example, if the value of the |Sec-WebSocket-Key| header + field in the client's handshake were "dGhlIHNhbXBsZSBub25jZQ==", the + server would append the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + to form the string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA- + C5AB0DC85B11". The server would then take the SHA-1 hash of this + string, giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90 + 0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea. This value + is then base64-encoded, to give the value + "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", which would be returned in the + |Sec-WebSocket-Accept| header field. +*/ +static const char *websocket_accept_str(const tal_t *ctx, const char *key) +{ + u8 sha1[20]; + const char *concat; + char base64[100]; + + concat = tal_fmt(tmpctx, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + key); + sha1digest(sha1, (const u8 *)concat, strlen(concat)); + if (base64_encode(base64, sizeof(base64), (const char *)sha1, sizeof(sha1)) == -1) + abort(); + + return tal_strdup(ctx, base64); +} + +static void NORETURN PRINTF_FMT(2,3) +bad_http(int fd, const char *fmt, ...) +{ + va_list ap; + char *resp; + + resp = tal_strdup(tmpctx, "HTTP/1.1 400 I only speak websocket\r\n\r\n"); + va_start(ap, fmt); + tal_append_vfmt(&resp, fmt, ap); + va_end(ap); + + write_all(fd, resp, strlen(resp)); + exit(1); +} + +/* We know headers are terminated by \r\n\r\n at this point */ +static const char *get_http_hdr(const tal_t *ctx, const u8 *buf, size_t buflen, + const char *hdrname) +{ + size_t hdrlen; + + for (;;) { + const u8 *end = memmem(buf, buflen, "\r\n", 2); + hdrlen = end - buf; + + /* Empty line? End of headers. */ + if (hdrlen == 0) + return NULL; + /* header name followed by : */ + if (memstarts(buf, hdrlen, hdrname, strlen(hdrname)) + && buf[strlen(hdrname)] == ':') + break; + buf = end + 2; + } + + buf += strlen(hdrname) + 1; + hdrlen -= strlen(hdrname) + 1; + + /* Ignore leading whitespace (technically, they can split + * fields over multiple lines, but that's silly for the fields + * we're dealing with, so Naah). */ + while (hdrlen && cisspace(*buf)) { + buf++; + hdrlen--; + } + + return tal_strndup(ctx, (const char *)buf, hdrlen); +} + +static bool http_headers_complete(const u8 *buf, size_t len) +{ + return memmem(buf, len, "\r\n\r\n", 4) != NULL; +} + +static void http_respond(int fd, const u8 *buf, size_t len) +{ + const char *hdr; + char *resp; + + /* RFC-6455: + + The client's opening handshake consists of the following + parts. If the server, while reading the handshake, finds + that the client did not send a handshake that matches the + description below ... the server MUST stop processing the + client's handshake and return an HTTP response with an + appropriate error code (such as 400 Bad Request). + + 1. An HTTP/1.1 or higher GET request, including a "Request-URI" + [RFC2616] that should be interpreted as a /resource name/ + defined in Section 3 (or an absolute HTTP/HTTPS URI containing + the /resource name/). + + 2. A |Host| header field containing the server's authority. + + 3. An |Upgrade| header field containing the value "websocket", + treated as an ASCII case-insensitive value. + + 4. A |Connection| header field that includes the token "Upgrade", + treated as an ASCII case-insensitive value. + + 5. A |Sec-WebSocket-Key| header field with a base64-encoded (see + Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in + length. + + 6. A |Sec-WebSocket-Version| header field, with a value of 13. + */ + hdr = get_http_hdr(tmpctx, buf, len, "Upgrade"); + if (!hdr || !strstr(hdr, "websocket")) + bad_http(fd, "Upgrade: websocket missing"); + hdr = get_http_hdr(tmpctx, buf, len, "Connection"); + if (!hdr || !strstr(hdr, "Upgrade")) + bad_http(fd, "Connection: Upgrade missing"); + hdr = get_http_hdr(tmpctx, buf, len, "Sec-WebSocket-Version"); + if (!hdr || !streq(hdr, "13")) + bad_http(fd, "Sec-WebSocket-Version: must be 13"); + hdr = get_http_hdr(tmpctx, buf, len, "Sec-WebSocket-Key"); + if (!hdr) + bad_http(fd, "Sec-WebSocket-Key missing"); + + resp = tal_fmt(tmpctx, + "HTTP/1.1 101 Switching Protocols\r\n" + "Upgrade: websocket\r\n" + "Connection: Upgrade\r\n" + "Sec-WebSocket-Accept: %s\r\n\r\n", + websocket_accept_str(tmpctx, hdr)); + + if (!write_all(fd, resp, strlen(resp))) + exit(0); +} + +static void http_upgrade(int fd, const char *hexstart) +{ + u8 buf[65536]; + size_t len = hex_data_size(strlen(hexstart)); + + if (len > sizeof(buf)) + errx(1, "Unacceptable datasize"); + if (!hex_decode(hexstart, strlen(hexstart), buf, len)) + errx(1, "Bad hex"); + + alarm(60); + while (!http_headers_complete(buf, len)) { + int r; + r = read(STDIN_FILENO, buf + len, sizeof(buf) - len); + if (r <= 0) + bad_http(STDIN_FILENO, "No header end after %zu bytes", + len); + len += r; + } + http_respond(STDIN_FILENO, buf, len); + alarm(0); +} + +static void lightningd_to_websocket(int lightningfd, int wsfd) +{ + /* We prepend ws header */ + u8 buf[4 + 65535]; + int len; + /* Not continued frame (0x80), opcode = 2 (binary) */ + const u8 firstbyte = 0x82; + size_t off; + + len = read(lightningfd, 4 + buf, sizeof(buf) - 4); + if (len <= 0) + exit(0); + + if (len > 125) { + buf[0] = firstbyte; + buf[1] = 126; + buf[2] = (len >> 8); + buf[3] = len; + off = 0; + len += 4; + } else { + buf[2] = firstbyte; + buf[3] = len; + off = 2; + len += 2; + } + if (!write_all(wsfd, buf + off, len)) + exit(0); +} + +/* Returns payload size, sets inmask, is_binframe */ +static size_t read_payload_header(int fd, u8 inmask[4], bool *is_binframe) +{ + /* Worst case header. */ + u8 frame_hdr[20]; + bool mask_set; + size_t hdrsize = 2, len; + + /* First two bytes define hdr size. */ + if (!read_all(fd, frame_hdr, 2)) + exit(0); + + /* RFC-6455: + * %x2 denotes a binary frame + */ + *is_binframe = ((frame_hdr[0] & 0x0F) == 2); + mask_set = (frame_hdr[1] & 0x80); + len = (frame_hdr[1] & 0x7f); + + if (len == 126) + hdrsize += 2; + else if (len == 127) + hdrsize += 8; + + if (mask_set) + hdrsize += 4; + + /* Read rest of hdr if necessary */ + if (hdrsize > 2 && !read_all(fd, frame_hdr + 2, hdrsize - 2)) + exit(0); + + if (len == 126) { + be16 be16len; + memcpy(&be16len, frame_hdr + 2, 2); + len = be16_to_cpu(be16len); + } else if (len == 127) { + be64 be64len; + memcpy(&be64len, frame_hdr + 2, 8); + len = be64_to_cpu(be64len); + } + + if (mask_set) { + memcpy(inmask, frame_hdr + hdrsize - 4, 4); + hdrsize += 4; + } else + memset(inmask, 0, 4); + + return len; +} + +static void apply_mask(u8 *buf, size_t len, const u8 inmask[4]) +{ + for (size_t i = 0; i < len; i++) + buf[i] ^= inmask[i % 4]; +} + +static void websocket_to_lightningd(int wsfd, int lightningfd) +{ + size_t len; + u8 inmask[4]; + bool is_binframe; + + len = read_payload_header(wsfd, inmask, &is_binframe); + while (len > 0) { + u8 buf[65536]; + int rlen = len; + + if (rlen > sizeof(buf)) + rlen = sizeof(buf); + + rlen = read(wsfd, buf, rlen); + if (rlen <= 0) + exit(0); + apply_mask(buf, rlen, inmask); + len -= rlen; + /* We ignore non binary frames (FIXME: Send error!) */ + if (is_binframe && !write_all(lightningfd, buf, rlen)) + exit(0); + } +} + +/* stdin goes to the client, stdout goes to lightningd */ +int main(int argc, char *argv[]) +{ + struct pollfd pfds[2]; + + common_setup(argv[0]); + + if (argc != 2) + errx(1, "Usage: %s ", argv[0]); + + /* Do HTTP-style negotiation to get into websocket frames. */ + http_upgrade(STDIN_FILENO, argv[1]); + + pfds[0].fd = STDIN_FILENO; + pfds[0].events = POLLIN; + pfds[1].fd = STDOUT_FILENO; + pfds[1].events = POLLIN; + + for (;;) { + poll(pfds, 2, -1); + + if (pfds[1].revents & POLLIN) + lightningd_to_websocket(STDOUT_FILENO, STDIN_FILENO); + if (pfds[0].revents & POLLIN) + websocket_to_lightningd(STDIN_FILENO, STDOUT_FILENO); + } + + common_shutdown(); + exit(0); +}