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