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