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 #include "connections.h" 94 95 #define MOD_WEBSOCKET_LOG_NONE 0 96 #define MOD_WEBSOCKET_LOG_ERR 1 97 #define MOD_WEBSOCKET_LOG_WARN 2 98 #define MOD_WEBSOCKET_LOG_INFO 3 99 #define MOD_WEBSOCKET_LOG_DEBUG 4 100 101 #define DEBUG_LOG_ERR(format, ...) \ 102 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_ERR) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); } 103 104 #define DEBUG_LOG_WARN(format, ...) \ 105 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_WARN) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); } 106 107 #define DEBUG_LOG_INFO(format, ...) \ 108 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_INFO) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); } 109 110 #define DEBUG_LOG_DEBUG(format, ...) \ 111 if (hctx->gw.conf.debug >= MOD_WEBSOCKET_LOG_DEBUG) { log_error(hctx->errh, __FILE__, __LINE__, (format), __VA_ARGS__); } 112 113 typedef struct { 114 gw_plugin_config gw; /* start must match layout of gw_plugin_config */ 115 const array *origins; 116 unsigned int frame_type; 117 unsigned short int ping_interval; 118 } plugin_config; 119 120 typedef struct plugin_data { 121 PLUGIN_DATA; 122 pid_t srv_pid; /* must match layout of gw_plugin_data through conf member */ 123 plugin_config conf; 124 plugin_config defaults; 125 } plugin_data; 126 127 typedef enum { 128 MOD_WEBSOCKET_FRAME_STATE_INIT, 129 130 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 131 MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH, 132 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH, 133 MOD_WEBSOCKET_FRAME_STATE_READ_MASK, 134 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 135 136 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD 137 } mod_wstunnel_frame_state_t; 138 139 typedef enum { 140 MOD_WEBSOCKET_FRAME_TYPE_TEXT, 141 MOD_WEBSOCKET_FRAME_TYPE_BIN, 142 MOD_WEBSOCKET_FRAME_TYPE_CLOSE, 143 144 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 145 MOD_WEBSOCKET_FRAME_TYPE_PING, 146 MOD_WEBSOCKET_FRAME_TYPE_PONG 147 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 148 149 } mod_wstunnel_frame_type_t; 150 151 typedef struct { 152 uint64_t siz; 153 154 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 155 int siz_cnt; 156 int mask_cnt; 157 #define MOD_WEBSOCKET_MASK_CNT 4 158 unsigned char mask[MOD_WEBSOCKET_MASK_CNT]; 159 /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 160 161 } mod_wstunnel_frame_control_t; 162 163 typedef struct { 164 mod_wstunnel_frame_state_t state; 165 mod_wstunnel_frame_control_t ctl; 166 mod_wstunnel_frame_type_t type, type_before, type_backend; 167 buffer *payload; 168 } mod_wstunnel_frame_t; 169 170 typedef struct { 171 gw_handler_ctx gw; 172 mod_wstunnel_frame_t frame; 173 174 int hybivers; 175 time_t ping_ts; 176 int subproto; 177 178 log_error_st *errh; /*(for mod_wstunnel module-specific DEBUG_*() macros)*/ 179 plugin_config conf; 180 } handler_ctx; 181 182 /* prototypes */ 183 static handler_t mod_wstunnel_handshake_create_response(handler_ctx *); 184 static int mod_wstunnel_frame_send(handler_ctx *, mod_wstunnel_frame_type_t, const char *, size_t); 185 static int mod_wstunnel_frame_recv(handler_ctx *); 186 #define _MOD_WEBSOCKET_SPEC_IETF_00_ 187 #define _MOD_WEBSOCKET_SPEC_RFC_6455_ 188 189 INIT_FUNC(mod_wstunnel_init) { 190 return calloc(1, sizeof(plugin_data)); 191 } 192 193 static void mod_wstunnel_merge_config_cpv(plugin_config * const pconf, const config_plugin_value_t * const cpv) { 194 switch (cpv->k_id) { /* index into static config_plugin_keys_t cpk[] */ 195 case 0: /* wstunnel.server */ 196 if (cpv->vtype == T_CONFIG_LOCAL) { 197 gw_plugin_config * const gw = cpv->v.v; 198 pconf->gw.exts = gw->exts; 199 pconf->gw.exts_auth = gw->exts_auth; 200 pconf->gw.exts_resp = gw->exts_resp; 201 } 202 break; 203 case 1: /* wstunnel.balance */ 204 /*if (cpv->vtype == T_CONFIG_LOCAL)*//*always true here for this param*/ 205 pconf->gw.balance = (int)cpv->v.u; 206 break; 207 case 2: /* wstunnel.debug */ 208 pconf->gw.debug = (int)cpv->v.u; 209 break; 210 case 3: /* wstunnel.map-extensions */ 211 pconf->gw.ext_mapping = cpv->v.a; 212 break; 213 case 4: /* wstunnel.frame-type */ 214 pconf->frame_type = cpv->v.u; 215 break; 216 case 5: /* wstunnel.origins */ 217 pconf->origins = cpv->v.a; 218 break; 219 case 6: /* wstunnel.ping-interval */ 220 pconf->ping_interval = cpv->v.shrt; 221 break; 222 default:/* should not happen */ 223 return; 224 } 225 } 226 227 static void mod_wstunnel_merge_config(plugin_config * const pconf, const config_plugin_value_t *cpv) { 228 do { 229 mod_wstunnel_merge_config_cpv(pconf, cpv); 230 } while ((++cpv)->k_id != -1); 231 } 232 233 static void mod_wstunnel_patch_config(request_st * const r, plugin_data * const p) { 234 memcpy(&p->conf, &p->defaults, sizeof(plugin_config)); 235 for (int i = 1, used = p->nconfig; i < used; ++i) { 236 if (config_check_cond(r, (uint32_t)p->cvlist[i].k_id)) 237 mod_wstunnel_merge_config(&p->conf, p->cvlist+p->cvlist[i].v.u2[0]); 238 } 239 } 240 241 SETDEFAULTS_FUNC(mod_wstunnel_set_defaults) { 242 static const config_plugin_keys_t cpk[] = { 243 { CONST_STR_LEN("wstunnel.server"), 244 T_CONFIG_ARRAY_KVARRAY, 245 T_CONFIG_SCOPE_CONNECTION } 246 ,{ CONST_STR_LEN("wstunnel.balance"), 247 T_CONFIG_STRING, 248 T_CONFIG_SCOPE_CONNECTION } 249 ,{ CONST_STR_LEN("wstunnel.debug"), 250 T_CONFIG_INT, 251 T_CONFIG_SCOPE_CONNECTION } 252 ,{ CONST_STR_LEN("wstunnel.map-extensions"), 253 T_CONFIG_ARRAY_KVSTRING, 254 T_CONFIG_SCOPE_CONNECTION } 255 ,{ CONST_STR_LEN("wstunnel.frame-type"), 256 T_CONFIG_STRING, 257 T_CONFIG_SCOPE_CONNECTION } 258 ,{ CONST_STR_LEN("wstunnel.origins"), 259 T_CONFIG_ARRAY_VLIST, 260 T_CONFIG_SCOPE_CONNECTION } 261 ,{ CONST_STR_LEN("wstunnel.ping-interval"), 262 T_CONFIG_SHORT, 263 T_CONFIG_SCOPE_CONNECTION } 264 ,{ NULL, 0, 265 T_CONFIG_UNSET, 266 T_CONFIG_SCOPE_UNSET } 267 }; 268 269 plugin_data * const p = p_d; 270 if (!config_plugin_values_init(srv, p, cpk, "mod_wstunnel")) 271 return HANDLER_ERROR; 272 273 /* process and validate config directives 274 * (init i to 0 if global context; to 1 to skip empty global context) */ 275 for (int i = !p->cvlist[0].v.u2[1]; i < p->nconfig; ++i) { 276 config_plugin_value_t *cpv = p->cvlist + p->cvlist[i].v.u2[0]; 277 gw_plugin_config *gw = NULL; 278 for (; -1 != cpv->k_id; ++cpv) { 279 switch (cpv->k_id) { 280 case 0: /* wstunnel.server */ 281 gw = calloc(1, sizeof(gw_plugin_config)); 282 force_assert(gw); 283 if (!gw_set_defaults_backend(srv, (gw_plugin_data *)p, cpv->v.a, 284 gw, 0, cpk[cpv->k_id].k)) { 285 gw_plugin_config_free(gw); 286 return HANDLER_ERROR; 287 } 288 /* error if "mode" = "authorizer"; 289 * wstunnel can not act as authorizer */ 290 /*(check after gw_set_defaults_backend())*/ 291 if (gw->exts_auth && gw->exts_auth->used) { 292 log_error(srv->errh, __FILE__, __LINE__, 293 "%s must not define any hosts with " 294 "attribute \"mode\" = \"authorizer\"", cpk[cpv->k_id].k); 295 gw_plugin_config_free(gw); 296 return HANDLER_ERROR; 297 } 298 cpv->v.v = gw; 299 cpv->vtype = T_CONFIG_LOCAL; 300 break; 301 case 1: /* wstunnel.balance */ 302 cpv->v.u = (unsigned int)gw_get_defaults_balance(srv, cpv->v.b); 303 break; 304 case 2: /* wstunnel.debug */ 305 case 3: /* wstunnel.map-extensions */ 306 break; 307 case 4: /* wstunnel.frame-type */ 308 /*(default frame-type to "text" unless "binary" is specified)*/ 309 cpv->v.u = 310 buffer_eq_icase_slen(cpv->v.b, CONST_STR_LEN("binary")); 311 break; 312 case 5: /* wstunnel.origins */ 313 for (uint32_t j = 0; j < cpv->v.a->used; ++j) { 314 buffer *origin = &((data_string *)cpv->v.a->data[j])->value; 315 if (buffer_string_is_empty(origin)) { 316 log_error(srv->errh, __FILE__, __LINE__, 317 "unexpected empty string in %s", cpk[cpv->k_id].k); 318 return HANDLER_ERROR; 319 } 320 } 321 break; 322 case 6: /* wstunnel.ping-interval */ 323 break; 324 default:/* should not happen */ 325 break; 326 } 327 } 328 329 /* disable check-local for all exts (default enabled) */ 330 if (gw && gw->exts) { /*(check after gw_set_defaults_backend())*/ 331 gw_exts_clear_check_local(gw->exts); 332 } 333 } 334 335 /* default is 0 */ 336 /*p->defaults.balance = (unsigned int)gw_get_defaults_balance(srv, NULL);*/ 337 p->defaults.ping_interval = 0; /* do not send ping */ 338 339 /* initialize p->defaults from global config context */ 340 if (p->nconfig > 0 && p->cvlist->v.u2[1]) { 341 const config_plugin_value_t *cpv = p->cvlist + p->cvlist->v.u2[0]; 342 if (-1 != cpv->k_id) 343 mod_wstunnel_merge_config(&p->defaults, cpv); 344 } 345 346 return HANDLER_GO_ON; 347 } 348 349 static handler_t wstunnel_create_env(gw_handler_ctx *gwhctx) { 350 handler_ctx *hctx = (handler_ctx *)gwhctx; 351 request_st * const r = hctx->gw.r; 352 handler_t rc; 353 if (0 == r->reqbody_length) { 354 http_response_upgrade_read_body_unknown(r); 355 chunkqueue_append_chunkqueue(&r->reqbody_queue, &r->read_queue); 356 } 357 rc = mod_wstunnel_handshake_create_response(hctx); 358 if (rc != HANDLER_GO_ON) return rc; 359 360 r->http_status = 101; /* Switching Protocols */ 361 r->resp_body_started = 1; 362 363 hctx->ping_ts = log_epoch_secs; 364 gw_set_transparent(&hctx->gw); 365 366 return HANDLER_GO_ON; 367 } 368 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 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 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 = buffer_string_length(origin); 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_string_length(b); 432 if ((olen > blen ? origin->ptr[olen-blen-1] == '.' : olen == blen) 433 && buffer_is_equal_right_len(origin, b, 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 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 (buffer_is_empty(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 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 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 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) return HANDLER_FINISHED; 488 hctx->hybivers = hybivers; 489 if (0 == hybivers) { 490 DEBUG_LOG_INFO("WebSocket Version = %s", "hybi-00"); 491 } 492 else { 493 DEBUG_LOG_INFO("WebSocket Version = %d", hybivers); 494 } 495 496 hctx->gw.opts.backend = BACKEND_PROXY; /*(act proxy-like; not used)*/ 497 hctx->gw.opts.pdata = hctx; 498 hctx->gw.opts.parse = wstunnel_recv_parse; 499 hctx->gw.stdin_append = wstunnel_stdin_append; 500 hctx->gw.create_env = wstunnel_create_env; 501 hctx->gw.handler_ctx_free = wstunnel_handler_ctx_free; 502 hctx->gw.backend_error = wstunnel_backend_error; 503 hctx->gw.response = chunk_buffer_acquire(); 504 505 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 506 hctx->frame.ctl.siz = 0; 507 hctx->frame.payload = chunk_buffer_acquire(); 508 509 unsigned int binary = hctx->conf.frame_type; /*(0 = "text"; 1 = "binary")*/ 510 if (!binary) { 511 const buffer *vb = 512 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Protocol")); 513 if (NULL != vb) { 514 for (const char *s = vb->ptr; *s; ++s) { 515 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 516 if (buffer_eq_icase_ssn(s, CONST_STR_LEN("binary"))) { 517 s += sizeof("binary")-1; 518 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 519 if (*s==','||*s=='\0') { 520 hctx->subproto = 1; 521 binary = 1; 522 break; 523 } 524 } 525 else if (buffer_eq_icase_ssn(s, CONST_STR_LEN("base64"))) { 526 s += sizeof("base64")-1; 527 while (*s==' '||*s=='\t'||*s=='\r'||*s=='\n') ++s; 528 if (*s==','||*s=='\0') { 529 hctx->subproto = -1; 530 break; 531 } 532 } 533 s = strchr(s, ','); 534 if (NULL == s) break; 535 } 536 } 537 } 538 539 if (binary) { 540 DEBUG_LOG_INFO("%s", "will recv binary data from backend"); 541 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN; 542 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_BIN; 543 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_BIN; 544 } 545 else { 546 DEBUG_LOG_INFO("%s", "will recv text data from backend"); 547 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 548 hctx->frame.type_before = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 549 hctx->frame.type_backend = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 550 } 551 552 return HANDLER_GO_ON; 553 } 554 555 static handler_t mod_wstunnel_check_extension(request_st * const r, void *p_d) { 556 plugin_data *p = p_d; 557 const buffer *vb; 558 handler_t rc; 559 560 if (NULL != r->handler_module) 561 return HANDLER_GO_ON; 562 if (r->http_method != HTTP_METHOD_GET) 563 return HANDLER_GO_ON; 564 if (r->http_version != HTTP_VERSION_1_1) 565 return HANDLER_GO_ON; 566 567 /* 568 * Connection: upgrade, keep-alive, ... 569 * Upgrade: WebSocket, ... 570 */ 571 vb = http_header_request_get(r, HTTP_HEADER_UPGRADE, CONST_STR_LEN("Upgrade")); 572 if (NULL == vb 573 || !http_header_str_contains_token(CONST_BUF_LEN(vb), CONST_STR_LEN("websocket"))) 574 return HANDLER_GO_ON; 575 vb = http_header_request_get(r, HTTP_HEADER_CONNECTION, CONST_STR_LEN("Connection")); 576 if (NULL == vb 577 || !http_header_str_contains_token(CONST_BUF_LEN(vb), CONST_STR_LEN("upgrade"))) 578 return HANDLER_GO_ON; 579 580 mod_wstunnel_patch_config(r, p); 581 if (NULL == p->conf.gw.exts) return HANDLER_GO_ON; 582 583 rc = gw_check_extension(r, (gw_plugin_data *)p, 1, sizeof(handler_ctx)); 584 return (HANDLER_GO_ON == rc && r->handler_module == p->self) 585 ? wstunnel_handler_setup(r, p) 586 : rc; 587 } 588 589 TRIGGER_FUNC(mod_wstunnel_handle_trigger) { 590 const plugin_data * const p = p_d; 591 const time_t cur_ts = log_epoch_secs + 1; 592 593 gw_handle_trigger(srv, p_d); 594 595 for (uint32_t i = 0; i < srv->conns.used; ++i) { 596 connection *con = srv->conns.ptr[i]; 597 request_st * const r = &con->request; 598 handler_ctx *hctx = r->plugin_ctx[p->id]; 599 if (NULL == hctx || r->handler_module != p->self) 600 continue; 601 602 if (hctx->gw.state != GW_STATE_WRITE && hctx->gw.state != GW_STATE_READ) 603 continue; 604 605 if (cur_ts - con->read_idle_ts > r->conf.max_read_idle) { 606 DEBUG_LOG_INFO("timeout client (fd=%d)", con->fd); 607 mod_wstunnel_frame_send(hctx,MOD_WEBSOCKET_FRAME_TYPE_CLOSE,NULL,0); 608 gw_handle_request_reset(r, p_d); 609 joblist_append(con); 610 /* avoid server.c closing connection with error due to max_read_idle 611 * (might instead run joblist after plugins_call_handle_trigger())*/ 612 con->read_idle_ts = cur_ts; 613 continue; 614 } 615 616 if (0 != hctx->hybivers 617 && hctx->conf.ping_interval > 0 618 && (time_t)hctx->conf.ping_interval + hctx->ping_ts < cur_ts) { 619 hctx->ping_ts = cur_ts; 620 mod_wstunnel_frame_send(hctx, MOD_WEBSOCKET_FRAME_TYPE_PING, CONST_STR_LEN("ping")); 621 joblist_append(con); 622 continue; 623 } 624 } 625 626 return HANDLER_GO_ON; 627 } 628 629 int mod_wstunnel_plugin_init(plugin *p); 630 int mod_wstunnel_plugin_init(plugin *p) { 631 p->version = LIGHTTPD_VERSION_ID; 632 p->name = "wstunnel"; 633 p->init = mod_wstunnel_init; 634 p->cleanup = gw_free; 635 p->set_defaults = mod_wstunnel_set_defaults; 636 p->handle_request_reset = gw_handle_request_reset; 637 p->handle_uri_clean = mod_wstunnel_check_extension; 638 p->handle_subrequest = gw_handle_subrequest; 639 p->handle_trigger = mod_wstunnel_handle_trigger; 640 p->handle_waitpid = gw_handle_waitpid_cb; 641 return 0; 642 } 643 644 645 646 647 /* 648 * modified from Norio Kobota mod_websocket_handshake.c 649 */ 650 651 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 652 653 #include "sys-crypto-md.h" /* lighttpd */ 654 #include "sys-endian.h" /* lighttpd */ 655 656 static int get_key3(request_st * const r, char *buf, uint32_t bytes) { 657 /* 8 bytes should have been sent with request 658 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 659 chunkqueue *cq = &r->reqbody_queue; 660 /*(caller should ensure bytes available prior to calling this routine)*/ 661 /*assert(chunkqueue_length(cq) >= 8);*/ 662 /*assert(8 == bytes);*/ 663 return chunkqueue_read_data(cq, buf, bytes, r->conf.errh); 664 } 665 666 static int get_key_number(uint32_t *ret, const buffer *b) { 667 const char * const s = b->ptr; 668 size_t j = 0; 669 unsigned long n; 670 uint32_t sp = 0; 671 char tmp[10 + 1]; /* #define UINT32_MAX_STRLEN 10 */ 672 673 for (size_t i = 0, used = buffer_string_length(b); i < used; ++i) { 674 if (light_isdigit(s[i])) { 675 tmp[j] = s[i]; 676 if (++j >= sizeof(tmp)) return -1; 677 } 678 else if (s[i] == ' ') ++sp; /* count num spaces */ 679 } 680 tmp[j] = '\0'; 681 n = strtoul(tmp, NULL, 10); 682 if (n > UINT32_MAX || 0 == sp || !light_isdigit(*tmp)) return -1; 683 *ret = (uint32_t)n / sp; 684 return 0; 685 } 686 687 static int create_MD5_sum(request_st * const r) { 688 uint32_t buf[4]; /* MD5 binary hash len */ 689 li_MD5_CTX ctx; 690 691 const buffer *key1 = 692 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key1")); 693 const buffer *key2 = 694 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key2")); 695 696 if (NULL == key1 || get_key_number(buf+0, key1) < 0 || 697 NULL == key2 || get_key_number(buf+1, key2) < 0 || 698 get_key3(r, (char *)(buf+2), 2*sizeof(uint32_t)) < 0) { 699 return -1; 700 } 701 #ifdef __BIG_ENDIAN__ 702 #define ws_htole32(s,u)\ 703 (s)[0]=((u)>>24); \ 704 (s)[1]=((u)>>16); \ 705 (s)[2]=((u)>>8); \ 706 (s)[3]=((u)) 707 ws_htole32((unsigned char *)(buf+0), buf[0]); 708 ws_htole32((unsigned char *)(buf+1), buf[1]); 709 #endif 710 li_MD5_Init(&ctx); 711 li_MD5_Update(&ctx, buf, sizeof(buf)); 712 li_MD5_Final((unsigned char *)buf, &ctx); /*(overwrite buf[] with result)*/ 713 chunkqueue_append_mem(&r->write_queue, (char *)buf, sizeof(buf)); 714 return 0; 715 } 716 717 static int create_response_ietf_00(handler_ctx *hctx) { 718 request_st * const r = hctx->gw.r; 719 buffer *value = r->tmp_buf; 720 721 /* "Origin" header is preferred 722 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */ 723 const buffer *origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin")); 724 if (NULL == origin) { 725 origin = 726 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin")); 727 } 728 if (NULL == origin) { 729 DEBUG_LOG_ERR("%s", "Origin header is invalid"); 730 return -1; 731 } 732 if (buffer_is_empty(r->http_host)) { 733 DEBUG_LOG_ERR("%s", "Host header does not exist"); 734 return -1; 735 } 736 737 /* calc MD5 sum from keys */ 738 if (create_MD5_sum(r) < 0) { 739 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 740 return -1; 741 } 742 743 http_header_response_set(r, HTTP_HEADER_UPGRADE, 744 CONST_STR_LEN("Upgrade"), 745 CONST_STR_LEN("websocket")); 746 #if 0 /*(added later in http_response_write_header())*/ 747 http_header_response_append(r, HTTP_HEADER_CONNECTION, 748 CONST_STR_LEN("Connection"), 749 CONST_STR_LEN("upgrade")); 750 #endif 751 #if 0 /*(Sec-WebSocket-Origin header is not required for hybi-00)*/ 752 /* Note: it is insecure to simply reflect back origin provided by client 753 * (if admin did not configure restricted list of valid origins) 754 * (see wstunnel_check_request()) */ 755 http_header_response_set(r, HTTP_HEADER_OTHER, 756 CONST_STR_LEN("Sec-WebSocket-Origin"), 757 CONST_BUF_LEN(origin)); 758 #endif 759 760 if (buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https"))) 761 buffer_copy_string_len(value, CONST_STR_LEN("wss://")); 762 else 763 buffer_copy_string_len(value, CONST_STR_LEN("ws://")); 764 buffer_append_string_buffer(value, r->http_host); 765 buffer_append_string_buffer(value, &r->uri.path); 766 http_header_response_set(r, HTTP_HEADER_OTHER, 767 CONST_STR_LEN("Sec-WebSocket-Location"), 768 CONST_BUF_LEN(value)); 769 770 return 0; 771 } 772 773 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 774 775 776 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 777 778 #include "sys-crypto-md.h" /* lighttpd */ 779 #include "base64.h" /* lighttpd */ 780 781 static int create_response_rfc_6455(handler_ctx *hctx) { 782 request_st * const r = hctx->gw.r; 783 SHA_CTX sha; 784 unsigned char sha_digest[SHA_DIGEST_LENGTH]; 785 786 const buffer *value_wskey = 787 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key")); 788 if (NULL == value_wskey) { 789 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 790 return -1; 791 } 792 793 /* get SHA1 hash of key */ 794 /* refer: RFC-6455 Sec.1.3 Opening Handshake */ 795 SHA1_Init(&sha); 796 SHA1_Update(&sha, (const unsigned char *)CONST_BUF_LEN(value_wskey)); 797 SHA1_Update(&sha, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); 798 SHA1_Final(sha_digest, &sha); 799 800 http_header_response_set(r, HTTP_HEADER_UPGRADE, 801 CONST_STR_LEN("Upgrade"), 802 CONST_STR_LEN("websocket")); 803 #if 0 /*(added later in http_response_write_header())*/ 804 http_header_response_append(r, HTTP_HEADER_CONNECTION, 805 CONST_STR_LEN("Connection"), 806 CONST_STR_LEN("upgrade")); 807 #endif 808 809 buffer *value = r->tmp_buf; 810 buffer_clear(value); 811 buffer_append_base64_encode(value, sha_digest, SHA_DIGEST_LENGTH, BASE64_STANDARD); 812 http_header_response_set(r, HTTP_HEADER_OTHER, 813 CONST_STR_LEN("Sec-WebSocket-Accept"), 814 CONST_BUF_LEN(value)); 815 816 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_BIN) 817 http_header_response_set(r, HTTP_HEADER_OTHER, 818 CONST_STR_LEN("Sec-WebSocket-Protocol"), 819 CONST_STR_LEN("binary")); 820 else if (-1 == hctx->subproto) 821 http_header_response_set(r, HTTP_HEADER_OTHER, 822 CONST_STR_LEN("Sec-WebSocket-Protocol"), 823 CONST_STR_LEN("base64")); 824 825 return 0; 826 } 827 828 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 829 830 831 handler_t mod_wstunnel_handshake_create_response(handler_ctx *hctx) { 832 request_st * const r = hctx->gw.r; 833 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 834 if (hctx->hybivers >= 8) { 835 DEBUG_LOG_DEBUG("%s", "send handshake response"); 836 if (0 != create_response_rfc_6455(hctx)) { 837 r->http_status = 400; /* Bad Request */ 838 return HANDLER_ERROR; 839 } 840 return HANDLER_GO_ON; 841 } 842 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 843 844 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 845 if (hctx->hybivers == 0) { 846 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 847 /* 8 bytes should have been sent with request 848 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 849 chunkqueue *cq = &r->reqbody_queue; 850 if (chunkqueue_length(cq) < 8) 851 return HANDLER_WAIT_FOR_EVENT; 852 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 853 854 DEBUG_LOG_DEBUG("%s", "send handshake response"); 855 if (0 != create_response_ietf_00(hctx)) { 856 r->http_status = 400; /* Bad Request */ 857 return HANDLER_ERROR; 858 } 859 return HANDLER_GO_ON; 860 } 861 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 862 863 DEBUG_LOG_ERR("%s", "not supported WebSocket Version"); 864 r->http_status = 503; /* Service Unavailable */ 865 return HANDLER_ERROR; 866 } 867 868 869 870 871 /* 872 * modified from Norio Kobota mod_websocket_frame.c 873 */ 874 875 #include "base64.h" /* lighttpd */ 876 #include "http_chunk.h" /* lighttpd */ 877 878 #define MOD_WEBSOCKET_BUFMAX (0x0fffff) 879 880 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 881 882 #include <stdlib.h> 883 static int send_ietf_00(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 884 static const char head = 0; /* 0x00 */ 885 static const char tail = ~0; /* 0xff */ 886 request_st * const r = hctx->gw.r; 887 char *mem; 888 size_t len; 889 890 switch (type) { 891 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 892 if (0 == siz) return 0; 893 http_chunk_append_mem(r, &head, 1); 894 http_chunk_append_mem(r, payload, siz); 895 http_chunk_append_mem(r, &tail, 1); 896 len = siz+2; 897 break; 898 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 899 if (0 == siz) return 0; 900 http_chunk_append_mem(r, &head, 1); 901 len = 4*(siz/3)+4+1; 902 /* avoid accumulating too much data in memory; send to tmpfile */ 903 mem = malloc(len); 904 force_assert(mem); 905 len=li_to_base64(mem,len,(unsigned char *)payload,siz,BASE64_STANDARD); 906 http_chunk_append_mem(r, mem, len); 907 free(mem); 908 http_chunk_append_mem(r, &tail, 1); 909 len += 2; 910 break; 911 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 912 http_chunk_append_mem(r, &tail, 1); 913 http_chunk_append_mem(r, &head, 1); 914 len = 2; 915 break; 916 default: 917 DEBUG_LOG_ERR("%s", "invalid frame type"); 918 return -1; 919 } 920 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 921 r->con->fd, len); 922 return 0; 923 } 924 925 static int recv_ietf_00(handler_ctx *hctx) { 926 request_st * const r = hctx->gw.r; 927 chunkqueue *cq = &r->reqbody_queue; 928 buffer *payload = hctx->frame.payload; 929 char *mem; 930 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 931 r->con->fd, (long long)chunkqueue_length(cq)); 932 for (chunk *c = cq->first; c; c = c->next) { 933 char *frame = c->mem->ptr+c->offset; 934 /*(chunk_remaining_length() on MEM_CHUNK)*/ 935 size_t flen = (size_t)(buffer_string_length(c->mem) - c->offset); 936 /*(FILE_CHUNK not handled, but might need to add support)*/ 937 force_assert(c->type == MEM_CHUNK); 938 for (size_t i = 0; i < flen; ) { 939 switch (hctx->frame.state) { 940 case MOD_WEBSOCKET_FRAME_STATE_INIT: 941 hctx->frame.ctl.siz = 0; 942 if (frame[i] == 0x00) { 943 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 944 i++; 945 } 946 else if (((unsigned char *)frame)[i] == 0xff) { 947 DEBUG_LOG_DEBUG("%s", "recv close frame"); 948 return -1; 949 } 950 else { 951 DEBUG_LOG_DEBUG("%s", "recv invalid frame"); 952 return -1; 953 } 954 break; 955 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 956 mem = (char *)memchr(frame+i, 0xff, flen - i); 957 if (mem == NULL) { 958 DEBUG_LOG_DEBUG("got continuous payload, size=%zx", flen-i); 959 hctx->frame.ctl.siz += flen - i; 960 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 961 DEBUG_LOG_WARN("frame size has been exceeded: %x", 962 MOD_WEBSOCKET_BUFMAX); 963 return -1; 964 } 965 buffer_append_string_len(payload, frame+i, flen - i); 966 i += flen - i; 967 } 968 else { 969 DEBUG_LOG_DEBUG("got final payload, size=%zx", 970 mem - (frame+i)); 971 hctx->frame.ctl.siz += (mem - (frame+i)); 972 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 973 DEBUG_LOG_WARN("frame size has been exceeded: %x", 974 MOD_WEBSOCKET_BUFMAX); 975 return -1; 976 } 977 buffer_append_string_len(payload, frame+i, mem - (frame+i)); 978 i += (mem - (frame+i)); 979 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 980 } 981 i++; 982 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 983 && !buffer_is_empty(payload)) { 984 hctx->frame.ctl.siz = 0; 985 chunkqueue_append_buffer(&hctx->gw.wb, payload); 986 buffer_clear(payload); 987 } 988 else { 989 if (hctx->frame.state == MOD_WEBSOCKET_FRAME_STATE_INIT 990 && !buffer_is_empty(payload)) { 991 buffer *b; 992 size_t len = buffer_string_length(payload); 993 len = (len+3)/4*3+1; 994 chunkqueue_get_memory(&hctx->gw.wb, &len); 995 b = hctx->gw.wb.last->mem; 996 len = buffer_string_length(b); 997 DEBUG_LOG_DEBUG("try to base64 decode: %s", 998 payload->ptr); 999 if (NULL == buffer_append_base64_decode(b, CONST_BUF_LEN(payload), BASE64_STANDARD)) { 1000 DEBUG_LOG_ERR("%s", "fail to base64-decode"); 1001 return -1; 1002 } 1003 buffer_clear(payload); 1004 /*chunkqueue_use_memory()*/ 1005 hctx->gw.wb.bytes_in += buffer_string_length(b)-len; 1006 } 1007 } 1008 break; 1009 default: /* never reach */ 1010 DEBUG_LOG_ERR("%s", "BUG: unknown state"); 1011 return -1; 1012 } 1013 } 1014 } 1015 /* XXX: should add ability to handle and preserve partial frames above */ 1016 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1017 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1018 return 0; 1019 } 1020 1021 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1022 1023 1024 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1025 1026 #define MOD_WEBSOCKET_OPCODE_CONT 0x00 1027 #define MOD_WEBSOCKET_OPCODE_TEXT 0x01 1028 #define MOD_WEBSOCKET_OPCODE_BIN 0x02 1029 #define MOD_WEBSOCKET_OPCODE_CLOSE 0x08 1030 #define MOD_WEBSOCKET_OPCODE_PING 0x09 1031 #define MOD_WEBSOCKET_OPCODE_PONG 0x0A 1032 1033 #define MOD_WEBSOCKET_FRAME_LEN16 0x7E 1034 #define MOD_WEBSOCKET_FRAME_LEN63 0x7F 1035 #define MOD_WEBSOCKET_FRAME_LEN16_CNT 2 1036 #define MOD_WEBSOCKET_FRAME_LEN63_CNT 8 1037 1038 static int send_rfc_6455(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 1039 char mem[10]; 1040 size_t len; 1041 1042 /* allowed null payload for ping, pong, close frame */ 1043 if (payload == NULL && ( type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 1044 || type == MOD_WEBSOCKET_FRAME_TYPE_BIN )) { 1045 return -1; 1046 } 1047 1048 switch (type) { 1049 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1050 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_TEXT); 1051 DEBUG_LOG_DEBUG("%s", "type = text"); 1052 break; 1053 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1054 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_BIN); 1055 DEBUG_LOG_DEBUG("%s", "type = binary"); 1056 break; 1057 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1058 mem[0] = (char) (0x80 | MOD_WEBSOCKET_OPCODE_PING); 1059 DEBUG_LOG_DEBUG("%s", "type = ping"); 1060 break; 1061 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1062 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_PONG); 1063 DEBUG_LOG_DEBUG("%s", "type = pong"); 1064 break; 1065 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1066 default: 1067 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_CLOSE); 1068 DEBUG_LOG_DEBUG("%s", "type = close"); 1069 break; 1070 } 1071 1072 DEBUG_LOG_DEBUG("payload size=%zx", siz); 1073 if (siz < MOD_WEBSOCKET_FRAME_LEN16) { 1074 mem[1] = siz; 1075 len = 2; 1076 } 1077 else if (siz <= UINT16_MAX) { 1078 mem[1] = MOD_WEBSOCKET_FRAME_LEN16; 1079 mem[2] = (siz >> 8) & 0xff; 1080 mem[3] = siz & 0xff; 1081 len = 1+MOD_WEBSOCKET_FRAME_LEN16_CNT+1; 1082 } 1083 else { 1084 mem[1] = MOD_WEBSOCKET_FRAME_LEN63; 1085 mem[2] = 0; 1086 mem[3] = 0; 1087 mem[4] = 0; 1088 mem[5] = 0; 1089 mem[6] = (siz >> 24) & 0xff; 1090 mem[7] = (siz >> 16) & 0xff; 1091 mem[8] = (siz >> 8) & 0xff; 1092 mem[9] = siz & 0xff; 1093 len = 1+MOD_WEBSOCKET_FRAME_LEN63_CNT+1; 1094 } 1095 request_st * const r = hctx->gw.r; 1096 http_chunk_append_mem(r, mem, len); 1097 if (siz) http_chunk_append_mem(r, payload, siz); 1098 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 1099 r->con->fd, len+siz); 1100 return 0; 1101 } 1102 1103 static void unmask_payload(handler_ctx *hctx) { 1104 buffer * const b = hctx->frame.payload; 1105 for (size_t i = 0, used = buffer_string_length(b); i < used; ++i) { 1106 b->ptr[i] ^= hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt]; 1107 hctx->frame.ctl.mask_cnt = (hctx->frame.ctl.mask_cnt + 1) % 4; 1108 } 1109 } 1110 1111 static int recv_rfc_6455(handler_ctx *hctx) { 1112 request_st * const r = hctx->gw.r; 1113 chunkqueue *cq = &r->reqbody_queue; 1114 buffer *payload = hctx->frame.payload; 1115 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 1116 r->con->fd, (long long)chunkqueue_length(cq)); 1117 for (chunk *c = cq->first; c; c = c->next) { 1118 char *frame = c->mem->ptr+c->offset; 1119 /*(chunk_remaining_length() on MEM_CHUNK)*/ 1120 size_t flen = (size_t)(buffer_string_length(c->mem) - c->offset); 1121 /*(FILE_CHUNK not handled, but might need to add support)*/ 1122 force_assert(c->type == MEM_CHUNK); 1123 for (size_t i = 0; i < flen; ) { 1124 switch (hctx->frame.state) { 1125 case MOD_WEBSOCKET_FRAME_STATE_INIT: 1126 switch (frame[i] & 0x0f) { 1127 case MOD_WEBSOCKET_OPCODE_CONT: 1128 DEBUG_LOG_DEBUG("%s", "type = continue"); 1129 hctx->frame.type = hctx->frame.type_before; 1130 break; 1131 case MOD_WEBSOCKET_OPCODE_TEXT: 1132 DEBUG_LOG_DEBUG("%s", "type = text"); 1133 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 1134 hctx->frame.type_before = hctx->frame.type; 1135 break; 1136 case MOD_WEBSOCKET_OPCODE_BIN: 1137 DEBUG_LOG_DEBUG("%s", "type = binary"); 1138 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN; 1139 hctx->frame.type_before = hctx->frame.type; 1140 break; 1141 case MOD_WEBSOCKET_OPCODE_PING: 1142 DEBUG_LOG_DEBUG("%s", "type = ping"); 1143 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PING; 1144 break; 1145 case MOD_WEBSOCKET_OPCODE_PONG: 1146 DEBUG_LOG_DEBUG("%s", "type = pong"); 1147 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PONG; 1148 break; 1149 case MOD_WEBSOCKET_OPCODE_CLOSE: 1150 DEBUG_LOG_DEBUG("%s", "type = close"); 1151 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_CLOSE; 1152 return -1; 1153 break; 1154 default: 1155 DEBUG_LOG_ERR("%s", "type is invalid"); 1156 return -1; 1157 break; 1158 } 1159 i++; 1160 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH; 1161 break; 1162 case MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH: 1163 if ((frame[i] & 0x80) != 0x80) { 1164 DEBUG_LOG_ERR("%s", "payload was not masked"); 1165 return -1; 1166 } 1167 hctx->frame.ctl.mask_cnt = 0; 1168 hctx->frame.ctl.siz = (uint64_t)(frame[i] & 0x7f); 1169 if (hctx->frame.ctl.siz == 0) { 1170 DEBUG_LOG_DEBUG("specified payload size=%llx", 1171 (unsigned long long)hctx->frame.ctl.siz); 1172 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1173 } 1174 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN16) { 1175 hctx->frame.ctl.siz = 0; 1176 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN16_CNT; 1177 hctx->frame.state = 1178 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1179 } 1180 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN63) { 1181 hctx->frame.ctl.siz = 0; 1182 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN63_CNT; 1183 hctx->frame.state = 1184 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1185 } 1186 else { 1187 DEBUG_LOG_DEBUG("specified payload size=%llx", 1188 (unsigned long long)hctx->frame.ctl.siz); 1189 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1190 } 1191 i++; 1192 break; 1193 case MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH: 1194 hctx->frame.ctl.siz = 1195 (hctx->frame.ctl.siz << 8) + (frame[i] & 0xff); 1196 hctx->frame.ctl.siz_cnt--; 1197 if (hctx->frame.ctl.siz_cnt <= 0) { 1198 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1199 hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 1200 DEBUG_LOG_WARN("frame size has been exceeded: %x", 1201 MOD_WEBSOCKET_BUFMAX); 1202 return -1; 1203 } 1204 DEBUG_LOG_DEBUG("specified payload size=%llx", 1205 (unsigned long long)hctx->frame.ctl.siz); 1206 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1207 } 1208 i++; 1209 break; 1210 case MOD_WEBSOCKET_FRAME_STATE_READ_MASK: 1211 hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt] = frame[i]; 1212 hctx->frame.ctl.mask_cnt++; 1213 if (hctx->frame.ctl.mask_cnt >= MOD_WEBSOCKET_MASK_CNT) { 1214 hctx->frame.ctl.mask_cnt = 0; 1215 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1216 hctx->frame.ctl.siz == 0) { 1217 mod_wstunnel_frame_send(hctx, 1218 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1219 NULL, 0); 1220 } 1221 if (hctx->frame.ctl.siz == 0) { 1222 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1223 } 1224 else { 1225 hctx->frame.state = 1226 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 1227 } 1228 } 1229 i++; 1230 break; 1231 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 1232 /* hctx->frame.ctl.siz <= SIZE_MAX */ 1233 if (hctx->frame.ctl.siz <= flen - i) { 1234 DEBUG_LOG_DEBUG("read payload, size=%llx", 1235 (unsigned long long)hctx->frame.ctl.siz); 1236 buffer_append_string_len(payload, frame+i, (size_t) 1237 (hctx->frame.ctl.siz & SIZE_MAX)); 1238 i += (size_t)(hctx->frame.ctl.siz & SIZE_MAX); 1239 hctx->frame.ctl.siz = 0; 1240 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1241 DEBUG_LOG_DEBUG("rest of frame size=%zx", flen - i); 1242 /* SIZE_MAX < hctx->frame.ctl.siz */ 1243 } 1244 else { 1245 DEBUG_LOG_DEBUG("read payload, size=%zx", flen - i); 1246 buffer_append_string_len(payload, frame+i, flen - i); 1247 hctx->frame.ctl.siz -= flen - i; 1248 i += flen - i; 1249 DEBUG_LOG_DEBUG("rest of payload size=%llx", 1250 (unsigned long long)hctx->frame.ctl.siz); 1251 } 1252 switch (hctx->frame.type) { 1253 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1254 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1255 { 1256 unmask_payload(hctx); 1257 chunkqueue_append_buffer(&hctx->gw.wb, payload); 1258 buffer_clear(payload); 1259 break; 1260 } 1261 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1262 if (hctx->frame.ctl.siz == 0) { 1263 unmask_payload(hctx); 1264 mod_wstunnel_frame_send(hctx, 1265 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1266 payload->ptr, buffer_string_length(payload)); 1267 buffer_clear(payload); 1268 } 1269 break; 1270 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1271 buffer_clear(payload); 1272 break; 1273 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1274 default: 1275 DEBUG_LOG_ERR("%s", "BUG: invalid frame type"); 1276 return -1; 1277 } 1278 break; 1279 default: 1280 DEBUG_LOG_ERR("%s", "BUG: invalid state"); 1281 return -1; 1282 } 1283 } 1284 } 1285 /* XXX: should add ability to handle and preserve partial frames above */ 1286 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1287 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1288 return 0; 1289 } 1290 1291 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1292 1293 1294 int mod_wstunnel_frame_send(handler_ctx *hctx, mod_wstunnel_frame_type_t type, 1295 const char *payload, size_t siz) { 1296 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1297 if (hctx->hybivers >= 8) return send_rfc_6455(hctx, type, payload, siz); 1298 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1299 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1300 if (0 == hctx->hybivers) return send_ietf_00(hctx, type, payload, siz); 1301 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1302 return -1; 1303 } 1304 1305 int mod_wstunnel_frame_recv(handler_ctx *hctx) { 1306 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1307 if (hctx->hybivers >= 8) return recv_rfc_6455(hctx); 1308 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1309 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1310 if (0 == hctx->hybivers) return recv_ietf_00(hctx); 1311 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1312 return -1; 1313 } 1314