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) { 657 /* 8 bytes should have been sent with request 658 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 659 chunkqueue *cq = r->reqbody_queue; 660 size_t bytes = 8; 661 /*(caller should ensure bytes available prior to calling this routine)*/ 662 /*assert(chunkqueue_length(cq) >= 8);*/ 663 for (chunk *c = cq->first; NULL != c; c = c->next) { 664 /*(chunk_remaining_length() on MEM_CHUNK)*/ 665 size_t n = (size_t)(buffer_string_length(c->mem) - c->offset); 666 /*(expecting 8 bytes to be in memory directly after headers)*/ 667 if (c->type != MEM_CHUNK) break; /* FILE_CHUNK not handled here */ 668 if (n > bytes) n = bytes; 669 memcpy(buf, c->mem->ptr+c->offset, n); 670 buf += n; 671 if (0 == (bytes -= n)) break; 672 } 673 if (0 != bytes) return -1; 674 chunkqueue_mark_written(cq, 8); 675 return 0; 676 } 677 678 static int get_key_number(uint32_t *ret, const buffer *b) { 679 const char * const s = b->ptr; 680 size_t j = 0; 681 unsigned long n; 682 uint32_t sp = 0; 683 char tmp[10 + 1]; /* #define UINT32_MAX_STRLEN 10 */ 684 685 for (size_t i = 0, used = buffer_string_length(b); i < used; ++i) { 686 if (light_isdigit(s[i])) { 687 tmp[j] = s[i]; 688 if (++j >= sizeof(tmp)) return -1; 689 } 690 else if (s[i] == ' ') ++sp; /* count num spaces */ 691 } 692 tmp[j] = '\0'; 693 n = strtoul(tmp, NULL, 10); 694 if (n > UINT32_MAX || 0 == sp || !light_isdigit(*tmp)) return -1; 695 *ret = (uint32_t)n / sp; 696 return 0; 697 } 698 699 static int create_MD5_sum(request_st * const r) { 700 uint32_t buf[4]; /* MD5 binary hash len */ 701 li_MD5_CTX ctx; 702 703 const buffer *key1 = 704 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key1")); 705 const buffer *key2 = 706 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key2")); 707 708 if (NULL == key1 || get_key_number(buf+0, key1) < 0 || 709 NULL == key2 || get_key_number(buf+1, key2) < 0 || 710 get_key3(r, (char *)(buf+2)) < 0) { 711 return -1; 712 } 713 #ifdef __BIG_ENDIAN__ 714 #define ws_htole32(s,u)\ 715 (s)[0]=((u)>>24); \ 716 (s)[1]=((u)>>16); \ 717 (s)[2]=((u)>>8); \ 718 (s)[3]=((u)) 719 ws_htole32((unsigned char *)(buf+0), buf[0]); 720 ws_htole32((unsigned char *)(buf+1), buf[1]); 721 #endif 722 li_MD5_Init(&ctx); 723 li_MD5_Update(&ctx, buf, sizeof(buf)); 724 li_MD5_Final((unsigned char *)buf, &ctx); /*(overwrite buf[] with result)*/ 725 chunkqueue_append_mem(r->write_queue, (char *)buf, sizeof(buf)); 726 return 0; 727 } 728 729 static int create_response_ietf_00(handler_ctx *hctx) { 730 request_st * const r = hctx->gw.r; 731 buffer *value = r->tmp_buf; 732 733 /* "Origin" header is preferred 734 * ("Sec-WebSocket-Origin" is from older drafts of websocket spec) */ 735 const buffer *origin = http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Origin")); 736 if (NULL == origin) { 737 origin = 738 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Origin")); 739 } 740 if (NULL == origin) { 741 DEBUG_LOG_ERR("%s", "Origin header is invalid"); 742 return -1; 743 } 744 if (buffer_is_empty(r->http_host)) { 745 DEBUG_LOG_ERR("%s", "Host header does not exist"); 746 return -1; 747 } 748 749 /* calc MD5 sum from keys */ 750 if (create_MD5_sum(r) < 0) { 751 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 752 return -1; 753 } 754 755 http_header_response_set(r, HTTP_HEADER_UPGRADE, 756 CONST_STR_LEN("Upgrade"), 757 CONST_STR_LEN("websocket")); 758 #if 0 /*(added later in http_response_write_header())*/ 759 http_header_response_append(r, HTTP_HEADER_CONNECTION, 760 CONST_STR_LEN("Connection"), 761 CONST_STR_LEN("upgrade")); 762 #endif 763 #if 0 /*(Sec-WebSocket-Origin header is not required for hybi-00)*/ 764 /* Note: it is insecure to simply reflect back origin provided by client 765 * (if admin did not configure restricted list of valid origins) 766 * (see wstunnel_check_request()) */ 767 http_header_response_set(r, HTTP_HEADER_OTHER, 768 CONST_STR_LEN("Sec-WebSocket-Origin"), 769 CONST_BUF_LEN(origin)); 770 #endif 771 772 if (buffer_is_equal_string(&r->uri.scheme, CONST_STR_LEN("https"))) 773 buffer_copy_string_len(value, CONST_STR_LEN("wss://")); 774 else 775 buffer_copy_string_len(value, CONST_STR_LEN("ws://")); 776 buffer_append_string_buffer(value, r->http_host); 777 buffer_append_string_buffer(value, &r->uri.path); 778 http_header_response_set(r, HTTP_HEADER_OTHER, 779 CONST_STR_LEN("Sec-WebSocket-Location"), 780 CONST_BUF_LEN(value)); 781 782 return 0; 783 } 784 785 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 786 787 788 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 789 790 #include "sys-crypto-md.h" /* lighttpd */ 791 #include "base64.h" /* lighttpd */ 792 793 static int create_response_rfc_6455(handler_ctx *hctx) { 794 request_st * const r = hctx->gw.r; 795 SHA_CTX sha; 796 unsigned char sha_digest[SHA_DIGEST_LENGTH]; 797 798 const buffer *value_wskey = 799 http_header_request_get(r, HTTP_HEADER_OTHER, CONST_STR_LEN("Sec-WebSocket-Key")); 800 if (NULL == value_wskey) { 801 DEBUG_LOG_ERR("%s", "Sec-WebSocket-Key is invalid"); 802 return -1; 803 } 804 805 /* get SHA1 hash of key */ 806 /* refer: RFC-6455 Sec.1.3 Opening Handshake */ 807 SHA1_Init(&sha); 808 SHA1_Update(&sha, (const unsigned char *)CONST_BUF_LEN(value_wskey)); 809 SHA1_Update(&sha, (const unsigned char *)CONST_STR_LEN("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")); 810 SHA1_Final(sha_digest, &sha); 811 812 http_header_response_set(r, HTTP_HEADER_UPGRADE, 813 CONST_STR_LEN("Upgrade"), 814 CONST_STR_LEN("websocket")); 815 #if 0 /*(added later in http_response_write_header())*/ 816 http_header_response_append(r, HTTP_HEADER_CONNECTION, 817 CONST_STR_LEN("Connection"), 818 CONST_STR_LEN("upgrade")); 819 #endif 820 821 buffer *value = r->tmp_buf; 822 buffer_clear(value); 823 buffer_append_base64_encode(value, sha_digest, SHA_DIGEST_LENGTH, BASE64_STANDARD); 824 http_header_response_set(r, HTTP_HEADER_OTHER, 825 CONST_STR_LEN("Sec-WebSocket-Accept"), 826 CONST_BUF_LEN(value)); 827 828 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_BIN) 829 http_header_response_set(r, HTTP_HEADER_OTHER, 830 CONST_STR_LEN("Sec-WebSocket-Protocol"), 831 CONST_STR_LEN("binary")); 832 else if (-1 == hctx->subproto) 833 http_header_response_set(r, HTTP_HEADER_OTHER, 834 CONST_STR_LEN("Sec-WebSocket-Protocol"), 835 CONST_STR_LEN("base64")); 836 837 return 0; 838 } 839 840 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 841 842 843 handler_t mod_wstunnel_handshake_create_response(handler_ctx *hctx) { 844 request_st * const r = hctx->gw.r; 845 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 846 if (hctx->hybivers >= 8) { 847 DEBUG_LOG_DEBUG("%s", "send handshake response"); 848 if (0 != create_response_rfc_6455(hctx)) { 849 r->http_status = 400; /* Bad Request */ 850 return HANDLER_ERROR; 851 } 852 return HANDLER_GO_ON; 853 } 854 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 855 856 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 857 if (hctx->hybivers == 0) { 858 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 859 /* 8 bytes should have been sent with request 860 * for draft-ietf-hybi-thewebsocketprotocol-00 */ 861 chunkqueue *cq = r->reqbody_queue; 862 if (chunkqueue_length(cq) < 8) 863 return HANDLER_WAIT_FOR_EVENT; 864 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 865 866 DEBUG_LOG_DEBUG("%s", "send handshake response"); 867 if (0 != create_response_ietf_00(hctx)) { 868 r->http_status = 400; /* Bad Request */ 869 return HANDLER_ERROR; 870 } 871 return HANDLER_GO_ON; 872 } 873 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 874 875 DEBUG_LOG_ERR("%s", "not supported WebSocket Version"); 876 r->http_status = 503; /* Service Unavailable */ 877 return HANDLER_ERROR; 878 } 879 880 881 882 883 /* 884 * modified from Norio Kobota mod_websocket_frame.c 885 */ 886 887 #include "base64.h" /* lighttpd */ 888 #include "http_chunk.h" /* lighttpd */ 889 890 #define MOD_WEBSOCKET_BUFMAX (0x0fffff) 891 892 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 893 894 #include <stdlib.h> 895 static int send_ietf_00(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 896 static const char head = 0; /* 0x00 */ 897 static const char tail = ~0; /* 0xff */ 898 request_st * const r = hctx->gw.r; 899 char *mem; 900 size_t len; 901 902 switch (type) { 903 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 904 if (0 == siz) return 0; 905 http_chunk_append_mem(r, &head, 1); 906 http_chunk_append_mem(r, payload, siz); 907 http_chunk_append_mem(r, &tail, 1); 908 len = siz+2; 909 break; 910 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 911 if (0 == siz) return 0; 912 http_chunk_append_mem(r, &head, 1); 913 len = 4*(siz/3)+4+1; 914 /* avoid accumulating too much data in memory; send to tmpfile */ 915 mem = malloc(len); 916 force_assert(mem); 917 len=li_to_base64(mem,len,(unsigned char *)payload,siz,BASE64_STANDARD); 918 http_chunk_append_mem(r, mem, len); 919 free(mem); 920 http_chunk_append_mem(r, &tail, 1); 921 len += 2; 922 break; 923 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 924 http_chunk_append_mem(r, &tail, 1); 925 http_chunk_append_mem(r, &head, 1); 926 len = 2; 927 break; 928 default: 929 DEBUG_LOG_ERR("%s", "invalid frame type"); 930 return -1; 931 } 932 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 933 r->con->fd, len); 934 return 0; 935 } 936 937 static int recv_ietf_00(handler_ctx *hctx) { 938 request_st * const r = hctx->gw.r; 939 chunkqueue *cq = r->reqbody_queue; 940 buffer *payload = hctx->frame.payload; 941 char *mem; 942 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 943 r->con->fd, (long long)chunkqueue_length(cq)); 944 for (chunk *c = cq->first; c; c = c->next) { 945 char *frame = c->mem->ptr+c->offset; 946 /*(chunk_remaining_length() on MEM_CHUNK)*/ 947 size_t flen = (size_t)(buffer_string_length(c->mem) - c->offset); 948 /*(FILE_CHUNK not handled, but might need to add support)*/ 949 force_assert(c->type == MEM_CHUNK); 950 for (size_t i = 0; i < flen; ) { 951 switch (hctx->frame.state) { 952 case MOD_WEBSOCKET_FRAME_STATE_INIT: 953 hctx->frame.ctl.siz = 0; 954 if (frame[i] == 0x00) { 955 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 956 i++; 957 } 958 else if (((unsigned char *)frame)[i] == 0xff) { 959 DEBUG_LOG_DEBUG("%s", "recv close frame"); 960 return -1; 961 } 962 else { 963 DEBUG_LOG_DEBUG("%s", "recv invalid frame"); 964 return -1; 965 } 966 break; 967 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 968 mem = (char *)memchr(frame+i, 0xff, flen - i); 969 if (mem == NULL) { 970 DEBUG_LOG_DEBUG("got continuous payload, size=%zx", flen-i); 971 hctx->frame.ctl.siz += flen - 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, flen - i); 978 i += flen - i; 979 } 980 else { 981 DEBUG_LOG_DEBUG("got final payload, size=%zx", 982 mem - (frame+i)); 983 hctx->frame.ctl.siz += (mem - (frame+i)); 984 if (hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 985 DEBUG_LOG_WARN("frame size has been exceeded: %x", 986 MOD_WEBSOCKET_BUFMAX); 987 return -1; 988 } 989 buffer_append_string_len(payload, frame+i, mem - (frame+i)); 990 i += (mem - (frame+i)); 991 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 992 } 993 i++; 994 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 995 && !buffer_is_empty(payload)) { 996 hctx->frame.ctl.siz = 0; 997 chunkqueue_append_buffer(hctx->gw.wb, payload); 998 buffer_clear(payload); 999 } 1000 else { 1001 if (hctx->frame.state == MOD_WEBSOCKET_FRAME_STATE_INIT 1002 && !buffer_is_empty(payload)) { 1003 buffer *b; 1004 size_t len = buffer_string_length(payload); 1005 len = (len+3)/4*3+1; 1006 chunkqueue_get_memory(hctx->gw.wb, &len); 1007 b = hctx->gw.wb->last->mem; 1008 len = buffer_string_length(b); 1009 DEBUG_LOG_DEBUG("try to base64 decode: %s", 1010 payload->ptr); 1011 if (NULL == buffer_append_base64_decode(b, CONST_BUF_LEN(payload), BASE64_STANDARD)) { 1012 DEBUG_LOG_ERR("%s", "fail to base64-decode"); 1013 return -1; 1014 } 1015 buffer_clear(payload); 1016 /*chunkqueue_use_memory()*/ 1017 hctx->gw.wb->bytes_in += buffer_string_length(b)-len; 1018 } 1019 } 1020 break; 1021 default: /* never reach */ 1022 DEBUG_LOG_ERR("%s", "BUG: unknown state"); 1023 return -1; 1024 } 1025 } 1026 } 1027 /* XXX: should add ability to handle and preserve partial frames above */ 1028 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1029 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1030 return 0; 1031 } 1032 1033 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1034 1035 1036 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1037 1038 #define MOD_WEBSOCKET_OPCODE_CONT 0x00 1039 #define MOD_WEBSOCKET_OPCODE_TEXT 0x01 1040 #define MOD_WEBSOCKET_OPCODE_BIN 0x02 1041 #define MOD_WEBSOCKET_OPCODE_CLOSE 0x08 1042 #define MOD_WEBSOCKET_OPCODE_PING 0x09 1043 #define MOD_WEBSOCKET_OPCODE_PONG 0x0A 1044 1045 #define MOD_WEBSOCKET_FRAME_LEN16 0x7E 1046 #define MOD_WEBSOCKET_FRAME_LEN63 0x7F 1047 #define MOD_WEBSOCKET_FRAME_LEN16_CNT 2 1048 #define MOD_WEBSOCKET_FRAME_LEN63_CNT 8 1049 1050 static int send_rfc_6455(handler_ctx *hctx, mod_wstunnel_frame_type_t type, const char *payload, size_t siz) { 1051 char mem[10]; 1052 size_t len; 1053 1054 /* allowed null payload for ping, pong, close frame */ 1055 if (payload == NULL && ( type == MOD_WEBSOCKET_FRAME_TYPE_TEXT 1056 || type == MOD_WEBSOCKET_FRAME_TYPE_BIN )) { 1057 return -1; 1058 } 1059 1060 switch (type) { 1061 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1062 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_TEXT); 1063 DEBUG_LOG_DEBUG("%s", "type = text"); 1064 break; 1065 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1066 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_BIN); 1067 DEBUG_LOG_DEBUG("%s", "type = binary"); 1068 break; 1069 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1070 mem[0] = (char) (0x80 | MOD_WEBSOCKET_OPCODE_PING); 1071 DEBUG_LOG_DEBUG("%s", "type = ping"); 1072 break; 1073 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1074 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_PONG); 1075 DEBUG_LOG_DEBUG("%s", "type = pong"); 1076 break; 1077 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1078 default: 1079 mem[0] = (char)(0x80 | MOD_WEBSOCKET_OPCODE_CLOSE); 1080 DEBUG_LOG_DEBUG("%s", "type = close"); 1081 break; 1082 } 1083 1084 DEBUG_LOG_DEBUG("payload size=%zx", siz); 1085 if (siz < MOD_WEBSOCKET_FRAME_LEN16) { 1086 mem[1] = siz; 1087 len = 2; 1088 } 1089 else if (siz <= UINT16_MAX) { 1090 mem[1] = MOD_WEBSOCKET_FRAME_LEN16; 1091 mem[2] = (siz >> 8) & 0xff; 1092 mem[3] = siz & 0xff; 1093 len = 1+MOD_WEBSOCKET_FRAME_LEN16_CNT+1; 1094 } 1095 else { 1096 mem[1] = MOD_WEBSOCKET_FRAME_LEN63; 1097 mem[2] = 0; 1098 mem[3] = 0; 1099 mem[4] = 0; 1100 mem[5] = 0; 1101 mem[6] = (siz >> 24) & 0xff; 1102 mem[7] = (siz >> 16) & 0xff; 1103 mem[8] = (siz >> 8) & 0xff; 1104 mem[9] = siz & 0xff; 1105 len = 1+MOD_WEBSOCKET_FRAME_LEN63_CNT+1; 1106 } 1107 request_st * const r = hctx->gw.r; 1108 http_chunk_append_mem(r, mem, len); 1109 if (siz) http_chunk_append_mem(r, payload, siz); 1110 DEBUG_LOG_DEBUG("send data to client (fd=%d), frame size=%zx", 1111 r->con->fd, len+siz); 1112 return 0; 1113 } 1114 1115 static void unmask_payload(handler_ctx *hctx) { 1116 buffer * const b = hctx->frame.payload; 1117 for (size_t i = 0, used = buffer_string_length(b); i < used; ++i) { 1118 b->ptr[i] ^= hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt]; 1119 hctx->frame.ctl.mask_cnt = (hctx->frame.ctl.mask_cnt + 1) % 4; 1120 } 1121 } 1122 1123 static int recv_rfc_6455(handler_ctx *hctx) { 1124 request_st * const r = hctx->gw.r; 1125 chunkqueue *cq = r->reqbody_queue; 1126 buffer *payload = hctx->frame.payload; 1127 DEBUG_LOG_DEBUG("recv data from client (fd=%d), size=%llx", 1128 r->con->fd, (long long)chunkqueue_length(cq)); 1129 for (chunk *c = cq->first; c; c = c->next) { 1130 char *frame = c->mem->ptr+c->offset; 1131 /*(chunk_remaining_length() on MEM_CHUNK)*/ 1132 size_t flen = (size_t)(buffer_string_length(c->mem) - c->offset); 1133 /*(FILE_CHUNK not handled, but might need to add support)*/ 1134 force_assert(c->type == MEM_CHUNK); 1135 for (size_t i = 0; i < flen; ) { 1136 switch (hctx->frame.state) { 1137 case MOD_WEBSOCKET_FRAME_STATE_INIT: 1138 switch (frame[i] & 0x0f) { 1139 case MOD_WEBSOCKET_OPCODE_CONT: 1140 DEBUG_LOG_DEBUG("%s", "type = continue"); 1141 hctx->frame.type = hctx->frame.type_before; 1142 break; 1143 case MOD_WEBSOCKET_OPCODE_TEXT: 1144 DEBUG_LOG_DEBUG("%s", "type = text"); 1145 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_TEXT; 1146 hctx->frame.type_before = hctx->frame.type; 1147 break; 1148 case MOD_WEBSOCKET_OPCODE_BIN: 1149 DEBUG_LOG_DEBUG("%s", "type = binary"); 1150 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_BIN; 1151 hctx->frame.type_before = hctx->frame.type; 1152 break; 1153 case MOD_WEBSOCKET_OPCODE_PING: 1154 DEBUG_LOG_DEBUG("%s", "type = ping"); 1155 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PING; 1156 break; 1157 case MOD_WEBSOCKET_OPCODE_PONG: 1158 DEBUG_LOG_DEBUG("%s", "type = pong"); 1159 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_PONG; 1160 break; 1161 case MOD_WEBSOCKET_OPCODE_CLOSE: 1162 DEBUG_LOG_DEBUG("%s", "type = close"); 1163 hctx->frame.type = MOD_WEBSOCKET_FRAME_TYPE_CLOSE; 1164 return -1; 1165 break; 1166 default: 1167 DEBUG_LOG_ERR("%s", "type is invalid"); 1168 return -1; 1169 break; 1170 } 1171 i++; 1172 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH; 1173 break; 1174 case MOD_WEBSOCKET_FRAME_STATE_READ_LENGTH: 1175 if ((frame[i] & 0x80) != 0x80) { 1176 DEBUG_LOG_ERR("%s", "payload was not masked"); 1177 return -1; 1178 } 1179 hctx->frame.ctl.mask_cnt = 0; 1180 hctx->frame.ctl.siz = (uint64_t)(frame[i] & 0x7f); 1181 if (hctx->frame.ctl.siz == 0) { 1182 DEBUG_LOG_DEBUG("specified payload size=%llx", 1183 (unsigned long long)hctx->frame.ctl.siz); 1184 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1185 } 1186 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN16) { 1187 hctx->frame.ctl.siz = 0; 1188 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN16_CNT; 1189 hctx->frame.state = 1190 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1191 } 1192 else if (hctx->frame.ctl.siz == MOD_WEBSOCKET_FRAME_LEN63) { 1193 hctx->frame.ctl.siz = 0; 1194 hctx->frame.ctl.siz_cnt = MOD_WEBSOCKET_FRAME_LEN63_CNT; 1195 hctx->frame.state = 1196 MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH; 1197 } 1198 else { 1199 DEBUG_LOG_DEBUG("specified payload size=%llx", 1200 (unsigned long long)hctx->frame.ctl.siz); 1201 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1202 } 1203 i++; 1204 break; 1205 case MOD_WEBSOCKET_FRAME_STATE_READ_EX_LENGTH: 1206 hctx->frame.ctl.siz = 1207 (hctx->frame.ctl.siz << 8) + (frame[i] & 0xff); 1208 hctx->frame.ctl.siz_cnt--; 1209 if (hctx->frame.ctl.siz_cnt <= 0) { 1210 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1211 hctx->frame.ctl.siz > MOD_WEBSOCKET_BUFMAX) { 1212 DEBUG_LOG_WARN("frame size has been exceeded: %x", 1213 MOD_WEBSOCKET_BUFMAX); 1214 return -1; 1215 } 1216 DEBUG_LOG_DEBUG("specified payload size=%llx", 1217 (unsigned long long)hctx->frame.ctl.siz); 1218 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_READ_MASK; 1219 } 1220 i++; 1221 break; 1222 case MOD_WEBSOCKET_FRAME_STATE_READ_MASK: 1223 hctx->frame.ctl.mask[hctx->frame.ctl.mask_cnt] = frame[i]; 1224 hctx->frame.ctl.mask_cnt++; 1225 if (hctx->frame.ctl.mask_cnt >= MOD_WEBSOCKET_MASK_CNT) { 1226 hctx->frame.ctl.mask_cnt = 0; 1227 if (hctx->frame.type == MOD_WEBSOCKET_FRAME_TYPE_PING && 1228 hctx->frame.ctl.siz == 0) { 1229 mod_wstunnel_frame_send(hctx, 1230 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1231 NULL, 0); 1232 } 1233 if (hctx->frame.ctl.siz == 0) { 1234 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1235 } 1236 else { 1237 hctx->frame.state = 1238 MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD; 1239 } 1240 } 1241 i++; 1242 break; 1243 case MOD_WEBSOCKET_FRAME_STATE_READ_PAYLOAD: 1244 /* hctx->frame.ctl.siz <= SIZE_MAX */ 1245 if (hctx->frame.ctl.siz <= flen - i) { 1246 DEBUG_LOG_DEBUG("read payload, size=%llx", 1247 (unsigned long long)hctx->frame.ctl.siz); 1248 buffer_append_string_len(payload, frame+i, (size_t) 1249 (hctx->frame.ctl.siz & SIZE_MAX)); 1250 i += (size_t)(hctx->frame.ctl.siz & SIZE_MAX); 1251 hctx->frame.ctl.siz = 0; 1252 hctx->frame.state = MOD_WEBSOCKET_FRAME_STATE_INIT; 1253 DEBUG_LOG_DEBUG("rest of frame size=%zx", flen - i); 1254 /* SIZE_MAX < hctx->frame.ctl.siz */ 1255 } 1256 else { 1257 DEBUG_LOG_DEBUG("read payload, size=%zx", flen - i); 1258 buffer_append_string_len(payload, frame+i, flen - i); 1259 hctx->frame.ctl.siz -= flen - i; 1260 i += flen - i; 1261 DEBUG_LOG_DEBUG("rest of payload size=%llx", 1262 (unsigned long long)hctx->frame.ctl.siz); 1263 } 1264 switch (hctx->frame.type) { 1265 case MOD_WEBSOCKET_FRAME_TYPE_TEXT: 1266 case MOD_WEBSOCKET_FRAME_TYPE_BIN: 1267 { 1268 unmask_payload(hctx); 1269 chunkqueue_append_buffer(hctx->gw.wb, payload); 1270 buffer_clear(payload); 1271 break; 1272 } 1273 case MOD_WEBSOCKET_FRAME_TYPE_PING: 1274 if (hctx->frame.ctl.siz == 0) { 1275 unmask_payload(hctx); 1276 mod_wstunnel_frame_send(hctx, 1277 MOD_WEBSOCKET_FRAME_TYPE_PONG, 1278 payload->ptr, buffer_string_length(payload)); 1279 buffer_clear(payload); 1280 } 1281 break; 1282 case MOD_WEBSOCKET_FRAME_TYPE_PONG: 1283 buffer_clear(payload); 1284 break; 1285 case MOD_WEBSOCKET_FRAME_TYPE_CLOSE: 1286 default: 1287 DEBUG_LOG_ERR("%s", "BUG: invalid frame type"); 1288 return -1; 1289 } 1290 break; 1291 default: 1292 DEBUG_LOG_ERR("%s", "BUG: invalid state"); 1293 return -1; 1294 } 1295 } 1296 } 1297 /* XXX: should add ability to handle and preserve partial frames above */ 1298 /*(not chunkqueue_reset(); do not reset cq->bytes_in, cq->bytes_out)*/ 1299 chunkqueue_mark_written(cq, chunkqueue_length(cq)); 1300 return 0; 1301 } 1302 1303 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1304 1305 1306 int mod_wstunnel_frame_send(handler_ctx *hctx, mod_wstunnel_frame_type_t type, 1307 const char *payload, size_t siz) { 1308 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1309 if (hctx->hybivers >= 8) return send_rfc_6455(hctx, type, payload, siz); 1310 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1311 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1312 if (0 == hctx->hybivers) return send_ietf_00(hctx, type, payload, siz); 1313 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1314 return -1; 1315 } 1316 1317 int mod_wstunnel_frame_recv(handler_ctx *hctx) { 1318 #ifdef _MOD_WEBSOCKET_SPEC_RFC_6455_ 1319 if (hctx->hybivers >= 8) return recv_rfc_6455(hctx); 1320 #endif /* _MOD_WEBSOCKET_SPEC_RFC_6455_ */ 1321 #ifdef _MOD_WEBSOCKET_SPEC_IETF_00_ 1322 if (0 == hctx->hybivers) return recv_ietf_00(hctx); 1323 #endif /* _MOD_WEBSOCKET_SPEC_IETF_00_ */ 1324 return -1; 1325 } 1326