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