1 /**
2 * Copyright 2011 Christian Liesch
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17 /**
18 * @file
19 *
20 * @Author christian liesch <liesch@gmx.ch>
21 *
22 * Implementation of the HTTP Test Tool Websocket Extention
23 */
24
25 /************************************************************************
26 * Includes
27 ***********************************************************************/
28 #include "module.h"
29
30 /************************************************************************
31 * Definitions
32 ***********************************************************************/
33 #define WS_8_TYPE_CONTINUE 0x0
34 #define WS_8_TYPE_TEXT 0x1
35 #define WS_8_TYPE_BINARY 0x2
36 #define WS_8_TYPE_CLOSE 0x8
37 #define WS_8_TYPE_PING 0x9
38 #define WS_8_TYPE_PONG 0xA
39
40 const char * ws_module = "ws_module";
41
42 typedef struct ws_socket_config_s {
43 int version;
44 } ws_socket_config_t;
45
46 /************************************************************************
47 * Private
48 ***********************************************************************/
49
50 /**
51 * GET ssl socket config from socket
52 * @param worker IN worker
53 * @return socket config
54 */
ws_get_socket_config(worker_t * worker)55 static ws_socket_config_t *ws_get_socket_config(worker_t *worker) {
56 ws_socket_config_t *config;
57 if (!worker || !worker->socket) {
58 return NULL;
59 }
60
61 config = module_get_config(worker->socket->config, ws_module);
62 if (config == NULL) {
63 config = apr_pcalloc(worker->pbody, sizeof(*config));
64 module_set_config(worker->socket->config,
65 apr_pstrdup(worker->pbody, ws_module), config);
66 }
67 return config;
68 }
69
70 /**
71 * Do hex to bin transformation with this hook, this is called
72 * after late variable replacement.
73 *
74 * @param worker IN worker context
75 * @param payload IN payload as a string of space separated hex digits
76 * @param binary OUT binary data buffer
77 * @param binary_len OUT binary data buffer length
78 */
ws_hex_to_binary(worker_t * worker,char * payload,char ** binary,uint64_t * binary_len)79 static apr_status_t ws_hex_to_binary(worker_t *worker, char *payload, char **binary, uint64_t *binary_len) {
80 char *buf;
81 apr_size_t i;
82 apr_size_t len;
83
84 apr_collapse_spaces(payload, payload);
85 /* callculate buf len */
86 len = strlen(payload);
87 if (len && len%2 != 1) {
88 len /= 2;
89 }
90 else {
91 worker_log(worker, LOG_ERR, "Binary data must have an equal number of digits");
92 return APR_EINVAL;
93 }
94
95 buf = apr_pcalloc(worker->pbody, len);
96
97 for (i = 0; i < len; i++) {
98 char hex[3];
99 hex[0] = payload[i * 2];
100 hex[1] = payload[i * 2 + 1];
101 hex[2] = 0;
102 buf[i] = (char )apr_strtoi64(hex, NULL, 16);
103 }
104 *binary = buf;
105 *binary_len = len;
106
107 return APR_SUCCESS;
108 }
109
110 /************************************************************************
111 * Commands
112 ***********************************************************************/
113 /**
114 * Set version
115 * @param worker IN callee
116 * @param parent IN caller
117 * @param ptmp IN temp pool
118 * @return apr status
119 */
block_WS_VERSION(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)120 static apr_status_t block_WS_VERSION(worker_t *worker, worker_t *parent,
121 apr_pool_t *ptmp) {
122 ws_socket_config_t *sconf = ws_get_socket_config(worker);
123 sconf->version = 13;
124 return APR_SUCCESS;
125 }
126
127 /**
128 * Receive websocket frames
129 * @param worker IN callee
130 * @param parent IN caller
131 * @param ptmp IN temp pool
132 * @return apr status
133 */
block_WS_RECV(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)134 static apr_status_t block_WS_RECV(worker_t *worker, worker_t *parent,
135 apr_pool_t *ptmp) {
136 apr_status_t status;
137 apr_size_t len;
138 int masked;
139 uint8_t op;
140 uint8_t pl_len;
141 uint32_t mask = 0x0;
142 uint64_t payload_len = 0;
143 char *type;
144 char *payload = NULL;
145
146 const char *type_param = store_get(worker->params, "1");
147 const char *len_param = store_get(worker->params, "2");
148
149 if (!worker->socket->sockreader) {
150 worker_log(worker, LOG_ERR,
151 "Websockets need a open HTTP stream, use _SOCKET");
152 return APR_ENOSOCKET;
153 }
154
155 len = 1;
156 if ((status = sockreader_read_block(worker->socket->sockreader,
157 (char *)&op, &len)) != APR_SUCCESS) {
158 worker_log(worker, LOG_ERR, "Could not read first frame byte");
159 goto exit;
160 }
161 worker_log(worker, LOG_DEBUG, "Got opcode 0x%X", op);
162 type = NULL;
163 if ((op >> 7) & 1) {
164 type = apr_pstrcat(ptmp, "FIN", type?",":NULL, type, NULL);
165 }
166
167 switch (op &0xf) {
168 case WS_8_TYPE_CONTINUE:
169 type = apr_pstrcat(ptmp, "CONTINUE", type?",":NULL, type, NULL);
170 break;
171 case WS_8_TYPE_TEXT:
172 type = apr_pstrcat(ptmp, "TEXT", type?",":NULL, type, NULL);
173 break;
174 case WS_8_TYPE_BINARY:
175 type = apr_pstrcat(ptmp, "BINARY", type?",":NULL, type, NULL);
176 break;
177 case WS_8_TYPE_CLOSE:
178 type = apr_pstrcat(ptmp, "CLOSE", type?",":NULL, type, NULL);
179 break;
180 case WS_8_TYPE_PING:
181 type = apr_pstrcat(ptmp, "PING", type?",":NULL, type, NULL);
182 break;
183 case WS_8_TYPE_PONG:
184 type = apr_pstrcat(ptmp, "PONG", type?",":NULL, type, NULL);
185 break;
186 }
187
188 len = 1;
189 if ((status = sockreader_read_block(worker->socket->sockreader,
190 (char *)&pl_len, &len)) != APR_SUCCESS) {
191 worker_log(worker, LOG_ERR, "Could not read first lenght byte");
192 goto exit;
193 }
194 worker_log(worker, LOG_DEBUG, "Got first length byte %x", pl_len);
195 masked = (pl_len & 0x80);
196 if (masked) {
197 type = apr_pstrcat(ptmp, "MASKED", type?",":NULL, type, NULL);
198 }
199 worker_log(worker, LOG_DEBUG, "Opcode: %s", type);
200 if (type_param) {
201 worker_var_set(worker, type_param, type?type:"<NONE>");
202 }
203 pl_len = pl_len & 0x7f;
204
205 #if APR_IS_BIGENDIAN
206 worker_log(worker, LOG_DEBUG, "bigendian", type);
207 #else
208 worker_log(worker, LOG_DEBUG, "littlendian", type);
209 #endif
210 if (pl_len == 126) {
211 uint16_t length;
212 worker_log(worker, LOG_DEBUG, "payload uint16 read 2 length bytes", type);
213 len = 2;
214 if ((status = sockreader_read_block(worker->socket->sockreader,
215 (char *)&length, &len))
216 != APR_SUCCESS) {
217 worker_log(worker, LOG_ERR, "Could not read 16 bit payload length");
218 goto exit;
219 }
220 #if APR_IS_BIGENDIAN
221 payload_len = length;
222 #else
223 payload_len = swap16(length);
224 #endif
225 }
226 else if (pl_len == 127) {
227 uint64_t length;
228 worker_log(worker, LOG_DEBUG, "payload uint64 read 4 length bytes", type);
229 len = 8;
230 if ((status = sockreader_read_block(worker->socket->sockreader,
231 (char *)&length, &len))
232 != APR_SUCCESS) {
233 worker_log(worker, LOG_ERR, "Could not read 64 bit payload length");
234 goto exit;
235 }
236 #if APR_IS_BIGENDIAN
237 payload_len = length;
238 #else
239 payload_len = swap64(length);
240 #endif
241 }
242 else {
243 worker_log(worker, LOG_DEBUG, "payload uint8", type);
244 payload_len = pl_len;
245 }
246
247 if (masked) {
248 len = 4;
249 if ((status = sockreader_read_block(worker->socket->sockreader,
250 (char *)&mask, &len)) != APR_SUCCESS) {
251 worker_log(worker, LOG_ERR, "Could not read mask");
252 goto exit;
253 }
254 }
255
256 if (len_param) {
257 worker_var_set(worker, len_param, apr_itoa(ptmp, payload_len));
258 }
259 worker_log(worker, LOG_DEBUG, "Payload-Length: %ld", payload_len);
260 payload = apr_pcalloc(worker->pbody, payload_len + 1);
261 len = payload_len;
262 status = sockreader_read_block(worker->socket->sockreader, payload, &len);
263 worker_log(worker, LOG_DEBUG, "Got: %ld bytes; Status: %d", payload_len, status);
264 if (status != APR_SUCCESS) {
265 worker_log(worker, LOG_ERR, "Could not read payload");
266 goto exit;
267 }
268
269 if (masked) {
270 int i, j;
271 for (i = 0; i < payload_len; i++) {
272 j = i % 4;
273 payload[i] ^= ((uint8_t *)&mask)[j];
274 }
275 }
276
277 exit:
278 {
279 apr_status_t hndl_buf_status;
280 hndl_buf_status = worker_handle_buf(worker, ptmp, payload, payload_len);
281 if (hndl_buf_status != APR_SUCCESS) {
282 worker_log(worker, LOG_ERR, "inspect payload failed");
283 return status;
284 }
285 status = worker_assert(worker, status);
286 return status;
287 }
288 }
289
290 /**
291 * Send websocket frames
292 * @param worker IN callee
293 * @param parent IN caller
294 * @param ptmp IN temp pool
295 * @return apr status
296 */
block_WS_SEND(worker_t * worker,worker_t * parent,apr_pool_t * ptmp)297 static apr_status_t block_WS_SEND(worker_t *worker, worker_t *parent,
298 apr_pool_t *ptmp) {
299 apr_status_t status;
300 char *last;
301 char *e;
302 char *op_param = store_get_copy(worker->params, ptmp, "1");
303 const char *payload_len = store_get(worker->params, "2");
304 char *payload = store_get_copy(worker->params, ptmp, "3");
305 const char *mask_str = store_get(worker->params, "4");
306 uint8_t op = 0;
307 uint8_t pl_len_8 = 0;
308 uint16_t pl_len_16 = 0;
309 uint64_t pl_len_64 = 0;
310 uint64_t len;
311 int is_binary = 0;
312
313 if (!worker->socket || !worker->socket->transport) {
314 worker_log(worker, LOG_ERR, "No established socket for websocket protocol");
315 return APR_ENOSOCKET;
316 }
317
318 worker_log(worker, LOG_DEBUG, "payload: \"%s\"", payload);
319
320 e = apr_strtok(op_param, ",", &last);
321 while (e) {
322 if (strcmp(e, "FIN") == 0) {
323 op |= 1 << 7;
324 }
325 else if (strcmp(e, "CONTINUE") == 0) {
326 op |= WS_8_TYPE_CONTINUE;
327 }
328 else if (strcmp(e, "TEXT") == 0) {
329 op |= WS_8_TYPE_TEXT;
330 }
331 else if (strcmp(e, "BINARY") == 0) {
332 op |= WS_8_TYPE_BINARY;
333 is_binary = 1;
334 }
335 else if (strcmp(e, "CLOSE") == 0) {
336 op |= WS_8_TYPE_CLOSE;
337 }
338 else if (strcmp(e, "PING") == 0) {
339 op |= WS_8_TYPE_PING;
340 }
341 else if (strcmp(e, "PONG") == 0) {
342 op |= WS_8_TYPE_PONG;
343 }
344 e = apr_strtok(NULL, ",", &last);
345 }
346 worker_log(worker, LOG_DEBUG, "Send opcod 0x%X", op);
347
348 if (strcmp(payload_len, "AUTO") == 0) {
349 if (payload) {
350 if (is_binary) {
351 char *result;
352 if ((status = ws_hex_to_binary(worker, payload, &result, &len)) != APR_SUCCESS) {
353 return status;
354 }
355 payload = result;
356 }
357 else {
358 len = strlen(payload);
359 }
360 }
361 else {
362 len = 0;
363 }
364 }
365 else {
366 len = apr_atoi64(payload_len);
367 }
368
369 worker_log(worker, LOG_DEBUG, "Payload length: %ld", len);
370
371 if (len < 126) {
372 pl_len_8 = len;
373 }
374 else if (len <= 0xFFFF) {
375 uint16_t tmp;
376 pl_len_8 = 126;
377 tmp = len;
378 #if APR_IS_BIGENDIAN
379 pl_len_16 = tmp;
380 #else
381 pl_len_16 = swap16(tmp);
382 #endif
383 }
384 else {
385 uint64_t tmp;
386 pl_len_8 = 127;
387 tmp = len;
388 #if APR_IS_BIGENDIAN
389 pl_len_64 = tmp;
390 #else
391 pl_len_64 = swap64(tmp);
392 #endif
393 }
394
395 pl_len_8 = pl_len_8;
396 worker_log(worker, LOG_DEBUG, "pl_len: %0x", pl_len_8);
397 if (mask_str) {
398 pl_len_8 |= 0x80;
399 }
400 worker_log(worker, LOG_DEBUG, "pl_len_8: %0x, pl_len_16: %ld, pl_len_64: %ld",
401 pl_len_8, pl_len_16, pl_len_64);
402
403 if ((status = transport_write(worker->socket->transport,
404 (const char *)&op, 1)) != APR_SUCCESS) {
405 worker_log(worker, LOG_ERR, "Could not send Opcode");
406 return status;
407 }
408
409 if ((status = transport_write(worker->socket->transport,
410 (const char *)&pl_len_8, 1)) != APR_SUCCESS) {
411 worker_log(worker, LOG_ERR, "Could not send first len byte");
412 return status;
413 }
414
415 if (pl_len_16) {
416 if ((status = transport_write(worker->socket->transport,
417 (const char *)&pl_len_16, 2))
418 != APR_SUCCESS) {
419 worker_log(worker, LOG_ERR, "Could not send 16 bit len bytes");
420 return status;
421 }
422 }
423
424 if (pl_len_64) {
425 if ((status = transport_write(worker->socket->transport,
426 (const char *)&pl_len_64, 8))
427 != APR_SUCCESS) {
428 worker_log(worker, LOG_ERR, "Could not send 64 bit len bytes");
429 return status;
430 }
431 }
432
433 if (mask_str) {
434 uint32_t mask = apr_strtoi64(mask_str, NULL, 0);
435 int i, j;
436 for (i = 0; i < len; i++) {
437 j = i % 4;
438 payload[i] ^= ((uint8_t *)&mask)[j];
439 }
440 if ((status = transport_write(worker->socket->transport,
441 (const char *)&mask, 4)) != APR_SUCCESS) {
442 worker_log(worker, LOG_ERR, "Could not send mask bytes");
443 return status;
444 }
445 }
446
447 if ((status = transport_write(worker->socket->transport, payload, len))
448 != APR_SUCCESS) {
449 worker_log(worker, LOG_ERR, "Could not send payload");
450 return status;
451 }
452 logger_log_buf(worker->logger, LOG_INFO, '>', payload, len);
453
454
455 return APR_SUCCESS;
456 }
457
458 /************************************************************************
459 * Module
460 ***********************************************************************/
websocket_module_init(global_t * global)461 apr_status_t websocket_module_init(global_t *global) {
462 apr_status_t status;
463
464 if ((status = module_command_new(global, "WS", "_RECV", "",
465 "Receive websocket frames",
466 block_WS_RECV)) != APR_SUCCESS) {
467 return status;
468 }
469
470 if ((status = module_command_new(global, "WS", "_SEND", "<type> <length> <data> <mask>",
471 "Send websocket frames\n"
472 " <type>: can be one or more of the following keywords\n"
473 " FIN, CONTINUE, CLOSE, TEXT, BINARY, PING, PONG\n"
474 " there are combinations which will not work, see also RFC\n"
475 " of websockets to get a clue what is possible and what not.\n"
476 " <length>: Length of data or AUTO to do this automaticaly\n"
477 " <data>: Data to be send if spaces the data must be quoted\n"
478 " <mask>: Optional 64 Byte number to mask data",
479 block_WS_SEND)) != APR_SUCCESS) {
480 return status;
481 }
482
483 if ((status = module_command_new(global, "WS", "_VERSION", "",
484 "Set version, for the moment only version 13 is implemented",
485 block_WS_VERSION)) != APR_SUCCESS) {
486 return status;
487 }
488
489 return APR_SUCCESS;
490 }
491
492