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