1 /* A simple standalone websocket <-> binary proxy.
2  * See https://datatracker.ietf.org/doc/html/rfc6455
3  */
4 #include "config.h"
5 #include <ccan/base64/base64.h>
6 #include <ccan/endian/endian.h>
7 #include <ccan/err/err.h>
8 #include <ccan/mem/mem.h>
9 #include <ccan/read_write_all/read_write_all.h>
10 #include <ccan/str/hex/hex.h>
11 #include <ccan/tal/str/str.h>
12 #include <common/setup.h>
13 #include <common/utils.h>
14 #include <connectd/sha1.h>
15 #include <poll.h>
16 #include <stdarg.h>
17 #include <stdio.h>
18 #include <unistd.h>
19 
20 /*
21       0                   1                   2                   3
22       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
23      +-+-+-+-+-------+-+-------------+-------------------------------+
24      |F|R|R|R| opcode|M| Payload len |    Extended payload length    |
25      |I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
26      |N|V|V|V|       |S|             |   (if payload len==126/127)   |
27      | |1|2|3|       |K|             |                               |
28      +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
29      |     Extended payload length continued, if payload len == 127  |
30      + - - - - - - - - - - - - - - - +-------------------------------+
31      |                               |Masking-key, if MASK set to 1  |
32      +-------------------------------+-------------------------------+
33      | Masking-key (continued)       |          Payload Data         |
34      +-------------------------------- - - - - - - - - - - - - - - - +
35      :                     Payload Data continued ...                :
36      + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
37      |                     Payload Data continued ...                |
38      +---------------------------------------------------------------+
39 */
40 
41 /* RFC-6455:
42 
43   A |Sec-WebSocket-Accept| header field.  The value of this header field
44   is constructed by concatenating /key/, defined above in step 4 in
45   Section 4.2.2, with the string "258EAFA5-
46   E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this
47   concatenated value to obtain a 20-byte value and base64- encoding (see
48   Section 4 of [RFC4648]) this 20-byte hash.
49 
50 ...
51 
52    NOTE: As an example, if the value of the |Sec-WebSocket-Key| header
53    field in the client's handshake were "dGhlIHNhbXBsZSBub25jZQ==", the
54    server would append the string "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
55    to form the string "dGhlIHNhbXBsZSBub25jZQ==258EAFA5-E914-47DA-95CA-
56    C5AB0DC85B11".  The server would then take the SHA-1 hash of this
57    string, giving the value 0xb3 0x7a 0x4f 0x2c 0xc0 0x62 0x4f 0x16 0x90
58    0xf6 0x46 0x06 0xcf 0x38 0x59 0x45 0xb2 0xbe 0xc4 0xea.  This value
59    is then base64-encoded, to give the value
60    "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=", which would be returned in the
61    |Sec-WebSocket-Accept| header field.
62 */
websocket_accept_str(const tal_t * ctx,const char * key)63 static const char *websocket_accept_str(const tal_t *ctx, const char *key)
64 {
65 	u8 sha1[20];
66 	const char *concat;
67 	char base64[100];
68 
69 	concat = tal_fmt(tmpctx, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11",
70 			 key);
71 	sha1digest(sha1, (const u8 *)concat, strlen(concat));
72 	if (base64_encode(base64, sizeof(base64), (const char *)sha1, sizeof(sha1)) == -1)
73 		abort();
74 
75 	return tal_strdup(ctx, base64);
76 }
77 
78 static void NORETURN PRINTF_FMT(2,3)
bad_http(int fd,const char * fmt,...)79 bad_http(int fd, const char *fmt, ...)
80 {
81 	va_list ap;
82 	char *resp;
83 
84 	resp = tal_strdup(tmpctx, "HTTP/1.1 400 I only speak websocket\r\n\r\n");
85 	va_start(ap, fmt);
86 	tal_append_vfmt(&resp, fmt, ap);
87 	va_end(ap);
88 
89 	write_all(fd, resp, strlen(resp));
90 	exit(1);
91 }
92 
93 /* We know headers are terminated by \r\n\r\n at this point */
get_http_hdr(const tal_t * ctx,const u8 * buf,size_t buflen,const char * hdrname)94 static const char *get_http_hdr(const tal_t *ctx, const u8 *buf, size_t buflen,
95 				const char *hdrname)
96 {
97 	size_t hdrlen;
98 
99 	for (;;) {
100 		const u8 *end = memmem(buf, buflen, "\r\n", 2);
101 		hdrlen = end - buf;
102 
103 		/* Empty line?  End of headers. */
104 		if (hdrlen == 0)
105 			return NULL;
106 		/* header name followed by : */
107 		if (memstarts(buf, hdrlen, hdrname, strlen(hdrname))
108 		    && buf[strlen(hdrname)] == ':')
109 			break;
110 		buf = end + 2;
111 	}
112 
113 	buf += strlen(hdrname) + 1;
114 	hdrlen -= strlen(hdrname) + 1;
115 
116 	/* Ignore leading whitespace (technically, they can split
117 	 * fields over multiple lines, but that's silly for the fields
118 	 * we're dealing with, so Naah). */
119 	while (hdrlen && cisspace(*buf)) {
120 		buf++;
121 		hdrlen--;
122 	}
123 
124 	return tal_strndup(ctx, (const char *)buf, hdrlen);
125 }
126 
http_headers_complete(const u8 * buf,size_t len)127 static bool http_headers_complete(const u8 *buf, size_t len)
128 {
129 	return memmem(buf, len, "\r\n\r\n", 4) != NULL;
130 }
131 
http_respond(int fd,const u8 * buf,size_t len)132 static void http_respond(int fd, const u8 *buf, size_t len)
133 {
134 	const char *hdr;
135 	char *resp;
136 
137 	/* RFC-6455:
138 
139 	   The client's opening handshake consists of the following
140 	   parts.  If the server, while reading the handshake, finds
141 	   that the client did not send a handshake that matches the
142 	   description below ...  the server MUST stop processing the
143 	   client's handshake and return an HTTP response with an
144 	   appropriate error code (such as 400 Bad Request).
145 
146 	   1.   An HTTP/1.1 or higher GET request, including a "Request-URI"
147 	   [RFC2616] that should be interpreted as a /resource name/
148 	   defined in Section 3 (or an absolute HTTP/HTTPS URI containing
149 	   the /resource name/).
150 
151 	   2.   A |Host| header field containing the server's authority.
152 
153 	   3.   An |Upgrade| header field containing the value "websocket",
154 	   treated as an ASCII case-insensitive value.
155 
156 	   4.   A |Connection| header field that includes the token "Upgrade",
157 	   treated as an ASCII case-insensitive value.
158 
159 	   5.   A |Sec-WebSocket-Key| header field with a base64-encoded (see
160 	   Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in
161 	   length.
162 
163 	   6.   A |Sec-WebSocket-Version| header field, with a value of 13.
164 	*/
165 	hdr = get_http_hdr(tmpctx, buf, len, "Upgrade");
166 	if (!hdr || !strstr(hdr, "websocket"))
167 		bad_http(fd, "Upgrade: websocket missing");
168 	hdr = get_http_hdr(tmpctx, buf, len, "Connection");
169 	if (!hdr || !strstr(hdr, "Upgrade"))
170 		bad_http(fd, "Connection: Upgrade missing");
171 	hdr = get_http_hdr(tmpctx, buf, len, "Sec-WebSocket-Version");
172 	if (!hdr || !streq(hdr, "13"))
173 		bad_http(fd, "Sec-WebSocket-Version: must be 13");
174 	hdr = get_http_hdr(tmpctx, buf, len, "Sec-WebSocket-Key");
175 	if (!hdr)
176 		bad_http(fd, "Sec-WebSocket-Key missing");
177 
178 	resp = tal_fmt(tmpctx,
179 		       "HTTP/1.1 101 Switching Protocols\r\n"
180 		       "Upgrade: websocket\r\n"
181 		       "Connection: Upgrade\r\n"
182 		       "Sec-WebSocket-Accept: %s\r\n\r\n",
183 		       websocket_accept_str(tmpctx, hdr));
184 
185 	if (!write_all(fd, resp, strlen(resp)))
186 		exit(0);
187 }
188 
http_upgrade(int fd)189 static void http_upgrade(int fd)
190 {
191 	u8 buf[65536];
192 	size_t len = 0;
193 
194 	alarm(60);
195 	while (!http_headers_complete(buf, len)) {
196 		int r;
197 		r = read(STDIN_FILENO, buf + len, sizeof(buf) - len);
198 		if (r <= 0)
199 			bad_http(STDIN_FILENO, "No header end after %zu bytes",
200 				 len);
201 		len += r;
202 	}
203 	http_respond(STDIN_FILENO, buf, len);
204 	alarm(0);
205 }
206 
lightningd_to_websocket(int lightningfd,int wsfd)207 static void lightningd_to_websocket(int lightningfd, int wsfd)
208 {
209 	/* We prepend ws header */
210 	u8 buf[4 + 65535];
211 	int len;
212 	/* Not continued frame (0x80), opcode = 2 (binary) */
213 	const u8 firstbyte = 0x82;
214 	size_t off;
215 
216 	len = read(lightningfd, 4 + buf, sizeof(buf) - 4);
217 	if (len <= 0)
218 		exit(0);
219 
220 	if (len > 125) {
221 		buf[0] = firstbyte;
222 		buf[1] = 126;
223 		buf[2] = (len >> 8);
224 		buf[3] = len;
225 		off = 0;
226 		len += 4;
227 	} else {
228 		buf[2] = firstbyte;
229 		buf[3] = len;
230 		off = 2;
231 		len += 2;
232 	}
233 	if (!write_all(wsfd, buf + off, len))
234 		exit(0);
235 }
236 
237 /* Returns payload size, sets inmask, is_binframe */
read_payload_header(int fd,u8 inmask[4],bool * is_binframe)238 static size_t read_payload_header(int fd, u8 inmask[4], bool *is_binframe)
239 {
240 	/* Worst case header. */
241 	u8 frame_hdr[20];
242 	bool mask_set;
243 	size_t hdrsize = 2, len;
244 
245 	/* First two bytes define hdr size. */
246 	if (!read_all(fd, frame_hdr, 2))
247 		exit(0);
248 
249 	/* RFC-6455:
250 	 *  %x2 denotes a binary frame
251 	 */
252 	*is_binframe = ((frame_hdr[0] & 0x0F) == 2);
253 	mask_set = (frame_hdr[1] & 0x80);
254 	len = (frame_hdr[1] & 0x7f);
255 
256 	if (len == 126)
257 		hdrsize += 2;
258 	else if (len == 127)
259 		hdrsize += 8;
260 
261 	if (mask_set)
262 		hdrsize += 4;
263 
264 	/* Read rest of hdr if necessary */
265 	if (hdrsize > 2 && !read_all(fd, frame_hdr + 2, hdrsize - 2))
266 		exit(0);
267 
268 	if (len == 126) {
269 		be16 be16len;
270 		memcpy(&be16len, frame_hdr + 2, 2);
271 		len = be16_to_cpu(be16len);
272 	} else if (len == 127) {
273 		be64 be64len;
274 		memcpy(&be64len, frame_hdr + 2, 8);
275 		len = be64_to_cpu(be64len);
276 	}
277 
278 	if (mask_set) {
279 		memcpy(inmask, frame_hdr + hdrsize - 4, 4);
280 		hdrsize += 4;
281 	} else
282 		memset(inmask, 0, 4);
283 
284 	return len;
285 }
286 
apply_mask(u8 * buf,size_t len,const u8 inmask[4])287 static void apply_mask(u8 *buf, size_t len, const u8 inmask[4])
288 {
289 	for (size_t i = 0; i < len; i++)
290 		buf[i] ^= inmask[i % 4];
291 }
292 
websocket_to_lightningd(int wsfd,int lightningfd)293 static void websocket_to_lightningd(int wsfd, int lightningfd)
294 {
295 	size_t len;
296 	u8 inmask[4];
297 	bool is_binframe;
298 
299 	len = read_payload_header(wsfd, inmask, &is_binframe);
300 	while (len > 0) {
301 		u8 buf[65536];
302 		int rlen = len;
303 
304 		if (rlen > sizeof(buf))
305 			rlen = sizeof(buf);
306 
307 		rlen = read(wsfd, buf, rlen);
308 		if (rlen <= 0)
309 			exit(0);
310 		apply_mask(buf, rlen, inmask);
311 		len -= rlen;
312 		/* We ignore non binary frames (FIXME: Send error!) */
313 		if (is_binframe && !write_all(lightningfd, buf, rlen))
314 			exit(0);
315 	}
316 }
317 
318 /* stdin goes to the client, stdout goes to lightningd */
main(int argc,char * argv[])319 int main(int argc, char *argv[])
320 {
321 	struct pollfd pfds[2];
322 
323 	common_setup(argv[0]);
324 
325 	if (argc != 1)
326 		errx(1, "Usage: %s", argv[0]);
327 
328 	/* Do HTTP-style negotiation to get into websocket frames. */
329 	http_upgrade(STDIN_FILENO);
330 
331 	pfds[0].fd = STDIN_FILENO;
332 	pfds[0].events = POLLIN;
333 	pfds[1].fd = STDOUT_FILENO;
334 	pfds[1].events = POLLIN;
335 
336 	for (;;) {
337 		poll(pfds, 2, -1);
338 
339 		if (pfds[1].revents & POLLIN)
340 			lightningd_to_websocket(STDOUT_FILENO, STDIN_FILENO);
341 		if (pfds[0].revents & POLLIN)
342 			websocket_to_lightningd(STDIN_FILENO, STDOUT_FILENO);
343 	}
344 
345 	common_shutdown();
346 	exit(0);
347 }
348