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