forked from ElementsProject/lightning
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathwebsocketd.c
349 lines (294 loc) · 9.57 KB
/
websocketd.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
/* A simple standalone websocket <-> binary proxy.
* See https://datatracker.ietf.org/doc/html/rfc6455
*/
#include "config.h"
#include <ccan/base64/base64.h>
#include <ccan/endian/endian.h>
#include <ccan/err/err.h>
#include <ccan/io/io.h>
#include <ccan/mem/mem.h>
#include <ccan/read_write_all/read_write_all.h>
#include <ccan/str/hex/hex.h>
#include <ccan/tal/str/str.h>
#include <common/setup.h>
#include <common/utils.h>
#include <connectd/sha1.h>
#include <poll.h>
#include <stdarg.h>
#include <stdio.h>
#include <unistd.h>
/*
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)
{
u8 buf[65536];
size_t len = 0;
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 != 1)
errx(1, "Usage: %s", argv[0]);
/* Do HTTP-style negotiation to get into websocket frames. */
io_fd_block(STDIN_FILENO, true);
http_upgrade(STDIN_FILENO);
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);
}