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