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