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