xref: /lighttpd1.4/src/mod_wstunnel.c (revision 5e14db43)
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