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 188 INIT_FUNC(mod_wstunnel_init) { 189 return calloc(1, sizeof(plugin_data)); 190 } 191 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 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 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 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 = calloc(1, sizeof(gw_plugin_config)); 281 force_assert(gw); 282 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, cpv->v.a, 283 gw, 0, cpk[cpv->k_id].k)) { 284 gw_plugin_config_free(gw); 285 return HANDLER_ERROR; 286 } 287 /* error if "mode" = "authorizer"; 288 * wstunnel can not act as authorizer */ 289 /*(check after gw_set_defaults_backend())*/ 290 if (gw->exts_auth && gw->exts_auth->used) { 291 log_error(srv->errh, __FILE__, __LINE__, 292 "%s must not define any hosts with " 293 "attribute \"mode\" = \"authorizer\"", cpk[cpv->k_id].k); 294 gw_plugin_config_free(gw); 295 return HANDLER_ERROR; 296 } 297 cpv->v.v = gw; 298 cpv->vtype = T_CONFIG_LOCAL; 299 break; 300 case 1: /* wstunnel.balance */ 301 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 302 break; 303 case 2: /* wstunnel.debug */ 304 case 3: /* wstunnel.map-extensions */ 305 break; 306 case 4: /* wstunnel.frame-type */ 307 /*(default frame-type to "text" unless "binary" is specified)*/ 308 cpv->v.u = 309 buffer_eq_icase_slen(cpv->v.b, CONST_STR_LEN("binary")); 310 break; 311 case 5: /* wstunnel.origins */ 312 for (uint32_t j = 0; j < cpv->v.a->used; ++j) { 313 buffer *origin = &((data_string *)cpv->v.a->data[j])->value; 314 if (buffer_is_blank(origin)) { 315 log_error(srv->errh, __FILE__, __LINE__, 316 "unexpected empty string in %s", cpk[cpv->k_id].k); 317 return HANDLER_ERROR; 318 } 319 } 320 break; 321 case 6: /* wstunnel.ping-interval */ 322 break; 323 default:/* should not happen */ 324 break; 325 } 326 } 327 328 /* disable check-local for all exts (default enabled) */ 329 if (gw && gw->exts) { /*(check after gw_set_defaults_backend())*/ 330 gw_exts_clear_check_local(gw->exts); 331 } 332 } 333 334 /* default is 0 */ 335 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 336 p->defaults.ping_interval = 0; /* do not send ping */ 337 338 /* initialize p->defaults from global config context */ 339 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 340 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 341 if (-1 != cpv->k_id) 342 mod_wstunnel_merge_config(&p->defaults, cpv); 343 } 344 345 return HANDLER_GO_ON; 346 } 347 348 static handler_t wstunnel_create_env(gw_handler_ctx *gwhctx) { 349 handler_ctx *hctx = (handler_ctx *)gwhctx; 350 request_st * const r = hctx->gw.r; 351 handler_t rc; 352 if (0 == r->reqbody_length) { 353 http_response_upgrade_read_body_unknown(r); 354 chunkqueue_append_chunkqueue(&r->reqbody_queue, &r->read_queue); 355 } 356 rc = mod_wstunnel_handshake_create_response(hctx); 357 if (rc != HANDLER_GO_ON) return rc; 358 359 r->http_status = 101; /* Switching Protocols */ 360 r->resp_body_started = 1; 361 362 hctx->ping_ts = log_monotonic_secs; 363 gw_set_transparent(&hctx->gw); 364 365 return HANDLER_GO_ON; 366 } 367 368 static handler_t wstunnel_stdin_append(gw_handler_ctx *gwhctx) { 369 /* prepare websocket frames to backend */ 370 /* (caller should verify r->reqbody_queue) */ 371 /*assert(!chunkqueue_is_empty(&r->reqbody_queue));*/ 372 handler_ctx *hctx = (handler_ctx *)gwhctx; 373 if (0 == mod_wstunnel_frame_recv(hctx)) 374 return HANDLER_GO_ON; 375 else { 376 /*(error)*/ 377 /* future: might differentiate client close request from client error, 378 * and then send 1000 or 1001 */ 379 request_st * const r = hctx->gw.r; 380 DEBUG_LOG_INFO("disconnected from client (fd=%d)", r->con->fd); 381 DEBUG_LOG_DEBUG("send close response to client (fd=%d)", r->con->fd); 382 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_CLOSE, CONST_STR_LEN("1000")); /* 1000 Normal Closure */ 383 gw_handle_request_reset(r, hctx->gw.plugin_data); 384 return HANDLER_FINISHED; 385 } 386 } 387 388 static handler_t wstunnel_recv_parse(request_st * const r, http_response_opts * const opts, buffer * const b, size_t n) { 389 handler_ctx *hctx = (handler_ctx *)opts->pdata; 390 DEBUG_LOG_DEBUG("recv data from backend (fd=%d), size=%zx", hctx->gw.fd, n); 391 if (0 == n) return HANDLER_FINISHED; 392 if (mod_wstunnel_frame_send(hctx,hctx->frame.type_backend,b->ptr,n) < 0) { 393 DEBUG_LOG_ERR("%s", "fail to send data to client"); 394 return HANDLER_ERROR; 395 } 396 buffer_clear(b); 397 UNUSED(r); 398 return HANDLER_GO_ON; 399 } 400 401 static int wstunnel_is_allowed_origin(request_st * const r, handler_ctx * const hctx) { 402 /* If allowed origins is set (and not empty list), fail closed if no match. 403 * Note that origin provided in request header has not been normalized, so 404 * change in case or other non-normal forms might not match allowed list */ 405 const array * const allowed_origins = hctx->conf.origins; 406 const buffer *origin = NULL; 407 size_t olen; 408 409 if (NULL == allowed_origins || 0 == allowed_origins->used) { 410 DEBUG_LOG_INFO("%s", "allowed origins not specified"); 411 return 1; 412 } 413 414 /* "Origin" header is preferred 415 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */ 416 origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin")); 417 if (NULL == origin) { 418 origin = 419 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin")); 420 } 421 olen = origin ? buffer_clen(origin) : 0; 422 if (0 == olen) { 423 DEBUG_LOG_ERR("%s", "Origin header is invalid"); 424 r->http_status = 400; /* Bad Request */ 425 return 0; 426 } 427 428 for (size_t i = 0; i < allowed_origins->used; ++i) { 429 buffer *b = &((data_string *)allowed_origins->data[i])->value; 430 size_t blen = buffer_clen(b); 431 if ((olen > blen ? origin->ptr[olen-blen-1] == '.' : olen == blen) 432 && 0 == memcmp(origin->ptr+olen-blen, b->ptr, blen)) { 433 DEBUG_LOG_INFO("%s matches allowed origin: %s",origin->ptr,b->ptr); 434 return 1; 435 } 436 } 437 DEBUG_LOG_INFO("%s does not match any allowed origins", origin->ptr); 438 r->http_status = 403; /* Forbidden */ 439 return 0; 440 } 441 442 static int wstunnel_check_request(request_st * const r, handler_ctx * const hctx) { 443 const buffer * const vers = 444 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Version")); 445 const long hybivers = (NULL != vers) 446 ? light_isdigit(*vers->ptr) ? strtol(vers->ptr, NULL, 10) : -1 447 : 0; 448 if (hybivers < 0 || hybivers > INT_MAX) { 449 DEBUG_LOG_ERR("%s", "invalid Sec-WebSocket-Version"); 450 r->http_status = 400; /* Bad Request */ 451 return -1; 452 } 453 454 /*(redundant since HTTP/1.1 required in mod_wstunnel_check_extension())*/ 455 if (!r->http_host || buffer_is_blank(r->http_host)) { 456 DEBUG_LOG_ERR("%s", "Host header does not exist"); 457 r->http_status = 400; /* Bad Request */ 458 return -1; 459 } 460 461 if (!wstunnel_is_allowed_origin(r, hctx)) { 462 return -1; 463 } 464 465 return (int)hybivers; 466 } 467 468 static void wstunnel_backend_error(gw_handler_ctx *gwhctx) { 469 handler_ctx *hctx = (handler_ctx *)gwhctx; 470 if (hctx->gw.state == GW_STATE_WRITE || hctx->gw.state == GW_STATE_READ) { 471 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_CLOSE, CONST_STR_LEN("1001")); /* 1001 Going Away */ 472 } 473 } 474 475 static void wstunnel_handler_ctx_free(void *gwhctx) { 476 handler_ctx *hctx = (handler_ctx *)gwhctx; 477 chunk_buffer_release(hctx->frame.payload); 478 } 479 480 static handler_t wstunnel_handler_setup (request_st * const r, plugin_data * const p) { 481 handler_ctx *hctx = r->plugin_ctx[p->id]; 482 int hybivers; 483 hctx->errh = r->conf.errh;/*(for mod_wstunnel-specific DEBUG_* macros)*/ 484 hctx->conf = p->conf; /*(copies struct)*/ 485 hybivers = wstunnel_check_request(r, hctx); 486 if (hybivers < 0) return HANDLER_FINISHED; 487 hctx->hybivers = hybivers; 488 if (0 == hybivers) { 489 DEBUG_LOG_INFO("WebSocket Version = %s", "hybi-00"); 490 } 491 else { 492 DEBUG_LOG_INFO("WebSocket Version = %d", hybivers); 493 } 494 495 hctx->gw.opts.backend = BACKEND_PROXY; /*(act proxy-like; not used)*/ 496 hctx->gw.opts.pdata = hctx; 497 hctx->gw.opts.parse = wstunnel_recv_parse; 498 hctx->gw.stdin_append = wstunnel_stdin_append; 499 hctx->gw.create_env = wstunnel_create_env; 500 hctx->gw.handler_ctx_free = wstunnel_handler_ctx_free; 501 hctx->gw.backend_error = wstunnel_backend_error; 502 hctx->gw.response = chunk_buffer_acquire(); 503 504 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 505 hctx->frame.ctl.siz = 0; 506 hctx->frame.payload = chunk_buffer_acquire(); 507 508 unsigned int binary = hctx->conf.frame_type; /*(0 = "text"; 1 = "binary")*/ 509 if (!binary) { 510 const buffer *vb = 511 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Protocol")); 512 if (NULL != vb) { 513 for (const char *s = vb->ptr; *s; ++s) { 514 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 515 if (buffer_eq_icase_ssn(s, CONST_STR_LEN("binary"))) { 516 s += sizeof("binary")-1; 517 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 518 if (*s==','||*s=='\0') { 519 hctx->subproto = 1; 520 binary = 1; 521 break; 522 } 523 } 524 else if (buffer_eq_icase_ssn(s, CONST_STR_LEN("base64"))) { 525 s += sizeof("base64")-1; 526 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 527 if (*s==','||*s=='\0') { 528 hctx->subproto = -1; 529 break; 530 } 531 } 532 s = strchr(s, ','); 533 if (NULL == s) break; 534 } 535 } 536 } 537 538 if (binary) { 539 DEBUG_LOG_INFO("%s", "will recv binary data from backend"); 540 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN; 541 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_BIN; 542 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_BIN; 543 } 544 else { 545 DEBUG_LOG_INFO("%s", "will recv text data from backend"); 546 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 547 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 548 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 549 } 550 551 return HANDLER_GO_ON; 552 } 553 554 static handler_t mod_wstunnel_check_extension(request_st * const r, void *p_d) { 555 plugin_data *p = p_d; 556 const buffer *vb; 557 handler_t rc; 558 559 if (NULL != r->handler_module) 560 return HANDLER_GO_ON; 561 if (r->http_method != HTTP_METHOD_GET) 562 return HANDLER_GO_ON; 563 if (r->http_version != HTTP_VERSION_1_1) 564 return HANDLER_GO_ON; 565 566 /* 567 * Connection: upgrade, keep-alive, ... 568 * Upgrade: WebSocket, ... 569 */ 570 vb = http_header_request_get(r, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade")); 571 if (NULL == vb 572 || !http_header_str_contains_token(BUF_PTR_LEN(vb), CONST_STR_LEN("websocket"))) 573 return HANDLER_GO_ON; 574 vb = http_header_request_get(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection")); 575 if (NULL == vb 576 || !http_header_str_contains_token(BUF_PTR_LEN(vb), CONST_STR_LEN("upgrade"))) 577 return HANDLER_GO_ON; 578 579 mod_wstunnel_patch_config(r, p); 580 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON; 581 582 rc = gw_check_extension(r, (gw_plugin_data *)p, 1, sizeof(handler_ctx)); 583 return (HANDLER_GO_ON == rc && r->handler_module == p->self) 584 ? wstunnel_handler_setup(r, p) 585 : rc; 586 } 587 588 TRIGGER_FUNC(mod_wstunnel_handle_trigger) { 589 const plugin_data * const p = p_d; 590 const unix_time64_t cur_ts = log_monotonic_secs + 1; 591 592 gw_handle_trigger(srv, p_d); 593 594 for (connection *con = srv->conns; con; con = con->next) { 595 request_st * const r = &con->request; 596 handler_ctx *hctx = r->plugin_ctx[p->id]; 597 if (NULL == hctx || r->handler_module != p->self) 598 continue; 599 600 if (hctx->gw.state != GW_STATE_WRITE && hctx->gw.state != GW_STATE_READ) 601 continue; 602 603 if (cur_ts - con->read_idle_ts > r->conf.max_read_idle) { 604 DEBUG_LOG_INFO("timeout client (fd=%d)", con->fd); 605 mod_wstunnel_frame_send(hctx,MOD_WEBSOCKET_FRAME_TYPE_CLOSE,NULL,0); 606 gw_handle_request_reset(r, p_d); 607 joblist_append(con); 608 /* avoid server.c closing connection with error due to max_read_idle 609 * (might instead run joblist after plugins_call_handle_trigger())*/ 610 con->read_idle_ts = cur_ts; 611 continue; 612 } 613 614 if (0 != hctx->hybivers 615 && hctx->conf.ping_interval > 0 616 && (int32_t)hctx->conf.ping_interval + hctx->ping_ts < cur_ts) { 617 hctx->ping_ts = cur_ts; 618 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_PING, CONST_STR_LEN("ping")); 619 joblist_append(con); 620 continue; 621 } 622 } 623 624 return HANDLER_GO_ON; 625 } 626 627 int mod_wstunnel_plugin_init(plugin *p); 628 int mod_wstunnel_plugin_init(plugin *p) { 629 p->version = LIGHTTPD_VERSION_ID; 630 p->name = "wstunnel"; 631 p->init = mod_wstunnel_init; 632 p->cleanup = gw_free; 633 p->set_defaults = mod_wstunnel_set_defaults; 634 p->handle_request_reset = gw_handle_request_reset; 635 p->handle_uri_clean = mod_wstunnel_check_extension; 636 p->handle_subrequest = gw_handle_subrequest; 637 p->handle_trigger = mod_wstunnel_handle_trigger; 638 p->handle_waitpid = gw_handle_waitpid_cb; 639 return 0; 640 } 641 642 643 644 645 /* 646 * modified from Norio Kobota mod_websocket_handshake.c 647 */ 648 649 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 650 651 #include "sys-crypto-md.h" /* lighttpd */ 652 #include "sys-endian.h" /* lighttpd */ 653 654 static int get_key3(request_st * const r, char *buf, uint32_t bytes) { 655 /* 8 bytes should have been sent with request 656 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 657 chunkqueue *cq = &r->reqbody_queue; 658 /*(caller should ensure bytes available prior to calling this routine)*/ 659 /*assert(chunkqueue_length(cq) >= 8);*/ 660 /*assert(8 == bytes);*/ 661 return chunkqueue_read_data(cq, buf, bytes, r->conf.errh); 662 } 663 664 static int get_key_number(uint32_t *ret, const buffer *b) { 665 const char * const s = b->ptr; 666 size_t j = 0; 667 unsigned long n; 668 uint32_t sp = 0; 669 char tmp[10 + 1]; /* #define UINT32_MAX_STRLEN 10 */ 670 671 for (size_t i = 0, used = buffer_clen(b); i < used; ++i) { 672 if (light_isdigit(s[i])) { 673 tmp[j] = s[i]; 674 if (++j >= sizeof(tmp)) return -1; 675 } 676 else if (s[i] == ' ') ++sp; /* count num spaces */ 677 } 678 tmp[j] = '\0'; 679 n = strtoul(tmp, NULL, 10); 680 if (n > UINT32_MAX || 0 == sp || !light_isdigit(*tmp)) return -1; 681 *ret = (uint32_t)n / sp; 682 return 0; 683 } 684 685 static int create_MD5_sum(request_st * const r) { 686 uint32_t buf[4]; /* MD5 binary hash len */ 687 688 const buffer *key1 = 689 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key1")); 690 const buffer *key2 = 691 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key2")); 692 693 if (NULL == key1 || get_key_number(buf+0, key1) < 0 || 694 NULL == key2 || get_key_number(buf+1, key2) < 0 || 695 get_key3(r, (char *)(buf+2), 2*sizeof(uint32_t)) < 0) { 696 return -1; 697 } 698 #ifdef __BIG_ENDIAN__ 699 #define ws_htole32(s,u)\ 700 (s)[0]=((u)>>24); \ 701 (s)[1]=((u)>>16); \ 702 (s)[2]=((u)>>8); \ 703 (s)[3]=((u)) 704 ws_htole32((unsigned char *)(buf+0), buf[0]); 705 ws_htole32((unsigned char *)(buf+1), buf[1]); 706 #endif 707 /*(overwrite buf[] with result)*/ 708 MD5_once((unsigned char *)buf, buf, sizeof(buf)); 709 chunkqueue_append_mem(&r->write_queue, (char *)buf, sizeof(buf)); 710 return 0; 711 } 712 713 static int create_response_ietf_00(handler_ctx *hctx) { 714 request_st * const r = hctx->gw.r; 715 716 /* "Origin" header is preferred 717 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */ 718 const buffer *origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin")); 719 if (NULL == origin) { 720 origin = 721 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin")); 722 } 723 if (NULL == origin) { 724 DEBUG_LOG_ERR("%s", "Origin header is invalid"); 725 return -1; 726 } 727 if (!r->http_host || buffer_is_blank(r->http_host)) { 728 DEBUG_LOG_ERR("%s", "Host header does not exist"); 729 return -1; 730 } 731 732 /* calc MD5 sum from keys */ 733 if (create_MD5_sum(r) < 0) { 734 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 735 return -1; 736 } 737 738 http_header_response_set(r, HTTP_HEADER_UPGRADE, 739 CONST_STR_LEN("Upgrade"), 740 CONST_STR_LEN("websocket")); 741 #if 0 /*(added later in http_response_write_header())*/ 742 http_header_response_append(r, HTTP_HEADER_CONNECTION, 743 CONST_STR_LEN("Connection"), 744 CONST_STR_LEN("upgrade")); 745 #endif 746 #if 0 /*(Sec-WebSocket-Origin header is not required for hybi-00)*/ 747 /* Note: it is insecure to simply reflect back origin provided by client 748 * (if admin did not configure restricted list of valid origins) 749 * (see wstunnel_check_request()) */ 750 http_header_response_set(r, HTTP_HEADER_OTHER, 751 CONST_STR_LEN("Sec-WebSocket-Origin"), 752 BUF_PTR_LEN(origin)); 753 #endif 754 755 buffer * const value = 756 http_header_response_set_ptr(r, HTTP_HEADER_OTHER, 757 CONST_STR_LEN("Sec-WebSocket-Location")); 758 if (buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https"))) 759 buffer_copy_string_len(value, CONST_STR_LEN("wss://")); 760 else 761 buffer_copy_string_len(value, CONST_STR_LEN("ws://")); 762 buffer_append_str2(value, BUF_PTR_LEN(r->http_host), 763 BUF_PTR_LEN(&r->uri.path)); 764 return 0; 765 } 766 767 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 768 769 770 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 771 772 #include "sys-crypto-md.h" /* lighttpd */ 773 #include "base64.h" /* lighttpd */ 774 775 static int create_response_rfc_6455(handler_ctx *hctx) { 776 request_st * const r = hctx->gw.r; 777 SHA_CTX sha; 778 unsigned char sha_digest[SHA_DIGEST_LENGTH]; 779 780 const buffer *value_wskey = 781 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key")); 782 if (NULL == value_wskey) { 783 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 784 return -1; 785 } 786 787 /* get SHA1 hash of key */ 788 /* refer: RFC-6455 Sec.1.3 Opening Handshake */ 789 SHA1_Init(&sha); 790 SHA1_Update(&sha, (const unsigned char *)BUF_PTR_LEN(value_wskey)); 791 SHA1_Update(&sha, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); 792 SHA1_Final(sha_digest, &sha); 793 794 http_header_response_set(r, HTTP_HEADER_UPGRADE, 795 CONST_STR_LEN("Upgrade"), 796 CONST_STR_LEN("websocket")); 797 #if 0 /*(added later in http_response_write_header())*/ 798 http_header_response_append(r, HTTP_HEADER_CONNECTION, 799 CONST_STR_LEN("Connection"), 800 CONST_STR_LEN("upgrade")); 801 #endif 802 803 buffer * const value = 804 http_header_response_set_ptr(r, HTTP_HEADER_OTHER, 805 CONST_STR_LEN("Sec-WebSocket-Accept")); 806 buffer_append_base64_encode(value, sha_digest, SHA_DIGEST_LENGTH, BASE64_STANDARD); 807 808 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_BIN) 809 http_header_response_set(r, HTTP_HEADER_OTHER, 810 CONST_STR_LEN("Sec-WebSocket-Protocol"), 811 CONST_STR_LEN("binary")); 812 else if (-1 == hctx->subproto) 813 http_header_response_set(r, HTTP_HEADER_OTHER, 814 CONST_STR_LEN("Sec-WebSocket-Protocol"), 815 CONST_STR_LEN("base64")); 816 817 return 0; 818 } 819 820 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 821 822 823 handler_t mod_wstunnel_handshake_create_response(handler_ctx *hctx) { 824 request_st * const r = hctx->gw.r; 825 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 826 if (hctx->hybivers >= 8) { 827 DEBUG_LOG_DEBUG("%s", "send handshake response"); 828 if (0 != create_response_rfc_6455(hctx)) { 829 r->http_status = 400; /* Bad Request */ 830 return HANDLER_ERROR; 831 } 832 return HANDLER_GO_ON; 833 } 834 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 835 836 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 837 if (hctx->hybivers == 0) { 838 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 839 /* 8 bytes should have been sent with request 840 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 841 chunkqueue *cq = &r->reqbody_queue; 842 if (chunkqueue_length(cq) < 8) 843 return HANDLER_WAIT_FOR_EVENT; 844 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 845 846 DEBUG_LOG_DEBUG("%s", "send handshake response"); 847 if (0 != create_response_ietf_00(hctx)) { 848 r->http_status = 400; /* Bad Request */ 849 return HANDLER_ERROR; 850 } 851 return HANDLER_GO_ON; 852 } 853 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 854 855 DEBUG_LOG_ERR("%s", "not supported WebSocket Version"); 856 r->http_status = 503; /* Service Unavailable */ 857 return HANDLER_ERROR; 858 } 859 860 861 862 863 /* 864 * modified from Norio Kobota mod_websocket_frame.c 865 */ 866 867 #include "base64.h" /* lighttpd */ 868 #include "http_chunk.h" /* lighttpd */ 869 870 #define MOD_WEBSOCKET_BUFMAX (0x0fffff) 871 872 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 873 874 #include <stdlib.h> 875 static int send_ietf_00(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 876 static const char head = 0; /* 0x00 */ 877 static const char tail = ~0; /* 0xff */ 878 request_st * const r = hctx->gw.r; 879 char *mem; 880 size_t len; 881 882 switch (type) { 883 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 884 if (0 == siz) return 0; 885 http_chunk_append_mem(r, &head, 1); 886 http_chunk_append_mem(r, payload, siz); 887 http_chunk_append_mem(r, &tail, 1); 888 len = siz+2; 889 break; 890 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 891 if (0 == siz) return 0; 892 http_chunk_append_mem(r, &head, 1); 893 len = 4*(siz/3)+4+1; 894 /* avoid accumulating too much data in memory; send to tmpfile */ 895 mem = malloc(len); 896 force_assert(mem); 897 len=li_to_base64(mem,len,(unsigned char *)payload,siz,BASE64_STANDARD); 898 http_chunk_append_mem(r, mem, len); 899 free(mem); 900 http_chunk_append_mem(r, &tail, 1); 901 len += 2; 902 break; 903 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 904 http_chunk_append_mem(r, &tail, 1); 905 http_chunk_append_mem(r, &head, 1); 906 len = 2; 907 break; 908 default: 909 DEBUG_LOG_ERR("%s", "invalid frame type"); 910 return -1; 911 } 912 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 913 r->con->fd, len); 914 return 0; 915 } 916 917 static int recv_ietf_00(handler_ctx *hctx) { 918 request_st * const r = hctx->gw.r; 919 chunkqueue *cq = &r->reqbody_queue; 920 buffer *payload = hctx->frame.payload; 921 char *mem; 922 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 923 r->con->fd, (long long)chunkqueue_length(cq)); 924 for (chunk *c = cq->first; c; c = c->next) { 925 char *frame = c->mem->ptr+c->offset; 926 /*(chunk_remaining_length() on MEM_CHUNK)*/ 927 size_t flen = (size_t)(buffer_clen(c->mem) - c->offset); 928 /*(FILE_CHUNK not handled, but might need to add support)*/ 929 force_assert(c->type == MEM_CHUNK); 930 for (size_t i = 0; i < flen; ) { 931 switch (hctx->frame.state) { 932 case MOD_WEBSOCKET_FRAME_STATE_INIT: 933 hctx->frame.ctl.siz = 0; 934 if (frame[i] == 0x00) { 935 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 936 i++; 937 } 938 else if (((unsigned char *)frame)[i] == 0xff) { 939 DEBUG_LOG_DEBUG("%s", "recv close frame"); 940 return -1; 941 } 942 else { 943 DEBUG_LOG_DEBUG("%s", "recv invalid frame"); 944 return -1; 945 } 946 break; 947 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 948 mem = (char *)memchr(frame+i, 0xff, flen - i); 949 if (mem == NULL) { 950 DEBUG_LOG_DEBUG("got continuous payload, size=%zx", flen-i); 951 hctx->frame.ctl.siz += flen - i; 952 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 953 DEBUG_LOG_WARN("frame size has been exceeded: %x", 954 MOD_WEBSOCKET_BUFMAX); 955 return -1; 956 } 957 buffer_append_string_len(payload, frame+i, flen - i); 958 i += flen - i; 959 } 960 else { 961 DEBUG_LOG_DEBUG("got final payload, size=%zx", 962 mem - (frame+i)); 963 hctx->frame.ctl.siz += (mem - (frame+i)); 964 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 965 DEBUG_LOG_WARN("frame size has been exceeded: %x", 966 MOD_WEBSOCKET_BUFMAX); 967 return -1; 968 } 969 buffer_append_string_len(payload, frame+i, mem - (frame+i)); 970 i += (mem - (frame+i)); 971 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 972 } 973 i++; 974 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 975 && !buffer_is_unset(payload)) { /*XXX: buffer_is_blank?*/ 976 hctx->frame.ctl.siz = 0; 977 chunkqueue_append_buffer(&hctx->gw.wb, payload); 978 /*buffer_clear(payload);*//*chunkqueue_append_buffer clear*/ 979 } 980 else { 981 if (hctx->frame.state == MOD_WEBSOCKET_FRAME_STATE_INIT 982 && !buffer_is_unset(payload)) {/*XXX: buffer_is_blank?*/ 983 buffer *b; 984 size_t len = buffer_clen(payload); 985 len = (len+3)/4*3+1; 986 chunkqueue_get_memory(&hctx->gw.wb, &len); 987 b = hctx->gw.wb.last->mem; 988 len = buffer_clen(b); 989 DEBUG_LOG_DEBUG("try to base64 decode: %s", 990 payload->ptr); 991 if (NULL == 992 buffer_append_base64_decode(b, BUF_PTR_LEN(payload), 993 BASE64_STANDARD)) { 994 DEBUG_LOG_ERR("%s", "fail to base64-decode"); 995 return -1; 996 } 997 buffer_clear(payload); 998 /*chunkqueue_use_memory()*/ 999 hctx->gw.wb.bytes_in += buffer_clen(b)-len; 1000 } 1001 } 1002 break; 1003 default: /* never reach */ 1004 DEBUG_LOG_ERR("%s", "BUG: unknown state"); 1005 return -1; 1006 } 1007 } 1008 } 1009 /* XXX: should add ability to handle and preserve partial frames above */ 1010 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1011 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1012 return 0; 1013 } 1014 1015 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1016 1017 1018 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1019 1020 #define MOD_WEBSOCKET_OPCODE_CONT 0x00 1021 #define MOD_WEBSOCKET_OPCODE_TEXT 0x01 1022 #define MOD_WEBSOCKET_OPCODE_BIN 0x02 1023 #define MOD_WEBSOCKET_OPCODE_CLOSE 0x08 1024 #define MOD_WEBSOCKET_OPCODE_PING 0x09 1025 #define MOD_WEBSOCKET_OPCODE_PONG 0x0A 1026 1027 #define MOD_WEBSOCKET_FRAME_LEN16 0x7E 1028 #define MOD_WEBSOCKET_FRAME_LEN63 0x7F 1029 #define MOD_WEBSOCKET_FRAME_LEN16_CNT 2 1030 #define MOD_WEBSOCKET_FRAME_LEN63_CNT 8 1031 1032 static int send_rfc_6455(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 1033 char mem[10]; 1034 size_t len; 1035 1036 /* allowed null payload for ping, pong, close frame */ 1037 if (payload == NULL && ( type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 1038 || type == MOD_WEBSOCKET_FRAME_TYPE_BIN )) { 1039 return -1; 1040 } 1041 1042 switch (type) { 1043 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1044 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_TEXT); 1045 DEBUG_LOG_DEBUG("%s", "type = text"); 1046 break; 1047 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1048 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_BIN); 1049 DEBUG_LOG_DEBUG("%s", "type = binary"); 1050 break; 1051 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1052 mem[0] = (char) (0x80 | MOD_WEBSOCKET_OPCODE_PING); 1053 DEBUG_LOG_DEBUG("%s", "type = ping"); 1054 break; 1055 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1056 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_PONG); 1057 DEBUG_LOG_DEBUG("%s", "type = pong"); 1058 break; 1059 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1060 default: 1061 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_CLOSE); 1062 DEBUG_LOG_DEBUG("%s", "type = close"); 1063 break; 1064 } 1065 1066 DEBUG_LOG_DEBUG("payload size=%zx", siz); 1067 if (siz < MOD_WEBSOCKET_FRAME_LEN16) { 1068 mem[1] = siz; 1069 len = 2; 1070 } 1071 else if (siz <= UINT16_MAX) { 1072 mem[1] = MOD_WEBSOCKET_FRAME_LEN16; 1073 mem[2] = (siz >> 8) & 0xff; 1074 mem[3] = siz & 0xff; 1075 len = 1+MOD_WEBSOCKET_FRAME_LEN16_CNT+1; 1076 } 1077 else { 1078 mem[1] = MOD_WEBSOCKET_FRAME_LEN63; 1079 mem[2] = 0; 1080 mem[3] = 0; 1081 mem[4] = 0; 1082 mem[5] = 0; 1083 mem[6] = (siz >> 24) & 0xff; 1084 mem[7] = (siz >> 16) & 0xff; 1085 mem[8] = (siz >> 8) & 0xff; 1086 mem[9] = siz & 0xff; 1087 len = 1+MOD_WEBSOCKET_FRAME_LEN63_CNT+1; 1088 } 1089 request_st * const r = hctx->gw.r; 1090 http_chunk_append_mem(r, mem, len); 1091 if (siz) http_chunk_append_mem(r, payload, siz); 1092 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 1093 r->con->fd, len+siz); 1094 return 0; 1095 } 1096 1097 static void unmask_payload(handler_ctx *hctx) { 1098 buffer * const b = hctx->frame.payload; 1099 for (size_t i = 0, used = buffer_clen(b); i < used; ++i) { 1100 b->ptr[i] ^= hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt]; 1101 hctx->frame.ctl.mask_cnt = (hctx->frame.ctl.mask_cnt + 1) % 4; 1102 } 1103 } 1104 1105 static int recv_rfc_6455(handler_ctx *hctx) { 1106 request_st * const r = hctx->gw.r; 1107 chunkqueue *cq = &r->reqbody_queue; 1108 buffer *payload = hctx->frame.payload; 1109 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 1110 r->con->fd, (long long)chunkqueue_length(cq)); 1111 for (chunk *c = cq->first; c; c = c->next) { 1112 char *frame = c->mem->ptr+c->offset; 1113 /*(chunk_remaining_length() on MEM_CHUNK)*/ 1114 size_t flen = (size_t)(buffer_clen(c->mem) - c->offset); 1115 /*(FILE_CHUNK not handled, but might need to add support)*/ 1116 force_assert(c->type == MEM_CHUNK); 1117 for (size_t i = 0; i < flen; ) { 1118 switch (hctx->frame.state) { 1119 case MOD_WEBSOCKET_FRAME_STATE_INIT: 1120 switch (frame[i] & 0x0f) { 1121 case MOD_WEBSOCKET_OPCODE_CONT: 1122 DEBUG_LOG_DEBUG("%s", "type = continue"); 1123 hctx->frame.type = hctx->frame.type_before; 1124 break; 1125 case MOD_WEBSOCKET_OPCODE_TEXT: 1126 DEBUG_LOG_DEBUG("%s", "type = text"); 1127 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 1128 hctx->frame.type_before = hctx->frame.type; 1129 break; 1130 case MOD_WEBSOCKET_OPCODE_BIN: 1131 DEBUG_LOG_DEBUG("%s", "type = binary"); 1132 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN; 1133 hctx->frame.type_before = hctx->frame.type; 1134 break; 1135 case MOD_WEBSOCKET_OPCODE_PING: 1136 DEBUG_LOG_DEBUG("%s", "type = ping"); 1137 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PING; 1138 break; 1139 case MOD_WEBSOCKET_OPCODE_PONG: 1140 DEBUG_LOG_DEBUG("%s", "type = pong"); 1141 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PONG; 1142 break; 1143 case MOD_WEBSOCKET_OPCODE_CLOSE: 1144 DEBUG_LOG_DEBUG("%s", "type = close"); 1145 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_CLOSE; 1146 return -1; 1147 break; 1148 default: 1149 DEBUG_LOG_ERR("%s", "type is invalid"); 1150 return -1; 1151 break; 1152 } 1153 i++; 1154 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH; 1155 break; 1156 case MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH: 1157 if ((frame[i] & 0x80) != 0x80) { 1158 DEBUG_LOG_ERR("%s", "payload was not masked"); 1159 return -1; 1160 } 1161 hctx->frame.ctl.mask_cnt = 0; 1162 hctx->frame.ctl.siz = (uint64_t)(frame[i] & 0x7f); 1163 if (hctx->frame.ctl.siz == 0) { 1164 DEBUG_LOG_DEBUG("specified payload size=%llx", 1165 (unsigned long long)hctx->frame.ctl.siz); 1166 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1167 } 1168 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN16) { 1169 hctx->frame.ctl.siz = 0; 1170 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN16_CNT; 1171 hctx->frame.state = 1172 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1173 } 1174 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN63) { 1175 hctx->frame.ctl.siz = 0; 1176 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN63_CNT; 1177 hctx->frame.state = 1178 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1179 } 1180 else { 1181 DEBUG_LOG_DEBUG("specified payload size=%llx", 1182 (unsigned long long)hctx->frame.ctl.siz); 1183 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1184 } 1185 i++; 1186 break; 1187 case MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH: 1188 hctx->frame.ctl.siz = 1189 (hctx->frame.ctl.siz << 8) + (frame[i] & 0xff); 1190 hctx->frame.ctl.siz_cnt--; 1191 if (hctx->frame.ctl.siz_cnt <= 0) { 1192 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1193 hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 1194 DEBUG_LOG_WARN("frame size has been exceeded: %x", 1195 MOD_WEBSOCKET_BUFMAX); 1196 return -1; 1197 } 1198 DEBUG_LOG_DEBUG("specified payload size=%llx", 1199 (unsigned long long)hctx->frame.ctl.siz); 1200 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1201 } 1202 i++; 1203 break; 1204 case MOD_WEBSOCKET_FRAME_STATE_READ_MASK: 1205 hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt] = frame[i]; 1206 hctx->frame.ctl.mask_cnt++; 1207 if (hctx->frame.ctl.mask_cnt >= MOD_WEBSOCKET_MASK_CNT) { 1208 hctx->frame.ctl.mask_cnt = 0; 1209 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1210 hctx->frame.ctl.siz == 0) { 1211 mod_wstunnel_frame_send(hctx, 1212 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1213 NULL, 0); 1214 } 1215 if (hctx->frame.ctl.siz == 0) { 1216 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1217 } 1218 else { 1219 hctx->frame.state = 1220 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 1221 } 1222 } 1223 i++; 1224 break; 1225 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 1226 /* hctx->frame.ctl.siz <= SIZE_MAX */ 1227 if (hctx->frame.ctl.siz <= flen - i) { 1228 DEBUG_LOG_DEBUG("read payload, size=%llx", 1229 (unsigned long long)hctx->frame.ctl.siz); 1230 buffer_append_string_len(payload, frame+i, (size_t) 1231 (hctx->frame.ctl.siz & SIZE_MAX)); 1232 i += (size_t)(hctx->frame.ctl.siz & SIZE_MAX); 1233 hctx->frame.ctl.siz = 0; 1234 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1235 DEBUG_LOG_DEBUG("rest of frame size=%zx", flen - i); 1236 /* SIZE_MAX < hctx->frame.ctl.siz */ 1237 } 1238 else { 1239 DEBUG_LOG_DEBUG("read payload, size=%zx", flen - i); 1240 buffer_append_string_len(payload, frame+i, flen - i); 1241 hctx->frame.ctl.siz -= flen - i; 1242 i += flen - i; 1243 DEBUG_LOG_DEBUG("rest of payload size=%llx", 1244 (unsigned long long)hctx->frame.ctl.siz); 1245 } 1246 switch (hctx->frame.type) { 1247 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1248 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1249 { 1250 unmask_payload(hctx); 1251 chunkqueue_append_buffer(&hctx->gw.wb, payload); 1252 /*buffer_clear(payload);*//*chunkqueue_append_buffer clear*/ 1253 break; 1254 } 1255 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1256 if (hctx->frame.ctl.siz == 0) { 1257 unmask_payload(hctx); 1258 mod_wstunnel_frame_send(hctx, 1259 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1260 payload->ptr, buffer_clen(payload)); 1261 buffer_clear(payload); 1262 } 1263 break; 1264 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1265 buffer_clear(payload); 1266 break; 1267 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1268 default: 1269 DEBUG_LOG_ERR("%s", "BUG: invalid frame type"); 1270 return -1; 1271 } 1272 break; 1273 default: 1274 DEBUG_LOG_ERR("%s", "BUG: invalid state"); 1275 return -1; 1276 } 1277 } 1278 } 1279 /* XXX: should add ability to handle and preserve partial frames above */ 1280 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1281 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1282 return 0; 1283 } 1284 1285 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1286 1287 1288 int mod_wstunnel_frame_send(handler_ctx *hctx, mod_wstunnel_frame_type_t type, 1289 const char *payload, size_t siz) { 1290 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1291 if (hctx->hybivers >= 8) return send_rfc_6455(hctx, type, payload, siz); 1292 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1293 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1294 if (0 == hctx->hybivers) return send_ietf_00(hctx, type, payload, siz); 1295 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1296 return -1; 1297 } 1298 1299 int mod_wstunnel_frame_recv(handler_ctx *hctx) { 1300 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1301 if (hctx->hybivers >= 8) return recv_rfc_6455(hctx); 1302 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1303 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1304 if (0 == hctx->hybivers) return recv_ietf_00(hctx); 1305 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1306 return -1; 1307 } 1308