1 /**
2 * Copyright 2006 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 Proxy.
23 */
24
25 /* affects include files on Solaris */
26 #define BSD_COMP
27
28 /************************************************************************
29 * Includes
30 ***********************************************************************/
31 #ifdef HAVE_CONFIG_H
32 #include <config.h>
33 #endif
34 #include <apr_version.h>
35 #include "defines.h"
36
37 #include <openssl/ssl.h>
38
39 #include <apr.h>
40 #include <apr_lib.h>
41 #include <apr_signal.h>
42 #include <apr_strings.h>
43 #include <apr_getopt.h>
44 #include <apr_portable.h>
45 #include <apr_errno.h>
46 #include <apr_thread_proc.h>
47 #include <apr_thread_cond.h>
48 #include <apr_thread_mutex.h>
49 #include <apr_hash.h>
50
51 #include "defines.h"
52 #include "ssl.h"
53 #include "regex.h"
54 #include "file.h"
55 #include "socket.h"
56 #include "worker.h"
57 #include "logger.h"
58 #include "appender_std.h"
59 #include "conf.h"
60 #include "util.h"
61 #include "module.h"
62
63 /************************************************************************
64 * Defines
65 ***********************************************************************/
66 #define BLOCK_MAX 8192
67
68 /************************************************************************
69 * Typedefs
70 ***********************************************************************/
71 typedef struct self_s {
72 apr_pool_t *pool;
73 apr_file_t *ofp;
74 char *timeout;
75 int port;
76 worker_t *client;
77 char *url_filter;
78 htt_regex_t *url_filter_regex;
79 apr_thread_mutex_t *mutex;
80 char *host_var;
81 char *port_var;
82 char *uri_var;
83 char *host_port_var;
84 char *cookie_pre;
85 char *pre;
86 char *post;
87 int flags;
88 #define SELF_FLAGS_NONE 0
89 #define SELF_FLAGS_SKIP_COOKIE_FIRST_TIME 1
90 #define SELF_FLAGS_GOT_SET_COOKIE 2
91 logger_t *logger;
92 } self_t;
93
94 typedef struct request_s {
95 apr_pool_t *pool;
96 char *host;
97 char *port;
98 char *method;
99 char *url;
100 char *version;
101 char *protocol;
102 char *request_line;
103 apr_table_t *headers;
104 apr_size_t len;
105 char *body;
106 int is_ssl;
107 } request_t;
108
109 typedef struct response_s {
110 apr_pool_t *pool;
111 char *version;
112 char *status_text;
113 int status;
114 char *status_line;
115 apr_table_t *headers;
116 apr_size_t len;
117 char *body;
118 } response_t;
119
120 apr_status_t tcp_module_init(global_t *global);
121 /************************************************************************
122 * Implementation
123 ***********************************************************************/
124 char *none = "";
125 int new_session = 0;
126 int success = 1;
127
128 /**
129 * Helper
130 */
131
132 /**
133 * get the status string
134 *
135 * @param p IN pool
136 * @param rc IN status to print
137 *
138 * @return status string
139 */
get_status_str(apr_pool_t * p,apr_status_t rc)140 static char *get_status_str(apr_pool_t * p, apr_status_t rc) {
141 char *text = apr_pcalloc(p, 201);
142 apr_strerror(rc, text, 200);
143 return text;
144 }
145
146 apr_getopt_option_t options[] = {
147 { "version", 'v', 0, "Print version number and exit" },
148 { "help", 'h', 0, "Display usage information (this message)" },
149 { "port", 'p', 1, "Port" },
150 { "dest", 'd', 1, "Destination file, default file is \"file\"" },
151 { "url-filter", 'u', 1, "URL filter regex default is none (blacklist)" },
152 { "log-level", 'l', 1, "Log level 0-4" },
153 { "host-var", 'H', 1, "Variable name for host" },
154 { "port-var", 'P', 1, "Variable name for port" },
155 { "root-var", 'U', 1, "Web application root variable name" },
156 { "host-header-var", 'A', 1, "Variable name for host header value" },
157 { "socket-tmo", 't', 1, "Socket timeout [ms], default 30000 ms" },
158 { "header-file", 'i', 1, "File with header text" },
159 { "trailer-file", 'e', 1, "File with trailer text" },
160 { "cookie-prefix", 'c', 1, "Cookie variable prefix, default is COOKIE_" },
161 { "config", 'C', 1, "Configuration file" },
162 { NULL, 0, 0, NULL },
163 };
164
165 /**
166 * display usage information
167 *
168 * @progname IN name of the programm
169 */
usage(const char * progname)170 static void usage(const char *progname) {
171 int i = 0;
172
173 fprintf(stdout, "%s do record a HTTP session as a httest script", progname);
174 fprintf(stdout, "\nUsage: %s [OPTIONS]\n", progname);
175 fprintf(stdout, "\nOptions:");
176 while (options[i].optch) {
177 if (options[i].optch <= 255) {
178 fprintf(stdout, "\n -%c --%-15s %s", options[i].optch, options[i].name,
179 options[i].description);
180 }
181 else {
182 fprintf(stdout, "\n --%-15s %s", options[i].name,
183 options[i].description);
184 }
185 i++;
186 }
187 fprintf(stdout, "\n\nExample: %s -p 8888 -d init -H HOST -P PORT -u \"(.*\\.png\\;.*$)|(.*\\.css\\;.*$)|(.*\\.ico\\;.*$)|(.*\\.js\\;.*$)\"\n", progname);
188 fprintf(stdout, "\n");
189 }
190
191
192 /**
193 * print given file to the output file
194 *
195 * @param self IN self pointer
196 * @param file IN file name to read from
197 */
print_file(self_t * self,char * file)198 static void print_file(self_t *self, char *file) {
199 apr_status_t status;
200 apr_file_t *fp;
201 apr_pool_t *pool;
202 bufreader_t *br;
203 char *line;
204
205 if (!file) {
206 return;
207 }
208
209 apr_pool_create(&pool, NULL);
210
211 if ((status =
212 apr_file_open(&fp, file, APR_READ, APR_OS_DEFAULT, pool))
213 != APR_SUCCESS) {
214 fprintf(stderr, "\nWarning: Can not open file '%s': %s(%d)\n", file,
215 get_status_str(pool, status), status);
216 return;
217 }
218
219 if ((status = bufreader_new(&br, fp, pool)) != APR_SUCCESS) {
220 fprintf(stderr, "\nWarning: Could not create bufreader: %s(%d)\n",
221 get_status_str(self->pool, status), status);
222 return;
223 }
224
225 apr_file_printf(self->ofp, "\n");
226 while ((status = bufreader_read_line(br, &line)) == APR_SUCCESS) {
227 apr_file_printf(self->ofp, "%s\n", line);
228 }
229
230 apr_file_close(fp);
231
232 apr_pool_destroy(pool);
233 }
234
235 /**
236 * Call command
237 *
238 * @param cmd IN command
239 * @param func IN function
240 * @param name IN displayed name (optional may be NULL)
241 * @param params IN parameter line for the called function
242 *
243 * @return apr status
244 */
call_command(worker_t * worker,apr_status_t (* func)(command_t *,worker_t *,char *,apr_pool_t *),char * name,char * params)245 static apr_status_t call_command(worker_t *worker, apr_status_t (*func)(command_t*, worker_t*, char*, apr_pool_t*), char *name, char *params) {
246 command_t cmd;
247 memset(&cmd, 0, sizeof(cmd));
248 cmd.name = name;
249 cmd.func = (command_f)func;
250 return cmd.func(&cmd, worker, params, worker->pbody);
251 }
252
253 /**
254 * Wait for request
255 *
256 * @param worker IN worker object
257 * @param r IN request record
258 *
259 * @return an apr status
260 */
wait_request(worker_t * worker,request_t * r)261 static apr_status_t wait_request(worker_t * worker, request_t *r) {
262 char *line;
263 apr_status_t status;
264 sockreader_t *sockreader;
265 char *last;
266 char *key;
267 apr_size_t peeklen;
268
269 const char *val = "";
270 int i = 0;
271
272 r->len = 0;
273 r->body = NULL;
274
275 peeklen = worker->socket->peeklen;
276 worker->socket->peeklen = 0;
277 if ((status = sockreader_new(&sockreader, worker->socket->transport,
278 worker->socket->peek, peeklen)) != APR_SUCCESS) {
279 goto out_err;
280 }
281
282 r->headers = apr_table_make(r->pool, 10);
283
284 while ((status = sockreader_read_line(sockreader, &line)) == APR_SUCCESS &&
285 line[0] != 0) {
286 /** get request line */
287 if (i == 0) {
288 worker_log(worker, LOG_INFO, "Requested url: %s", line);
289 r->method = apr_strtok(line, " ", &r->url);
290 if (strcasecmp(r->method, "CONNECT") != 0) {
291 r->protocol = apr_strtok(NULL, "://", &r->url);
292 r->host = apr_strtok(NULL, "/", &r->url);
293 /* if url is empty do it special */
294 if (r->url[0] == ' ') {
295 r->url[0] = 0;
296 r->version = apr_strtok(&r->url[1], " ", &last);
297 }
298 else {
299 r->url = apr_strtok(r->url, " ", &r->version);
300 }
301 r->host = apr_strtok(r->host, ":", &last);
302 if (strcasecmp(r->protocol, "https") == 0) {
303 r->is_ssl = 1;
304 }
305 else {
306 r->is_ssl = 0;
307 }
308 if (last[0]) {
309 r->port = last;
310 }
311 else {
312 r->port = r->is_ssl ?
313 apr_pstrdup(r->pool, "443") : apr_pstrdup(r->pool, "80");
314 }
315 r->request_line = apr_psprintf(r->pool, "%s /%s %s", r->method,
316 r->url, r->version);
317 }
318 else {
319 worker_log(worker, LOG_ERR, "SSL tunneling is not supported");
320 call_command(worker, command_DATA, "_HTTP/1.1 400 Bad Request", "");
321 call_command(worker, command_DATA, "__", "");
322 call_command(worker, command_CLOSE, "_CLOSE", "");
323 }
324 }
325 else {
326 /* headers */
327 worker_log(worker, LOG_INFO, "<%s", line);
328 key = apr_strtok(line, ":", &last);
329 val = last;
330 if (val) {
331 if (strncasecmp(key, "Proxy-", 6) == 0) {
332 key = apr_strtok(key, "-", &last);
333 key = apr_strtok(NULL, "-", &last);
334 }
335 /* ignore If-Modified-Since and If-Match */
336 apr_table_add(r->headers, key, &val[1]);
337 }
338 }
339 ++i;
340 }
341
342 /* get transfer type */
343 if ((val = apr_table_get(r->headers, "Content-Length"))) {
344 r->len = apr_atoi64(val);
345 status = content_length_reader(sockreader, &r->body, &r->len, val);
346 }
347 else if ((val = apr_table_get(r->headers, "Transfer-Encoding"))) {
348 status = transfer_enc_reader(sockreader, &r->body, &r->len, val);
349 }
350 out_err:
351 return APR_SUCCESS;
352 }
353
354 /**
355 * Wait for data (same as command_recv)
356 *
357 * @param worker IN worker object
358 * @param r IN response record
359 *
360 * @return an apr status
361 */
wait_response(worker_t * worker,response_t * r)362 static apr_status_t wait_response(worker_t * worker, response_t *r) {
363 char *line;
364 apr_status_t status;
365 sockreader_t *sockreader;
366 char *last;
367 char *key;
368 apr_size_t peeklen;
369
370 const char *val = "";
371 int i = 0;
372
373 r->body = NULL;
374 r->len = 0;
375
376 if ((status = worker_flush(worker, worker->pbody)) != APR_SUCCESS) {
377 return status;
378 }
379
380 peeklen = worker->socket->peeklen;
381 worker->socket->peeklen = 0;
382 if ((status = sockreader_new(&sockreader, worker->socket->transport,
383 worker->socket->peek, peeklen)) != APR_SUCCESS) {
384 goto out_err;
385 }
386
387 r->headers = apr_table_make(r->pool, 10);
388
389 while ((status = sockreader_read_line(sockreader, &line)) == APR_SUCCESS &&
390 line[0] != 0) {
391 /** get request line */
392 if (i == 0) {
393 r->status_line = line;
394 }
395 else {
396 /* headers */
397 key = apr_strtok(line, ":", &last);
398 val = last;
399 if (val) {
400 if (strncasecmp(key, "Proxy-", 6) == 0) {
401 key = apr_strtok(key, "-", &last);
402 key = apr_strtok(NULL, "-", &last);
403 }
404 apr_table_add(r->headers, key, &val[1]);
405 }
406 }
407 ++i;
408 }
409
410 /* get transfer type */
411 if ((val = apr_table_get(r->headers, "Content-Length"))) {
412 r->len = apr_atoi64(val);
413 status = content_length_reader(sockreader, &r->body, &r->len, val);
414 }
415 else if ((val = apr_table_get(r->headers, "Transfer-Encoding"))) {
416 status = transfer_enc_reader(sockreader, &r->body, &r->len, val);
417 }
418 else if ((val = apr_table_get(r->headers, "Encapsulated"))) {
419 status = encapsulated_reader(sockreader, &r->body, &r->len, val,
420 apr_table_get(r->headers, "Preview"));
421 }
422 else if ((val = apr_table_get(r->headers, "Connection"))) {
423 status = eof_reader(sockreader, &r->body, &r->len, val);
424 }
425
426 out_err:
427 return status;
428 }
429
430 /**
431 * Connect to worker
432 *
433 * @param self IN self pointer
434 * @param worker IN worker to connect to
435 * @param p IN pool
436 * @param host IN
437 * @param port IN
438 * @param write IN write script or not
439 *
440 * @return apr status
441 */
do_connect(self_t * self,worker_t * worker,apr_pool_t * p,int is_ssl,char * host,char * port,int write)442 static apr_status_t do_connect(self_t *self, worker_t *worker, apr_pool_t *p,
443 int is_ssl, char *host, char *port, int write) {
444 apr_status_t status;
445 /* connect to server */
446 if ((status = call_command(worker, command_REQ, "_REQ",
447 apr_psprintf(p, "%s %s%s", host, is_ssl ? "SSL:" : "", port)))
448 != APR_SUCCESS) {
449 return status;
450 }
451 if (write) {
452 if (self->host_var) {
453 host = apr_psprintf(self->pool, "$%s", self->host_var);
454 }
455 if (self->port_var) {
456 port = apr_psprintf(self->pool, "%s$%s", is_ssl ? "SSL:" : "", self->port_var);
457 }
458 apr_file_printf(self->ofp, "\n\n_REQ %s %s", host, port);
459 }
460 return status;
461 }
462
463 /**
464 * write request line
465 *
466 * @param self IN self pointer
467 * @param worker IN worker to connect to
468 * @param p IN pool
469 * @param request_line IN
470 * @param write IN write script or not
471 *
472 * @return apr status
473 */
do_request_line(self_t * self,worker_t * worker,apr_pool_t * p,char * request_line,int write)474 static apr_status_t do_request_line(self_t *self, worker_t *worker, apr_pool_t *p,
475 char *request_line, int write) {
476 apr_status_t status;
477 char *tmp;
478 char *last;
479
480 /* write request line */
481 if ((status = call_command(worker, command_DATA, "__", request_line))
482 != APR_SUCCESS) {
483 return status;
484 }
485
486 if (self->uri_var) {
487 tmp = apr_strtok(request_line, " /", &last);
488 apr_strtok(NULL, "/", &last);
489 request_line = apr_pstrcat(worker->pbody, tmp, " $",self->uri_var, "/", last, NULL);
490 }
491 if (write) {
492 apr_file_printf(self->ofp, "\n__%s", request_line);
493 }
494
495 return status;
496 }
497
498 /**
499 * write headers
500 *
501 * @param self IN self pointer
502 * @param worker IN worker to connect to
503 * @param p IN pool
504 * @param headers IN
505 * @param write IN write script or not
506 *
507 * @return apr status
508 */
do_headers(self_t * self,worker_t * worker,apr_pool_t * p,apr_table_t * headers,int write)509 static apr_status_t do_headers(self_t *self, worker_t *worker, apr_pool_t *p,
510 apr_table_t *headers, int write) {
511 apr_status_t status;
512 apr_table_entry_t *e;
513 const char *val;
514 char *last;
515 char *key;
516 char *ignore;
517 char *cookie;
518 char *cookie_val;
519 int i;
520
521 /* write headers */
522 e = (apr_table_entry_t *) apr_table_elts(headers)->elts;
523 for (i = 0; i < apr_table_elts(headers)->nelts; ++i) {
524 if (strcasecmp(e[i].key, "Cookie") == 0) {
525 if (!(self->flags & SELF_FLAGS_SKIP_COOKIE_FIRST_TIME)) {
526 if ((status = call_command(worker, command_DATA, "__",
527 apr_psprintf(p, "%s: %s", e[i].key, e[i].val)))
528 != APR_SUCCESS) {
529 goto error;
530 }
531 }
532 }
533 else {
534 if ((status = call_command(worker, command_DATA, "__",
535 apr_psprintf(p, "%s: %s", e[i].key, e[i].val)))
536 != APR_SUCCESS) {
537 goto error;
538 }
539 }
540 if (write) {
541 if (strcasecmp(e[i].key, "Content-Length") == 0) {
542 apr_file_printf(self->ofp, "\n__Content-Length: AUTO");
543 }
544 else {
545 if (strcasecmp(e[i].key, "Host") == 0) {
546 if (self->host_port_var) {
547 apr_file_printf(self->ofp, "\n__%s: $%s", e[i].key, self->host_port_var);
548 }
549 }
550 else if (strcasecmp(e[i].key, "Referer") == 0) {
551 /* skip this header */
552 }
553 else if (strcasecmp(e[i].key, "Cookie") == 0) {
554 if (!(self->flags & SELF_FLAGS_SKIP_COOKIE_FIRST_TIME)) {
555 apr_file_printf(self->ofp, "\n__Cookie: ");
556 cookie = apr_pstrdup(worker->pbody, e[i].val);
557 cookie = apr_strtok(cookie, ";", &last);
558 while (cookie) {
559 /* split key from key=val */
560 key = apr_strtok(cookie, "=", &ignore);
561 while (*key == ' ') ++key;
562 val = apr_strtok(NULL, "=", &ignore);
563 if (val) {
564 cookie_val = apr_psprintf(worker->pbody, "%s=$%s%s",
565 key, self->cookie_pre, key);
566 }
567 else {
568 cookie_val = apr_psprintf(worker->pbody, "%s", key);
569 }
570 /* get next one here so we can detect last one in the next if cond */
571 cookie = apr_strtok(NULL, ";", &last);
572 if (cookie) {
573 apr_file_printf(self->ofp, "%s; ", cookie_val);
574 }
575 else {
576 /* last one */
577 apr_file_printf(self->ofp, "%s", cookie_val);
578 }
579 }
580 }
581 }
582 else {
583 apr_file_printf(self->ofp, "\n__%s: %s", e[i].key, e[i].val);
584 }
585 }
586 }
587 }
588
589 /* empty line */
590 if ((status = call_command(worker, command_DATA, "__", "")) != APR_SUCCESS) {
591 goto error;
592 }
593 if (write && (!(val = apr_table_get(headers, "Transfer-Encoding")) ||
594 strcasecmp(val, "chunked") != 0)) {
595 apr_file_printf(self->ofp, "\n__");
596 }
597
598 error:
599 self->flags &= ~SELF_FLAGS_SKIP_COOKIE_FIRST_TIME;
600 return status;
601 }
602
603 /**
604 * write body
605 *
606 * @param self IN self pointer
607 * @param worker IN worker to connect to
608 * @param p IN pool
609 * @param headers IN
610 * @param body IN
611 * @param len IN body len
612 * @param write IN write script or not
613 *
614 * @return apr status
615 */
do_body(self_t * self,worker_t * worker,apr_pool_t * p,apr_table_t * headers,char * body,apr_size_t len,int write)616 static apr_status_t do_body(self_t *self, worker_t *worker, apr_pool_t *p,
617 apr_table_t *headers, char *body, apr_size_t len,
618 int write) {
619 char *last;
620 char *line;
621 const char *val;
622 int chunked = 0;
623
624 apr_status_t status = APR_SUCCESS;
625
626 /* write body */
627 if (body) {
628 if ((val = apr_table_get(headers, "Transfer-Encoding")) &&
629 strcasecmp(val, "chunked") == 0) {
630 chunked = 1;
631 }
632 /* do pretty writting */
633 if (write) {
634 if (chunked) {
635 if ((status = call_command(worker, command_FLUSH, "_FLUSH", "")) != APR_SUCCESS) {
636 return status;
637 }
638 apr_file_printf(self->ofp, "\n_FLUSH");
639 }
640 line = apr_strtok(body, "\r\n", &last);
641 while (line) {
642 if ((status = call_command(worker, command_DATA, "__", line)) != APR_SUCCESS) {
643 return status;
644 }
645 apr_file_printf(self->ofp, "\n__%s", line);
646 line = apr_strtok(NULL, "\r\n", &last);
647 }
648 if (chunked) {
649 apr_file_printf(self->ofp, "\n_CHUNKED");
650 apr_file_printf(self->ofp, "\n_CHUNKED");
651 apr_file_printf(self->ofp, "\n__");
652 }
653 }
654 else {
655 worker_log(worker, LOG_INFO, "\n[Body len: %d]", len);
656 if ((status = call_command(worker, command_FLUSH, "_FLUSH", "")) != APR_SUCCESS) {
657 return status;
658 }
659
660 if (chunked) {
661 line = apr_psprintf(worker->pbody, "%x\r\n", (unsigned int)len);
662 if ((status = worker_socket_send(worker, line, strlen(line))) != APR_SUCCESS) {
663 return status;
664 }
665 }
666 if ((status = worker_socket_send(worker, body, len)) != APR_SUCCESS) {
667 return status;
668 }
669 if (chunked) {
670 if ((status = worker_socket_send(worker, "\r\n0\r\n\r\n", 7)) != APR_SUCCESS) {
671 return status;
672 }
673 }
674 }
675 }
676
677 return status;
678 }
679
680 /**
681 * write status line
682 *
683 * @param self IN self pointer
684 * @param worker IN worker to connect to
685 * @param p IN pool
686 * @param status_line IN
687 * @param write IN write script or not
688 *
689 * @return apr status
690 */
do_status_line(self_t * self,worker_t * worker,apr_pool_t * p,char * status_line,int write)691 static apr_status_t do_status_line(self_t *self, worker_t *worker,
692 apr_pool_t *p, char *status_line,
693 int write) {
694 apr_status_t status;
695
696 if ((status = call_command(worker, command_DATA, "__", status_line))
697 != APR_SUCCESS) {
698 return status;
699 }
700
701 if (write) {
702 apr_file_printf(self->ofp, "\n_EXPECT . \"%s\"", status_line);
703 }
704
705 return status;
706 }
707
708 /**
709 * Check if the filter let us write to the file
710 *
711 * @param self IN self pointer
712 * @param url IN url to inspect
713 *
714 * @return 0 if filter matches filter url else return 1
715 */
do_check_url(self_t * self,const char * url)716 static int do_check_url(self_t *self, const char *url) {
717 if (self->url_filter_regex) {
718 if ((htt_regexec(self->url_filter_regex, url, strlen(url), 0, NULL, 0) == 0)) {
719 return 0;
720 }
721 }
722 return 1;
723 }
724
725 /**
726 * Check if Connection: close is set
727 *
728 * @param headers IN headers to inspect
729 *
730 * @return 1 if Connection: close is set else 0
731 */
do_check_close(apr_table_t * headers)732 static int do_check_close(apr_table_t *headers) {
733 const char *connhdr;
734
735 if ((connhdr = apr_table_get(headers, "Connection")) &&
736 strcasecmp(connhdr, "close") == 0) {
737 return 1;
738 }
739
740 return 0;
741 }
742
743 /**
744 * Generate the neccessary _MATCH
745 *
746 * @param self IN self pointer
747 * @param headers IN headers to inspect
748 * @param write IN if 1 do write script
749 *
750 * @return apr status
751 */
do_matches(self_t * self,apr_table_t * headers,int write)752 static apr_status_t do_matches(self_t *self, apr_table_t *headers, int write) {
753 apr_table_entry_t *e;
754 const char *val;
755 int i;
756 char *dup;
757 char *cookie;
758 char *last;
759 char *ignore;
760 char *key;
761
762 if (!write) {
763 return APR_SUCCESS;
764 }
765
766 /* write headers */
767 e = (apr_table_entry_t *) apr_table_elts(headers)->elts;
768 for (i = 0; i < apr_table_elts(headers)->nelts; ++i) {
769 if (strcasecmp(e[i].key, "Set-Cookie") == 0) {
770 dup = apr_pstrdup(self->pool, e[i].val);
771 /* support only one cookie per Set-Cookie header */
772 cookie = apr_strtok(dup, ";", &last);
773 if (cookie) {
774 /* split key from key=val */
775 key = apr_strtok(cookie, "=", &ignore);
776 val = apr_strtok(NULL, "=", &ignore);
777 if (val) {
778 apr_file_printf(self->ofp, "\n_MATCH headers \"%s=([^;]*)\" %s%s",
779 key, self->cookie_pre, key);
780 }
781 else {
782 apr_file_printf(self->ofp, "\n_MATCH headers \"(%s)\" %s%s", key,
783 self->cookie_pre, key);
784 }
785 }
786 }
787 }
788 apr_file_printf(self->ofp, "\n_WAIT");
789 return APR_SUCCESS;
790 }
791
792
793 /**
794 * Proxy thread
795 *
796 * @param thread IN thread object
797 * @param selfv IN void pointer to self_t structure
798 *
799 * @return apr status
800 */
proxy_thread(apr_thread_t * thread,void * selfv)801 static void *APR_THREAD_FUNC proxy_thread(apr_thread_t * thread, void *selfv) {
802 apr_status_t status;
803 apr_pool_t *pool;
804 apr_pool_t *ptmp;
805 worker_t *server;
806 request_t request;
807 response_t response;
808 global_t global;
809
810 int write = 1;
811 self_t *self = selfv;
812 worker_t *client = self->client;
813
814 memset(&global, 0, sizeof(global));
815 global.logger = self->logger;
816 global.socktmo = 1000 * 300000;
817 global.modules = apr_hash_make(self->pool);
818 global.blocks = apr_hash_make(self->pool);
819
820 worker_new(&server, "", &global, NULL);
821
822
823 if ((status = call_command(client, command_TIMEOUT, "_TIMEOUT", "300000")) != APR_SUCCESS) {
824 apr_thread_mutex_unlock(self->mutex);
825 return NULL;
826 }
827
828 if ((status = call_command(server, command_TIMEOUT, "_TIMEMOUT", "300000")) != APR_SUCCESS) {
829 apr_thread_mutex_unlock(self->mutex);
830 return NULL;
831 }
832
833 /* as long we are connected */
834 while ( 1 ) {
835 /* create request and response objects */
836 apr_pool_create(&pool, NULL);
837 request.pool = pool;
838 apr_pool_create(&pool, NULL);
839 response.pool = pool;
840 apr_pool_create(&ptmp, NULL);
841
842 /* wait client request */
843 if ((status = wait_request(client, &request)) != APR_SUCCESS) {
844 /* connection failure break the loop */
845 goto error;
846 }
847
848 /* check if filter matches Accept header */
849 write = do_check_url(self, request.url);
850
851 /* CS BEGIN */
852 apr_thread_mutex_lock(self->mutex);
853 worker_log(client, LOG_DEBUG, "Enter critical section");
854
855 if ((status = do_connect(self, server, ptmp, request.is_ssl, request.host,
856 request.port, write)) != APR_SUCCESS) {
857 apr_thread_exit(thread, status);
858 }
859
860 if ((status = do_request_line(self, server, ptmp, request.request_line,
861 write)) != APR_SUCCESS) {
862 goto unlock;
863 }
864
865 if ((status = do_headers(self, server, ptmp, request.headers, write))
866 != APR_SUCCESS) {
867 goto unlock;
868 }
869
870 if ((status = do_body(self, server, ptmp, request.headers, request.body,
871 request.len, write)) != APR_SUCCESS) {
872 goto unlock;
873 }
874
875 if ((status = call_command(server, command_FLUSH, "_FLUSH", "")) != APR_SUCCESS) {
876 goto unlock;
877 }
878
879 if ((status = wait_response(server, &response)) != APR_SUCCESS) {
880 /* connection failure break the loop */
881 goto unlock;
882 }
883
884 if ((status = do_status_line(self, client, ptmp, response.status_line,
885 write)) != APR_SUCCESS) {
886 goto unlock;
887 }
888
889 if ((status = do_headers(self, client, ptmp, response.headers, 0))
890 != APR_SUCCESS) {
891 goto unlock;
892 }
893
894 if ((status = do_matches(self, response.headers, write)) != APR_SUCCESS) {
895 goto unlock;
896 }
897
898 apr_thread_mutex_unlock(self->mutex);
899 /* CS END */
900 worker_log(client, LOG_DEBUG, "Leave critical section");
901
902 if ((status = do_body(self, client, ptmp, response.headers, response.body,
903 response.len, 0)) != APR_SUCCESS) { goto error;
904 goto error;
905 }
906
907 if ((status = call_command(client, command_FLUSH, "_FLUSH", "")) != APR_SUCCESS) {
908 goto error;
909 }
910
911 if (do_check_close(response.headers)) {
912 break;
913 }
914
915 goto error;
916
917 unlock:
918 apr_thread_mutex_unlock(self->mutex);
919 /* CR END */
920
921 error:
922 apr_pool_destroy(ptmp);
923 apr_pool_destroy(request.pool);
924 apr_pool_destroy(response.pool);
925 if (status != APR_SUCCESS) {
926 break;
927 }
928 }
929
930 if (write) {
931 /* CS BEGIN */
932 apr_thread_mutex_lock(self->mutex);
933 apr_file_printf(self->ofp, "\n_CLOSE");
934 apr_thread_mutex_unlock(self->mutex);
935 /* CR END */
936 }
937
938 /* close connection */
939 call_command(client, command_CLOSE, "_CLOSE", "");
940 call_command(server, command_CLOSE, "_CLOSE", "");
941
942 apr_thread_exit(thread, APR_SUCCESS);
943 return NULL;
944 }
945
946 /**
947 * htproxy shell thread
948 *
949 * @param thread IN thread object
950 * @param selfv IN void pointer to self_t structure
951 *
952 * @return
953 */
admin_thread(apr_thread_t * thread,void * selfv)954 static void *APR_THREAD_FUNC admin_thread(apr_thread_t * thread, void *selfv) {
955 apr_status_t status;
956 apr_file_t *ifp;
957 bufreader_t *br;
958 char *line;
959
960 self_t *self = selfv;
961 fprintf(stdout, "HTTP Test Proxy Shell\n");
962 fprintf(stdout, "> ");
963 fflush(stdout);
964
965 if ((status = apr_file_open_stdin(&ifp, self->pool)) != APR_SUCCESS) {
966 fprintf(stderr, "\nCould not open stdin: %s(%d)\n",
967 get_status_str(self->pool, status), status);
968 fflush(stderr);
969 exit(1);
970 }
971
972 if ((status = bufreader_new(&br, ifp, self->pool)) != APR_SUCCESS) {
973 fprintf(stderr, "\nCould not create bufreader: %s(%d)\n",
974 get_status_str(self->pool, status), status);
975 fflush(stderr);
976 exit(1);
977 }
978
979 while ((status = bufreader_read_line(br, &line)) == APR_SUCCESS) {
980 char *last;
981 const char *file;
982 char *cmd = NULL;
983 apr_off_t offset;
984 apr_size_t len;
985 char *content;
986 apr_pool_t *pool;
987
988 apr_pool_create(&pool, NULL);
989
990 if (line) {
991 cmd = apr_strtok(line, " ", &last);
992 }
993
994 if (!cmd || cmd[0] == 0) {
995 goto next;
996 }
997
998 if (strcmp(cmd, "help") == 0 ||
999 strcmp(cmd, "H") == 0) {
1000 fprintf(stdout, "\nHelp text");
1001 fprintf(stdout, "\n H|help : This help text");
1002 fprintf(stdout, "\n c|comment <text> : Add comment to script");
1003 fprintf(stdout, "\n e|expect <regex> : Add expect before last _WAIT");
1004 fprintf(stdout, "\n h|command <httest command>: Add custom httest command");
1005 fprintf(stdout, "\n r|rotate <file name> : Copy current file away");
1006 fprintf(stdout, "\n n|new : New session");
1007 fprintf(stdout, "\n q|quit : Exit");
1008 fprintf(stdout, "\n");
1009 fflush(stdout);
1010 }
1011 else if (strcmp(cmd, "comment") == 0 ||
1012 strcmp(cmd, "c") == 0) {
1013 /* CS BEGIN */
1014 apr_thread_mutex_lock(self->mutex);
1015
1016 if (last) {
1017 apr_file_printf(self->ofp, "\n# %s", last);
1018 }
1019
1020 apr_thread_mutex_unlock(self->mutex);
1021 /* CS END */
1022 }
1023 else if (strcmp(cmd, "expect") == 0 ||
1024 strcmp(cmd, "e") == 0) {
1025 /* CS BEGIN */
1026 apr_thread_mutex_lock(self->mutex);
1027
1028 if (last) {
1029 /* seek over last _WAIT back */
1030 apr_finfo_t finfo;
1031 apr_off_t i = 1;
1032
1033 apr_file_info_get(&finfo, APR_FINFO_SIZE, self->ofp);
1034 content = apr_pstrdup(pool, "");
1035 while (i < finfo.size) {
1036 offset = -1 * i;
1037 apr_file_seek(self->ofp, APR_CUR, &offset);
1038 len = i;
1039 content = apr_pcalloc(pool, len);
1040 apr_file_read(self->ofp, content, &len);
1041 if (len >= 6 && strncmp(content, "\n_WAIT", 6) == 0) {
1042 break;
1043 }
1044 ++i;
1045 }
1046 if (strncmp(content, "\n_WAIT", 6) == 0) {
1047 status = apr_file_seek(self->ofp, APR_SET, &offset);
1048 apr_file_printf(self->ofp, "\n_EXPECT . \"%s\"", last);
1049 apr_file_write(self->ofp, content, &len);
1050 }
1051 else {
1052 fprintf(stderr, "Warning: Can not add _EXPECT here\n");
1053 }
1054 }
1055
1056 apr_thread_mutex_unlock(self->mutex);
1057 /* CS END */
1058 }
1059 else if (strcmp(cmd, "command") == 0 ||
1060 strcmp(cmd, "h") == 0) {
1061 /* CS BEGIN */
1062 apr_thread_mutex_lock(self->mutex);
1063
1064 if (last) {
1065 apr_file_printf(self->ofp, "\n%s", last);
1066 }
1067
1068 apr_thread_mutex_unlock(self->mutex);
1069 /* CS END */
1070 }
1071 else if (strcmp(cmd, "rotate") == 0 ||
1072 strcmp(cmd, "r") == 0) {
1073 /* CS BEGIN */
1074 apr_thread_mutex_lock(self->mutex);
1075 print_file(self, self->post);
1076 if (last) {
1077 if ((status = apr_file_name_get(&file, self->ofp)) == APR_SUCCESS) {
1078 if ((status = apr_file_copy(file, last, APR_FILE_SOURCE_PERMS,
1079 pool)) != APR_SUCCESS) {
1080 fprintf(stderr, "Could not copy \"%s\" to \"%s\": %s(%d)\n",
1081 file, last, get_status_str(pool, status), status);
1082 }
1083 else {
1084 apr_file_trunc(self->ofp, 0);
1085 offset = 0;
1086 apr_file_seek(self->ofp, APR_SET, &offset);
1087 print_file(self, self->pre);
1088 }
1089 }
1090 else {
1091 fprintf(stderr, "Can not get file name: %s(%d)\n",
1092 get_status_str(pool, status), status);
1093 }
1094 }
1095 apr_thread_mutex_unlock(self->mutex);
1096 /* CS END */
1097 }
1098 else if (strcmp(cmd, "new") == 0 ||
1099 strcmp(cmd, "n") == 0) {
1100 /* CS BEGIN */
1101 apr_thread_mutex_lock(self->mutex);
1102 new_session = 1;
1103 apr_thread_mutex_unlock(self->mutex);
1104 /* CS END */
1105 }
1106 else if (strcmp(cmd, "quit") == 0 ||
1107 strcmp(cmd, "q") == 0) {
1108 status = APR_EOF;
1109 break;
1110 }
1111 else {
1112 fprintf(stderr, "Unknown command %s: Enter 'H' for help\n", cmd);
1113 }
1114
1115 next:
1116 fprintf(stdout, "> ");
1117 fflush(stdout);
1118
1119 apr_pool_destroy(pool);
1120 }
1121
1122 fprintf(stderr, "\n%s(%d)\n",
1123 get_status_str(self->pool, status), status);
1124 exit(0);
1125 return NULL;
1126 }
1127
1128 /**
1129 * Proxy the request and write httest scrip
1130 *
1131 * @param self IN self pointer
1132 */
proxy(self_t * self)1133 int proxy(self_t *self) {
1134 apr_status_t status;
1135 worker_t *listener;
1136 worker_t *client;
1137 self_t *this;
1138 apr_threadattr_t *tattr;
1139 apr_thread_t *thread;
1140 int i = 0;
1141 int off;
1142 const char *err;
1143 htt_regex_t *compiled;
1144 global_t global;
1145
1146 if ((status = apr_threadattr_create(&tattr, self->pool)) != APR_SUCCESS) {
1147 return status;
1148 }
1149
1150 if ((status = apr_threadattr_stacksize_set(tattr, DEFAULT_THREAD_STACKSIZE))
1151 != APR_SUCCESS) {
1152 return status;
1153 }
1154
1155 if ((status = apr_threadattr_detach_set(tattr, 0)) != APR_SUCCESS) {
1156 return status;
1157 }
1158
1159 /** create a admin console thread */
1160 this = apr_pcalloc(self->pool, sizeof(*this));
1161 memcpy(this, self, sizeof(*this));
1162 if ((status =
1163 apr_thread_create(&thread, tattr, admin_thread,
1164 this, this->pool)) != APR_SUCCESS) {
1165 return status;
1166 }
1167
1168 memset(&global, 0, sizeof(global));
1169 global.pool = self->pool;
1170 global.logger = self->logger;
1171 global.socktmo = 1000 * 300000;
1172 global.modules = apr_hash_make(self->pool);
1173 global.blocks = apr_hash_make(self->pool);
1174
1175 /**
1176 * Initialize tcp module
1177 */
1178
1179 tcp_module_init(&global);
1180
1181 worker_new(&listener, "", &global, NULL);
1182
1183 listener->listener_port = self->port;
1184
1185 if ((status = call_command(listener, command_UP, "_UP", "")) != APR_SUCCESS) {
1186 return status;
1187 }
1188
1189 while ( 1 ) {
1190 if ((status = call_command(listener, command_RES, "_RES", "")) != APR_SUCCESS) {
1191 return status;
1192 }
1193
1194 /* test if main is back */
1195
1196 worker_new(&client, "", &global, NULL);
1197
1198 worker_get_socket(client, "Default", "0");
1199 client->socket->socket_state = listener->socket->socket_state;
1200 client->socket->socket = listener->socket->socket;
1201 client->socket->transport = listener->socket->transport;
1202
1203 /* new thread */
1204 this = apr_pcalloc(client->pbody, sizeof(*this));
1205 memcpy(this, self, sizeof(*this));
1206 this->pool = client->pbody;
1207 this->client = client;
1208 if (this->url_filter &&
1209 (compiled = htt_regexcomp(self->pool, this->url_filter, &err, &off))) {
1210 this->url_filter_regex = compiled;
1211 }
1212 /* CS BEGIN */
1213 apr_thread_mutex_lock(self->mutex);
1214 if (new_session == 1) {
1215 this->flags |= SELF_FLAGS_SKIP_COOKIE_FIRST_TIME;
1216 new_session = 0;
1217 }
1218 apr_thread_mutex_unlock(self->mutex);
1219 /* CS END */
1220 if ((status =
1221 apr_thread_create(&thread, tattr, proxy_thread,
1222 this, this->pool)) != APR_SUCCESS) {
1223 return status;
1224 }
1225
1226 /* bad hack should have a method for this */
1227 listener->socket->socket_state = SOCKET_CLOSED;
1228
1229 ++i;
1230 }
1231
1232 return 0;
1233 }
1234
1235 /**
1236 * sort out command-line args and call proxy
1237 *
1238 * @param argc IN number of arguments
1239 * @param argv IN argument array
1240 *
1241 * @return 0 if success
1242 */
main(int argc,const char * const argv[])1243 int main(int argc, const char *const argv[]) {
1244 apr_status_t status;
1245 apr_getopt_t *opt;
1246 const char *optarg;
1247 int c;
1248 apr_pool_t *pool;
1249 self_t *self;
1250
1251 apr_table_t *conf = NULL;
1252 const char *conf_file = NULL;
1253 const char *host_var = NULL;
1254 const char *port_var = NULL;
1255 const char *uri_var = NULL;
1256 const char *host_port_var = NULL;
1257 int port = 8080;
1258 const char *dest = "file";
1259 const char *url_filter = NULL;
1260 int log_mode = 0;
1261 const char *tmo = "30000";
1262 const char *intro_file = NULL;
1263 const char *end_file = NULL;
1264 const char *cookie_pre = "COOKIE_";
1265 int flags = SELF_FLAGS_NONE;
1266 apr_file_t *out;
1267 apr_file_t *err;
1268 appender_t *appender;
1269
1270 srand(apr_time_now());
1271
1272 apr_app_initialize(&argc, &argv, NULL);
1273 apr_pool_create(&pool, NULL);
1274
1275 /* block broken pipe signal */
1276 #if !defined(WIN32)
1277 apr_signal_block(SIGPIPE);
1278 #endif
1279
1280
1281 HT_OPEN_STDERR(&err, APR_BUFFERED|APR_XTHREAD, pool);
1282 HT_OPEN_STDOUT(&out, APR_BUFFERED|APR_XTHREAD, pool);
1283
1284 /* get options */
1285 apr_getopt_init(&opt, pool, argc, argv);
1286 while ((status = apr_getopt_long(opt, options, &c, &optarg))
1287 == APR_SUCCESS) {
1288 switch (c) {
1289 case 'h':
1290 usage(filename(pool, argv[0]));
1291 exit(0);
1292 case 'v':
1293 copyright(filename(pool, argv[0]));
1294 return 0;
1295 break;
1296 case 'd':
1297 dest = optarg;
1298 break;
1299 case 'p':
1300 port = apr_atoi64(optarg);
1301 break;
1302 case 'H':
1303 host_var = optarg;
1304 break;
1305 case 'A':
1306 host_port_var = optarg;
1307 break;
1308 case 'P':
1309 port_var = optarg;
1310 break;
1311 case 'l':
1312 log_mode = apr_atoi64(optarg);
1313 break;
1314 case 't':
1315 tmo = optarg;
1316 break;
1317 case 'i':
1318 intro_file = optarg;
1319 break;
1320 case 'e':
1321 end_file = optarg;
1322 break;
1323 case 'u':
1324 url_filter = optarg;
1325 break;
1326 case 'c':
1327 cookie_pre = optarg;
1328 break;
1329 case 'C':
1330 conf_file = apr_pstrdup(pool, optarg);
1331 break;
1332 case 'U':
1333 uri_var = optarg;
1334 break;
1335 }
1336 }
1337
1338 /* test for wrong options */
1339 if (!APR_STATUS_IS_EOF(status)) {
1340 fprintf(stderr, "try \"%s --help\" to get more information\n", filename(pool, argv[0]));
1341 exit(1);
1342 }
1343
1344 #ifdef USE_SSL
1345 /* setup ssl library */
1346 #ifdef RSAREF
1347 R_malloc_init();
1348 #else
1349 #if OPENSSL_VERSION_NUMBER < 0x10100000
1350 CRYPTO_malloc_init();
1351 #endif
1352 #endif
1353 #if OPENSSL_VERSION_NUMBER < 0x10100000
1354 SSL_load_error_strings();
1355 SSL_library_init();
1356 #endif
1357 ssl_util_thread_setup(pool);
1358 #endif
1359
1360 self = apr_pcalloc(pool, sizeof(*self));
1361
1362 if ((status =
1363 apr_file_open(&self->ofp, dest, APR_READ|APR_WRITE|APR_CREATE|APR_TRUNCATE,
1364 APR_OS_DEFAULT, pool)) != APR_SUCCESS) {
1365 fprintf(stderr, "\nCan not open file '%s': %s(%d)\n", dest,
1366 get_status_str(pool, status), status);
1367 return status;
1368 }
1369
1370 if ((status = apr_thread_mutex_create(&self->mutex,
1371 APR_THREAD_MUTEX_DEFAULT,
1372 pool)) != APR_SUCCESS) {
1373 fprintf(stderr, "\nCan not create mutex: %s(%d)\n",
1374 get_status_str(pool, status), status);
1375 return status;
1376 }
1377
1378 /* read config first */
1379 if (conf_file && (conf = conf_reader(pool, conf_file))) {
1380 int i;
1381 apr_table_entry_t *e;
1382
1383 e = (apr_table_entry_t *) apr_table_elts(conf)->elts;
1384 /* iterate over and notify unknown stuff */
1385 for (i = 0; i < apr_table_elts(conf)->nelts; ++i) {
1386 if (strcmp(e[i].key, "Port") == 0) {
1387 port = apr_atoi64(e[i].val);
1388 }
1389 else if (strcmp(e[i].key, "Timeout") == 0) {
1390 tmo = e[i].val;
1391 }
1392 else if (strcmp(e[i].key, "HostVar") == 0) {
1393 host_var = e[i].val;
1394 }
1395 else if (strcmp(e[i].key, "PortVar") == 0) {
1396 port_var = e[i].val;
1397 }
1398 else if (strcmp(e[i].key, "HostPortVar") == 0) {
1399 host_port_var = e[i].val;
1400 }
1401 else if (strcmp(e[i].key, "CookieVarPrefix") == 0) {
1402 cookie_pre = e[i].val;
1403 }
1404 else if (strcmp(e[i].key, "ScriptHeader") == 0) {
1405 intro_file = e[i].val;
1406 }
1407 else if (strcmp(e[i].key, "ScriptTrailer") == 0) {
1408 end_file = e[i].val;
1409 }
1410 else if (strcmp(e[i].key, "UrlBlacklist") == 0) {
1411 url_filter = e[i].val;
1412 }
1413 else {
1414 fprintf(stderr, "\nUnknown parameter %s", e[i].key);
1415 }
1416
1417 }
1418 }
1419 else if (conf_file) {
1420 return -1;
1421 }
1422
1423 apr_hook_global_pool = pool;
1424
1425 /* overwrites config */
1426 self->pool = pool;
1427 self->port = port;
1428 self->logger = logger_new(pool, log_mode, 0);
1429 appender = appender_std_new(pool, out, APPENDER_STD_NONE);
1430 logger_set_appender(self->logger, appender, "std", LOG_NONE, LOG_DEBUG);
1431 self->timeout = apr_pstrdup(pool, tmo);
1432 self->host_var = host_var ? apr_pstrdup(pool, host_var) : NULL;
1433 self->port_var = port_var ? apr_pstrdup(pool, port_var) : NULL;
1434 self->uri_var = uri_var ? apr_pstrdup(pool, uri_var) : NULL;
1435 self->host_port_var = host_port_var ? apr_pstrdup(pool, host_port_var) : NULL;
1436 self->cookie_pre = apr_pstrdup(pool, cookie_pre);
1437 self->pre = intro_file ? apr_pstrdup(pool, intro_file) : NULL;
1438 self->post = end_file ? apr_pstrdup(pool, end_file) : NULL;
1439 self->url_filter = url_filter ? apr_pstrdup(pool, url_filter) : NULL;
1440 self->flags = flags;
1441
1442 fprintf(stdout, "Start proxy on port %d\n", port);
1443
1444 print_file(self, self->pre);
1445
1446 proxy(self);
1447
1448 fprintf(stdout, "\n--normal end\n");
1449
1450 return 0;
1451 }
1452
1453