76ac5854965100f7b082e946c033832c0800afe7
[fwknop.git] / test / test-fwknop.pl
1 #!/usr/bin/perl -w
2
3 use File::Copy;
4 use File::Path;
5 use IO::Socket;
6 use Data::Dumper;
7 use Getopt::Long 'GetOptions';
8 use strict;
9
10 #==================== config =====================
11 my $logfile        = 'test.log';
12 my $local_key_file = 'local_spa.key';
13 my $output_dir     = 'output';
14 my $lib_dir        = '../lib/.libs';
15 my $conf_dir       = 'conf';
16 my $run_dir        = 'run';
17 my $configure_path = '../configure';
18 my $cmd_out_tmp    = 'cmd.out';
19 my $server_cmd_tmp = 'server_cmd.out';
20 my $gpg_client_home_dir = "$conf_dir/client-gpg";
21
22 my $nat_conf            = "$conf_dir/nat_fwknopd.conf";
23 my $default_conf        = "$conf_dir/default_fwknopd.conf";
24 my $default_access_conf = "$conf_dir/default_access.conf";
25 my $expired_access_conf = "$conf_dir/expired_stanza_access.conf";
26 my $future_expired_access_conf = "$conf_dir/future_expired_stanza_access.conf";
27 my $expired_epoch_access_conf = "$conf_dir/expired_epoch_stanza_access.conf";
28 my $invalid_expire_access_conf = "$conf_dir/invalid_expire_access.conf";
29 my $force_nat_access_conf = "$conf_dir/force_nat_access.conf";
30 my $dual_key_usage_access_conf = "$conf_dir/dual_key_usage_access.conf";
31 my $gpg_access_conf     = "$conf_dir/gpg_access.conf";
32 my $default_digest_file = "$run_dir/digest.cache";
33 my $default_pid_file    = "$run_dir/fwknopd.pid";
34 my $open_ports_access_conf = "$conf_dir/open_ports_access.conf";
35 my $multi_gpg_access_conf  = "$conf_dir/multi_gpg_access.conf";
36 my $multi_stanzas_access_conf = "$conf_dir/multi_stanzas_access.conf";
37 my $multi_stanzas_with_broken_keys_conf = "$conf_dir/multi_stanzas_with_broken_keys.conf";
38 my $mismatch_open_ports_access_conf = "$conf_dir/mismatch_open_ports_access.conf";
39 my $require_user_access_conf = "$conf_dir/require_user_access.conf";
40 my $mismatch_user_access_conf = "$conf_dir/mismatch_user_access.conf";
41 my $require_src_access_conf = "$conf_dir/require_src_access.conf";
42 my $no_source_match_access_conf = "$conf_dir/no_source_match_access.conf";
43 my $no_subnet_source_match_access_conf = "$conf_dir/no_subnet_source_match_access.conf";
44 my $no_multi_source_match_access_conf = "$conf_dir/no_multi_source_match_access.conf";
45 my $multi_source_match_access_conf = "$conf_dir/multi_source_match_access.conf";
46 my $ip_source_match_access_conf = "$conf_dir/ip_source_match_access.conf";
47 my $subnet_source_match_access_conf = "$conf_dir/subnet_source_match_access.conf";
48
49 my $fwknopCmd   = '../client/.libs/fwknop';
50 my $fwknopdCmd  = '../server/.libs/fwknopd';
51 my $libfko_bin  = "$lib_dir/libfko.so";  ### this is usually a link
52 my $valgrindCmd = '/usr/bin/valgrind';
53
54 my $gpg_server_key = '361BBAD4';
55 my $gpg_client_key = '6A3FAD56';
56
57 my $loopback_ip = '127.0.0.1';
58 my $fake_ip     = '127.0.0.2';
59 my $internal_nat_host = '192.168.1.2';
60 my $force_nat_host = '192.168.1.123';
61 my $default_spa_port = 62201;
62 my $non_std_spa_port = 12345;
63
64 my $spoof_user = 'testuser';
65 #================== end config ===================
66
67 my $passed = 0;
68 my $failed = 0;
69 my $executed = 0;
70 my $test_include = '';
71 my @tests_to_include = ();
72 my $test_exclude = '';
73 my @tests_to_exclude = ();
74 my %valgrind_suspect_functions = ();
75 my $list_mode = 0;
76 my $diff_dir1 = '';
77 my $diff_dir2 = '';
78 my $loopback_intf = '';
79 my $anonymize_results = 0;
80 my $current_test_file = "$output_dir/init";
81 my $tarfile = 'test_fwknop.tar.gz';
82 my $server_test_file  = '';
83 my $use_valgrind = 0;
84 my $valgrind_str = '';
85 my $saved_last_results = 0;
86 my $diff_mode = 0;
87 my $enable_recompilation_warnings_check = 0;
88 my $sudo_path = '';
89 my $platform = '';
90 my $help = 0;
91 my $YES = 1;
92 my $NO  = 0;
93 my $PRINT_LEN = 68;
94 my $USE_PREDEF_PKTS = 1;
95 my $USE_CLIENT = 2;
96 my $REQUIRED = 1;
97 my $OPTIONAL = 0;
98 my $NEW_RULE_REQUIRED = 1;
99 my $REQUIRE_NO_NEW_RULE = 2;
100 my $NEW_RULE_REMOVED = 1;
101 my $REQUIRE_NO_NEW_REMOVED = 2;
102
103 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;  ### IPv4
104
105 my @args_cp = @ARGV;
106
107 exit 1 unless GetOptions(
108     'Anonymize-results' => \$anonymize_results,
109     'fwknop-path=s'     => \$fwknopCmd,
110     'fwknopd-path=s'    => \$fwknopdCmd,
111     'libfko-path=s'     => \$libfko_bin,
112     'loopback-intf=s'   => \$loopback_intf,
113     'test-include=s'    => \$test_include,
114     'include=s'         => \$test_include,  ### synonym
115     'test-exclude=s'    => \$test_exclude,
116     'exclude=s'         => \$test_exclude,  ### synonym
117     'enable-recompile-check' => \$enable_recompilation_warnings_check,
118     'List-mode'         => \$list_mode,
119     'enable-valgrind'   => \$use_valgrind,
120     'valgrind-path=s'   => \$valgrindCmd,
121     'output-dir=s'      => \$output_dir,
122     'diff'              => \$diff_mode,
123     'diff-dir1=s'       => \$diff_dir1,
124     'diff-dir2=s'       => \$diff_dir2,
125     'help'              => \$help
126 );
127
128 &usage() if $help;
129
130 ### create an anonymized tar file of test suite results that can be
131 ### emailed around to assist in debugging fwknop communications
132 exit &anonymize_results() if $anonymize_results;
133
134 &identify_loopback_intf();
135
136 $valgrind_str = "$valgrindCmd --leak-check=full " .
137     "--show-reachable=yes --track-origins=yes" if $use_valgrind;
138
139 my $intf_str = "-i $loopback_intf --foreground --verbose --verbose";
140
141 my $default_client_args = "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
142     "$fwknopCmd -A tcp/22 -a $fake_ip -D $loopback_ip --get-key " .
143     "$local_key_file --verbose --verbose";
144
145 my $default_client_gpg_args = "$default_client_args " .
146     "--gpg-recipient-key $gpg_server_key " .
147     "--gpg-signer-key $gpg_client_key " .
148     "--gpg-home-dir $gpg_client_home_dir";
149
150 my $default_server_conf_args = "-c $default_conf -a $default_access_conf " .
151     "-d $default_digest_file -p $default_pid_file";
152
153 my $default_server_gpg_args = "LD_LIBRARY_PATH=$lib_dir " .
154     "$valgrind_str $fwknopdCmd -c $default_conf " .
155     "-a $gpg_access_conf $intf_str " .
156     "-d $default_digest_file -p $default_pid_file";
157
158 ### point the compiled binaries at the local libary path
159 ### instead of any installed libfko instance
160 $ENV{'LD_LIBRARY_PATH'} = $lib_dir;
161
162 ### main array that defines the tests we will run
163 my @tests = (
164     {
165         'category' => 'recompilation',
166         'detail'   => 'recompile and look for compilation warnings',
167         'err_msg'  => 'compile warnings exist',
168         'function' => \&compile_warnings,
169         'fatal'    => $NO
170     },
171     {
172         'category' => 'build',
173         'subcategory' => 'client',
174         'detail'   => 'binary exists',
175         'err_msg'  => 'binary not found',
176         'function' => \&binary_exists,
177         'binary'   => $fwknopCmd,
178         'fatal'    => $YES
179     },
180     {
181         'category' => 'build security',
182         'subcategory' => 'client',
183         'detail'   => 'Position Independent Executable (PIE)',
184         'err_msg'  => 'non PIE binary (fwknop client)',
185         'function' => \&pie_binary,
186         'binary'   => $fwknopCmd,
187         'fatal'    => $NO
188     },
189     {
190         'category' => 'build security',
191         'subcategory' => 'client',
192         'detail'   => 'stack protected binary',
193         'err_msg'  => 'non stack protected binary (fwknop client)',
194         'function' => \&stack_protected_binary,
195         'binary'   => $fwknopCmd,
196         'fatal'    => $NO
197     },
198     {
199         'category' => 'build security',
200         'subcategory' => 'client',
201         'detail'   => 'fortify source functions',
202         'err_msg'  => 'source functions not fortified (fwknop client)',
203         'function' => \&fortify_source_functions,
204         'binary'   => $fwknopCmd,
205         'fatal'    => $NO
206     },
207     {
208         'category' => 'build security',
209         'subcategory' => 'client',
210         'detail'   => 'read-only relocations',
211         'err_msg'  => 'no read-only relocations (fwknop client)',
212         'function' => \&read_only_relocations,
213         'binary'   => $fwknopCmd,
214         'fatal'    => $NO
215     },
216     {
217         'category' => 'build security',
218         'subcategory' => 'client',
219         'detail'   => 'immediate binding',
220         'err_msg'  => 'no immediate binding (fwknop client)',
221         'function' => \&immediate_binding,
222         'binary'   => $fwknopCmd,
223         'fatal'    => $NO
224     },
225
226     {
227         'category' => 'build',
228         'subcategory' => 'server',
229         'detail'   => 'binary exists',
230         'err_msg'  => 'binary not found',
231         'function' => \&binary_exists,
232         'binary'   => $fwknopdCmd,
233         'fatal'    => $YES
234     },
235
236     {
237         'category' => 'build security',
238         'subcategory' => 'server',
239         'detail'   => 'Position Independent Executable (PIE)',
240         'err_msg'  => 'non PIE binary (fwknopd server)',
241         'function' => \&pie_binary,
242         'binary'   => $fwknopdCmd,
243         'fatal'    => $NO
244     },
245     {
246         'category' => 'build security',
247         'subcategory' => 'server',
248         'detail'   => 'stack protected binary',
249         'err_msg'  => 'non stack protected binary (fwknopd server)',
250         'function' => \&stack_protected_binary,
251         'binary'   => $fwknopdCmd,
252         'fatal'    => $NO
253     },
254     {
255         'category' => 'build security',
256         'subcategory' => 'server',
257         'detail'   => 'fortify source functions',
258         'err_msg'  => 'source functions not fortified (fwknopd server)',
259         'function' => \&fortify_source_functions,
260         'binary'   => $fwknopdCmd,
261         'fatal'    => $NO
262     },
263     {
264         'category' => 'build security',
265         'subcategory' => 'server',
266         'detail'   => 'read-only relocations',
267         'err_msg'  => 'no read-only relocations (fwknopd server)',
268         'function' => \&read_only_relocations,
269         'binary'   => $fwknopdCmd,
270         'fatal'    => $NO
271     },
272     {
273         'category' => 'build security',
274         'subcategory' => 'server',
275         'detail'   => 'immediate binding',
276         'err_msg'  => 'no immediate binding (fwknopd server)',
277         'function' => \&immediate_binding,
278         'binary'   => $fwknopdCmd,
279         'fatal'    => $NO
280     },
281
282     {
283         'category' => 'build',
284         'subcategory' => 'libfko',
285         'detail'   => 'binary exists',
286         'err_msg'  => 'binary not found',
287         'function' => \&binary_exists,
288         'binary'   => $libfko_bin,
289         'fatal'    => $YES
290     },
291     {
292         'category' => 'build security',
293         'subcategory' => 'libfko',
294         'detail'   => 'stack protected binary',
295         'err_msg'  => 'non stack protected binary (libfko)',
296         'function' => \&stack_protected_binary,
297         'binary'   => $libfko_bin,
298         'fatal'    => $NO
299     },
300     {
301         'category' => 'build security',
302         'subcategory' => 'libfko',
303         'detail'   => 'fortify source functions',
304         'err_msg'  => 'source functions not fortified (libfko)',
305         'function' => \&fortify_source_functions,
306         'binary'   => $libfko_bin,
307         'fatal'    => $NO
308     },
309     {
310         'category' => 'build security',
311         'subcategory' => 'libfko',
312         'detail'   => 'read-only relocations',
313         'err_msg'  => 'no read-only relocations (libfko)',
314         'function' => \&read_only_relocations,
315         'binary'   => $libfko_bin,
316         'fatal'    => $NO
317     },
318     {
319         'category' => 'build security',
320         'subcategory' => 'libfko',
321         'detail'   => 'immediate binding',
322         'err_msg'  => 'no immediate binding (libfko)',
323         'function' => \&immediate_binding,
324         'binary'   => $libfko_bin,
325         'fatal'    => $NO
326     },
327
328     {
329         'category' => 'preliminaries',
330         'subcategory' => 'client',
331         'detail'   => 'usage info',
332         'err_msg'  => 'could not get usage info',
333         'function' => \&generic_exec,
334         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopCmd -h",
335         'fatal'    => $NO
336     },
337     {
338         'category' => 'preliminaries',
339         'subcategory' => 'client',
340         'detail'   => 'getopt() no such argument',
341         'err_msg'  => 'getopt() allowed non-existant argument',
342         'function' => \&generic_exec,
343         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopCmd --no-such-arg",
344         'exec_err' => $YES,
345         'fatal'    => $NO
346     },
347     {
348         'category' => 'preliminaries',
349         'subcategory' => 'client',
350         'detail'   => '--test mode, packet not sent',
351         'err_msg'  => '--test mode, packet sent?',
352         'function' => \&generic_exec,
353         'positive_output_matches' => [qr/test\smode\senabled/],
354         'cmdline'  => "$default_client_args --test",
355         'fatal'    => $NO
356     },
357
358     {
359         'category' => 'preliminaries',
360         'subcategory' => 'client',
361         'detail'   => 'expected code version',
362         'err_msg'  => 'code version mis-match',
363         'function' => \&expected_code_version,
364         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopCmd --version",
365         'fatal'    => $NO
366     },
367
368     {
369         'category' => 'preliminaries',
370         'subcategory' => 'server',
371         'detail'   => 'usage info',
372         'err_msg'  => 'could not get usage info',
373         'function' => \&generic_exec,
374         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopdCmd -h",
375         'fatal'    => $NO
376     },
377     {
378         'category' => 'preliminaries',
379         'subcategory' => 'server',
380         'detail'   => 'getopt() no such argument',
381         'err_msg'  => 'getopt() allowed non-existant argument',
382         'function' => \&generic_exec,
383         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopdCmd --no-such-arg",
384         'exec_err' => $YES,
385         'fatal'    => $NO
386     },
387
388     {
389         'category' => 'preliminaries',
390         'subcategory' => 'server',
391         'detail'   => 'expected code version',
392         'err_msg'  => 'code version mis-match',
393         'function' => \&expected_code_version,
394         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
395             "$fwknopdCmd -c $default_conf -a " .
396             "$default_access_conf --version",
397         'fatal'    => $NO
398     },
399     {
400         'category' => 'preliminaries',
401         'detail'   => 'collecting system specifics',
402         'err_msg'  => 'could not get complete system specs',
403         'function' => \&specs,
404         'binary'   => $fwknopdCmd,
405         'fatal'    => $NO
406     },
407
408     {
409         'category' => 'basic operations',
410         'detail'   => 'dump config',
411         'err_msg'  => 'could not dump configuration',
412         'function' => \&generic_exec,
413         'positive_output_matches' => [qr/SYSLOG_IDENTITY/],
414         'exec_err' => $NO,
415         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
416             "$fwknopdCmd -c $default_conf " .
417             "-a $default_access_conf --dump-config",
418         'fatal'    => $NO
419     },
420     {
421         'category' => 'basic operations',
422         'detail'   => 'override config',
423         'err_msg'  => 'could not override configuration',
424         'function' => \&generic_exec,
425         'positive_output_matches' => [qr/ENABLE_PCAP_PROMISC.*\'Y\'/],
426         'exec_err' => $NO,
427         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
428             "$fwknopdCmd $default_server_conf_args " .
429             "-O $conf_dir/override_fwknopd.conf --dump-config",
430         'fatal'    => $NO
431     },
432
433     {
434         'category' => 'basic operations',
435         'subcategory' => 'client',
436         'detail'   => '--get-key path validation',
437         'err_msg'  => 'accepted improper --get-key path',
438         'function' => \&generic_exec,
439         'positive_output_matches' => [qr/could\snot\sopen/i],
440         'exec_err' => $YES,
441         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
442             "$fwknopCmd -A tcp/22 -s $fake_ip " .
443             "-D $loopback_ip --get-key not/there",
444         'fatal'    => $YES
445     },
446     {
447         'category' => 'basic operations',
448         'subcategory' => 'client',
449         'detail'   => 'require [-s|-R|-a]',
450         'err_msg'  => 'allowed null allow IP',
451         'function' => \&generic_exec,
452         'positive_output_matches' => [qr/must\suse\sone\sof/i],
453         'exec_err' => $YES,
454         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
455             "$fwknopCmd -D $loopback_ip",
456         'fatal'    => $NO
457     },
458     {
459         'category' => 'basic operations',
460         'subcategory' => 'client',
461         'detail'   => '--allow-ip <IP> valid IP',
462         'err_msg'  => 'permitted invalid --allow-ip arg',
463         'function' => \&generic_exec,
464         'positive_output_matches' => [qr/Invalid\sallow\sIP\saddress/i],
465         'exec_err' => $YES,
466         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
467             "$fwknopCmd -A tcp/22 -a invalidIP -D $loopback_ip",
468         'fatal'    => $NO
469     },
470     {
471         'category' => 'basic operations',
472         'subcategory' => 'client',
473         'detail'   => '-A <proto>/<port> specification',
474         'err_msg'  => 'permitted invalid -A <proto>/<port>',
475         'function' => \&generic_exec,
476         'positive_output_matches' => [qr/Invalid\sSPA\saccess\smessage/i],
477         'exec_err' => $YES,
478         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
479             "$fwknopCmd -A invalid/22 -a $fake_ip -D $loopback_ip",
480         'fatal'    => $NO
481     },
482     {
483         'category' => 'basic operations',
484         'subcategory' => 'client',
485         'detail'   => 'generate SPA packet',
486         'err_msg'  => 'could not generate SPA packet',
487         'function' => \&client_send_spa_packet,
488         'cmdline'  => $default_client_args,
489         'fatal'    => $YES
490     },
491
492     {
493         'category' => 'basic operations',
494         'subcategory' => 'server',
495         'detail'   => 'list current fwknopd fw rules',
496         'err_msg'  => 'could not list current fwknopd fw rules',
497         'function' => \&generic_exec,
498         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
499             "$fwknopdCmd $default_server_conf_args --fw-list",
500         'fatal'    => $NO
501     },
502     {
503         'category' => 'basic operations',
504         'subcategory' => 'server',
505         'detail'   => 'list all current fw rules',
506         'err_msg'  => 'could not list all current fw rules',
507         'function' => \&generic_exec,
508         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
509             "$fwknopdCmd $default_server_conf_args --fw-list-all",
510         'fatal'    => $NO
511     },
512     {
513         'category' => 'basic operations',
514         'subcategory' => 'server',
515         'detail'   => 'flush current firewall rules',
516         'err_msg'  => 'could not flush current fw rules',
517         'function' => \&generic_exec,
518         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
519             "$fwknopdCmd $default_server_conf_args --fw-flush",
520         'fatal'    => $NO
521     },
522
523     {
524         'category' => 'basic operations',
525         'subcategory' => 'server',
526         'detail'   => 'start',
527         'err_msg'  => 'start error',
528         'function' => \&server_start,
529         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
530             "$fwknopdCmd $default_server_conf_args $intf_str",
531         'fatal'    => $NO
532     },
533     {
534         'category' => 'basic operations',
535         'subcategory' => 'server',
536         'detail'   => 'stop',
537         'err_msg'  => 'stop error',
538         'function' => \&server_stop,
539         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
540             "$fwknopdCmd $default_server_conf_args $intf_str",
541         'fatal'    => $NO
542     },
543     {
544         'category' => 'basic operations',
545         'subcategory' => 'server',
546         'detail'   => 'write PID',
547         'err_msg'  => 'did not write PID',
548         'function' => \&write_pid,
549         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
550             "$fwknopdCmd $default_server_conf_args $intf_str",
551         'fatal'    => $NO
552     },
553
554     {
555         'category' => 'basic operations',
556         'subcategory' => 'server',
557         'detail'   => '--packet-limit 1 exit',
558         'err_msg'  => 'did not exit after one packet',
559         'function' => \&server_packet_limit,
560         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
561             "$fwknopdCmd $default_server_conf_args --packet-limit 1 $intf_str",
562         'fatal'    => $NO
563     },
564     {
565         'category' => 'basic operations',
566         'subcategory' => 'server',
567         'detail'   => 'ignore packets < min SPA len (140)',
568         'err_msg'  => 'did not ignore small packets',
569         'function' => \&server_ignore_small_packets,
570         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
571             "$fwknopdCmd $default_server_conf_args --packet-limit 1 $intf_str",
572         'fatal'    => $NO
573     },
574     {
575         'category' => 'basic operations',
576         'subcategory' => 'server',
577         'detail'   => '-P bpf filter ignore packet',
578         'err_msg'  => 'filter did not ignore packet',
579         'function' => \&server_bpf_ignore_packet,
580         'cmdline'  => $default_client_args,
581         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
582             "$fwknopdCmd $default_server_conf_args --packet-limit 1 $intf_str " .
583             qq|-P "udp port $non_std_spa_port"|,
584         'fatal'    => $NO
585     },
586
587     {
588         'category' => 'Rijndael SPA',
589         'subcategory' => 'client+server',
590         'detail'   => 'complete cycle (tcp/22 ssh)',
591         'err_msg'  => 'could not complete SPA cycle',
592         'function' => \&spa_cycle,
593         'cmdline'  => $default_client_args,
594         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
595             "$fwknopdCmd $default_server_conf_args $intf_str",
596         'fw_rule_created' => $NEW_RULE_REQUIRED,
597         'fw_rule_removed' => $NEW_RULE_REMOVED,
598         'fatal'    => $NO
599     },
600     {
601         'category' => 'Rijndael SPA',
602         'subcategory' => 'client+server',
603         'detail'   => 'dual usage access key (tcp/80 http)',
604         'err_msg'  => 'could not complete SPA cycle',
605         'function' => \&spa_cycle,
606         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
607             "$fwknopCmd -A tcp/80 -a $fake_ip -D $loopback_ip --get-key " .
608             "$local_key_file --verbose --verbose",
609         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
610             "$fwknopdCmd -c $default_conf -a $dual_key_usage_access_conf " .
611             "-d $default_digest_file -p $default_pid_file $intf_str",
612         ### check for the first stanza that does not allow tcp/80 - the
613         ### second stanza allows this
614         'server_positive_output_matches' => [qr/stanza #1\)\sOne\sor\smore\srequested\sprotocol\/ports\swas\sdenied/],
615         'fw_rule_created' => $NEW_RULE_REQUIRED,
616         'fw_rule_removed' => $NEW_RULE_REMOVED,
617         'fatal'    => $NO
618     },
619     {
620         'category' => 'Rijndael SPA',
621         'subcategory' => 'client+server',
622         'detail'   => 'packet aging (past) (tcp/22 ssh)',
623         'err_msg'  => 'old SPA packet accepted',
624         'function' => \&spa_cycle,
625         'cmdline'  => "$default_client_args --time-offset-minus 300s",
626         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
627             "$fwknopdCmd $default_server_conf_args $intf_str",
628         'server_positive_output_matches' => [qr/SPA\sdata\stime\sdifference/],
629         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
630         'fatal'    => $NO
631     },
632     {
633         'category' => 'Rijndael SPA',
634         'subcategory' => 'client+server',
635         'detail'   => 'packet aging (future) (tcp/22 ssh)',
636         'err_msg'  => 'future SPA packet accepted',
637         'function' => \&spa_cycle,
638         'cmdline'  => "$default_client_args --time-offset-plus 300s",
639         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
640             "$fwknopdCmd $default_server_conf_args $intf_str",
641         'server_positive_output_matches' => [qr/SPA\sdata\stime\sdifference/],
642         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
643         'fatal'    => $NO
644     },
645     {
646         'category' => 'Rijndael SPA',
647         'subcategory' => 'client+server',
648         'detail'   => 'expired stanza (tcp/22 ssh)',
649         'err_msg'  => 'SPA packet accepted',
650         'function' => \&spa_cycle,
651         'cmdline'  => $default_client_args,
652         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
653             "$fwknopdCmd -c $default_conf -a $expired_access_conf " .
654             "-d $default_digest_file -p $default_pid_file $intf_str",
655         'server_positive_output_matches' => [qr/Access\sstanza\shas\sexpired/],
656         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
657         'fatal'    => $NO
658     },
659     {
660         'category' => 'Rijndael SPA',
661         'subcategory' => 'client+server',
662         'detail'   => 'invalid expire date (tcp/22 ssh)',
663         'err_msg'  => 'SPA packet accepted',
664         'function' => \&spa_cycle,
665         'cmdline'  => $default_client_args,
666         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
667             "$fwknopdCmd -c $default_conf -a $invalid_expire_access_conf " .
668             "-d $default_digest_file -p $default_pid_file $intf_str",
669         'server_positive_output_matches' => [qr/invalid\sdate\svalue/],
670         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
671         'fatal'    => $NO
672     },
673     {
674         'category' => 'Rijndael SPA',
675         'subcategory' => 'client+server',
676         'detail'   => 'expired epoch stanza (tcp/22 ssh)',
677         'err_msg'  => 'SPA packet accepted',
678         'function' => \&spa_cycle,
679         'cmdline'  => $default_client_args,
680         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
681             "$fwknopdCmd -c $default_conf -a $expired_epoch_access_conf " .
682             "-d $default_digest_file -p $default_pid_file $intf_str",
683         'server_positive_output_matches' => [qr/Access\sstanza\shas\sexpired/],
684         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
685         'fatal'    => $NO
686     },
687     {
688         'category' => 'Rijndael SPA',
689         'subcategory' => 'client+server',
690         'detail'   => 'future expired stanza (tcp/22 ssh)',
691         'err_msg'  => 'SPA packet not accepted',
692         'function' => \&spa_cycle,
693         'cmdline'  => $default_client_args,
694         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
695             "$fwknopdCmd -c $default_conf -a $future_expired_access_conf " .
696             "-d $default_digest_file -p $default_pid_file $intf_str",
697         'fw_rule_created' => $NEW_RULE_REQUIRED,
698         'fw_rule_removed' => $NEW_RULE_REMOVED,
699         'fatal'    => $NO
700     },
701
702     {
703         'category' => 'Rijndael SPA',
704         'subcategory' => 'client+server',
705         'detail'   => 'OPEN_PORTS (tcp/22 ssh)',
706         'err_msg'  => "improper OPEN_PORTS result",
707         'function' => \&spa_cycle,
708         'cmdline'  => $default_client_args,
709         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
710             "$fwknopdCmd -c $default_conf -a $open_ports_access_conf " .
711             "-d $default_digest_file -p $default_pid_file $intf_str",
712         'fw_rule_created' => $NEW_RULE_REQUIRED,
713         'fw_rule_removed' => $NEW_RULE_REMOVED,
714         'fatal'    => $NO
715     },
716     {
717         'category' => 'Rijndael SPA',
718         'subcategory' => 'client+server',
719         'detail'   => 'OPEN_PORTS mismatch',
720         'err_msg'  => "SPA packet accepted",
721         'function' => \&spa_cycle,
722         'cmdline'  => $default_client_args,
723         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
724             "$fwknopdCmd -c $default_conf -a $mismatch_open_ports_access_conf " .
725             "-d $default_digest_file -p $default_pid_file $intf_str",
726         'server_positive_output_matches' => [qr/One\s+or\s+more\s+requested/],
727         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
728         'fatal'    => $NO
729     },
730     {
731         'category' => 'Rijndael SPA',
732         'subcategory' => 'client+server',
733         'detail'   => 'require user (tcp/22 ssh)',
734         'err_msg'  => "missed require user criteria",
735         'function' => \&spa_cycle,
736         'cmdline'  => "SPOOF_USER=$spoof_user $default_client_args",
737         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
738             "$fwknopdCmd -c $default_conf -a $require_user_access_conf " .
739             "-d $default_digest_file -p $default_pid_file $intf_str",
740         'fw_rule_created' => $NEW_RULE_REQUIRED,
741         'fw_rule_removed' => $NEW_RULE_REMOVED,
742         'fatal'    => $NO
743     },
744     {
745         'category' => 'Rijndael SPA',
746         'subcategory' => 'client+server',
747         'detail'   => 'user mismatch (tcp/22 ssh)',
748         'err_msg'  => "improper user accepted for access",
749         'function' => \&user_mismatch,
750         'function' => \&spa_cycle,
751         'cmdline'  => $default_client_args,
752         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
753             "$fwknopdCmd -c $default_conf -a $mismatch_user_access_conf " .
754             "-d $default_digest_file -p $default_pid_file $intf_str",
755         'server_positive_output_matches' => [qr/Username\s+in\s+SPA\s+data/],
756         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
757         'fatal'    => $NO
758     },
759     {
760         'category' => 'Rijndael SPA',
761         'subcategory' => 'client+server',
762         'detail'   => 'require src (tcp/22 ssh)',
763         'err_msg'  => "fw rule not created",
764         'function' => \&spa_cycle,
765         'cmdline'  => $default_client_args,
766         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
767             "$fwknopdCmd -c $default_conf -a $require_src_access_conf " .
768             "-d $default_digest_file -p $default_pid_file $intf_str",
769         'fw_rule_created' => $NEW_RULE_REQUIRED,
770         'fw_rule_removed' => $NEW_RULE_REMOVED,
771         'fatal'    => $NO
772     },
773     {
774         'category' => 'Rijndael SPA',
775         'subcategory' => 'client+server',
776         'detail'   => 'mismatch require src (tcp/22 ssh)',
777         'err_msg'  => "fw rule created",
778         'function' => \&spa_cycle,
779         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
780             "$fwknopCmd -A tcp/22 -s -D $loopback_ip --get-key " .
781             "$local_key_file --verbose --verbose",
782         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
783             "$fwknopdCmd -c $default_conf -a $require_src_access_conf " .
784             "-d $default_digest_file -p $default_pid_file $intf_str",
785         'server_positive_output_matches' => [qr/Got\s0.0.0.0\swhen\svalid\ssource\sIP/],
786         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
787         'fatal'    => $NO
788     },
789
790     {
791         'category' => 'Rijndael SPA',
792         'subcategory' => 'client+server',
793         'detail'   => 'IP filtering (tcp/22 ssh)',
794         'err_msg'  => "did not filter $loopback_ip",
795         'function' => \&spa_cycle,
796         'cmdline'  => $default_client_args,
797         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
798             "$fwknopdCmd -c $default_conf -a $no_source_match_access_conf " .
799             "-d $default_digest_file -p $default_pid_file $intf_str",
800         'server_positive_output_matches' => [qr/No\saccess\sdata\sfound/],
801         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
802         'fatal'    => $NO
803     },
804     {
805         'category' => 'Rijndael SPA',
806         'subcategory' => 'client+server',
807         'detail'   => 'subnet filtering (tcp/22 ssh)',
808         'err_msg'  => "did not filter $loopback_ip",
809         'function' => \&spa_cycle,
810         'cmdline'  => $default_client_args,
811         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
812             "$fwknopdCmd -c $default_conf -a $no_subnet_source_match_access_conf " .
813             "-d $default_digest_file -p $default_pid_file $intf_str",
814         'server_positive_output_matches' => [qr/No\saccess\sdata\sfound/],
815         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
816         'fatal'    => $NO
817     },
818     {
819         'category' => 'Rijndael SPA',
820         'subcategory' => 'client+server',
821         'detail'   => 'IP+subnet filtering (tcp/22 ssh)',
822         'err_msg'  => "did not filter $loopback_ip",
823         'function' => \&spa_cycle,
824         'cmdline'  => $default_client_args,
825         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
826             "$fwknopdCmd -c $default_conf -a $no_multi_source_match_access_conf " .
827             "-d $default_digest_file -p $default_pid_file $intf_str",
828         'server_positive_output_matches' => [qr/No\saccess\sdata\sfound/],
829         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
830         'fatal'    => $NO
831     },
832     {
833         'category' => 'Rijndael SPA',
834         'subcategory' => 'client+server',
835         'detail'   => 'IP match (tcp/22 ssh)',
836         'err_msg'  => "did not filter $loopback_ip",
837         'function' => \&spa_cycle,
838         'cmdline'  => $default_client_args,
839         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
840             "$fwknopdCmd -c $default_conf -a $ip_source_match_access_conf " .
841             "-d $default_digest_file -p $default_pid_file $intf_str",
842         'fw_rule_created' => $NEW_RULE_REQUIRED,
843         'fw_rule_removed' => $NEW_RULE_REMOVED,
844         'fatal'    => $NO
845     },
846     {
847         'category' => 'Rijndael SPA',
848         'subcategory' => 'client+server',
849         'detail'   => 'subnet match (tcp/22 ssh)',
850         'err_msg'  => "did not filter $loopback_ip",
851         'function' => \&spa_cycle,
852         'cmdline'  => $default_client_args,
853         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
854             "$fwknopdCmd -c $default_conf -a $subnet_source_match_access_conf " .
855             "-d $default_digest_file -p $default_pid_file $intf_str",
856         'fw_rule_created' => $NEW_RULE_REQUIRED,
857         'fw_rule_removed' => $NEW_RULE_REMOVED,
858         'fatal'    => $NO
859     },
860     {
861         'category' => 'Rijndael SPA',
862         'subcategory' => 'client+server',
863         'detail'   => 'multi IP/net match (tcp/22 ssh)',
864         'err_msg'  => "did not filter $loopback_ip",
865         'function' => \&spa_cycle,
866         'cmdline'  => $default_client_args,
867         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
868             "$fwknopdCmd -c $default_conf -a $multi_source_match_access_conf " .
869             "-d $default_digest_file -p $default_pid_file $intf_str",
870         'fw_rule_created' => $NEW_RULE_REQUIRED,
871         'fw_rule_removed' => $NEW_RULE_REMOVED,
872         'fatal'    => $NO
873     },
874     {
875         'category' => 'Rijndael SPA',
876         'subcategory' => 'client+server',
877         'detail'   => 'multi access stanzas (tcp/22 ssh)',
878         'err_msg'  => "could not complete SPA cycle",
879         'function' => \&spa_cycle,
880         'cmdline'  => $default_client_args,
881         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
882             "$fwknopdCmd -c $default_conf -a $multi_stanzas_access_conf " .
883             "-d $default_digest_file -p $default_pid_file $intf_str",
884         'fw_rule_created' => $NEW_RULE_REQUIRED,
885         'fw_rule_removed' => $NEW_RULE_REMOVED,
886         'fatal'    => $NO
887     },
888     {
889         'category' => 'Rijndael SPA',
890         'subcategory' => 'client+server',
891         'detail'   => 'bad/good key stanzas (tcp/22 ssh)',
892         'err_msg'  => "could not complete SPA cycle",
893         'function' => \&spa_cycle,
894         'cmdline'  => $default_client_args,
895         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
896             "$fwknopdCmd -c $default_conf -a $multi_stanzas_with_broken_keys_conf " .
897             "-d $default_digest_file -p $default_pid_file $intf_str",
898         'fw_rule_created' => $NEW_RULE_REQUIRED,
899         'fw_rule_removed' => $NEW_RULE_REMOVED,
900         'fatal'    => $NO
901     },
902
903     {
904         'category' => 'Rijndael SPA',
905         'subcategory' => 'client+server',
906         'detail'   => "non-enabled NAT (tcp/22 ssh)",
907         'err_msg'  => "SPA packet not filtered",
908         'function' => \&spa_cycle,
909         'cmdline'  => "$default_client_args -N $internal_nat_host:22",
910         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
911             "$fwknopdCmd $default_server_conf_args $intf_str",
912         'server_positive_output_matches' => [qr/requested\sNAT\saccess.*not\senabled/i],
913         'server_conf' => $nat_conf,
914         'fw_rule_created' => $REQUIRE_NO_NEW_RULE,
915         'fatal'    => $NO
916     },
917     {
918         'category' => 'Rijndael SPA',
919         'subcategory' => 'client+server',
920         'detail'   => "NAT to $internal_nat_host (tcp/22 ssh)",
921         'err_msg'  => "could not complete NAT SPA cycle",
922         'function' => \&spa_cycle,
923         'cmdline'  => "$default_client_args -N $internal_nat_host:22",
924         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
925             "$fwknopdCmd -c $nat_conf -a $open_ports_access_conf " .
926             "-d $default_digest_file -p $default_pid_file $intf_str",
927         'server_positive_output_matches' => [qr/to\:$internal_nat_host\:22/i],
928         'fw_rule_created' => $NEW_RULE_REQUIRED,
929         'fw_rule_removed' => $NEW_RULE_REMOVED,
930         'server_conf' => $nat_conf,
931         'fatal'    => $NO
932     },
933
934     {
935         'category' => 'Rijndael SPA',
936         'subcategory' => 'client+server',
937         'detail'   => "force NAT $force_nat_host (tcp/22 ssh)",
938         'err_msg'  => "could not complete NAT SPA cycle",
939         'function' => \&spa_cycle,
940         'cmdline'  => $default_client_args,
941         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
942             "$fwknopdCmd -c $nat_conf -a $force_nat_access_conf " .
943             "-d $default_digest_file -p $default_pid_file $intf_str",
944         'server_positive_output_matches' => [qr/to\:$force_nat_host\:22/i],
945         'server_negative_output_matches' => [qr/to\:$internal_nat_host\:22/i],
946         'fw_rule_created' => $NEW_RULE_REQUIRED,
947         'fw_rule_removed' => $NEW_RULE_REMOVED,
948         'server_conf' => $nat_conf,
949         'fatal'    => $NO
950     },
951
952     {
953         'category' => 'Rijndael SPA',
954         'subcategory' => 'client+server',
955         'detail'   => 'complete cycle (tcp/23 telnet)',
956         'err_msg'  => 'could not complete SPA cycle',
957         'function' => \&spa_cycle,
958         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
959             "$fwknopCmd -A tcp/23 -a $fake_ip -D $loopback_ip --get-key " .
960             "$local_key_file --verbose --verbose",
961         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
962             "$fwknopdCmd $default_server_conf_args $intf_str",
963         'fw_rule_created' => $NEW_RULE_REQUIRED,
964         'fw_rule_removed' => $NEW_RULE_REMOVED,
965         'fatal'    => $NO
966     },
967     {
968         'category' => 'Rijndael SPA',
969         'subcategory' => 'client+server',
970         'detail'   => 'complete cycle (tcp/9418 git)',
971         'err_msg'  => 'could not complete SPA cycle',
972         'function' => \&spa_cycle,
973         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
974             "$fwknopCmd -A tcp/9418 -a $fake_ip -D $loopback_ip --get-key " .
975             "$local_key_file --verbose --verbose",
976         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
977             "$fwknopdCmd $default_server_conf_args $intf_str",
978         'fw_rule_created' => $NEW_RULE_REQUIRED,
979         'fw_rule_removed' => $NEW_RULE_REMOVED,
980         'fatal'    => $NO
981     },
982     {
983         'category' => 'Rijndael SPA',
984         'subcategory' => 'client+server',
985         'detail'   => 'complete cycle (udp/53 dns)',
986         'err_msg'  => 'could not complete SPA cycle',
987         'function' => \&spa_cycle,
988         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
989             "$fwknopCmd -A udp/53 -a $fake_ip -D $loopback_ip --get-key " .
990             "$local_key_file --verbose --verbose",
991         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
992             "$fwknopdCmd $default_server_conf_args $intf_str",
993         'fw_rule_created' => $NEW_RULE_REQUIRED,
994         'fw_rule_removed' => $NEW_RULE_REMOVED,
995         'fatal'    => $NO
996     },
997     {
998         'category' => 'Rijndael SPA',
999         'subcategory' => 'client+server',
1000         'detail'   => "-P bpf SPA over port $non_std_spa_port",
1001         'err_msg'  => 'could not complete SPA cycle',
1002         'function' => \&spa_cycle,
1003         'cmdline'  => "$default_client_args --server-port $non_std_spa_port",
1004         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1005             "$fwknopdCmd $default_server_conf_args $intf_str " .
1006             qq|-P "udp port $non_std_spa_port"|,
1007         'server_positive_output_matches' => [qr/PCAP\sfilter.*\s$non_std_spa_port/],
1008         'fw_rule_created' => $NEW_RULE_REQUIRED,
1009         'fw_rule_removed' => $NEW_RULE_REMOVED,
1010         'fatal'    => $NO
1011     },
1012
1013     {
1014         'category' => 'Rijndael SPA',
1015         'subcategory' => 'client+server',
1016         'detail'   => 'random SPA port (tcp/22 ssh)',
1017         'err_msg'  => 'could not complete SPA cycle',
1018         'function' => \&spa_cycle,
1019         'cmdline'  => "$default_client_args -r",
1020         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1021             "$fwknopdCmd $default_server_conf_args $intf_str " .
1022             qq|-P "udp"|,
1023         'fw_rule_created' => $NEW_RULE_REQUIRED,
1024         'fw_rule_removed' => $NEW_RULE_REMOVED,
1025         'fatal'    => $NO
1026     },
1027
1028     {
1029         'category' => 'Rijndael SPA',
1030         'subcategory' => 'client+server',
1031         'detail'   => 'spoof username (tcp/22)',
1032         'err_msg'  => 'could not spoof username',
1033         'function' => \&spoof_username,
1034         'cmdline'  => "SPOOF_USER=$spoof_user LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1035             "$fwknopCmd -A tcp/22 -a $fake_ip -D $loopback_ip --get-key " .
1036             "$local_key_file --verbose --verbose",
1037         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1038             "$fwknopdCmd $default_server_conf_args $intf_str",
1039         'fatal'    => $NO
1040     },
1041
1042     {
1043         'category' => 'Rijndael SPA',
1044         'subcategory' => 'client+server',
1045         'detail'   => 'replay attack detection',
1046         'err_msg'  => 'could not detect replay attack',
1047         'function' => \&replay_detection,
1048         'cmdline'  => $default_client_args,
1049         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1050             "$fwknopdCmd $default_server_conf_args $intf_str",
1051         'fatal'    => $NO
1052     },
1053     {
1054         'category' => 'Rijndael SPA',
1055         'subcategory' => 'server',
1056         'detail'   => 'digest cache structure',
1057         'err_msg'  => 'improper digest cache structure',
1058         'function' => \&digest_cache_structure,
1059         'fatal'    => $NO
1060     },
1061
1062     {
1063         'category' => 'Rijndael SPA',
1064         'subcategory' => 'client+server',
1065         'detail'   => 'non-base64 altered SPA data',
1066         'err_msg'  => 'allowed improper SPA data',
1067         'function' => \&altered_non_base64_spa_data,
1068         'cmdline'  => $default_client_args,
1069         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1070             "$fwknopdCmd $default_server_conf_args $intf_str",
1071         'fatal'    => $NO
1072     },
1073     {
1074         'category' => 'Rijndael SPA',
1075         'subcategory' => 'client+server',
1076         'detail'   => 'base64 altered SPA data',
1077         'err_msg'  => 'allowed improper SPA data',
1078         'function' => \&altered_base64_spa_data,
1079         'cmdline'  => $default_client_args,
1080         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1081             "$fwknopdCmd $default_server_conf_args $intf_str",
1082         'fatal'    => $NO
1083     },
1084     {
1085         'category' => 'Rijndael SPA',
1086         'subcategory' => 'client+server',
1087         'detail'   => 'appended data to SPA pkt',
1088         'err_msg'  => 'allowed improper SPA data',
1089         'function' => \&appended_spa_data,
1090         'cmdline'  => $default_client_args,
1091         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1092             "$fwknopdCmd $default_server_conf_args $intf_str",
1093         'fatal'    => $NO
1094     },
1095     {
1096         'category' => 'Rijndael SPA',
1097         'subcategory' => 'client+server',
1098         'detail'   => 'prepended data to SPA pkt',
1099         'err_msg'  => 'allowed improper SPA data',
1100         'function' => \&prepended_spa_data,
1101         'cmdline'  => $default_client_args,
1102         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1103             "$fwknopdCmd $default_server_conf_args $intf_str",
1104         'fatal'    => $NO
1105     },
1106
1107     {
1108         'category' => 'GnuPG (GPG) SPA',
1109         'subcategory' => 'client+server',
1110         'detail'   => 'complete cycle (tcp/22 ssh)',
1111         'err_msg'  => 'could not complete SPA cycle',
1112         'function' => \&spa_cycle,
1113         'cmdline'  => $default_client_gpg_args,
1114         'fwknopd_cmdline'  => $default_server_gpg_args,
1115         'fw_rule_created' => $NEW_RULE_REQUIRED,
1116         'fw_rule_removed' => $NEW_RULE_REMOVED,
1117         'fatal'    => $NO
1118     },
1119     {
1120         'category' => 'GnuPG (GPG) SPA',
1121         'subcategory' => 'client+server',
1122         'detail'   => 'multi gpg-IDs (tcp/22 ssh)',
1123         'err_msg'  => 'could not complete SPA cycle',
1124         'function' => \&spa_cycle,
1125         'cmdline'  => $default_client_gpg_args,
1126         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir " .
1127             "$valgrind_str $fwknopdCmd -c $default_conf " .
1128             "-a $multi_gpg_access_conf $intf_str " .
1129             "-d $default_digest_file -p $default_pid_file",
1130         'fw_rule_created' => $NEW_RULE_REQUIRED,
1131         'fw_rule_removed' => $NEW_RULE_REMOVED,
1132         'fatal'    => $NO
1133     },
1134
1135     {
1136         'category' => 'GnuPG (GPG) SPA',
1137         'subcategory' => 'client+server',
1138         'detail'   => 'complete cycle (tcp/23 telnet)',
1139         'err_msg'  => 'could not complete SPA cycle',
1140         'function' => \&spa_cycle,
1141         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1142             "$fwknopCmd -A tcp/23 -a $fake_ip -D $loopback_ip --get-key " .
1143             "$local_key_file --verbose --verbose " .
1144             "--gpg-recipient-key $gpg_server_key " .
1145             "--gpg-signer-key $gpg_client_key " .
1146             "--gpg-home-dir $gpg_client_home_dir",
1147         'fwknopd_cmdline'  => $default_server_gpg_args,
1148         'fw_rule_created' => $NEW_RULE_REQUIRED,
1149         'fw_rule_removed' => $NEW_RULE_REMOVED,
1150         'fatal'    => $NO
1151     },
1152     {
1153         'category' => 'GnuPG (GPG) SPA',
1154         'subcategory' => 'client+server',
1155         'detail'   => 'complete cycle (tcp/9418 git)',
1156         'err_msg'  => 'could not complete SPA cycle',
1157         'function' => \&spa_cycle,
1158         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1159             "$fwknopCmd -A tcp/9418 -a $fake_ip -D $loopback_ip --get-key " .
1160             "$local_key_file --verbose --verbose " .
1161             "--gpg-recipient-key $gpg_server_key " .
1162             "--gpg-signer-key $gpg_client_key " .
1163             "--gpg-home-dir $gpg_client_home_dir",
1164         'fwknopd_cmdline'  => $default_server_gpg_args,
1165         'fw_rule_created' => $NEW_RULE_REQUIRED,
1166         'fw_rule_removed' => $NEW_RULE_REMOVED,
1167         'fatal'    => $NO
1168     },
1169     {
1170         'category' => 'GnuPG (GPG) SPA',
1171         'subcategory' => 'client+server',
1172         'detail'   => 'complete cycle (udp/53 dns)',
1173         'err_msg'  => 'could not complete SPA cycle',
1174         'function' => \&spa_cycle,
1175         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1176             "$fwknopCmd -A udp/53 -a $fake_ip -D $loopback_ip --get-key " .
1177             "$local_key_file --verbose --verbose " .
1178             "--gpg-recipient-key $gpg_server_key " .
1179             "--gpg-signer-key $gpg_client_key " .
1180             "--gpg-home-dir $gpg_client_home_dir",
1181         'fwknopd_cmdline'  => $default_server_gpg_args,
1182         'fw_rule_created' => $NEW_RULE_REQUIRED,
1183         'fw_rule_removed' => $NEW_RULE_REMOVED,
1184         'fatal'    => $NO
1185     },
1186
1187     {
1188         'category' => 'GnuPG (GPG) SPA',
1189         'subcategory' => 'client+server',
1190         'detail'   => 'replay attack detection',
1191         'err_msg'  => 'could not detect replay attack',
1192         'function' => \&replay_detection,
1193         'cmdline'  => $default_client_gpg_args,
1194         'fwknopd_cmdline'  => $default_server_gpg_args,
1195         'fatal'    => $NO
1196     },
1197
1198     {
1199         'category' => 'GnuPG (GPG) SPA',
1200         'subcategory' => 'client+server',
1201         'detail'   => 'non-base64 altered SPA data',
1202         'err_msg'  => 'allowed improper SPA data',
1203         'function' => \&altered_non_base64_spa_data,
1204         'cmdline'  => $default_client_gpg_args,
1205         'fwknopd_cmdline'  => $default_server_gpg_args,
1206         'fatal'    => $NO
1207     },
1208     {
1209         'category' => 'GnuPG (GPG) SPA',
1210         'subcategory' => 'client+server',
1211         'detail'   => 'base64 altered SPA data',
1212         'err_msg'  => 'allowed improper SPA data',
1213         'function' => \&altered_base64_spa_data,
1214         'cmdline'  => $default_client_gpg_args,
1215         'fwknopd_cmdline'  => $default_server_gpg_args,
1216         'fatal'    => $NO
1217     },
1218     {
1219         'category' => 'GnuPG (GPG) SPA',
1220         'subcategory' => 'client+server',
1221         'detail'   => 'appended data to SPA pkt',
1222         'err_msg'  => 'allowed improper SPA data',
1223         'function' => \&appended_spa_data,
1224         'cmdline'  => $default_client_gpg_args,
1225         'fwknopd_cmdline'  => $default_server_gpg_args,
1226         'fatal'    => $NO
1227     },
1228     {
1229         'category' => 'GnuPG (GPG) SPA',
1230         'subcategory' => 'client+server',
1231         'detail'   => 'prepended data to SPA pkt',
1232         'err_msg'  => 'allowed improper SPA data',
1233         'function' => \&prepended_spa_data,
1234         'cmdline'  => $default_client_gpg_args,
1235         'fwknopd_cmdline'  => $default_server_gpg_args,
1236         'fatal'    => $NO
1237     },
1238     {
1239         'category' => 'GnuPG (GPG) SPA',
1240         'subcategory' => 'client+server',
1241         'detail'   => 'spoof username (tcp/22 ssh)',
1242         'err_msg'  => 'could not spoof username',
1243         'function' => \&spoof_username,
1244         'cmdline'  => "SPOOF_USER=$spoof_user $default_client_gpg_args",
1245         'fwknopd_cmdline'  => $default_server_gpg_args,
1246         'fatal'    => $NO
1247     },
1248     {
1249         'category' => 'GnuPG (GPG) SPA',
1250         'subcategory' => 'server',
1251         'detail'   => 'digest cache structure',
1252         'err_msg'  => 'improper digest cache structure',
1253         'function' => \&digest_cache_structure,
1254         'fatal'    => $NO
1255     },
1256 );
1257
1258 if ($use_valgrind) {
1259     push @tests,
1260         {
1261             'category' => 'valgrind output',
1262             'subcategory' => 'suspect functions',
1263             'detail'   => '',
1264             'err_msg'  => 'could not parse suspect functions',
1265             'function' => \&parse_valgrind_suspect_functions,
1266             'fatal'    => $NO
1267         };
1268 }
1269
1270 my %test_keys = (
1271     'category'        => $REQUIRED,
1272     'subcategory'     => $OPTIONAL,
1273     'detail'          => $REQUIRED,
1274     'function'        => $REQUIRED,
1275     'binary'          => $OPTIONAL,
1276     'cmdline'         => $OPTIONAL,
1277     'fwknopd_cmdline' => $OPTIONAL,
1278     'fatal'           => $OPTIONAL,
1279     'exec_err'        => $OPTIONAL,
1280     'fw_rule_created' => $OPTIONAL,
1281     'fw_rule_removed' => $OPTIONAL,
1282     'server_conf'     => $OPTIONAL,
1283     'positive_output_matches' => $OPTIONAL,
1284     'negative_output_matches' => $OPTIONAL,
1285     'server_positive_output_matches' => $OPTIONAL,
1286     'server_negative_output_matches' => $OPTIONAL,
1287 );
1288
1289 if ($diff_mode) {
1290     &diff_test_results();
1291     exit 0;
1292 }
1293
1294 ### make sure everything looks as expected before continuing
1295 &init();
1296
1297 &logr("\n[+] Starting the fwknop test suite...\n\n" .
1298     "    args: @args_cp\n\n"
1299 );
1300
1301 ### save the results from any previous test suite run
1302 ### so that we can potentially compare them with --diff
1303 if ($saved_last_results) {
1304     &logr("    Saved results from previous run " .
1305         "to: ${output_dir}.last/\n\n");
1306 }
1307
1308 ### main loop through all of the tests
1309 for my $test_hr (@tests) {
1310     &run_test($test_hr);
1311 }
1312
1313 &logr("\n[+] passed/failed/executed: $passed/$failed/$executed tests\n\n");
1314
1315 copy $logfile, "$output_dir/$logfile" or die $!;
1316
1317 exit 0;
1318
1319 #===================== end main =======================
1320
1321 sub run_test() {
1322     my $test_hr = shift;
1323
1324     my $msg = "[$test_hr->{'category'}]";
1325     $msg .= " [$test_hr->{'subcategory'}]" if $test_hr->{'subcategory'};
1326     $msg .= " $test_hr->{'detail'}";
1327
1328     return unless &process_include_exclude($msg);
1329
1330     if ($list_mode) {
1331         print $msg, "\n";
1332         return;
1333     }
1334
1335     &dots_print($msg);
1336
1337     $executed++;
1338     $current_test_file  = "$output_dir/$executed.test";
1339     $server_test_file   = "$output_dir/${executed}_fwknopd.test";
1340
1341     &write_test_file("[+] TEST: $msg\n", $current_test_file);
1342     $test_hr->{'msg'} = $msg;
1343     if (&{$test_hr->{'function'}}($test_hr)) {
1344         &logr("pass ($executed)\n");
1345         $passed++;
1346     } else {
1347         &logr("fail ($executed)\n");
1348         $failed++;
1349
1350         if ($test_hr->{'fatal'} eq $YES) {
1351             die "[*] required test failed, exiting.";
1352         }
1353     }
1354
1355     return;
1356 }
1357
1358 sub process_include_exclude() {
1359     my $msg = shift;
1360
1361     ### inclusions/exclusions
1362     if (@tests_to_include) {
1363         my $found = 0;
1364         for my $test (@tests_to_include) {
1365             if ($msg =~ /$test/ or ($use_valgrind
1366                     and $msg =~ /valgrind\soutput/)) {
1367                 $found = 1;
1368                 last;
1369             }
1370         }
1371         return 0 unless $found;
1372     }
1373     if (@tests_to_exclude) {
1374         my $found = 0;
1375         for my $test (@tests_to_exclude) {
1376             if ($msg =~ /$test/) {
1377                 $found = 1;
1378                 last;
1379             }
1380         }
1381         return 0 if $found;
1382     }
1383     return 1;
1384 }
1385
1386 sub diff_test_results() {
1387
1388     $diff_dir1 = "${output_dir}.last" unless $diff_dir2;
1389     $diff_dir2 = $output_dir unless $diff_dir1;
1390
1391     die "[*] Need results from a previous run before running --diff"
1392         unless -d $diff_dir2;
1393     die "[*] Current results set does not exist." unless -d $diff_dir1;
1394
1395     my %current_tests  = ();
1396     my %previous_tests = ();
1397
1398     ### Only diff results for matching tests (parse the logfile to see which
1399     ### test numbers match across the two test cycles).
1400     &build_results_hash(\%current_tests, $diff_dir1);
1401     &build_results_hash(\%previous_tests, $diff_dir2);
1402
1403     for my $test_msg (sort {$current_tests{$a}{'num'} <=> $current_tests{$b}{'num'}}
1404                 keys %current_tests) {
1405         my $current_result = $current_tests{$test_msg}{'pass_fail'};
1406         my $current_num    = $current_tests{$test_msg}{'num'};
1407         if (defined $previous_tests{$test_msg}) {
1408             print "[+] Checking: $test_msg\n";
1409             my $previous_result = $previous_tests{$test_msg}{'pass_fail'};
1410             my $previous_num    = $previous_tests{$test_msg}{'num'};
1411             if ($current_result ne $previous_result) {
1412                 print " DIFF: **$current_result** $test_msg\n";
1413             }
1414
1415             &diff_results($previous_num, $current_num);
1416             print "\n";
1417         }
1418     }
1419
1420     exit 0;
1421 }
1422
1423 sub diff_results() {
1424     my ($previous_num, $current_num) = @_;
1425
1426     ### edit out any valgrind "==354==" prefixes
1427     my $valgrind_search_re = qr/^==\d+==\s/;
1428
1429     ### remove CMD timestamps
1430     my $cmd_search_re = qr/^\S+\s.*?\s\d{4}\sCMD\:/;
1431
1432     for my $file ("$diff_dir1/${previous_num}.test",
1433         "$diff_dir1/${previous_num}_fwknopd.test",
1434         "$diff_dir2/${current_num}.test",
1435         "$diff_dir2/${current_num}_fwknopd.test",
1436     ) {
1437         system qq{perl -p -i -e 's|$valgrind_search_re||' $file} if -e $file;
1438         system qq{perl -p -i -e 's|$cmd_search_re|CMD:|' $file} if -e $file;
1439     }
1440
1441     if (-e "$diff_dir1/${previous_num}.test"
1442             and -e "$diff_dir2/${current_num}.test") {
1443         system "diff -u $diff_dir1/${previous_num}.test " .
1444             "$diff_dir2/${current_num}.test";
1445     }
1446
1447     if (-e "$diff_dir1/${previous_num}_fwknopd.test"
1448             and -e "$diff_dir2/${current_num}_fwknopd.test") {
1449         system "diff -u $diff_dir1/${previous_num}_fwknopd.test " .
1450             "$diff_dir2/${current_num}_fwknopd.test";
1451     }
1452
1453     return;
1454 }
1455
1456 sub build_results_hash() {
1457     my ($hr, $dir) = @_;
1458
1459     open F, "< $dir/$logfile" or die $!;
1460     while (<F>) {
1461         if (/^(.*?)\.\.\..*(pass|fail)\s\((\d+)\)/) {
1462             $hr->{$1}{'pass_fail'} = $2;
1463             $hr->{$1}{'num'}       = $3;
1464         }
1465     }
1466     return;
1467 }
1468
1469 sub compile_warnings() {
1470
1471     ### 'make clean' as root
1472     return 0 unless &run_cmd('make -C .. clean',
1473         $cmd_out_tmp, $current_test_file);
1474
1475     if ($sudo_path) {
1476         my $username = getpwuid((stat($configure_path))[4]);
1477         die "[*] Could not determine $configure_path owner"
1478             unless $username;
1479
1480         return 0 unless &run_cmd("$sudo_path -u $username make -C ..",
1481             $cmd_out_tmp, $current_test_file);
1482
1483     } else {
1484
1485         return 0 unless &run_cmd('make -C ..',
1486             $cmd_out_tmp, $current_test_file);
1487
1488     }
1489
1490     ### look for compilation warnings - something like:
1491     ###     warning: ‘test’ is used uninitialized in this function
1492     return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/], $current_test_file);
1493
1494     ### the new binaries should exist
1495     unless (-e $fwknopCmd and -x $fwknopCmd) {
1496         &write_test_file("[-] $fwknopCmd does not exist or not executable.\n",
1497             $current_test_file);
1498     }
1499     unless (-e $fwknopdCmd and -x $fwknopdCmd) {
1500         &write_test_file("[-] $fwknopdCmd does not exist or not executable.\n",
1501             $current_test_file);
1502     }
1503
1504     return 1;
1505 }
1506
1507 sub binary_exists() {
1508     my $test_hr = shift;
1509     return 0 unless $test_hr->{'binary'};
1510
1511     ### account for different libfko.so paths (e.g. libfko.so.0.3 with no
1512     ### libfko.so link on OpenBSD)
1513
1514     if ($test_hr->{'binary'} =~ /libfko/) {
1515         unless (-e $test_hr->{'binary'}) {
1516             for my $file (glob("$lib_dir/libfko.so*")) {
1517                 if (-e $file and -x $file) {
1518                     $test_hr->{'binary'} = $file;
1519                     $libfko_bin = $file;
1520                     last;
1521                 }
1522             }
1523         }
1524     }
1525
1526     return 0 unless -e $test_hr->{'binary'} and -x $test_hr->{'binary'};
1527     return 1;
1528 }
1529
1530 sub expected_code_version() {
1531     my $test_hr = shift;
1532
1533     unless (-e '../VERSION') {
1534         &write_test_file("[-] ../VERSION file does not exist.\n",
1535             $current_test_file);
1536         return 0;
1537     }
1538
1539     open F, '< ../VERSION' or die $!;
1540     my $line = <F>;
1541     close F;
1542     if ($line =~ /(\d.*\d)/) {
1543         my $version = $1;
1544         return 0 unless &run_cmd($test_hr->{'cmdline'},
1545             $cmd_out_tmp, $current_test_file);
1546         return 1 if &file_find_regex([qr/$version/], $current_test_file);
1547     }
1548     return 0;
1549 }
1550
1551 sub client_send_spa_packet() {
1552     my $test_hr = shift;
1553
1554     &write_key('fwknoptest', $local_key_file);
1555
1556     return 0 unless &run_cmd($test_hr->{'cmdline'},
1557             $cmd_out_tmp, $current_test_file);
1558     return 0 unless &file_find_regex([qr/final\spacked/i],
1559         $current_test_file);
1560
1561     return 1;
1562 }
1563
1564 sub spa_cycle() {
1565     my $test_hr = shift;
1566
1567     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1568             = &client_server_interaction($test_hr, [], $USE_CLIENT);
1569
1570     if ($test_hr->{'fw_rule_created'} eq $NEW_RULE_REQUIRED) {
1571         $rv = 0 unless $fw_rule_created;
1572     } elsif ($test_hr->{'fw_rule_created'} eq $REQUIRE_NO_NEW_RULE) {
1573         $rv = 0 if $fw_rule_created;
1574     }
1575
1576     if ($test_hr->{'fw_rule_removed'} eq $NEW_RULE_REMOVED) {
1577         $rv = 0 unless $fw_rule_removed;
1578     } elsif ($test_hr->{'fw_rule_removed'} eq $REQUIRE_NO_NEW_REMOVED) {
1579         $rv = 0 if $fw_rule_removed;
1580     }
1581
1582     if ($test_hr->{'server_positive_output_matches'}) {
1583         $rv = 0 unless &file_find_regex(
1584             $test_hr->{'server_positive_output_matches'},
1585             $server_test_file);
1586     }
1587
1588     if ($test_hr->{'server_negative_output_matches'}) {
1589         $rv = 0 if &file_find_regex(
1590             $test_hr->{'server_negative_output_matches'},
1591             $server_test_file);
1592     }
1593
1594     return $rv;
1595 }
1596
1597 sub spoof_username() {
1598     my $test_hr = shift;
1599
1600     my $rv = &spa_cycle($test_hr);
1601
1602     unless (&file_find_regex([qr/Username:\s*$spoof_user/],
1603             $current_test_file)) {
1604         $rv = 0;
1605     }
1606
1607     unless (&file_find_regex([qr/Username:\s*$spoof_user/],
1608             $server_test_file)) {
1609         $rv = 0;
1610     }
1611
1612     return $rv;
1613 }
1614
1615 sub replay_detection() {
1616     my $test_hr = shift;
1617
1618     ### do a complete SPA cycle and then parse the SPA packet out of the
1619     ### current test file and re-send
1620
1621     return 0 unless &spa_cycle($test_hr);
1622
1623     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1624
1625     unless ($spa_pkt) {
1626         &write_test_file("[-] could not get SPA packet " .
1627             "from file: $current_test_file\n",
1628             $current_test_file);
1629         return 0;
1630     }
1631
1632     my @packets = (
1633         {
1634             'proto'  => 'udp',
1635             'port'   => $default_spa_port,
1636             'dst_ip' => $loopback_ip,
1637             'data'   => $spa_pkt,
1638         },
1639     );
1640
1641     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1642         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1643
1644     $rv = 0 unless $server_was_stopped;
1645
1646     unless (&file_find_regex([qr/Replay\sdetected\sfrom\ssource\sIP/i],
1647             $server_test_file)) {
1648         $rv = 0;
1649     }
1650
1651     return $rv;
1652 }
1653
1654 sub digest_cache_structure() {
1655     my $test_hr = shift;
1656     my $rv = 1;
1657
1658     &run_cmd("file $default_digest_file", $cmd_out_tmp, $current_test_file);
1659
1660     if (&file_find_regex([qr/ASCII/i], $cmd_out_tmp)) {
1661
1662         ### the format should be:
1663         ### <digest> <proto> <src_ip> <src_port> <dst_ip> <dst_port> <time>
1664         open F, "< $default_digest_file" or
1665             die "[*] could not open $default_digest_file: $!";
1666         while (<F>) {
1667             next if /^#/;
1668             next unless /\S/;
1669             unless (m|^\S+\s+\d+\s+$ip_re\s+\d+\s+$ip_re\s+\d+\s+\d+|) {
1670                 &write_test_file("[-] invalid digest.cache line: $_",
1671                     $current_test_file);
1672                 $rv = 0;
1673                 last;
1674             }
1675         }
1676         close F;
1677     } elsif (&file_find_regex([qr/dbm/i], $cmd_out_tmp)) {
1678         &write_test_file("[+] DBM digest file format, " .
1679             "assuming this is valid.\n", $current_test_file);
1680     } else {
1681         ### don't know what kind of file the digest.cache is
1682         &write_test_file("[-] unrecognized file type for " .
1683             "$default_digest_file.\n", $current_test_file);
1684         $rv = 0;
1685     }
1686
1687     if ($rv) {
1688         &write_test_file("[+] valid digest.cache structure.\n",
1689             $current_test_file);
1690     }
1691
1692     return $rv;
1693 }
1694
1695 sub server_bpf_ignore_packet() {
1696     my $test_hr = shift;
1697
1698     my $rv = 1;
1699     my $server_was_stopped = 0;
1700     my $fw_rule_created = 0;
1701     my $fw_rule_removed = 0;
1702
1703     unless (&client_send_spa_packet($test_hr)) {
1704         &write_test_file("[-] fwknop client execution error.\n",
1705             $current_test_file);
1706         $rv = 0;
1707     }
1708
1709     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1710
1711     unless ($spa_pkt) {
1712         &write_test_file("[-] could not get SPA packet " .
1713             "from file: $current_test_file\n", $current_test_file);
1714         return 0;
1715     }
1716
1717     my @packets = (
1718         {
1719             'proto'  => 'udp',
1720             'port'   => $default_spa_port,
1721             'dst_ip' => $loopback_ip,
1722             'data'   => $spa_pkt,
1723         },
1724     );
1725
1726     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1727         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1728
1729     unless (&file_find_regex([qr/PCAP\sfilter.*\s$non_std_spa_port/],
1730             $server_test_file)) {
1731         $rv = 0;
1732     }
1733
1734     return $rv;
1735 }
1736
1737 sub altered_non_base64_spa_data() {
1738     my $test_hr = shift;
1739
1740     my $rv = 1;
1741     my $server_was_stopped = 0;
1742     my $fw_rule_created = 0;
1743     my $fw_rule_removed = 0;
1744
1745     unless (&client_send_spa_packet($test_hr)) {
1746         &write_test_file("[-] fwknop client execution error.\n",
1747             $current_test_file);
1748         $rv = 0;
1749     }
1750
1751     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1752
1753     unless ($spa_pkt) {
1754         &write_test_file("[-] could not get SPA packet " .
1755             "from file: $current_test_file\n", $current_test_file);
1756         return 0;
1757     }
1758
1759     ### alter one byte (change to a ":")
1760     $spa_pkt =~ s|^(.{3}).|$1:|;
1761
1762     my @packets = (
1763         {
1764             'proto'  => 'udp',
1765             'port'   => $default_spa_port,
1766             'dst_ip' => $loopback_ip,
1767             'data'   => $spa_pkt,
1768         },
1769     );
1770
1771     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1772         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1773
1774     $rv = 0 unless $server_was_stopped;
1775
1776     return $rv;
1777 }
1778
1779 sub altered_base64_spa_data() {
1780     my $test_hr = shift;
1781
1782     my $rv = 1;
1783     my $server_was_stopped = 0;
1784     my $fw_rule_created = 0;
1785     my $fw_rule_removed = 0;
1786
1787     unless (&client_send_spa_packet($test_hr)) {
1788         &write_test_file("[-] fwknop client execution error.\n",
1789             $current_test_file);
1790         $rv = 0;
1791     }
1792
1793     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1794
1795     unless ($spa_pkt) {
1796         &write_test_file("[-] could not get SPA packet " .
1797             "from file: $current_test_file\n", $current_test_file);
1798         return 0;
1799     }
1800
1801     $spa_pkt =~ s|^(.{3}).|AAAA|;
1802
1803     my @packets = (
1804         {
1805             'proto'  => 'udp',
1806             'port'   => $default_spa_port,
1807             'dst_ip' => $loopback_ip,
1808             'data'   => $spa_pkt,
1809         },
1810     );
1811
1812     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1813         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1814
1815     $rv = 0 unless $server_was_stopped;
1816
1817     if ($fw_rule_created) {
1818         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1819         $rv = 0;
1820     } else {
1821         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1822     }
1823
1824     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1825             $server_test_file)) {
1826         $rv = 0;
1827     }
1828
1829     return $rv;
1830 }
1831
1832 sub appended_spa_data() {
1833     my $test_hr = shift;
1834
1835     my $rv = 1;
1836     my $server_was_stopped = 0;
1837     my $fw_rule_created = 0;
1838     my $fw_rule_removed = 0;
1839
1840     unless (&client_send_spa_packet($test_hr)) {
1841         &write_test_file("[-] fwknop client execution error.\n",
1842             $current_test_file);
1843         $rv = 0;
1844     }
1845
1846     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1847
1848     unless ($spa_pkt) {
1849         &write_test_file("[-] could not get SPA packet " .
1850             "from file: $current_test_file\n", $current_test_file);
1851         return 0;
1852     }
1853
1854     $spa_pkt .= 'AAAA';
1855
1856     my @packets = (
1857         {
1858             'proto'  => 'udp',
1859             'port'   => $default_spa_port,
1860             'dst_ip' => $loopback_ip,
1861             'data'   => $spa_pkt,
1862         },
1863     );
1864
1865     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1866         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1867
1868     $rv = 0 unless $server_was_stopped;
1869
1870     if ($fw_rule_created) {
1871         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1872         $rv = 0;
1873     } else {
1874         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1875     }
1876
1877     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1878             $server_test_file)) {
1879         $rv = 0;
1880     }
1881
1882     return $rv;
1883 }
1884
1885 sub prepended_spa_data() {
1886     my $test_hr = shift;
1887
1888     my $rv = 1;
1889     my $server_was_stopped = 0;
1890     my $fw_rule_created = 0;
1891     my $fw_rule_removed = 0;
1892
1893     unless (&client_send_spa_packet($test_hr)) {
1894         &write_test_file("[-] fwknop client execution error.\n",
1895             $current_test_file);
1896         $rv = 0;
1897     }
1898
1899     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1900
1901     unless ($spa_pkt) {
1902         &write_test_file("[-] could not get SPA packet " .
1903             "from file: $current_test_file\n", $current_test_file);
1904         return 0;
1905     }
1906
1907     $spa_pkt = 'AAAA' . $spa_pkt;
1908
1909     my @packets = (
1910         {
1911             'proto'  => 'udp',
1912             'port'   => $default_spa_port,
1913             'dst_ip' => $loopback_ip,
1914             'data'   => $spa_pkt,
1915         },
1916     );
1917
1918     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1919         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1920
1921     $rv = 0 unless $server_was_stopped;
1922
1923     if ($fw_rule_created) {
1924         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1925         $rv = 0;
1926     } else {
1927         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1928     }
1929
1930     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1931             $server_test_file)) {
1932         $rv = 0;
1933     }
1934
1935     return $rv;
1936 }
1937
1938 sub server_start() {
1939     my $test_hr = shift;
1940
1941     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1942         = &client_server_interaction($test_hr, [], $USE_PREDEF_PKTS);
1943
1944     unless (&file_find_regex([qr/Starting\sfwknopd\smain\sevent\sloop/],
1945             $server_test_file)) {
1946         $rv = 0;
1947     }
1948
1949     $rv = 0 unless $server_was_stopped;
1950
1951     return $rv;
1952 }
1953
1954 sub server_stop() {
1955     my $test_hr = shift;
1956
1957     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1958         = &client_server_interaction($test_hr, [], $USE_PREDEF_PKTS);
1959
1960     $rv = 0 unless $server_was_stopped;
1961
1962     return $rv;
1963 }
1964
1965 sub server_packet_limit() {
1966     my $test_hr = shift;
1967
1968     my @packets = (
1969         {
1970             'proto'  => 'udp',
1971             'port'   => $default_spa_port,
1972             'dst_ip' => $loopback_ip,
1973             'data'   => 'A'x700,
1974         },
1975     );
1976
1977     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1978         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1979
1980     if (&is_fwknopd_running()) {
1981         &stop_fwknopd();
1982         $rv = 0;
1983     }
1984
1985     unless (&file_find_regex([qr/count\slimit\sof\s1\sreached/],
1986             $server_test_file)) {
1987         $rv = 0;
1988     }
1989
1990     unless (&file_find_regex([qr/Shutting\sDown\sfwknopd/i],
1991             $server_test_file)) {
1992         $rv = 0;
1993     }
1994
1995     return $rv;
1996 }
1997
1998 sub server_ignore_small_packets() {
1999     my $test_hr = shift;
2000
2001     my @packets = (
2002         {
2003             'proto'  => 'udp',
2004             'port'   => $default_spa_port,
2005             'dst_ip' => $loopback_ip,
2006             'data'   => 'A'x120,  ### < MIN_SPA_DATA_SIZE
2007         },
2008     );
2009
2010     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
2011         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
2012
2013     sleep 2;
2014
2015     if (&is_fwknopd_running()) {
2016         &stop_fwknopd();
2017         $rv = 0;
2018     }
2019
2020     return $rv;
2021 }
2022
2023 sub client_server_interaction() {
2024     my ($test_hr, $pkts_hr, $spa_client_flag, $fw_rules_flag) = @_;
2025
2026     my $rv = 1;
2027     my $server_was_stopped = 1;
2028     my $fw_rule_created = 1;
2029     my $fw_rule_removed = 0;
2030
2031     ### start fwknopd to monitor for the SPA packet over the loopback interface
2032     my $fwknopd_parent_pid = &start_fwknopd($test_hr);
2033
2034     ### give fwknopd a chance to parse its config and start sniffing
2035     ### on the loopback interface
2036     if ($use_valgrind) {
2037         sleep 3;
2038     } else {
2039         sleep 2;
2040     }
2041
2042     ### send the SPA packet(s) to the server either manually using IO::Socket or
2043     ### with the fwknopd client
2044     if ($spa_client_flag == $USE_CLIENT) {
2045         unless (&client_send_spa_packet($test_hr)) {
2046             &write_test_file("[-] fwknop client execution error.\n",
2047                 $current_test_file);
2048             $rv = 0;
2049         }
2050     } else {
2051         &send_packets($pkts_hr);
2052     }
2053
2054     ### check to see if the SPA packet resulted in a new fw access rule
2055     my $ctr = 0;
2056     while (not &is_fw_rule_active($test_hr)) {
2057         &write_test_file("[-] new fw rule does not exist.\n",
2058             $current_test_file);
2059         $ctr++;
2060         last if $ctr == 3;
2061         sleep 1;
2062     }
2063     if ($ctr == 3) {
2064         $fw_rule_created = 0;
2065         $fw_rule_removed = 0;
2066     }
2067
2068     &time_for_valgrind() if $use_valgrind;
2069
2070     if ($fw_rule_created) {
2071         sleep 3;  ### allow time for rule time out.
2072         if (&is_fw_rule_active($test_hr)) {
2073             &write_test_file("[-] new fw rule not timed out.\n",
2074                 $current_test_file);
2075             $rv = 0;
2076         } else {
2077             &write_test_file("[+] new fw rule timed out.\n",
2078                 $current_test_file);
2079             $fw_rule_removed = 1;
2080         }
2081     }
2082
2083     if (&is_fwknopd_running()) {
2084         &stop_fwknopd();
2085         unless (&file_find_regex([qr/Got\sSIGTERM/, qr/^Terminated/],
2086                 $server_test_file)) {
2087             $server_was_stopped = 0;
2088         }
2089     } else {
2090         &write_test_file("[-] server is not running.\n",
2091             $current_test_file);
2092         $server_was_stopped = 0;
2093     }
2094
2095     return ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed);
2096 }
2097
2098 sub get_spa_packet_from_file() {
2099     my $file = shift;
2100
2101     my $spa_pkt = '';
2102
2103     my $found_trigger_line = 0;
2104     open F, "< $file" or die "[*] Could not open file $file: $!";
2105     while (<F>) {
2106         if (/final\spacked/i) {
2107             $found_trigger_line = 1;
2108             next;
2109         }
2110         next unless $found_trigger_line;
2111
2112         ### the next line with non whitespace is the SPA packet
2113         if (/(\S+)/) {
2114             $spa_pkt = $1;
2115             last;
2116         }
2117     }
2118     close F;
2119
2120     return $spa_pkt;
2121 }
2122
2123 sub send_packets() {
2124     my $pkts_ar = shift;
2125
2126     open F, ">> $current_test_file" or die $!;
2127     print F "[+] send_packets(): Sending the following packets...\n";
2128     print F Dumper $pkts_ar;
2129     close F;
2130
2131     for my $pkt_hr (@$pkts_ar) {
2132         if ($pkt_hr->{'proto'} eq 'tcp' or $pkt_hr->{'proto'} eq 'udp') {
2133             my $socket = IO::Socket::INET->new(
2134                 PeerAddr => $pkt_hr->{'dst_ip'},
2135                 PeerPort => $pkt_hr->{'port'},
2136                 Proto    => $pkt_hr->{'proto'},
2137                 Timeout  => 1
2138             ) or die "[*] Could not acquire $pkt_hr->{'proto'}/$pkt_hr->{'port'} " .
2139                 "socket to $pkt_hr->{'dst_ip'}: $!";
2140
2141             $socket->send($pkt_hr->{'data'});
2142             undef $socket;
2143
2144         } elsif ($pkt_hr->{'proto'} eq 'http') {
2145             ### FIXME
2146         } elsif ($pkt_hr->{'proto'} eq 'icmp') {
2147             ### FIXME
2148         }
2149
2150         sleep $pkt_hr->{'delay'} if defined $pkt_hr->{'delay'};
2151     }
2152     return;
2153 }
2154
2155 sub generic_exec() {
2156     my $test_hr = shift;
2157
2158     my $rv = 1;
2159
2160     my $exec_rv = &run_cmd($test_hr->{'cmdline'},
2161                 $cmd_out_tmp, $current_test_file);
2162
2163     if ($test_hr->{'exec_err'} eq $YES) {
2164         $rv = 0 if $exec_rv;
2165     } else {
2166         $rv = 0 unless $exec_rv;
2167     }
2168
2169     if ($test_hr->{'positive_output_matches'}) {
2170         $rv = 0 unless &file_find_regex(
2171             $test_hr->{'positive_output_matches'},
2172             $current_test_file);
2173     }
2174
2175     if ($test_hr->{'negative_output_matches'}) {
2176         $rv = 0 if &file_find_regex(
2177             $test_hr->{'negative_output_matches'},
2178             $current_test_file);
2179     }
2180
2181     return $rv;
2182 }
2183
2184 ### check for PIE
2185 sub pie_binary() {
2186     my $test_hr = shift;
2187     return 0 unless $test_hr->{'binary'};
2188     &run_cmd("./hardening-check $test_hr->{'binary'}",
2189             $cmd_out_tmp, $current_test_file);
2190     return 0 if &file_find_regex([qr/Position\sIndependent.*:\sno/i],
2191         $current_test_file);
2192     return 1;
2193 }
2194
2195 ### check for stack protection
2196 sub stack_protected_binary() {
2197     my $test_hr = shift;
2198     return 0 unless $test_hr->{'binary'};
2199     &run_cmd("./hardening-check $test_hr->{'binary'}",
2200             $cmd_out_tmp, $current_test_file);
2201     return 0 if &file_find_regex([qr/Stack\sprotected.*:\sno/i],
2202         $current_test_file);
2203     return 1;
2204 }
2205
2206 ### check for fortified source functions
2207 sub fortify_source_functions() {
2208     my $test_hr = shift;
2209     return 0 unless $test_hr->{'binary'};
2210     &run_cmd("./hardening-check $test_hr->{'binary'}",
2211             $cmd_out_tmp, $current_test_file);
2212     return 0 if &file_find_regex([qr/Fortify\sSource\sfunctions:\sno/i],
2213         $current_test_file);
2214     return 1;
2215 }
2216
2217 ### check for read-only relocations
2218 sub read_only_relocations() {
2219     my $test_hr = shift;
2220     return 0 unless $test_hr->{'binary'};
2221     &run_cmd("./hardening-check $test_hr->{'binary'}",
2222             $cmd_out_tmp, $current_test_file);
2223     return 0 if &file_find_regex([qr/Read.only\srelocations:\sno/i],
2224         $current_test_file);
2225     return 1;
2226 }
2227
2228 ### check for immediate binding
2229 sub immediate_binding() {
2230     my $test_hr = shift;
2231     return 0 unless $test_hr->{'binary'};
2232     &run_cmd("./hardening-check $test_hr->{'binary'}",
2233             $cmd_out_tmp, $current_test_file);
2234     return 0 if &file_find_regex([qr/Immediate\sbinding:\sno/i],
2235         $current_test_file);
2236     return 1;
2237 }
2238
2239 sub specs() {
2240
2241      &run_cmd("LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopdCmd " .
2242             "$default_server_conf_args --fw-list-all",
2243             $cmd_out_tmp, $current_test_file);
2244
2245     my $have_gpgme = 0;
2246
2247     for my $cmd (
2248         'uname -a',
2249         'uptime',
2250         'ifconfig -a',
2251         'ls -l /etc', 'if [ -e /etc/issue ]; then cat /etc/issue; fi',
2252         'if [ `which iptables` ]; then iptables -V; fi',
2253         'if [ -e /proc/cpuinfo ]; then cat /proc/cpuinfo; fi',
2254         'if [ -e /proc/config.gz ]; then zcat /proc/config.gz; fi',
2255         'if [ `which gpg` ]; then gpg --version; fi',
2256         'if [ `which tcpdump` ]; then ldd `which tcpdump`; fi',
2257         "ldd $fwknopCmd",
2258         "ldd $fwknopdCmd",
2259         "ldd $libfko_bin",
2260         'ls -l /usr/lib/*pcap*',
2261         'ls -l /usr/local/lib/*pcap*',
2262         'ls -l /usr/lib/*fko*',
2263         'ls -l /usr/local/lib/*fko*',
2264     ) {
2265         &run_cmd($cmd, $cmd_out_tmp, $current_test_file);
2266
2267         if ($cmd =~ /^ldd/) {
2268             $have_gpgme++ if &file_find_regex([qr/gpgme/], $cmd_out_tmp);
2269         }
2270     }
2271
2272     ### all three of fwknop/fwknopd/libfko must link against gpgme in order
2273     ### to enable gpg tests
2274     unless ($have_gpgme == 3) {
2275         push @tests_to_exclude, "GPG";
2276     }
2277
2278     return 1;
2279 }
2280
2281 sub time_for_valgrind() {
2282     my $ctr = 0;
2283     while (&run_cmd("ps axuww | grep LD_LIBRARY_PATH | " .
2284             "grep valgrind |grep -v perl | grep -v grep",
2285             $cmd_out_tmp, $current_test_file)) {
2286         $ctr++;
2287         last if $ctr == 5;
2288         sleep 1;
2289     }
2290     return;
2291 }
2292
2293 sub anonymize_results() {
2294     my $rv = 0;
2295     die "[*] $output_dir does not exist" unless -d $output_dir;
2296     die "[*] $logfile does not exist, has $0 been executed?"
2297         unless -e $logfile;
2298     if (-e $tarfile) {
2299         unlink $tarfile or die "[*] Could not unlink $tarfile: $!";
2300     }
2301
2302     ### remove non-loopback IP addresses
2303     my $search_re = qr/\b127\.0\.0\.1\b/;
2304     system "perl -p -i -e 's|$search_re|00MY1271STR00|g' $output_dir/*.test";
2305     $search_re = qr/\b127\.0\.0\.2\b/;
2306     system "perl -p -i -e 's|$search_re|00MY1272STR00|g' $output_dir/*.test";
2307     $search_re = qr/\b0\.0\.0\.0\b/;
2308     system "perl -p -i -e 's|$search_re|00MY0000STR00|g' $output_dir/*.test";
2309     $search_re = qr/\b(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}\b/;
2310     system "perl -p -i -e 's|$search_re|N.N.N.N|g' $output_dir/*.test";
2311     system "perl -p -i -e 's|00MY1271STR00|127.0.0.1|g' $output_dir/*.test";
2312     system "perl -p -i -e 's|00MY1272STR00|127.0.0.2|g' $output_dir/*.test";
2313     system "perl -p -i -e 's|00MY0000STR00|0.0.0.0|g' $output_dir/*.test";
2314
2315     ### remove hostname from any uname output
2316     $search_re = qr/\suname\s+\-a\s*\n\s*(\S+)\s+\S+/;
2317     system "perl -p -i -e 'undef \$/; s|$search_re" .
2318         "| uname -a\n\$1 (removed)|s' $output_dir/*.test";
2319
2320     $search_re = qr/uname=\x27(\S+)\s+\S+/;
2321     system "perl -p -i -e 's|$search_re|uname= \$1 (removed)|' $output_dir/*.test";
2322
2323     ### create tarball
2324     system "tar cvfz $tarfile $logfile $output_dir";
2325     print "[+] Anonymized test results file: $tarfile\n";
2326     if (-e $tarfile) {
2327         $rv = 1;
2328     }
2329     return $rv;
2330 }
2331
2332
2333 sub write_pid() {
2334     my $test_hr = shift;
2335
2336     open F, "> $default_pid_file" or die $!;
2337     print F "1\n";
2338     close F;
2339
2340     &server_start($test_hr);
2341
2342     open F, "< $default_pid_file" or die $!;
2343     my $pid = <F>;
2344     chomp $pid;
2345     close F;
2346
2347     if ($pid != 1) {
2348         return 1;
2349     }
2350
2351     return 0;
2352 }
2353
2354 sub start_fwknopd() {
2355     my $test_hr = shift;
2356
2357     &write_test_file("[+] TEST: $test_hr->{'msg'}\n", $server_test_file);
2358
2359     my $pid = fork();
2360     die "[*] Could not fork: $!" unless defined $pid;
2361
2362     if ($pid == 0) {
2363
2364         ### we are the child, so start fwknopd
2365         exit &run_cmd($test_hr->{'fwknopd_cmdline'},
2366             $server_cmd_tmp, $server_test_file);
2367     }
2368     return $pid;
2369 }
2370
2371 sub write_key() {
2372     my ($key, $file) = @_;
2373
2374     open K, "> $file" or die "[*] Could not open $file: $!";
2375     print K "$loopback_ip: $key\n";
2376     print K "localhost: $key\n";
2377     print K "some.host.through.proxy.com: $key\n";
2378     close K;
2379     return;
2380 }
2381
2382 sub dump_pids() {
2383     open C, ">> $current_test_file"
2384         or die "[*] Could not open $current_test_file: $!";
2385     print C "\n" . localtime() . " [+] PID dump:\n";
2386     close C;
2387     &run_cmd("ps auxww | grep knop |grep -v grep",
2388         $cmd_out_tmp, $current_test_file);
2389     return;
2390 }
2391
2392 sub run_cmd() {
2393     my ($cmd, $cmd_out, $file) = @_;
2394
2395     if (-e $file) {
2396         open F, ">> $file"
2397             or die "[*] Could not open $file: $!";
2398         print F localtime() . " CMD: $cmd\n";
2399         close F;
2400     } else {
2401         open F, "> $file"
2402             or die "[*] Could not open $file: $!";
2403         print F localtime() . " CMD: $cmd\n";
2404         close F;
2405     }
2406
2407     my $rv = ((system "$cmd > $cmd_out 2>&1") >> 8);
2408
2409     open C, "< $cmd_out" or die "[*] Could not open $cmd_out: $!";
2410     my @cmd_lines = <C>;
2411     close C;
2412
2413     open F, ">> $file" or die "[*] Could not open $file: $!";
2414     print F $_ for @cmd_lines;
2415     close F;
2416
2417     if ($rv == 0) {
2418         return 1;
2419     }
2420     return 0;
2421 }
2422
2423 sub dots_print() {
2424     my $msg = shift;
2425     &logr($msg);
2426     my $dots = '';
2427     for (my $i=length($msg); $i < $PRINT_LEN; $i++) {
2428         $dots .= '.';
2429     }
2430     &logr($dots);
2431     return;
2432 }
2433
2434 sub init() {
2435
2436     $|++; ### turn off buffering
2437
2438     $< == 0 && $> == 0 or
2439         die "[*] $0: You must be root (or equivalent ",
2440             "UID 0 account) to effectively test fwknop";
2441
2442     ### validate test hashes
2443     my $hash_num = 0;
2444     for my $test_hr (@tests) {
2445         for my $key (keys %test_keys) {
2446             if ($test_keys{$key} == $REQUIRED) {
2447                 die "[*] Missing '$key' element in hash: $hash_num"
2448                     unless defined $test_hr->{$key};
2449             } else {
2450                 $test_hr->{$key} = '' unless defined $test_hr->{$key};
2451             }
2452         }
2453         $hash_num++;
2454     }
2455
2456     if ($use_valgrind) {
2457         die "[*] $valgrindCmd exec problem, use --valgrind-path"
2458             unless -e $valgrindCmd and -x $valgrindCmd;
2459     }
2460
2461     die "[*] $conf_dir directory does not exist." unless -d $conf_dir;
2462     die "[*] $lib_dir directory does not exist." unless -d $lib_dir;
2463
2464     for my $file ($configure_path,
2465             $default_conf,
2466             $nat_conf,
2467             $default_access_conf,
2468             $no_source_match_access_conf,
2469             $ip_source_match_access_conf,
2470             $subnet_source_match_access_conf,
2471             $no_subnet_source_match_access_conf,
2472             $no_multi_source_match_access_conf,
2473             $multi_source_match_access_conf,
2474             $open_ports_access_conf,
2475             $mismatch_open_ports_access_conf,
2476             $require_user_access_conf,
2477             $mismatch_user_access_conf,
2478             $require_src_access_conf,
2479             $multi_gpg_access_conf,
2480             $multi_stanzas_access_conf,
2481             $expired_access_conf,
2482             $expired_epoch_access_conf,
2483             $future_expired_access_conf,
2484             $invalid_expire_access_conf,
2485             $force_nat_access_conf,
2486     ) {
2487         die "[*] $file does not exist" unless -e $file;
2488     }
2489
2490     if (-d $output_dir) {
2491         if (-d "${output_dir}.last") {
2492             rmtree "${output_dir}.last"
2493                 or die "[*] rmtree ${output_dir}.last $!";
2494         }
2495         mkdir "${output_dir}.last"
2496             or die "[*] ${output_dir}.last: $!";
2497         for my $file (glob("$output_dir/*.test")) {
2498             if ($file =~ m|.*/(.*)|) {
2499                 copy $file, "${output_dir}.last/$1" or die $!;
2500             }
2501         }
2502         if (-e "$output_dir/init") {
2503             copy "$output_dir/init", "${output_dir}.last/init";
2504         }
2505         if (-e $logfile) {
2506             copy $logfile, "${output_dir}.last/$logfile" or die $!;
2507         }
2508         $saved_last_results = 1;
2509     } else {
2510         mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!";
2511     }
2512     unless (-d $run_dir) {
2513         mkdir $run_dir or die "[*] Could not mkdir $run_dir: $!";
2514     }
2515
2516     for my $file (glob("$output_dir/*.test")) {
2517         unlink $file or die "[*] Could not unlink($file)";
2518     }
2519     if (-e "$output_dir/init") {
2520         unlink "$output_dir/init" or die $!;
2521     }
2522
2523     if (-e $logfile) {
2524         unlink $logfile or die $!;
2525     }
2526
2527     if ($test_include) {
2528         @tests_to_include = split /\s*,\s*/, $test_include;
2529     }
2530     if ($test_exclude) {
2531         @tests_to_exclude = split /\s*,\s*/, $test_exclude;
2532     }
2533
2534     ### make sure no fwknopd instance is currently running
2535     die "[*] Please stop the running fwknopd instance."
2536         if &is_fwknopd_running();
2537
2538     unless ($enable_recompilation_warnings_check) {
2539         push @tests_to_exclude, 'recompilation';
2540     }
2541
2542     $sudo_path = &find_command('sudo');
2543
2544     unless ((&find_command('cc') or &find_command('gcc')) and &find_command('make')) {
2545         ### disable compilation checks
2546         push @tests_to_exclude, 'recompilation';
2547     }
2548
2549     open UNAME, "uname |" or die "[*] Could not execute uname: $!";
2550     while (<UNAME>) {
2551         if (/linux/i) {
2552             $platform = 'linux';
2553             last;
2554         }
2555     }
2556     close UNAME;
2557
2558     unless ($platform eq 'linux') {
2559         push @tests_to_exclude, 'NAT';
2560     }
2561
2562     return;
2563 }
2564
2565 sub identify_loopback_intf() {
2566     return if $loopback_intf;
2567
2568     ### Linux:
2569
2570     ### lo    Link encap:Local Loopback
2571     ###       inet addr:127.0.0.1  Mask:255.0.0.0
2572     ###       inet6 addr: ::1/128 Scope:Host
2573     ###       UP LOOPBACK RUNNING  MTU:16436  Metric:1
2574     ###       RX packets:534709 errors:0 dropped:0 overruns:0 frame:0
2575     ###       TX packets:534709 errors:0 dropped:0 overruns:0 carrier:0
2576     ###       collisions:0 txqueuelen:0
2577     ###       RX bytes:101110617 (101.1 MB)  TX bytes:101110617 (101.1 MB)
2578
2579     ### Freebsd:
2580
2581     ### lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
2582     ###         options=3<RXCSUM,TXCSUM>
2583     ###         inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
2584     ###         inet6 ::1 prefixlen 128
2585     ###         inet 127.0.0.1 netmask 0xff000000
2586     ###         nd6 options=3<PERFORMNUD,ACCEPT_RTADV>
2587
2588     my $intf = '';
2589     my $found_loopback_intf = 0;
2590
2591     my $cmd = 'ifconfig -a';
2592     open C, "$cmd |" or die "[*] (use --loopback <name>) $cmd: $!";
2593     while (<C>) {
2594         if (/^(\S+?):?\s+.*loopback/i) {
2595             $intf = $1;
2596             next;
2597         }
2598         if (/^\S/ and $intf and not $found_loopback_intf) {
2599             ### should not happen
2600             last;
2601         }
2602         if ($intf and /\b127\.0\.0\.1\b/) {
2603             $found_loopback_intf = 1;
2604             last;
2605         }
2606     }
2607     close C;
2608
2609     die "[*] could not determine loopback interface, use --loopback <name>"
2610         unless $found_loopback_intf;
2611
2612     $loopback_intf = $intf;
2613
2614     return;
2615 }
2616
2617 sub parse_valgrind_suspect_functions() {
2618     for my $file (glob("$output_dir/*.test")) {
2619         my $type = 'server';
2620         $type = 'client' if $file =~ /\d\.test/;
2621         open F, "< $file" or die $!;
2622         while (<F>) {
2623             ### ==30969==    by 0x4E3983A: fko_set_username (fko_user.c:65)
2624             if (/^==.*\sby\s\S+\:\s(.*)/) {
2625                 $valgrind_suspect_functions{$type}{$1}++;
2626             }
2627         }
2628         close F;
2629     }
2630
2631     open F, ">> $current_test_file" or die $!;
2632     for my $type ('client', 'server') {
2633         print F "[+] fwknop $type functions:\n";
2634         next unless defined $valgrind_suspect_functions{$type};
2635         for my $fcn (sort {$valgrind_suspect_functions{$type}{$b}
2636                 <=> $valgrind_suspect_functions{$type}{$a}} keys %{$valgrind_suspect_functions{$type}}) {
2637             printf F "    %5d : %s\n", $valgrind_suspect_functions{$type}{$fcn}, $fcn;
2638         }
2639         print F "\n";
2640     }
2641     close F;
2642     return 1;
2643 }
2644
2645 sub is_fw_rule_active() {
2646     my $test_hr = shift;
2647
2648     my $conf_args = $default_server_conf_args;
2649
2650     if ($test_hr->{'server_conf'}) {
2651         $conf_args = "-c $test_hr->{'server_conf'} -a $default_access_conf " .
2652             "-d $default_digest_file -p $default_pid_file";
2653     }
2654
2655     return 1 if &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd " .
2656             qq{$conf_args --fw-list | grep -v "# DISABLED" |grep $fake_ip |grep _exp_},
2657             $cmd_out_tmp, $current_test_file);
2658     return 0;
2659 }
2660
2661 sub is_fwknopd_running() {
2662
2663     sleep 2 if $use_valgrind;
2664
2665     &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd $default_server_conf_args " .
2666         "--status", $cmd_out_tmp, $current_test_file);
2667
2668     return 0 if &file_find_regex([qr/no\s+running/i], $cmd_out_tmp);
2669
2670     return 1;
2671 }
2672
2673 sub stop_fwknopd() {
2674
2675     &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd " .
2676         "$default_server_conf_args -K", $cmd_out_tmp, $current_test_file);
2677
2678     if ($use_valgrind) {
2679         &time_for_valgrind();
2680     } else {
2681         sleep 1;
2682     }
2683
2684     return;
2685 }
2686
2687 sub file_find_regex() {
2688     my ($re_ar, $file) = @_;
2689
2690     my $found = 0;
2691     my @write_lines = ();
2692
2693     open F, "< $file" or die "[*] Could not open $file: $!";
2694     LINE: while (<F>) {
2695         my $line = $_;
2696         next LINE if $line =~ /file_file_regex\(\)/;
2697         for my $re (@$re_ar) {
2698             if ($line =~ $re) {
2699                 push @write_lines, "[.] file_find_regex() " .
2700                     "Matched '$re' with line: $line";
2701                 $found = 1;
2702                 last LINE;
2703             }
2704         }
2705     }
2706     close F;
2707
2708     if ($found) {
2709         for my $line (@write_lines) {
2710             &write_test_file($line, $file);
2711         }
2712     } else {
2713         &write_test_file("[.] find_find_regex() Did not " .
2714             "match any regex in: '@$re_ar'\n", $file);
2715     }
2716
2717     return $found;
2718 }
2719
2720 sub find_command() {
2721     my $cmd = shift;
2722
2723     my $path = '';
2724     open C, "which $cmd |" or die "[*] Could not execute: which $cmd: $!";
2725     while (<C>) {
2726         if (m|^(/.*$cmd)$|) {
2727             $path = $1;
2728             last;
2729         }
2730     }
2731     close C;
2732     return $path;
2733 }
2734
2735 sub write_test_file() {
2736     my ($msg, $file) = @_;
2737
2738     if (-e $file) {
2739         open F, ">> $file"
2740             or die "[*] Could not open $file: $!";
2741         print F $msg;
2742         close F;
2743     } else {
2744         open F, "> $file"
2745             or die "[*] Could not open $file: $!";
2746         print F $msg;
2747         close F;
2748     }
2749     return;
2750 }
2751
2752 sub logr() {
2753     my $msg = shift;
2754     print STDOUT $msg;
2755     open F, ">> $logfile" or die $!;
2756     print F $msg;
2757     close F;
2758     return;
2759 }
2760
2761 sub usage() {
2762     print <<_HELP_;
2763
2764 [+] $0 <options>
2765
2766     -A   --Anonymize-results      - Prepare anonymized results at:
2767                                     $tarfile
2768     --diff                        - Compare the results of one test run to
2769                                     another.  By default this compares output
2770                                     in ${output_dir}.last to $output_dir
2771     --diff-dir1=<path>            - Left hand side of diff directory path,
2772                                     default is: ${output_dir}.last
2773     --diff-dir2=<path>            - Right hand side of diff directory path,
2774                                     default is: $output_dir
2775     --include=<regex>             - Specify a regex to be used over test
2776                                     names that must match.
2777     --exclude=<regex>             - Specify a regex to be used over test
2778                                     names that must not match.
2779     --enable-recompile            - Recompile fwknop sources and look for
2780                                     compilation warnings.
2781     --enable-valgrind             - Run every test underneath valgrind.
2782     --List                        - List test names.
2783     --loopback-intf=<intf>        - Specify loopback interface name (default
2784                                     depends on the OS where the test suite
2785                                     is executed).
2786     --output-dir=<path>           - Path to output directory, default is:
2787                                     $output_dir
2788     --fwknop-path=<path>          - Path to fwknop binary, default is:
2789                                     $fwknopCmd
2790     --fwknopd-path=<path>         - Path to fwknopd binary, default is:
2791                                     $fwknopdCmd
2792     --libfko-path=<path>          - Path to libfko, default is:
2793                                     $libfko_bin
2794     --valgrind-path=<path>        - Path to valgrind, default is:
2795                                     $valgrindCmd
2796     -h   --help                   - Display usage on STDOUT and exit.
2797
2798 _HELP_
2799     exit 0;
2800 }