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