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