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