Imported Upstream version 2.5.11
[libapache-mod-security.git] / apache2 / t / run-regression-tests.pl.in
1 #!@PERL@
2 #
3 # Run regression tests.
4 #
5 # Syntax: run-regression-tests.pl [options] [file [N]]
6 #
7 #          All: run-regression-tests.pl
8 #   All in file: run-regression-tests.pl file
9 #   Nth in file: run-regression-tests.pl file N
10 #
11 use strict;
12 use Time::HiRes qw(gettimeofday sleep);
13 use POSIX qw(WIFEXITED WEXITSTATUS WIFSIGNALED WTERMSIG);
14 use File::Spec qw(rel2abs);
15 use File::Basename qw(basename dirname);
16 use FileHandle;
17 use IPC::Open2 qw(open2);
18 use IPC::Open3 qw(open3);
19 use Getopt::Std;
20 use Data::Dumper;
21 use IO::Socket;
22 use LWP::UserAgent;
23
24 my @TYPES = qw(config misc action target rule);
25 my $SCRIPT = basename($0);
26 my $SCRIPT_DIR = File::Spec->rel2abs(dirname($0));
27 my $REG_DIR = "$SCRIPT_DIR/regression";
28 my $SROOT_DIR = "$REG_DIR/server_root";
29 my $DATA_DIR = "$SROOT_DIR/data";
30 my $TEMP_DIR = "$SROOT_DIR/tmp";
31 my $UPLOAD_DIR = "$SROOT_DIR/upload";
32 my $CONF_DIR = "$SROOT_DIR/conf";
33 my $MODULES_DIR = q(@APXS_LIBEXECDIR@);
34 my $FILES_DIR = "$SROOT_DIR/logs";
35 my $PID_FILE = "$FILES_DIR/httpd.pid";
36 my $HTTPD = q(@APXS_HTTPD@);
37 my $PASSED = 0;
38 my $TOTAL = 0;
39 my $BUFSIZ = 32768;
40 my %C = ();
41 my %FILE = ();
42 my $UA_NAME = "ModSecurity Regression Tests/1.2.3";
43 my $UA = LWP::UserAgent->new;
44 $UA->agent($UA_NAME);
45
46 # Hack for testing the script w/o configure
47 if ($HTTPD eq "\@APXS_HTTPD\@") {
48     $HTTPD = "/usr/local/apache2/bin/httpd";
49     $MODULES_DIR = "/usr/local/apache2/modules";
50 }
51
52 $SIG{TERM} = $SIG{INT} = \&handle_interrupt;
53
54 my %opt;
55 getopts('A:E:D:C:T:H:a:p:dvh', \%opt);
56
57 if ($opt{d}) {
58     $Data::Dumper::Indent = 1;
59     $Data::Dumper::Terse = 1;
60     $Data::Dumper::Pad = "";
61     $Data::Dumper::Quotekeys = 0;
62 }
63
64 sub usage {
65     print stderr <<"EOT";
66 @_
67 Usage: $SCRIPT [options] [file [N]]
68
69  Options:
70   -A file   Specify ModSecurity audit log to read.
71   -D file   Specify ModSecurity debug log to read.
72   -E file   Specify Apache httpd error log to read.
73   -C file   Specify Apache httpd base conf file to generate/reload.
74   -H path   Specify Apache httpd htdocs path.
75   -S path   Specify Apache httpd server root path.
76   -a file   Specify Apache httpd binary (default: httpd)
77   -p port   Specify Apache httpd port (default: 8088)
78   -v        Enable verbose output (details on failure).
79   -d        Enable debugging output.
80   -h        This help.
81
82 EOT
83
84     exit(1);
85 }
86
87 usage() if ($opt{h});
88
89 ### Check httpd binary
90 if (defined $opt{a}) {
91     $HTTPD = $opt{a};
92 }
93 else {
94     $opt{a} = $HTTPD;
95 }
96 usage("Invalid Apache startup script: $HTTPD\n") unless (-e $HTTPD);
97
98 ### Defaults
99 $opt{A} = "$FILES_DIR/modsec_audit.log" unless (defined $opt{A});
100 $opt{D} = "$FILES_DIR/modsec_debug.log" unless (defined $opt{D});
101 $opt{E} = "$FILES_DIR/error.log" unless (defined $opt{E});
102 $opt{C} = "$CONF_DIR/httpd.conf" unless (defined $opt{C});
103 $opt{H} = "$SROOT_DIR/htdocs" unless (defined $opt{H});
104 $opt{p} = 8088 unless (defined $opt{p});
105 $opt{v} = 1 if ($opt{d});
106
107 unless (defined $opt{S}) {
108     my $httpd_root = `$HTTPD -V`;
109     ($opt{S} = $httpd_root) =~ s/.*-D HTTPD_ROOT="([^"]*)".*/$1/sm;
110 }
111
112 %ENV = (
113     %ENV,
114     SERVER_ROOT => $opt{S},
115     SERVER_PORT => $opt{p},
116     SERVER_NAME => "localhost",
117     TEST_SERVER_ROOT => $SROOT_DIR,
118     DATA_DIR => $DATA_DIR,
119     TEMP_DIR => $TEMP_DIR,
120     UPLOAD_DIR => $UPLOAD_DIR,
121     CONF_DIR => $CONF_DIR,
122     MODULES_DIR => $MODULES_DIR,
123     LOGS_DIR => $FILES_DIR,
124     SCRIPT_DIR => $SCRIPT_DIR,
125     REGRESSION_DIR => $REG_DIR,
126     DIST_ROOT => File::Spec->rel2abs(dirname("$SCRIPT_DIR/../../..")),
127     AUDIT_LOG => $opt{A},
128     DEBUG_LOG => $opt{D},
129     ERROR_LOG => $opt{E},
130     HTTPD_CONF => $opt{C},
131     HTDOCS => $opt{H},
132     USER_AGENT => $UA_NAME,
133 );
134
135 #dbg("OPTIONS: ", \%opt);
136
137 if (-e "$PID_FILE") {
138     msg("Shutting down previous instance: $PID_FILE");
139     httpd_stop();
140 }
141
142 if (defined $ARGV[0]) {
143     runfile(dirname($ARGV[0]), basename($ARGV[0]), $ARGV[1]);
144     done();
145 }
146
147 for my $type (@TYPES) {
148     my $dir = "$SCRIPT_DIR/regression/$type";
149     my @cfg = ();
150
151     # Get test names
152     opendir(DIR, "$dir") or quit(1, "Failed to open \"$dir\": $!");
153     @cfg = grep { /\.t$/ && -f "$dir/$_" } readdir(DIR);
154     closedir(DIR);
155
156     for my $cfg (sort @cfg) {
157         runfile($dir, $cfg);
158     }
159 }
160 done();
161
162
163 sub runfile {
164     my($dir, $cfg, $testnum) = @_;
165     my $fn = "$dir/$cfg";
166     my @data = ();
167     my $edata;
168     my @C = ();
169     my @test = ();
170     my $teststr;
171     my $n = 0;
172     my $pass = 0;
173
174     open(CFG, "<$fn") or quit(1, "Failed to open \"$fn\": $!");
175     @data = <CFG>;
176   
177     $edata = q/@C = (/ . join("", @data) . q/)/;
178     eval $edata;
179     quit(1, "Failed to read test data \"$cfg\": $@") if ($@);
180
181     unless (@C) {
182         msg("\nNo tests defined for $fn");
183         return;
184     }
185
186     msg("\nLoaded ".@C." tests from $fn");
187     for my $t (@C) {
188         $n++;
189         next if (defined $testnum and $n != $testnum);
190
191         my $httpd_up = 0;
192         my %t = %{$t || {}};
193         my $id = sprintf("%3d", $n);
194         my $out = "";
195         my $rc = 0;
196         my $conf_fn;
197
198         # Startup httpd with optionally included conf.
199         if (exists $t{conf} and defined $t{conf}) {
200             $conf_fn = sprintf "%s/%s_%s_%06d.conf",
201                          $CONF_DIR, $t{type}, $cfg, $n;
202             #dbg("Writing test config to: $conf_fn");
203             open(CONF, ">$conf_fn") or die "Failed to open conf \"$conf_fn\": $!\n";
204             print CONF (ref $t{conf} eq "CODE" ? eval { &{$t{conf}} } : $t{conf});
205             msg("$@") if ($@);
206             close CONF;
207             $httpd_up = httpd_start(\%t, "Include $conf_fn") ? 0 : 1;
208         }
209         else {
210             $httpd_up = httpd_start(\%t) ? 0 : 1;
211         }
212
213         # Run any prerun setup
214         if ($rc == 0 and exists $t{prerun} and defined $t{prerun}) {
215             vrb("Executing perl prerun...");
216             $rc = &{$t{prerun}};
217             vrb("Perl prerun returned: $rc");
218         }
219
220         if ($httpd_up) {
221             # Perform the request and check response
222             if (exists $t{request}) {
223                 my $resp = do_request($t{request});
224                 if (!$resp) {
225                     msg("invalid response");
226                     vrb("RESPONSE: ", $resp);
227                     $rc = 1;
228                 }
229                 else {
230                     for my $key (keys %{ $t{match_response} || {}}) {
231                         my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
232                         my $m = $t{match_response}{$key};
233                         my $match = match_response($mtype, $resp, $m);
234                         if ($neg and defined $match) {
235                             $rc = 1;
236                             msg("response $mtype matched: $m");
237                             vrb($resp);
238                             last;
239                         }
240                         elsif (!$neg and !defined $match) {
241                             $rc = 1;
242                             msg("response $mtype failed to match: $m");
243                             vrb($resp);
244                             last;
245                         }
246                     }
247                 }
248             }
249
250             # Run any arbitrary perl tests
251             if ($rc == 0 and exists $t{test} and defined $t{test}) {
252                 dbg("Executing perl test(s)...");
253                 $rc = eval { &{$t{test}} };
254                 if (! defined $rc) {
255                     msg("Error running test: $@");
256                     $rc = -1;
257                 }
258                 dbg("Perl tests returned: $rc");
259             }
260
261             # Search for all log matches
262             if ($rc == 0 and exists $t{match_log} and defined $t{match_log}) {
263                 for my $key (keys %{ $t{match_log} || {}}) {
264                     my($neg,$mtype) = ($key =~ m/^(-?)(.*)$/);
265                     my $m = $t{match_log}{$key};
266                     my $match = match_log($mtype, @{$m || []});
267                     if ($neg and defined $match) {
268                         $rc = 1;
269                         msg("$mtype log matched: $m->[0]");
270                         last;
271                     }
272                     elsif (!$neg and !defined $match) {
273                         $rc = 1;
274                         msg("$mtype log failed to match: $m->[0]");
275                         last;
276                     }
277                 }
278             }
279
280             # Search for all file matches
281             if ($rc == 0 and exists $t{match_file} and defined $t{match_file}) {
282                 sleep 1; # Make sure the file exists
283                 for my $key (keys %{ $t{match_file} || {}}) {
284                     my($neg,$fn) = ($key =~ m/^(-?)(.*)$/);
285                     my $m = $t{match_file}{$key};
286                     my $match = match_file($fn, $m);
287                     if ($neg and defined $match) {
288                         $rc = 1;
289                         msg("$fn file matched: $m");
290                         last;
291                     }
292                     elsif (!$neg and !defined $match) {
293                         $rc = 1;
294                         msg("$fn file failed match: $m");
295                         last;
296                     }
297                 }
298             }
299         }
300         else {
301             msg("Failed to start httpd.");
302             $rc = 1;
303         }
304
305         if ($rc == 0) {
306             $pass++;
307         }
308         else {
309             vrb("Test Config: $conf_fn");
310             vrb("Debug Log: $FILE{debug}{fn}");
311             dbg(escape("$FILE{debug}{buf}"));
312             vrb("Error Log: $FILE{error}{fn}");
313             dbg(escape("$FILE{error}{buf}"));
314         }
315
316         msg(sprintf("%s) %s%s: %s%s", $id, $t{type}, (exists($t{comment}) ? " - $t{comment}" : ""), ($rc ? "failed" : "passed"), ((defined($out) && $out ne "")? " ($out)" : "")));
317     
318         if ($httpd_up) {
319             $httpd_up = httpd_stop(\%t) ? 0 : 1;
320         }
321
322     }
323
324     $TOTAL += $testnum ? 1 : $n;
325     $PASSED += $pass;
326
327     msg(sprintf("Passed: %2d; Failed: %2d", $pass, $testnum ? (1 - $pass) : ($n - $pass)));
328 }
329
330 # Take out any indenting and translate LF -> CRLF
331 sub normalize_raw_request_data {
332     my $r = $_[0];
333
334     # Allow for indenting in test file
335     $r =~ s/^[ \t]*\x0d?\x0a//s;
336     my($indention) = ($r =~ m/^([ \t]*)/s); # indention taken from first line
337     $r =~ s/^$indention//mg;
338     $r =~ s/(\x0d?\x0a)[ \t]+$/$1/s;
339
340     # Translate LF to CRLF
341     $r =~ s/^\x0a/\x0d\x0a/mg;
342     $r =~ s/([^\x0d])\x0a/$1\x0d\x0a/mg;
343
344     return $r;
345 }
346
347 sub do_raw_request {
348     my $sock = new IO::Socket::INET(
349         Proto => "tcp",
350         PeerAddr => "localhost",
351         PeerPort => $opt{p},
352     ) or msg("Failed to connect to localhost:$opt{p}: $@");
353     return unless ($sock);
354
355     # Join togeather the request
356     my $r = join("", @_);
357     dbg($r);
358
359     # Write to socket
360     print $sock "$r";
361     $sock->shutdown(1);
362
363     # Read from socket
364     my @resp = <$sock>;
365     $sock->close();
366
367     return HTTP::Response->parse(join("", @resp));
368 }
369
370 sub do_request {
371     my $r = $_[0];
372   
373     # Allow test to execute code
374     if (ref $r eq "CODE") {
375         $r = eval { &$r };
376         msg("$@") unless (defined $r);
377     }
378
379     if (ref $r eq "HTTP::Request") {
380         my $resp = $UA->request($r);
381         dbg($resp->request()->as_string()) if ($opt{d});
382         return $resp
383     }
384     else {
385         return do_raw_request($r);
386     }
387
388     return;
389 }
390
391
392 sub match_response {
393     my($name, $resp, $re) = @_;
394
395     msg("Warning: Empty regular expression.") if (!defined $re or $re eq "");
396
397     if ($name eq "status") {
398         return $& if ($resp->code =~ m/$re/);
399     }
400     elsif ($name eq "content") {
401         return $& if ($resp->content =~ m/$re/m);
402     }
403     elsif ($name eq "raw") {
404         return $& if ($resp->as_string =~ m/$re/m);
405     }
406
407     return;
408 }
409
410 sub read_log {
411     my($name, $timeout, $graph) = @_;
412     return match_log($name, undef, $timeout, $graph);
413 }
414
415 sub match_log {
416     my($name, $re, $timeout, $graph) = @_;
417     my $t0 = gettimeofday;
418     my($fh,$rbuf) = ($FILE{$name}{fd}, \$FILE{$name}{buf});
419     my $n = length($$rbuf);
420     my $rc = undef;
421
422     unless (defined $fh) {
423         msg("Error: File \"$name\" is not opened for matching.");
424         return;
425     }
426
427     $timeout = 0 unless (defined $timeout);
428
429     my $i = 0;
430     my $graphed = 0;
431     READ: {
432         do {
433             my $nbytes = $fh->sysread($$rbuf, $BUFSIZ, $n);
434             if (!defined($nbytes)) {
435                 msg("Error: Could not read \"$name\" log: $!");
436                 last;
437             }
438             elsif (!defined($re) and $nbytes == 0) {
439                 last;
440             }
441
442             # Remove APR pool debugging
443             $$rbuf =~ s/POOL DEBUG:[^\n]+PALLOC[^\n]+\n//sg;
444
445             $n = length($$rbuf);
446
447             #dbg("Match \"$re\" in $name \"$$rbuf\" ($n)");
448             if ($$rbuf =~ m/$re/m) {
449                 $rc = $&;
450                 last;
451             }
452             # TODO: Use select()/poll()
453             sleep 0.1 unless ($nbytes == $BUFSIZ);
454             if ($graph and $opt{d}) {
455                 $i++;
456                 if ($i == 10) {
457                     $graphed++;
458                     $i=0;
459                     print STDERR $graph if ($graphed == 1);
460                     print STDERR "."
461                 }
462             }
463         } while (gettimeofday - $t0 < $timeout);
464     }
465     print STDERR "\n" if ($graphed);
466
467     return $rc;
468 }
469
470 sub match_file {
471     my($neg,$fn) = ($_[0] =~ m/^(-?)(.*)$/);
472     unless (exists $FILE{$fn}) {
473         eval {
474             $FILE{$fn}{fn} = $fn;
475             $FILE{$fn}{fd} = new FileHandle($fn, O_RDONLY) or die "$!\n";
476             $FILE{$fn}{fd}->blocking(0);
477             $FILE{$fn}{buf} = "";
478         };
479         if ($@) {
480             msg("Warning: Failed to open file \"$fn\": $@");
481             return;
482         }
483     }
484     return match_log($_[0], $_[1]); # timeout makes no sense
485 }
486
487 sub quote_shell {
488     my($s) = @_;
489     return $s unless ($s =~ m|[^\w!%+,\-./:@^]|);
490     $s =~ s/(['\\])/\\$1/g;
491     return "'$s'";
492 }
493
494 sub escape {
495     my @new = ();
496     for my $c (split(//, $_[0])) {
497         my $oc = ord($c);
498         push @new, ((($oc >= 0x20 and $oc <= 0x7e) or $oc == 0x0a or $oc == 0x0d) ? $c : sprintf("\\x%02x", ord($c)));
499     }
500     join('', @new);
501 }
502
503 sub dbg {
504     return unless(@_ and $opt{d});
505     my $out = join "", map {
506         (ref $_ ne "" ? Dumper($_) : $_)
507     } @_;
508     $out =~ s/^/DBG: /mg;
509     print STDOUT "$out\n";
510 }
511
512 sub vrb {
513     return unless(@_ and $opt{v});
514     msg(@_);
515 }
516
517 sub msg {
518     return unless(@_);
519     my $out = join "", map {
520         (ref $_ ne "" ? Dumper($_) : $_)
521     } @_;
522     print STDOUT "$out\n";
523 }
524
525 sub handle_interrupt {
526     $SIG{TERM} = $SIG{INT} = \&handle_interrupt;
527
528     msg("Interrupted via SIG$_[0].  Shutting down tests...");
529     httpd_stop();
530
531     quit(1);
532 }
533
534 sub quit {
535     my($ec,$msg) = @_;
536     $ec = 0 unless (defined $_[0]);
537
538     msg("$msg") if (defined $msg);
539
540     exit $ec;
541 }
542
543 sub done {
544     if ($PASSED != $TOTAL) {
545         quit(1, "\n$PASSED/$TOTAL tests passed.");
546     }
547
548     quit(0, "\nAll tests passed ($TOTAL).");
549 }
550
551 sub httpd_start {
552     my $t = shift;
553     httpd_reset_fd($t);
554     my @p = (
555         $HTTPD,
556         -d => $opt{S},
557         -f => $opt{C},
558         (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
559         -k => "start",
560     );
561
562     my $httpd_out;
563     my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
564     my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
565     close $httpd_out;
566     waitpid($httpd_pid, 0);
567
568     my $rc = $?;
569     if ( WIFEXITED($rc) ) {
570         $rc = WEXITSTATUS($rc);
571         vrb("Httpd start returned with $rc.") if ($rc);
572     }
573     elsif( WIFSIGNALED($rc) ) {
574         msg("Httpd start failed with signal " . WTERMSIG($rc) . ".");
575         $rc = -1;
576     }
577     else {
578         msg("Httpd start failed with unknown error.");
579         $rc = -1;
580     }
581
582     if (defined $out and $out ne "") {
583         vrb(join(" ", map { quote_shell($_) } @p));
584         msg("Httpd start failed with error messages:\n$out");
585         return -1
586     }
587
588     # Look for startup msg
589     unless (defined match_log("error", qr/resuming normal operations/, 60, "Waiting on httpd to start: ")) {
590         vrb(join(" ", map { quote_shell($_) } @p));
591         vrb(match_log("error", qr/(^.*ModSecurity: .*)/sm, 10));
592         msg("Httpd server failed to start.");
593         return -1;
594     }
595
596     return $rc;
597 }
598
599 sub httpd_stop {
600     my $t = shift;
601     my @p = (
602         $HTTPD,
603         -d => $opt{S},
604         -f => $opt{C},
605         (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
606         -k => "stop",
607     );
608
609     my $httpd_out;
610     my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
611     my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
612     close $httpd_out;
613     waitpid($httpd_pid, 0);
614
615     if (defined $out and $out ne "") {
616         msg("Httpd stop failed with error messages:\n$out");
617         return -1
618     }
619
620     my $rc = $?;
621     if ( WIFEXITED($rc) ) {
622         $rc = WEXITSTATUS($rc);
623         vrb("Httpd stop returned with $rc.") if ($rc);
624     }
625     elsif( WIFSIGNALED($rc) ) {
626         msg("Httpd stop failed with signal " . WTERMSIG($rc) . ".");
627         $rc = -1;
628     }
629     else {
630         msg("Httpd stop failed with unknown error.");
631         $rc = -1;
632     }
633
634     # Look for startup msg
635     unless (defined match_log("error", qr/caught SIG[A-Z]+, shutting down/, 60, "Waiting on httpd to stop: ")) {
636         vrb(join(" ", map { quote_shell($_) } @p));
637         msg("Httpd server failed to shutdown.");
638         sleep 0.5;
639         return -1;
640     }
641
642     sleep 0.5;
643
644     return $rc;
645 }
646
647 sub httpd_reload {
648     my $t = shift;
649     httpd_reset_fd($t);
650     my @p = (
651         $HTTPD,
652         -d => $opt{S},
653         -f => $opt{C},
654         (map { (-c => $_) } ("Listen localhost:$opt{p}", @_)),
655         -k => "graceful",
656     );
657
658     my $httpd_out;
659     my $httpd_pid = open3(undef, $httpd_out, undef, @p) or quit(1);
660     my $out = join("\\n", grep(!/POOL DEBUG/, (<$httpd_out>)));
661     close $httpd_out;
662     waitpid($httpd_pid, 0);
663
664     if (defined $out and $out ne "") {
665         msg("Httpd reload failed with error messages:\n$out");
666         return -1
667     }
668
669     my $rc = $?;
670     if ( WIFEXITED($rc) ) {
671         $rc = WEXITSTATUS($rc);
672         vrb("Httpd reload returned with $rc.") if ($rc);
673     }
674     elsif( WIFSIGNALED($rc) ) {
675         msg("Httpd reload failed with signal " . WTERMSIG($rc) . ".");
676         $rc = -1;
677     }
678     else {
679         msg("Httpd reload failed with unknown error.");
680         $rc = -1;
681     }
682
683     # Look for startup msg
684     unless (defined match_log("error", qr/resuming normal operations/, 60, "Waiting on httpd to restart: ")) {
685         vrb(join(" ", map { quote_shell($_) } @p));
686         msg("Httpd server failed to reload.");
687         return -1;
688     }
689
690     return $rc;
691 }
692
693 sub httpd_reset_fd {
694     my($t) = @_;
695
696     # Cleanup
697     for my $key (keys %FILE) {
698         if (exists $FILE{$key}{fd} and defined $FILE{$key}{fd}) {
699             $FILE{$key}{fd}->close();
700         }
701         delete $FILE{$key};
702     }
703
704     # Error
705     eval {
706         $FILE{error}{fn} = $opt{E};
707         $FILE{error}{fd} = new FileHandle($opt{E}, O_RDWR|O_CREAT) or die "$!\n";
708         $FILE{error}{fd}->blocking(0);
709         $FILE{error}{fd}->sysseek(0, 2);
710         $FILE{error}{buf} = "";
711     };
712     if ($@) {
713         msg("Warning: Failed to open file \"$opt{E}\": $@");
714         return undef;
715     }
716
717     # Audit
718     eval {
719         $FILE{audit}{fn} = $opt{A};
720         $FILE{audit}{fd} = new FileHandle($opt{A}, O_RDWR|O_CREAT) or die "$!\n";
721         $FILE{audit}{fd}->blocking(0);
722         $FILE{audit}{fd}->sysseek(0, 2);
723         $FILE{audit}{buf} = "";
724     };
725     if ($@) {
726         msg("Warning: Failed to open file \"$opt{A}\": $@");
727         return undef;
728     }
729
730     # Debug
731     eval {
732         $FILE{debug}{fn} = $opt{D};
733         $FILE{debug}{fd} = new FileHandle($opt{D}, O_RDWR|O_CREAT) or die "$!\n";
734         $FILE{debug}{fd}->blocking(0);
735         $FILE{debug}{fd}->sysseek(0, 2);
736         $FILE{debug}{buf} = "";
737     };
738     if ($@) {
739         msg("Warning: Failed to open file \"$opt{D}\": $@");
740         return undef;
741     }
742
743     # Any extras listed in "match_log"
744     if ($t and exists $t->{match_log}) {
745         for my $k (keys %{ $t->{match_log} || {} }) {
746             my($neg,$fn) = ($k =~ m/^(-?)(.*)$/);
747             next if (!$fn or exists $FILE{$fn});
748             eval {
749                 $FILE{$fn}{fn} = $fn;
750                 $FILE{$fn}{fd} = new FileHandle($fn, O_RDWR|O_CREAT) or die "$!\n";
751                 $FILE{$fn}{fd}->blocking(0);
752                 $FILE{$fn}{fd}->sysseek(0, 2);
753                 $FILE{$fn}{buf} = "";
754             };
755             if ($@) {
756                 msg("Warning: Failed to open file \"$fn\": $@");
757                 return undef;
758             }
759         }
760     }
761 }
762
763 sub encode_chunked {
764     my($data, $size) = @_;
765     $size = 128 unless ($size);
766     my $chunked = "";
767   
768     my $n = 0;
769     my $bytes = length($data);
770     while ($bytes >= $size) {
771         $chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $size, substr($data, $n, $size);
772         $n += $size;
773         $bytes -= $size;
774     }
775     if ($bytes) {
776         $chunked .= sprintf "%x\x0d\x0a%s\x0d\x0a", $bytes, substr($data, $n, $bytes);
777     }
778     $chunked .= "0\x0d\x0a\x0d\x0a"
779 }