1 /*
2 * mod_wstunnel originally based off https://github.com/nori0428/mod_websocket
3 * Portions of this module Copyright(c) 2017, Glenn Strauss, All rights reserved
4 * Portions of this module Copyright(c) 2010, Norio Kobota, All rights reserved.
5 */
6
7 /*
8 * Copyright(c) 2010, Norio Kobota, All rights reserved.
9 *
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 *
13 * - Redistributions of source code must retain the above copyright notice,
14 * this list of conditions and the following disclaimer.
15 * - Redistributions in binary form must reproduce the above copyright notice,
16 * this list of conditions and the following disclaimer in the documentation
17 * and/or other materials provided with the distribution.
18 * - Neither the name of the 'incremental' nor the names of its contributors
19 * may be used to endorse or promote products derived from this software
20 * without specific prior written permission.
21 *
22 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
25 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
26 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
27 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
28 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
29 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
30 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
31 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
32 * THE POSSIBILITY OF SUCH DAMAGE.
33 */
34
35 /* NOTES:
36 *
37 * mod_wstunnel has been largely rewritten from Norio Kobota mod_websocket.
38 *
39 * highlighted differences from Norio Kobota mod_websocket
40 * - re-coded to use lighttpd 1.4.46 buffer, chunkqueue, and gw_backend APIs
41 * - websocket.server "ext" value is no longer regex;
42 * operates similar to mod_proxy for either path prefix or extension match
43 * - validation of "origins" value is no longer regex; operates as suffix match
44 * (admin could use lighttpd.conf regex on "Origin" or "Sec-WebSocket-Origin"
45 * and reject non-matches with mod_access if such regex validation required)
46 * - websocket transparent proxy mode removed; functionality is now in mod_proxy
47 * Backend server which responds to Connection: upgrade and Upgrade: websocket
48 * should check "Origin" and/or "Sec-WebSocket-Origin". lighttpd.conf could
49 * additionally be configured to check
50 * $REQUEST_HEADER["Sec-WebSocket-Origin"] !~ "..."
51 * with regex, and mod_access used to reject non-matches, if desired.
52 * - connections to backend no longer block, but only first address returned
53 * by getaddrinfo() is used; lighttpd does not cycle through all addresses
54 * returned by DNS resolution. Note: DNS resolution occurs once at startup.
55 * - directives renamed from websocket.* to wstunnel.*
56 * - directive websocket.ping_interval replaced with wstunnel.ping-interval
57 * (note the '_' changed to '-')
58 * - directive websocket.timeout should be replaced with server.max-read-idle
59 * - attribute "type" is an independent directive wstunnel.frame-type
60 * (default is "text" unless "binary" is specified)
61 * - attribute "origins" is an independent directive wstunnel.origins
62 * - attribute "proto" removed; mod_proxy can proxy to backend websocket server
63 * - attribute "subproto" should be replaced with mod_setenv directive
64 * setenv.set-response-header = ( "Sec-WebSocket-Protocol" => "..." )
65 * if header is required
66 *
67 * not reviewed:
68 * - websocket protocol compliance has not been reviewed
69 * e.g. when to send 1000 Normal Closure and when to send 1001 Going Away
70 * - websocket protocol sanity checking has not been reviewed
71 *
72 * References:
73 * https://en.wikipedia.org/wiki/WebSocket
74 * https://tools.ietf.org/html/rfc6455
75 * https://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-00
76 */
77 #include "first.h"
78
79 #include <sys/types.h>
80 #include <limits.h>
81 #include <stdlib.h>
82 #include <string.h>
83
84 #include "gw_backend.h"
85
86 #include "base.h"
87 #include "array.h"
88 #include "buffer.h"
89 #include "chunk.h"
90 #include "fdevent.h"
91 #include "http_header.h"
92 #include "log.h"
93
94 #define MOD_WEBSOCKET_LOG_NONE 0
95 #define MOD_WEBSOCKET_LOG_ERR 1
96 #define MOD_WEBSOCKET_LOG_WARN 2
97 #define MOD_WEBSOCKET_LOG_INFO 3
98 #define MOD_WEBSOCKET_LOG_DEBUG 4
99
100 #define DEBUG_LOG_ERR(format, ...) \
101 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_ERR) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); }
102
103 #define DEBUG_LOG_WARN(format, ...) \
104 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_WARN) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); }
105
106 #define DEBUG_LOG_INFO(format, ...) \
107 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_INFO) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); }
108
109 #define DEBUG_LOG_DEBUG(format, ...) \
110 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_DEBUG) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); }
111
112 typedef struct {
113 gw_plugin_config gw; /* start must match layout of gw_plugin_config */
114 const array *origins;
115 unsigned int frame_type;
116 unsigned short int ping_interval;
117 } plugin_config;
118
119 typedef struct plugin_data {
120 PLUGIN_DATA;
121 pid_t srv_pid; /* must match layout of gw_plugin_data through conf member */
122 plugin_config conf;
123 plugin_config defaults;
124 } plugin_data;
125
126 typedef enum {
127 MOD_WEBSOCKET_FRAME_STATE_INIT,
128
129 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
130 MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH,
131 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH,
132 MOD_WEBSOCKET_FRAME_STATE_READ_MASK,
133 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
134
135 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD
136 } mod_wstunnel_frame_state_t;
137
138 typedef enum {
139 MOD_WEBSOCKET_FRAME_TYPE_TEXT,
140 MOD_WEBSOCKET_FRAME_TYPE_BIN,
141 MOD_WEBSOCKET_FRAME_TYPE_CLOSE,
142
143 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
144 MOD_WEBSOCKET_FRAME_TYPE_PING,
145 MOD_WEBSOCKET_FRAME_TYPE_PONG
146 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
147
148 } mod_wstunnel_frame_type_t;
149
150 typedef struct {
151 uint64_t siz;
152
153 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
154 int siz_cnt;
155 int mask_cnt;
156 #define MOD_WEBSOCKET_MASK_CNT 4
157 unsigned char mask[MOD_WEBSOCKET_MASK_CNT];
158 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
159
160 } mod_wstunnel_frame_control_t;
161
162 typedef struct {
163 mod_wstunnel_frame_state_t state;
164 mod_wstunnel_frame_control_t ctl;
165 mod_wstunnel_frame_type_t type, type_before, type_backend;
166 buffer *payload;
167 } mod_wstunnel_frame_t;
168
169 typedef struct {
170 gw_handler_ctx gw;
171 mod_wstunnel_frame_t frame;
172
173 int hybivers;
174 unix_time64_t ping_ts;
175 int subproto;
176
177 log_error_st *errh; /*(for mod_wstunnel module-specific DEBUG_*() macros)*/
178 plugin_config conf;
179 } handler_ctx;
180
181 /* prototypes */
182 static handler_t mod_wstunnel_handshake_create_response(handler_ctx *);
183 static int mod_wstunnel_frame_send(handler_ctx *, mod_wstunnel_frame_type_t, const char *, size_t);
184 static int mod_wstunnel_frame_recv(handler_ctx *);
185 #define _MOD_WEBSOCKET_SPEC_IETF_00_
186 #define _MOD_WEBSOCKET_SPEC_RFC_6455_
187
INIT_FUNC(mod_wstunnel_init)188 INIT_FUNC(mod_wstunnel_init) {
189 return ck_calloc(1, sizeof(plugin_data));
190 }
191
mod_wstunnel_merge_config_cpv(plugin_config * const pconf,const config_plugin_value_t * const cpv)192 static void mod_wstunnel_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) {
193 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */
194 case 0: /* wstunnel.server */
195 if (cpv->vtype == T_CONFIG_LOCAL) {
196 gw_plugin_config * const gw = cpv->v.v;
197 pconf->gw.exts = gw->exts;
198 pconf->gw.exts_auth = gw->exts_auth;
199 pconf->gw.exts_resp = gw->exts_resp;
200 }
201 break;
202 case 1: /* wstunnel.balance */
203 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/
204 pconf->gw.balance = (int)cpv->v.u;
205 break;
206 case 2: /* wstunnel.debug */
207 pconf->gw.debug = (int)cpv->v.u;
208 break;
209 case 3: /* wstunnel.map-extensions */
210 pconf->gw.ext_mapping = cpv->v.a;
211 break;
212 case 4: /* wstunnel.frame-type */
213 pconf->frame_type = cpv->v.u;
214 break;
215 case 5: /* wstunnel.origins */
216 pconf->origins = cpv->v.a;
217 break;
218 case 6: /* wstunnel.ping-interval */
219 pconf->ping_interval = cpv->v.shrt;
220 break;
221 default:/* should not happen */
222 return;
223 }
224 }
225
mod_wstunnel_merge_config(plugin_config * const pconf,const config_plugin_value_t * cpv)226 static void mod_wstunnel_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) {
227 do {
228 mod_wstunnel_merge_config_cpv(pconf, cpv);
229 } while ((++cpv)->k_id != -1);
230 }
231
mod_wstunnel_patch_config(request_st * const r,plugin_data * const p)232 static void mod_wstunnel_patch_config(request_st * const r, plugin_data * const p) {
233 memcpy(&p->conf, &p->defaults, sizeof(plugin_config));
234 for (int i = 1, used = p->nconfig; i < used; ++i) {
235 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id))
236 mod_wstunnel_merge_config(&p->conf, p->cvlist+p->cvlist[i].v.u2[0]);
237 }
238 }
239
SETDEFAULTS_FUNC(mod_wstunnel_set_defaults)240 SETDEFAULTS_FUNC(mod_wstunnel_set_defaults) {
241 static const config_plugin_keys_t cpk[] = {
242 { CONST_STR_LEN("wstunnel.server"),
243 T_CONFIG_ARRAY_KVARRAY,
244 T_CONFIG_SCOPE_CONNECTION }
245 ,{ CONST_STR_LEN("wstunnel.balance"),
246 T_CONFIG_STRING,
247 T_CONFIG_SCOPE_CONNECTION }
248 ,{ CONST_STR_LEN("wstunnel.debug"),
249 T_CONFIG_INT,
250 T_CONFIG_SCOPE_CONNECTION }
251 ,{ CONST_STR_LEN("wstunnel.map-extensions"),
252 T_CONFIG_ARRAY_KVSTRING,
253 T_CONFIG_SCOPE_CONNECTION }
254 ,{ CONST_STR_LEN("wstunnel.frame-type"),
255 T_CONFIG_STRING,
256 T_CONFIG_SCOPE_CONNECTION }
257 ,{ CONST_STR_LEN("wstunnel.origins"),
258 T_CONFIG_ARRAY_VLIST,
259 T_CONFIG_SCOPE_CONNECTION }
260 ,{ CONST_STR_LEN("wstunnel.ping-interval"),
261 T_CONFIG_SHORT,
262 T_CONFIG_SCOPE_CONNECTION }
263 ,{ NULL, 0,
264 T_CONFIG_UNSET,
265 T_CONFIG_SCOPE_UNSET }
266 };
267
268 plugin_data * const p = p_d;
269 if (!config_plugin_values_init(srv, p, cpk, "mod_wstunnel"))
270 return HANDLER_ERROR;
271
272 /* process and validate config directives
273 * (init i to 0 if global context; to 1 to skip empty global context) */
274 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) {
275 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0];
276 gw_plugin_config *gw = NULL;
277 for (; -1 != cpv->k_id; ++cpv) {
278 switch (cpv->k_id) {
279 case 0: /* wstunnel.server */
280 gw = ck_calloc(1, sizeof(gw_plugin_config));
281 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, cpv->v.a,
282 gw, 0, cpk[cpv->k_id].k)) {
283 gw_plugin_config_free(gw);
284 return HANDLER_ERROR;
285 }
286 /* error if "mode" = "authorizer";
287 * wstunnel can not act as authorizer */
288 /*(check after gw_set_defaults_backend())*/
289 if (gw->exts_auth && gw->exts_auth->used) {
290 log_error(srv->errh, __FILE__, __LINE__,
291 "%s must not define any hosts with "
292 "attribute \"mode\" = \"authorizer\"", cpk[cpv->k_id].k);
293 gw_plugin_config_free(gw);
294 return HANDLER_ERROR;
295 }
296 cpv->v.v = gw;
297 cpv->vtype = T_CONFIG_LOCAL;
298 break;
299 case 1: /* wstunnel.balance */
300 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b);
301 break;
302 case 2: /* wstunnel.debug */
303 case 3: /* wstunnel.map-extensions */
304 break;
305 case 4: /* wstunnel.frame-type */
306 /*(default frame-type to "text" unless "binary" is specified)*/
307 cpv->v.u =
308 buffer_eq_icase_slen(cpv->v.b, CONST_STR_LEN("binary"));
309 break;
310 case 5: /* wstunnel.origins */
311 for (uint32_t j = 0; j < cpv->v.a->used; ++j) {
312 buffer *origin = &((data_string *)cpv->v.a->data[j])->value;
313 if (buffer_is_blank(origin)) {
314 log_error(srv->errh, __FILE__, __LINE__,
315 "unexpected empty string in %s", cpk[cpv->k_id].k);
316 return HANDLER_ERROR;
317 }
318 }
319 break;
320 case 6: /* wstunnel.ping-interval */
321 break;
322 default:/* should not happen */
323 break;
324 }
325 }
326
327 /* disable check-local for all exts (default enabled) */
328 if (gw && gw->exts) { /*(check after gw_set_defaults_backend())*/
329 gw_exts_clear_check_local(gw->exts);
330 }
331 }
332
333 /* default is 0 */
334 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/
335 p->defaults.ping_interval = 0; /* do not send ping */
336
337 /* initialize p->defaults from global config context */
338 if (p->nconfig > 0 && p->cvlist->v.u2[1]) {
339 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0];
340 if (-1 != cpv->k_id)
341 mod_wstunnel_merge_config(&p->defaults, cpv);
342 }
343
344 return HANDLER_GO_ON;
345 }
346
wstunnel_create_env(gw_handler_ctx * gwhctx)347 static handler_t wstunnel_create_env(gw_handler_ctx *gwhctx) {
348 handler_ctx *hctx = (handler_ctx *)gwhctx;
349 request_st * const r = hctx->gw.r;
350 handler_t rc;
351 if (0 == r->reqbody_length || r->http_version > HTTP_VERSION_1_1) {
352 http_response_upgrade_read_body_unknown(r);
353 chunkqueue_append_chunkqueue(&r->reqbody_queue, &r->read_queue);
354 }
355 rc = mod_wstunnel_handshake_create_response(hctx);
356 if (rc != HANDLER_GO_ON) return rc;
357
358 r->http_status = (r->http_version > HTTP_VERSION_1_1)
359 ? 200 /* OK (response status for CONNECT) */
360 : 101; /* Switching Protocols */
361 r->resp_body_started = 1;
362
363 hctx->ping_ts = log_monotonic_secs;
364 gw_set_transparent(&hctx->gw);
365
366 return HANDLER_GO_ON;
367 }
368
wstunnel_stdin_append(gw_handler_ctx * gwhctx)369 static handler_t wstunnel_stdin_append(gw_handler_ctx *gwhctx) {
370 /* prepare websocket frames to backend */
371 /* (caller should verify r->reqbody_queue) */
372 /*assert(!chunkqueue_is_empty(&r->reqbody_queue));*/
373 handler_ctx *hctx = (handler_ctx *)gwhctx;
374 if (0 == mod_wstunnel_frame_recv(hctx))
375 return HANDLER_GO_ON;
376 else {
377 /*(error)*/
378 /* future: might differentiate client close request from client error,
379 * and then send 1000 or 1001 */
380 request_st * const r = hctx->gw.r;
381 DEBUG_LOG_INFO("disconnected from client (fd=%d)", r->con->fd);
382 DEBUG_LOG_DEBUG("send close response to client (fd=%d)", r->con->fd);
383 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_CLOSE, CONST_STR_LEN("1000")); /* 1000 Normal Closure */
384 gw_handle_request_reset(r, hctx->gw.plugin_data);
385 return HANDLER_FINISHED;
386 }
387 }
388
wstunnel_recv_parse(request_st * const r,http_response_opts * const opts,buffer * const b,size_t n)389 static handler_t wstunnel_recv_parse(request_st * const r, http_response_opts * const opts, buffer * const b, size_t n) {
390 handler_ctx *hctx = (handler_ctx *)opts->pdata;
391 DEBUG_LOG_DEBUG("recv data from backend (fd=%d), size=%zx", hctx->gw.fd, n);
392 if (0 == n) return HANDLER_FINISHED;
393 if (mod_wstunnel_frame_send(hctx,hctx->frame.type_backend,b->ptr,n) < 0) {
394 DEBUG_LOG_ERR("%s", "fail to send data to client");
395 return HANDLER_ERROR;
396 }
397 buffer_clear(b);
398 UNUSED(r);
399 return HANDLER_GO_ON;
400 }
401
wstunnel_is_allowed_origin(request_st * const r,handler_ctx * const hctx)402 static int wstunnel_is_allowed_origin(request_st * const r, handler_ctx * const hctx) {
403 /* If allowed origins is set (and not empty list), fail closed if no match.
404 * Note that origin provided in request header has not been normalized, so
405 * change in case or other non-normal forms might not match allowed list */
406 const array * const allowed_origins = hctx->conf.origins;
407 const buffer *origin = NULL;
408 size_t olen;
409
410 if (NULL == allowed_origins || 0 == allowed_origins->used) {
411 DEBUG_LOG_INFO("%s", "allowed origins not specified");
412 return 1;
413 }
414
415 /* "Origin" header is preferred
416 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */
417 origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin"));
418 if (NULL == origin) {
419 origin =
420 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin"));
421 }
422 olen = origin ? buffer_clen(origin) : 0;
423 if (0 == olen) {
424 DEBUG_LOG_ERR("%s", "Origin header is invalid");
425 r->http_status = 400; /* Bad Request */
426 return 0;
427 }
428
429 for (size_t i = 0; i < allowed_origins->used; ++i) {
430 buffer *b = &((data_string *)allowed_origins->data[i])->value;
431 size_t blen = buffer_clen(b);
432 if ((olen > blen ? origin->ptr[olen-blen-1] == '.' : olen == blen)
433 && 0 == memcmp(origin->ptr+olen-blen, b->ptr, blen)) {
434 DEBUG_LOG_INFO("%s matches allowed origin: %s",origin->ptr,b->ptr);
435 return 1;
436 }
437 }
438 DEBUG_LOG_INFO("%s does not match any allowed origins", origin->ptr);
439 r->http_status = 403; /* Forbidden */
440 return 0;
441 }
442
wstunnel_check_request(request_st * const r,handler_ctx * const hctx)443 static int wstunnel_check_request(request_st * const r, handler_ctx * const hctx) {
444 const buffer * const vers =
445 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Version"));
446 const long hybivers = (NULL != vers)
447 ? light_isdigit(*vers->ptr) ? strtol(vers->ptr, NULL, 10) : -1
448 : 0;
449 if (hybivers < 0 || hybivers > INT_MAX) {
450 DEBUG_LOG_ERR("%s", "invalid Sec-WebSocket-Version");
451 r->http_status = 400; /* Bad Request */
452 return -1;
453 }
454
455 /*(redundant since HTTP/1.1 required in mod_wstunnel_check_extension())*/
456 if (!r->http_host || buffer_is_blank(r->http_host)) {
457 DEBUG_LOG_ERR("%s", "Host header does not exist");
458 r->http_status = 400; /* Bad Request */
459 return -1;
460 }
461
462 if (!wstunnel_is_allowed_origin(r, hctx)) {
463 return -1;
464 }
465
466 return (int)hybivers;
467 }
468
wstunnel_backend_error(gw_handler_ctx * gwhctx)469 static void wstunnel_backend_error(gw_handler_ctx *gwhctx) {
470 handler_ctx *hctx = (handler_ctx *)gwhctx;
471 if (hctx->gw.state == GW_STATE_WRITE || hctx->gw.state == GW_STATE_READ) {
472 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_CLOSE, CONST_STR_LEN("1001")); /* 1001 Going Away */
473 }
474 }
475
wstunnel_handler_ctx_free(void * gwhctx)476 static void wstunnel_handler_ctx_free(void *gwhctx) {
477 handler_ctx *hctx = (handler_ctx *)gwhctx;
478 chunk_buffer_release(hctx->frame.payload);
479 }
480
wstunnel_handler_setup(request_st * const r,plugin_data * const p)481 static handler_t wstunnel_handler_setup (request_st * const r, plugin_data * const p) {
482 handler_ctx *hctx = r->plugin_ctx[p->id];
483 int hybivers;
484 hctx->errh = r->conf.errh;/*(for mod_wstunnel-specific DEBUG_* macros)*/
485 hctx->conf = p->conf; /*(copies struct)*/
486 hybivers = wstunnel_check_request(r, hctx);
487 if (hybivers < 0) {
488 r->handler_module = NULL;
489 return HANDLER_FINISHED;
490 }
491 hctx->hybivers = hybivers;
492 if (0 == hybivers) {
493 DEBUG_LOG_INFO("WebSocket Version = %s", "hybi-00");
494 }
495 else {
496 DEBUG_LOG_INFO("WebSocket Version = %d", hybivers);
497 }
498
499 hctx->gw.opts.backend = BACKEND_PROXY; /*(act proxy-like)*/
500 hctx->gw.opts.pdata = hctx;
501 hctx->gw.opts.parse = wstunnel_recv_parse;
502 hctx->gw.stdin_append = wstunnel_stdin_append;
503 hctx->gw.create_env = wstunnel_create_env;
504 hctx->gw.handler_ctx_free = wstunnel_handler_ctx_free;
505 hctx->gw.backend_error = wstunnel_backend_error;
506 hctx->gw.response = chunk_buffer_acquire();
507
508 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT;
509 hctx->frame.ctl.siz = 0;
510 hctx->frame.payload = chunk_buffer_acquire();
511
512 unsigned int binary = hctx->conf.frame_type; /*(0 = "text"; 1 = "binary")*/
513 if (!binary) {
514 const buffer *vb =
515 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Protocol"));
516 if (NULL != vb) {
517 for (const char *s = vb->ptr; *s; ++s) {
518 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s;
519 if (buffer_eq_icase_ssn(s, CONST_STR_LEN("binary"))) {
520 s += sizeof("binary")-1;
521 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s;
522 if (*s==','||*s=='\0') {
523 hctx->subproto = 1;
524 binary = 1;
525 break;
526 }
527 }
528 else if (buffer_eq_icase_ssn(s, CONST_STR_LEN("base64"))) {
529 s += sizeof("base64")-1;
530 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s;
531 if (*s==','||*s=='\0') {
532 hctx->subproto = -1;
533 break;
534 }
535 }
536 s = strchr(s, ',');
537 if (NULL == s) break;
538 }
539 }
540 }
541
542 if (binary) {
543 DEBUG_LOG_INFO("%s", "will recv binary data from backend");
544 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN;
545 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_BIN;
546 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_BIN;
547 }
548 else {
549 DEBUG_LOG_INFO("%s", "will recv text data from backend");
550 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT;
551 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_TEXT;
552 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_TEXT;
553 }
554
555 return HANDLER_GO_ON;
556 }
557
mod_wstunnel_check_extension(request_st * const r,void * p_d)558 static handler_t mod_wstunnel_check_extension(request_st * const r, void *p_d) {
559 plugin_data *p = p_d;
560 handler_t rc;
561
562 if (NULL != r->handler_module)
563 return HANDLER_GO_ON;
564 if (r->http_version > HTTP_VERSION_1_1) {
565 if (!r->h2_connect_ext)
566 return HANDLER_GO_ON;
567 }
568 else {
569 if (r->http_method != HTTP_METHOD_GET)
570 return HANDLER_GO_ON;
571 if (r->http_version != HTTP_VERSION_1_1)
572 return HANDLER_GO_ON;
573
574 /*
575 * Connection: upgrade, keep-alive, ...
576 * Upgrade: WebSocket, ...
577 */
578 const buffer *vb;
579 vb = http_header_request_get(r, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade"));
580 if (NULL == vb
581 || !http_header_str_contains_token(BUF_PTR_LEN(vb), CONST_STR_LEN("websocket")))
582 return HANDLER_GO_ON;
583 vb = http_header_request_get(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection"));
584 if (NULL == vb
585 || !http_header_str_contains_token(BUF_PTR_LEN(vb), CONST_STR_LEN("upgrade")))
586 return HANDLER_GO_ON;
587 }
588
589 mod_wstunnel_patch_config(r, p);
590 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON;
591
592 rc = gw_check_extension(r, (gw_plugin_data *)p, 1, sizeof(handler_ctx));
593 return (HANDLER_GO_ON == rc && r->handler_module == p->self)
594 ? wstunnel_handler_setup(r, p)
595 : rc;
596 }
597
TRIGGER_FUNC(mod_wstunnel_handle_trigger)598 TRIGGER_FUNC(mod_wstunnel_handle_trigger) {
599 const plugin_data * const p = p_d;
600 const unix_time64_t cur_ts = log_monotonic_secs + 1;
601
602 gw_handle_trigger(srv, p_d);
603
604 for (connection *con = srv->conns; con; con = con->next) {
605 request_st * const r = &con->request;
606 handler_ctx *hctx = r->plugin_ctx[p->id];
607 if (NULL == hctx || r->handler_module != p->self)
608 continue;
609
610 if (hctx->gw.state != GW_STATE_WRITE && hctx->gw.state != GW_STATE_READ)
611 continue;
612
613 if (cur_ts - con->read_idle_ts > r->conf.max_read_idle) {
614 DEBUG_LOG_INFO("timeout client (fd=%d)", con->fd);
615 mod_wstunnel_frame_send(hctx,MOD_WEBSOCKET_FRAME_TYPE_CLOSE,NULL,0);
616 gw_handle_request_reset(r, p_d);
617 joblist_append(con);
618 /* avoid server.c closing connection with error due to max_read_idle
619 * (might instead run joblist after plugins_call_handle_trigger())*/
620 con->read_idle_ts = cur_ts;
621 continue;
622 }
623
624 if (0 != hctx->hybivers
625 && hctx->conf.ping_interval > 0
626 && (int32_t)hctx->conf.ping_interval + hctx->ping_ts < cur_ts) {
627 hctx->ping_ts = cur_ts;
628 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_PING, CONST_STR_LEN("ping"));
629 joblist_append(con);
630 continue;
631 }
632 }
633
634 return HANDLER_GO_ON;
635 }
636
637
638 __attribute_cold__
639 int mod_wstunnel_plugin_init(plugin *p);
mod_wstunnel_plugin_init(plugin * p)640 int mod_wstunnel_plugin_init(plugin *p) {
641 p->version = LIGHTTPD_VERSION_ID;
642 p->name = "wstunnel";
643 p->init = mod_wstunnel_init;
644 p->cleanup = gw_free;
645 p->set_defaults = mod_wstunnel_set_defaults;
646 p->handle_request_reset = gw_handle_request_reset;
647 p->handle_uri_clean = mod_wstunnel_check_extension;
648 p->handle_subrequest = gw_handle_subrequest;
649 p->handle_trigger = mod_wstunnel_handle_trigger;
650 p->handle_waitpid = gw_handle_waitpid_cb;
651 return 0;
652 }
653
654
655
656
657 /*
658 * modified from Norio Kobota mod_websocket_handshake.c
659 */
660
661 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
662
663 #include "sys-crypto-md.h" /* lighttpd */
664 #include "sys-endian.h" /* lighttpd */
665
get_key3(request_st * const r,char * buf,uint32_t bytes)666 static int get_key3(request_st * const r, char *buf, uint32_t bytes) {
667 /* 8 bytes should have been sent with request
668 * for draft-ietf-hybi-thewebsocketprotocol-00 */
669 chunkqueue *cq = &r->reqbody_queue;
670 /*(caller should ensure bytes available prior to calling this routine)*/
671 /*assert(chunkqueue_length(cq) >= 8);*/
672 /*assert(8 == bytes);*/
673 return chunkqueue_read_data(cq, buf, bytes, r->conf.errh);
674 }
675
get_key_number(uint32_t * ret,const buffer * b)676 static int get_key_number(uint32_t *ret, const buffer *b) {
677 const char * const s = b->ptr;
678 size_t j = 0;
679 unsigned long n;
680 uint32_t sp = 0;
681 char tmp[10 + 1]; /* #define UINT32_MAX_STRLEN 10 */
682
683 for (size_t i = 0, used = buffer_clen(b); i < used; ++i) {
684 if (light_isdigit(s[i])) {
685 tmp[j] = s[i];
686 if (++j >= sizeof(tmp)) return -1;
687 }
688 else if (s[i] == ' ') ++sp; /* count num spaces */
689 }
690 tmp[j] = '\0';
691 n = strtoul(tmp, NULL, 10);
692 if (n > UINT32_MAX || 0 == sp || !light_isdigit(*tmp)) return -1;
693 *ret = (uint32_t)n / sp;
694 return 0;
695 }
696
create_MD5_sum(request_st * const r)697 static int create_MD5_sum(request_st * const r) {
698 uint32_t buf[4]; /* MD5 binary hash len */
699
700 const buffer *key1 =
701 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key1"));
702 const buffer *key2 =
703 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key2"));
704
705 if (NULL == key1 || get_key_number(buf+0, key1) < 0 ||
706 NULL == key2 || get_key_number(buf+1, key2) < 0 ||
707 get_key3(r, (char *)(buf+2), 2*sizeof(uint32_t)) < 0) {
708 return -1;
709 }
710 #ifdef __BIG_ENDIAN__
711 #define ws_htole32(s,u)\
712 (s)[0]=((u)>>24); \
713 (s)[1]=((u)>>16); \
714 (s)[2]=((u)>>8); \
715 (s)[3]=((u))
716 uint32_t u;
717 u = buf[0];
718 ws_htole32((unsigned char *)(buf+0), u);
719 u = buf[1];
720 ws_htole32((unsigned char *)(buf+1), u);
721 #endif
722 /*(overwrite buf[] with result)*/
723 MD5_once((unsigned char *)buf, buf, sizeof(buf));
724 chunkqueue_append_mem(&r->write_queue, (char *)buf, sizeof(buf));
725 return 0;
726 }
727
create_response_ietf_00(handler_ctx * hctx)728 static int create_response_ietf_00(handler_ctx *hctx) {
729 request_st * const r = hctx->gw.r;
730
731 /* "Origin" header is preferred
732 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */
733 const buffer *origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin"));
734 if (NULL == origin) {
735 origin =
736 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin"));
737 }
738 if (NULL == origin) {
739 DEBUG_LOG_ERR("%s", "Origin header is invalid");
740 return -1;
741 }
742 if (!r->http_host || buffer_is_blank(r->http_host)) {
743 DEBUG_LOG_ERR("%s", "Host header does not exist");
744 return -1;
745 }
746
747 /* calc MD5 sum from keys */
748 if (create_MD5_sum(r) < 0) {
749 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid");
750 return -1;
751 }
752
753 http_header_response_set(r, HTTP_HEADER_UPGRADE,
754 CONST_STR_LEN("Upgrade"),
755 CONST_STR_LEN("websocket"));
756 #if 0 /*(added later in http_response_write_header())*/
757 http_header_response_append(r, HTTP_HEADER_CONNECTION,
758 CONST_STR_LEN("Connection"),
759 CONST_STR_LEN("upgrade"));
760 #endif
761 #if 0 /*(Sec-WebSocket-Origin header is not required for hybi-00)*/
762 /* Note: it is insecure to simply reflect back origin provided by client
763 * (if admin did not configure restricted list of valid origins)
764 * (see wstunnel_check_request()) */
765 http_header_response_set(r, HTTP_HEADER_OTHER,
766 CONST_STR_LEN("Sec-WebSocket-Origin"),
767 BUF_PTR_LEN(origin));
768 #endif
769
770 buffer * const value =
771 http_header_response_set_ptr(r, HTTP_HEADER_OTHER,
772 CONST_STR_LEN("Sec-WebSocket-Location"));
773 if (buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https")))
774 buffer_copy_string_len(value, CONST_STR_LEN("wss://"));
775 else
776 buffer_copy_string_len(value, CONST_STR_LEN("ws://"));
777 buffer_append_str2(value, BUF_PTR_LEN(r->http_host),
778 BUF_PTR_LEN(&r->uri.path));
779 return 0;
780 }
781
782 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
783
784
785 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
786
787 #include "sys-crypto-md.h" /* lighttpd */
788 #include "base64.h" /* lighttpd */
789
create_response_rfc_6455(handler_ctx * hctx)790 static int create_response_rfc_6455(handler_ctx *hctx) {
791 request_st * const r = hctx->gw.r;
792 if (r->http_version == HTTP_VERSION_1_1) {
793 SHA_CTX sha;
794 unsigned char sha_digest[SHA_DIGEST_LENGTH];
795
796 const buffer *value_wskey =
797 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key"));
798 if (NULL == value_wskey) {
799 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid");
800 return -1;
801 }
802
803 /* get SHA1 hash of key */
804 /* refer: RFC-6455 Sec.1.3 Opening Handshake */
805 SHA1_Init(&sha);
806 SHA1_Update(&sha, (const unsigned char *)BUF_PTR_LEN(value_wskey));
807 SHA1_Update(&sha, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
808 SHA1_Final(sha_digest, &sha);
809
810 http_header_response_set(r, HTTP_HEADER_UPGRADE,
811 CONST_STR_LEN("Upgrade"),
812 CONST_STR_LEN("websocket"));
813 #if 0 /*(added later in http_response_write_header())*/
814 http_header_response_append(r, HTTP_HEADER_CONNECTION,
815 CONST_STR_LEN("Connection"),
816 CONST_STR_LEN("upgrade"));
817 #endif
818
819 buffer * const value =
820 http_header_response_set_ptr(r, HTTP_HEADER_OTHER,
821 CONST_STR_LEN("Sec-WebSocket-Accept"));
822 buffer_append_base64_encode(value, sha_digest, SHA_DIGEST_LENGTH, BASE64_STANDARD);
823 }
824
825 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_BIN)
826 http_header_response_set(r, HTTP_HEADER_OTHER,
827 CONST_STR_LEN("Sec-WebSocket-Protocol"),
828 CONST_STR_LEN("binary"));
829 else if (-1 == hctx->subproto)
830 http_header_response_set(r, HTTP_HEADER_OTHER,
831 CONST_STR_LEN("Sec-WebSocket-Protocol"),
832 CONST_STR_LEN("base64"));
833
834 return 0;
835 }
836
837 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
838
839
mod_wstunnel_handshake_create_response(handler_ctx * hctx)840 handler_t mod_wstunnel_handshake_create_response(handler_ctx *hctx) {
841 request_st * const r = hctx->gw.r;
842 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
843 if (hctx->hybivers >= 8) {
844 DEBUG_LOG_DEBUG("%s", "send handshake response");
845 if (0 != create_response_rfc_6455(hctx)) {
846 r->http_status = 400; /* Bad Request */
847 return HANDLER_ERROR;
848 }
849 return HANDLER_GO_ON;
850 }
851 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
852
853 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
854 if (hctx->hybivers == 0 && r->http_version == HTTP_VERSION_1_1) {
855 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
856 /* 8 bytes should have been sent with request
857 * for draft-ietf-hybi-thewebsocketprotocol-00 */
858 chunkqueue *cq = &r->reqbody_queue;
859 if (chunkqueue_length(cq) < 8)
860 return HANDLER_WAIT_FOR_EVENT;
861 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
862
863 DEBUG_LOG_DEBUG("%s", "send handshake response");
864 if (0 != create_response_ietf_00(hctx)) {
865 r->http_status = 400; /* Bad Request */
866 return HANDLER_ERROR;
867 }
868 return HANDLER_GO_ON;
869 }
870 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
871
872 DEBUG_LOG_ERR("%s", "not supported WebSocket Version");
873 r->http_status = 503; /* Service Unavailable */
874 return HANDLER_ERROR;
875 }
876
877
878
879
880 /*
881 * modified from Norio Kobota mod_websocket_frame.c
882 */
883
884 #include "base64.h" /* lighttpd */
885 #include "http_chunk.h" /* lighttpd */
886
887 #define MOD_WEBSOCKET_BUFMAX (0x0fffff)
888
889 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
890
891 #include <stdlib.h>
send_ietf_00(handler_ctx * hctx,mod_wstunnel_frame_type_t type,const char * payload,size_t siz)892 static int send_ietf_00(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) {
893 static const char head = 0; /* 0x00 */
894 static const char tail = ~0; /* 0xff */
895 request_st * const r = hctx->gw.r;
896 char *mem;
897 size_t len;
898
899 switch (type) {
900 case MOD_WEBSOCKET_FRAME_TYPE_TEXT:
901 if (0 == siz) return 0;
902 http_chunk_append_mem(r, &head, 1);
903 http_chunk_append_mem(r, payload, siz);
904 http_chunk_append_mem(r, &tail, 1);
905 len = siz+2;
906 break;
907 case MOD_WEBSOCKET_FRAME_TYPE_BIN:
908 if (0 == siz) return 0;
909 http_chunk_append_mem(r, &head, 1);
910 len = 4*(siz/3)+4+1;
911 /* avoid accumulating too much data in memory; send to tmpfile */
912 mem = ck_malloc(len);
913 len=li_to_base64(mem,len,(unsigned char *)payload,siz,BASE64_STANDARD);
914 http_chunk_append_mem(r, mem, len);
915 free(mem);
916 http_chunk_append_mem(r, &tail, 1);
917 len += 2;
918 break;
919 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE:
920 http_chunk_append_mem(r, &tail, 1);
921 http_chunk_append_mem(r, &head, 1);
922 len = 2;
923 break;
924 default:
925 DEBUG_LOG_ERR("%s", "invalid frame type");
926 return -1;
927 }
928 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx",
929 r->con->fd, len);
930 return 0;
931 }
932
recv_ietf_00(handler_ctx * hctx)933 static int recv_ietf_00(handler_ctx *hctx) {
934 request_st * const r = hctx->gw.r;
935 chunkqueue *cq = &r->reqbody_queue;
936 buffer *payload = hctx->frame.payload;
937 char *mem;
938 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx",
939 r->con->fd, (long long)chunkqueue_length(cq));
940 for (chunk *c = cq->first; c; c = c->next) {
941 char *frame = c->mem->ptr+c->offset;
942 /*(chunk_remaining_length() on MEM_CHUNK)*/
943 size_t flen = (size_t)(buffer_clen(c->mem) - c->offset);
944 /*(FILE_CHUNK not handled, but might need to add support)*/
945 force_assert(c->type == MEM_CHUNK);
946 for (size_t i = 0; i < flen; ) {
947 switch (hctx->frame.state) {
948 case MOD_WEBSOCKET_FRAME_STATE_INIT:
949 hctx->frame.ctl.siz = 0;
950 if (frame[i] == 0x00) {
951 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD;
952 i++;
953 }
954 else if (((unsigned char *)frame)[i] == 0xff) {
955 DEBUG_LOG_DEBUG("%s", "recv close frame");
956 return -1;
957 }
958 else {
959 DEBUG_LOG_DEBUG("%s", "recv invalid frame");
960 return -1;
961 }
962 break;
963 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD:
964 mem = (char *)memchr(frame+i, 0xff, flen - i);
965 if (mem == NULL) {
966 DEBUG_LOG_DEBUG("got continuous payload, size=%zx", flen-i);
967 hctx->frame.ctl.siz += flen - i;
968 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) {
969 DEBUG_LOG_WARN("frame size has been exceeded: %x",
970 MOD_WEBSOCKET_BUFMAX);
971 return -1;
972 }
973 buffer_append_string_len(payload, frame+i, flen - i);
974 i += flen - i;
975 }
976 else {
977 DEBUG_LOG_DEBUG("got final payload, size=%zx",
978 mem - (frame+i));
979 hctx->frame.ctl.siz += (mem - (frame+i));
980 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) {
981 DEBUG_LOG_WARN("frame size has been exceeded: %x",
982 MOD_WEBSOCKET_BUFMAX);
983 return -1;
984 }
985 buffer_append_string_len(payload, frame+i, mem - (frame+i));
986 i += (mem - (frame+i));
987 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT;
988 }
989 i++;
990 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_TEXT
991 && !buffer_is_unset(payload)) { /*XXX: buffer_is_blank?*/
992 hctx->frame.ctl.siz = 0;
993 chunkqueue_append_buffer(&hctx->gw.wb, payload);
994 /*buffer_clear(payload);*//*chunkqueue_append_buffer clear*/
995 }
996 else {
997 if (hctx->frame.state == MOD_WEBSOCKET_FRAME_STATE_INIT
998 && !buffer_is_unset(payload)) {/*XXX: buffer_is_blank?*/
999 buffer *b;
1000 size_t len = buffer_clen(payload);
1001 len = (len+3)/4*3+1;
1002 chunkqueue_get_memory(&hctx->gw.wb, &len);
1003 b = hctx->gw.wb.last->mem;
1004 len = buffer_clen(b);
1005 DEBUG_LOG_DEBUG("try to base64 decode: %s",
1006 payload->ptr);
1007 if (NULL ==
1008 buffer_append_base64_decode(b, BUF_PTR_LEN(payload),
1009 BASE64_STANDARD)) {
1010 DEBUG_LOG_ERR("%s", "fail to base64-decode");
1011 return -1;
1012 }
1013 buffer_clear(payload);
1014 /*chunkqueue_use_memory()*/
1015 hctx->gw.wb.bytes_in += buffer_clen(b)-len;
1016 }
1017 }
1018 break;
1019 default: /* never reach */
1020 DEBUG_LOG_ERR("%s", "BUG: unknown state");
1021 return -1;
1022 }
1023 }
1024 }
1025 /* XXX: should add ability to handle and preserve partial frames above */
1026 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/
1027 chunkqueue_mark_written(cq, chunkqueue_length(cq));
1028 return 0;
1029 }
1030
1031 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
1032
1033
1034 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1035
1036 #define MOD_WEBSOCKET_OPCODE_CONT 0x00
1037 #define MOD_WEBSOCKET_OPCODE_TEXT 0x01
1038 #define MOD_WEBSOCKET_OPCODE_BIN 0x02
1039 #define MOD_WEBSOCKET_OPCODE_CLOSE 0x08
1040 #define MOD_WEBSOCKET_OPCODE_PING 0x09
1041 #define MOD_WEBSOCKET_OPCODE_PONG 0x0A
1042
1043 #define MOD_WEBSOCKET_FRAME_LEN16 0x7E
1044 #define MOD_WEBSOCKET_FRAME_LEN63 0x7F
1045 #define MOD_WEBSOCKET_FRAME_LEN16_CNT 2
1046 #define MOD_WEBSOCKET_FRAME_LEN63_CNT 8
1047
send_rfc_6455(handler_ctx * hctx,mod_wstunnel_frame_type_t type,const char * payload,size_t siz)1048 static int send_rfc_6455(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) {
1049 char mem[10];
1050 size_t len;
1051
1052 /* allowed null payload for ping, pong, close frame */
1053 if (payload == NULL && ( type == MOD_WEBSOCKET_FRAME_TYPE_TEXT
1054 || type == MOD_WEBSOCKET_FRAME_TYPE_BIN )) {
1055 return -1;
1056 }
1057
1058 switch (type) {
1059 case MOD_WEBSOCKET_FRAME_TYPE_TEXT:
1060 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_TEXT);
1061 DEBUG_LOG_DEBUG("%s", "type = text");
1062 break;
1063 case MOD_WEBSOCKET_FRAME_TYPE_BIN:
1064 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_BIN);
1065 DEBUG_LOG_DEBUG("%s", "type = binary");
1066 break;
1067 case MOD_WEBSOCKET_FRAME_TYPE_PING:
1068 mem[0] = (char) (0x80 | MOD_WEBSOCKET_OPCODE_PING);
1069 DEBUG_LOG_DEBUG("%s", "type = ping");
1070 break;
1071 case MOD_WEBSOCKET_FRAME_TYPE_PONG:
1072 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_PONG);
1073 DEBUG_LOG_DEBUG("%s", "type = pong");
1074 break;
1075 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE:
1076 default:
1077 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_CLOSE);
1078 DEBUG_LOG_DEBUG("%s", "type = close");
1079 break;
1080 }
1081
1082 DEBUG_LOG_DEBUG("payload size=%zx", siz);
1083 if (siz < MOD_WEBSOCKET_FRAME_LEN16) {
1084 mem[1] = siz;
1085 len = 2;
1086 }
1087 else if (siz <= UINT16_MAX) {
1088 mem[1] = MOD_WEBSOCKET_FRAME_LEN16;
1089 mem[2] = (siz >> 8) & 0xff;
1090 mem[3] = siz & 0xff;
1091 len = 1+MOD_WEBSOCKET_FRAME_LEN16_CNT+1;
1092 }
1093 else {
1094 mem[1] = MOD_WEBSOCKET_FRAME_LEN63;
1095 mem[2] = 0;
1096 mem[3] = 0;
1097 mem[4] = 0;
1098 mem[5] = 0;
1099 mem[6] = (siz >> 24) & 0xff;
1100 mem[7] = (siz >> 16) & 0xff;
1101 mem[8] = (siz >> 8) & 0xff;
1102 mem[9] = siz & 0xff;
1103 len = 1+MOD_WEBSOCKET_FRAME_LEN63_CNT+1;
1104 }
1105 request_st * const r = hctx->gw.r;
1106 http_chunk_append_mem(r, mem, len);
1107 if (siz) http_chunk_append_mem(r, payload, siz);
1108 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx",
1109 r->con->fd, len+siz);
1110 return 0;
1111 }
1112
unmask_payload(handler_ctx * hctx)1113 static void unmask_payload(handler_ctx *hctx) {
1114 buffer * const b = hctx->frame.payload;
1115 for (size_t i = 0, used = buffer_clen(b); i < used; ++i) {
1116 b->ptr[i] ^= hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt];
1117 hctx->frame.ctl.mask_cnt = (hctx->frame.ctl.mask_cnt + 1) % 4;
1118 }
1119 }
1120
recv_rfc_6455(handler_ctx * hctx)1121 static int recv_rfc_6455(handler_ctx *hctx) {
1122 request_st * const r = hctx->gw.r;
1123 chunkqueue *cq = &r->reqbody_queue;
1124 buffer *payload = hctx->frame.payload;
1125 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx",
1126 r->con->fd, (long long)chunkqueue_length(cq));
1127 for (chunk *c = cq->first; c; c = c->next) {
1128 char *frame = c->mem->ptr+c->offset;
1129 /*(chunk_remaining_length() on MEM_CHUNK)*/
1130 size_t flen = (size_t)(buffer_clen(c->mem) - c->offset);
1131 /*(FILE_CHUNK not handled, but might need to add support)*/
1132 force_assert(c->type == MEM_CHUNK);
1133 for (size_t i = 0; i < flen; ) {
1134 switch (hctx->frame.state) {
1135 case MOD_WEBSOCKET_FRAME_STATE_INIT:
1136 switch (frame[i] & 0x0f) {
1137 case MOD_WEBSOCKET_OPCODE_CONT:
1138 DEBUG_LOG_DEBUG("%s", "type = continue");
1139 hctx->frame.type = hctx->frame.type_before;
1140 break;
1141 case MOD_WEBSOCKET_OPCODE_TEXT:
1142 DEBUG_LOG_DEBUG("%s", "type = text");
1143 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT;
1144 hctx->frame.type_before = hctx->frame.type;
1145 break;
1146 case MOD_WEBSOCKET_OPCODE_BIN:
1147 DEBUG_LOG_DEBUG("%s", "type = binary");
1148 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN;
1149 hctx->frame.type_before = hctx->frame.type;
1150 break;
1151 case MOD_WEBSOCKET_OPCODE_PING:
1152 DEBUG_LOG_DEBUG("%s", "type = ping");
1153 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PING;
1154 break;
1155 case MOD_WEBSOCKET_OPCODE_PONG:
1156 DEBUG_LOG_DEBUG("%s", "type = pong");
1157 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PONG;
1158 break;
1159 case MOD_WEBSOCKET_OPCODE_CLOSE:
1160 DEBUG_LOG_DEBUG("%s", "type = close");
1161 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_CLOSE;
1162 return -1;
1163 break;
1164 default:
1165 DEBUG_LOG_ERR("%s", "type is invalid");
1166 return -1;
1167 break;
1168 }
1169 i++;
1170 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH;
1171 break;
1172 case MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH:
1173 if ((frame[i] & 0x80) != 0x80) {
1174 DEBUG_LOG_ERR("%s", "payload was not masked");
1175 return -1;
1176 }
1177 hctx->frame.ctl.mask_cnt = 0;
1178 hctx->frame.ctl.siz = (uint64_t)(frame[i] & 0x7f);
1179 if (hctx->frame.ctl.siz == 0) {
1180 DEBUG_LOG_DEBUG("specified payload size=%llx",
1181 (unsigned long long)hctx->frame.ctl.siz);
1182 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK;
1183 }
1184 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN16) {
1185 hctx->frame.ctl.siz = 0;
1186 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN16_CNT;
1187 hctx->frame.state =
1188 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH;
1189 }
1190 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN63) {
1191 hctx->frame.ctl.siz = 0;
1192 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN63_CNT;
1193 hctx->frame.state =
1194 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH;
1195 }
1196 else {
1197 DEBUG_LOG_DEBUG("specified payload size=%llx",
1198 (unsigned long long)hctx->frame.ctl.siz);
1199 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK;
1200 }
1201 i++;
1202 break;
1203 case MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH:
1204 hctx->frame.ctl.siz =
1205 (hctx->frame.ctl.siz << 8) + (frame[i] & 0xff);
1206 hctx->frame.ctl.siz_cnt--;
1207 if (hctx->frame.ctl.siz_cnt <= 0) {
1208 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING &&
1209 hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) {
1210 DEBUG_LOG_WARN("frame size has been exceeded: %x",
1211 MOD_WEBSOCKET_BUFMAX);
1212 return -1;
1213 }
1214 DEBUG_LOG_DEBUG("specified payload size=%llx",
1215 (unsigned long long)hctx->frame.ctl.siz);
1216 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK;
1217 }
1218 i++;
1219 break;
1220 case MOD_WEBSOCKET_FRAME_STATE_READ_MASK:
1221 hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt] = frame[i];
1222 hctx->frame.ctl.mask_cnt++;
1223 if (hctx->frame.ctl.mask_cnt >= MOD_WEBSOCKET_MASK_CNT) {
1224 hctx->frame.ctl.mask_cnt = 0;
1225 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING &&
1226 hctx->frame.ctl.siz == 0) {
1227 mod_wstunnel_frame_send(hctx,
1228 MOD_WEBSOCKET_FRAME_TYPE_PONG,
1229 NULL, 0);
1230 }
1231 if (hctx->frame.ctl.siz == 0) {
1232 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT;
1233 }
1234 else {
1235 hctx->frame.state =
1236 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD;
1237 }
1238 }
1239 i++;
1240 break;
1241 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD:
1242 /* hctx->frame.ctl.siz <= SIZE_MAX */
1243 if (hctx->frame.ctl.siz <= flen - i) {
1244 DEBUG_LOG_DEBUG("read payload, size=%llx",
1245 (unsigned long long)hctx->frame.ctl.siz);
1246 buffer_append_string_len(payload, frame+i, (size_t)
1247 (hctx->frame.ctl.siz & SIZE_MAX));
1248 i += (size_t)(hctx->frame.ctl.siz & SIZE_MAX);
1249 hctx->frame.ctl.siz = 0;
1250 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT;
1251 DEBUG_LOG_DEBUG("rest of frame size=%zx", flen - i);
1252 /* SIZE_MAX < hctx->frame.ctl.siz */
1253 }
1254 else {
1255 DEBUG_LOG_DEBUG("read payload, size=%zx", flen - i);
1256 buffer_append_string_len(payload, frame+i, flen - i);
1257 hctx->frame.ctl.siz -= flen - i;
1258 i += flen - i;
1259 DEBUG_LOG_DEBUG("rest of payload size=%llx",
1260 (unsigned long long)hctx->frame.ctl.siz);
1261 }
1262 switch (hctx->frame.type) {
1263 case MOD_WEBSOCKET_FRAME_TYPE_TEXT:
1264 case MOD_WEBSOCKET_FRAME_TYPE_BIN:
1265 {
1266 unmask_payload(hctx);
1267 chunkqueue_append_buffer(&hctx->gw.wb, payload);
1268 /*buffer_clear(payload);*//*chunkqueue_append_buffer clear*/
1269 break;
1270 }
1271 case MOD_WEBSOCKET_FRAME_TYPE_PING:
1272 if (hctx->frame.ctl.siz == 0) {
1273 unmask_payload(hctx);
1274 mod_wstunnel_frame_send(hctx,
1275 MOD_WEBSOCKET_FRAME_TYPE_PONG,
1276 payload->ptr, buffer_clen(payload));
1277 buffer_clear(payload);
1278 }
1279 break;
1280 case MOD_WEBSOCKET_FRAME_TYPE_PONG:
1281 buffer_clear(payload);
1282 break;
1283 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE:
1284 default:
1285 DEBUG_LOG_ERR("%s", "BUG: invalid frame type");
1286 return -1;
1287 }
1288 break;
1289 default:
1290 DEBUG_LOG_ERR("%s", "BUG: invalid state");
1291 return -1;
1292 }
1293 }
1294 }
1295 /* XXX: should add ability to handle and preserve partial frames above */
1296 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/
1297 chunkqueue_mark_written(cq, chunkqueue_length(cq));
1298 return 0;
1299 }
1300
1301 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1302
1303
mod_wstunnel_frame_send(handler_ctx * hctx,mod_wstunnel_frame_type_t type,const char * payload,size_t siz)1304 int mod_wstunnel_frame_send(handler_ctx *hctx, mod_wstunnel_frame_type_t type,
1305 const char *payload, size_t siz) {
1306 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1307 if (hctx->hybivers >= 8) return send_rfc_6455(hctx, type, payload, siz);
1308 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1309 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
1310 if (0 == hctx->hybivers) return send_ietf_00(hctx, type, payload, siz);
1311 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
1312 return -1;
1313 }
1314
mod_wstunnel_frame_recv(handler_ctx * hctx)1315 int mod_wstunnel_frame_recv(handler_ctx *hctx) {
1316 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_
1317 if (hctx->hybivers >= 8) return recv_rfc_6455(hctx);
1318 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */
1319 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_
1320 if (0 == hctx->hybivers) return recv_ietf_00(hctx);
1321 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */
1322 return -1;
1323 }
1324