18f04783611de1e193f4c49c99218bce59f38eea
[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'   => 'complete cycle (tcp/23 telnet)',
974         'err_msg'  => 'could not complete SPA cycle',
975         'function' => \&spa_cycle,
976         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
977             "$fwknopCmd -A tcp/23 -a $fake_ip -D $loopback_ip --get-key " .
978             "$local_key_file --verbose --verbose",
979         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
980             "$fwknopdCmd $default_server_conf_args $intf_str",
981         'fw_rule_created' => $NEW_RULE_REQUIRED,
982         'fw_rule_removed' => $NEW_RULE_REMOVED,
983         'fatal'    => $NO
984     },
985     {
986         'category' => 'Rijndael SPA',
987         'subcategory' => 'client+server',
988         'detail'   => 'complete cycle (tcp/9418 git)',
989         'err_msg'  => 'could not complete SPA cycle',
990         'function' => \&spa_cycle,
991         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
992             "$fwknopCmd -A tcp/9418 -a $fake_ip -D $loopback_ip --get-key " .
993             "$local_key_file --verbose --verbose",
994         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
995             "$fwknopdCmd $default_server_conf_args $intf_str",
996         'fw_rule_created' => $NEW_RULE_REQUIRED,
997         'fw_rule_removed' => $NEW_RULE_REMOVED,
998         'fatal'    => $NO
999     },
1000     {
1001         'category' => 'Rijndael SPA',
1002         'subcategory' => 'client+server',
1003         'detail'   => 'complete cycle (udp/53 dns)',
1004         'err_msg'  => 'could not complete SPA cycle',
1005         'function' => \&spa_cycle,
1006         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1007             "$fwknopCmd -A udp/53 -a $fake_ip -D $loopback_ip --get-key " .
1008             "$local_key_file --verbose --verbose",
1009         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1010             "$fwknopdCmd $default_server_conf_args $intf_str",
1011         'fw_rule_created' => $NEW_RULE_REQUIRED,
1012         'fw_rule_removed' => $NEW_RULE_REMOVED,
1013         'fatal'    => $NO
1014     },
1015     {
1016         'category' => 'Rijndael SPA',
1017         'subcategory' => 'client+server',
1018         'detail'   => "-P bpf SPA over port $non_std_spa_port",
1019         'err_msg'  => 'could not complete SPA cycle',
1020         'function' => \&spa_cycle,
1021         'cmdline'  => "$default_client_args --server-port $non_std_spa_port",
1022         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1023             "$fwknopdCmd $default_server_conf_args $intf_str " .
1024             qq|-P "udp port $non_std_spa_port"|,
1025         'server_positive_output_matches' => [qr/PCAP\sfilter.*\s$non_std_spa_port/],
1026         'fw_rule_created' => $NEW_RULE_REQUIRED,
1027         'fw_rule_removed' => $NEW_RULE_REMOVED,
1028         'fatal'    => $NO
1029     },
1030
1031     {
1032         'category' => 'Rijndael SPA',
1033         'subcategory' => 'client+server',
1034         'detail'   => 'random SPA port (tcp/22 ssh)',
1035         'err_msg'  => 'could not complete SPA cycle',
1036         'function' => \&spa_cycle,
1037         'cmdline'  => "$default_client_args -r",
1038         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1039             "$fwknopdCmd $default_server_conf_args $intf_str " .
1040             qq|-P "udp"|,
1041         'fw_rule_created' => $NEW_RULE_REQUIRED,
1042         'fw_rule_removed' => $NEW_RULE_REMOVED,
1043         'fatal'    => $NO
1044     },
1045
1046     {
1047         'category' => 'Rijndael SPA',
1048         'subcategory' => 'client+server',
1049         'detail'   => 'spoof username (tcp/22)',
1050         'err_msg'  => 'could not spoof username',
1051         'function' => \&spoof_username,
1052         'cmdline'  => "SPOOF_USER=$spoof_user LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1053             "$fwknopCmd -A tcp/22 -a $fake_ip -D $loopback_ip --get-key " .
1054             "$local_key_file --verbose --verbose",
1055         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1056             "$fwknopdCmd $default_server_conf_args $intf_str",
1057         'fatal'    => $NO
1058     },
1059
1060     {
1061         'category' => 'Rijndael SPA',
1062         'subcategory' => 'client+server',
1063         'detail'   => 'replay attack detection',
1064         'err_msg'  => 'could not detect replay attack',
1065         'function' => \&replay_detection,
1066         'cmdline'  => $default_client_args,
1067         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1068             "$fwknopdCmd $default_server_conf_args $intf_str",
1069         'fatal'    => $NO
1070     },
1071     {
1072         'category' => 'Rijndael SPA',
1073         'subcategory' => 'server',
1074         'detail'   => 'digest cache structure',
1075         'err_msg'  => 'improper digest cache structure',
1076         'function' => \&digest_cache_structure,
1077         'fatal'    => $NO
1078     },
1079
1080     {
1081         'category' => 'Rijndael SPA',
1082         'subcategory' => 'client+server',
1083         'detail'   => 'non-base64 altered SPA data',
1084         'err_msg'  => 'allowed improper SPA data',
1085         'function' => \&altered_non_base64_spa_data,
1086         'cmdline'  => $default_client_args,
1087         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1088             "$fwknopdCmd $default_server_conf_args $intf_str",
1089         'fatal'    => $NO
1090     },
1091     {
1092         'category' => 'Rijndael SPA',
1093         'subcategory' => 'client+server',
1094         'detail'   => 'base64 altered SPA data',
1095         'err_msg'  => 'allowed improper SPA data',
1096         'function' => \&altered_base64_spa_data,
1097         'cmdline'  => $default_client_args,
1098         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1099             "$fwknopdCmd $default_server_conf_args $intf_str",
1100         'fatal'    => $NO
1101     },
1102     {
1103         'category' => 'Rijndael SPA',
1104         'subcategory' => 'client+server',
1105         'detail'   => 'appended data to SPA pkt',
1106         'err_msg'  => 'allowed improper SPA data',
1107         'function' => \&appended_spa_data,
1108         'cmdline'  => $default_client_args,
1109         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1110             "$fwknopdCmd $default_server_conf_args $intf_str",
1111         'fatal'    => $NO
1112     },
1113     {
1114         'category' => 'Rijndael SPA',
1115         'subcategory' => 'client+server',
1116         'detail'   => 'prepended data to SPA pkt',
1117         'err_msg'  => 'allowed improper SPA data',
1118         'function' => \&prepended_spa_data,
1119         'cmdline'  => $default_client_args,
1120         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1121             "$fwknopdCmd $default_server_conf_args $intf_str",
1122         'fatal'    => $NO
1123     },
1124
1125     {
1126         'category' => 'GnuPG (GPG) SPA',
1127         'subcategory' => 'client+server',
1128         'detail'   => 'complete cycle (tcp/22 ssh)',
1129         'err_msg'  => 'could not complete SPA cycle',
1130         'function' => \&spa_cycle,
1131         'cmdline'  => $default_client_gpg_args,
1132         'fwknopd_cmdline'  => $default_server_gpg_args,
1133         'fw_rule_created' => $NEW_RULE_REQUIRED,
1134         'fw_rule_removed' => $NEW_RULE_REMOVED,
1135         'fatal'    => $NO
1136     },
1137     {
1138         'category' => 'GnuPG (GPG) SPA',
1139         'subcategory' => 'client+server',
1140         'detail'   => 'multi gpg-IDs (tcp/22 ssh)',
1141         'err_msg'  => 'could not complete SPA cycle',
1142         'function' => \&spa_cycle,
1143         'cmdline'  => $default_client_gpg_args,
1144         'fwknopd_cmdline'  => "LD_LIBRARY_PATH=$lib_dir " .
1145             "$valgrind_str $fwknopdCmd -c $default_conf " .
1146             "-a $multi_gpg_access_conf $intf_str " .
1147             "-d $default_digest_file -p $default_pid_file",
1148         'fw_rule_created' => $NEW_RULE_REQUIRED,
1149         'fw_rule_removed' => $NEW_RULE_REMOVED,
1150         'fatal'    => $NO
1151     },
1152
1153     {
1154         'category' => 'GnuPG (GPG) SPA',
1155         'subcategory' => 'client+server',
1156         'detail'   => 'complete cycle (tcp/23 telnet)',
1157         'err_msg'  => 'could not complete SPA cycle',
1158         'function' => \&spa_cycle,
1159         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1160             "$fwknopCmd -A tcp/23 -a $fake_ip -D $loopback_ip --get-key " .
1161             "$local_key_file --verbose --verbose " .
1162             "--gpg-recipient-key $gpg_server_key " .
1163             "--gpg-signer-key $gpg_client_key " .
1164             "--gpg-home-dir $gpg_client_home_dir",
1165         'fwknopd_cmdline'  => $default_server_gpg_args,
1166         'fw_rule_created' => $NEW_RULE_REQUIRED,
1167         'fw_rule_removed' => $NEW_RULE_REMOVED,
1168         'fatal'    => $NO
1169     },
1170     {
1171         'category' => 'GnuPG (GPG) SPA',
1172         'subcategory' => 'client+server',
1173         'detail'   => 'complete cycle (tcp/9418 git)',
1174         'err_msg'  => 'could not complete SPA cycle',
1175         'function' => \&spa_cycle,
1176         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1177             "$fwknopCmd -A tcp/9418 -a $fake_ip -D $loopback_ip --get-key " .
1178             "$local_key_file --verbose --verbose " .
1179             "--gpg-recipient-key $gpg_server_key " .
1180             "--gpg-signer-key $gpg_client_key " .
1181             "--gpg-home-dir $gpg_client_home_dir",
1182         'fwknopd_cmdline'  => $default_server_gpg_args,
1183         'fw_rule_created' => $NEW_RULE_REQUIRED,
1184         'fw_rule_removed' => $NEW_RULE_REMOVED,
1185         'fatal'    => $NO
1186     },
1187     {
1188         'category' => 'GnuPG (GPG) SPA',
1189         'subcategory' => 'client+server',
1190         'detail'   => 'complete cycle (udp/53 dns)',
1191         'err_msg'  => 'could not complete SPA cycle',
1192         'function' => \&spa_cycle,
1193         'cmdline'  => "LD_LIBRARY_PATH=$lib_dir $valgrind_str " .
1194             "$fwknopCmd -A udp/53 -a $fake_ip -D $loopback_ip --get-key " .
1195             "$local_key_file --verbose --verbose " .
1196             "--gpg-recipient-key $gpg_server_key " .
1197             "--gpg-signer-key $gpg_client_key " .
1198             "--gpg-home-dir $gpg_client_home_dir",
1199         'fwknopd_cmdline'  => $default_server_gpg_args,
1200         'fw_rule_created' => $NEW_RULE_REQUIRED,
1201         'fw_rule_removed' => $NEW_RULE_REMOVED,
1202         'fatal'    => $NO
1203     },
1204
1205     {
1206         'category' => 'GnuPG (GPG) SPA',
1207         'subcategory' => 'client+server',
1208         'detail'   => 'replay attack detection',
1209         'err_msg'  => 'could not detect replay attack',
1210         'function' => \&replay_detection,
1211         'cmdline'  => $default_client_gpg_args,
1212         'fwknopd_cmdline'  => $default_server_gpg_args,
1213         'fatal'    => $NO
1214     },
1215
1216     {
1217         'category' => 'GnuPG (GPG) SPA',
1218         'subcategory' => 'client+server',
1219         'detail'   => 'non-base64 altered SPA data',
1220         'err_msg'  => 'allowed improper SPA data',
1221         'function' => \&altered_non_base64_spa_data,
1222         'cmdline'  => $default_client_gpg_args,
1223         'fwknopd_cmdline'  => $default_server_gpg_args,
1224         'fatal'    => $NO
1225     },
1226     {
1227         'category' => 'GnuPG (GPG) SPA',
1228         'subcategory' => 'client+server',
1229         'detail'   => 'base64 altered SPA data',
1230         'err_msg'  => 'allowed improper SPA data',
1231         'function' => \&altered_base64_spa_data,
1232         'cmdline'  => $default_client_gpg_args,
1233         'fwknopd_cmdline'  => $default_server_gpg_args,
1234         'fatal'    => $NO
1235     },
1236     {
1237         'category' => 'GnuPG (GPG) SPA',
1238         'subcategory' => 'client+server',
1239         'detail'   => 'appended data to SPA pkt',
1240         'err_msg'  => 'allowed improper SPA data',
1241         'function' => \&appended_spa_data,
1242         'cmdline'  => $default_client_gpg_args,
1243         'fwknopd_cmdline'  => $default_server_gpg_args,
1244         'fatal'    => $NO
1245     },
1246     {
1247         'category' => 'GnuPG (GPG) SPA',
1248         'subcategory' => 'client+server',
1249         'detail'   => 'prepended data to SPA pkt',
1250         'err_msg'  => 'allowed improper SPA data',
1251         'function' => \&prepended_spa_data,
1252         'cmdline'  => $default_client_gpg_args,
1253         'fwknopd_cmdline'  => $default_server_gpg_args,
1254         'fatal'    => $NO
1255     },
1256     {
1257         'category' => 'GnuPG (GPG) SPA',
1258         'subcategory' => 'client+server',
1259         'detail'   => 'spoof username (tcp/22 ssh)',
1260         'err_msg'  => 'could not spoof username',
1261         'function' => \&spoof_username,
1262         'cmdline'  => "SPOOF_USER=$spoof_user $default_client_gpg_args",
1263         'fwknopd_cmdline'  => $default_server_gpg_args,
1264         'fatal'    => $NO
1265     },
1266     {
1267         'category' => 'GnuPG (GPG) SPA',
1268         'subcategory' => 'server',
1269         'detail'   => 'digest cache structure',
1270         'err_msg'  => 'improper digest cache structure',
1271         'function' => \&digest_cache_structure,
1272         'fatal'    => $NO
1273     },
1274 );
1275
1276 if ($use_valgrind) {
1277     push @tests,
1278         {
1279             'category' => 'valgrind output',
1280             'subcategory' => 'flagged functions',
1281             'detail'   => '',
1282             'err_msg'  => 'could not parse flagged functions',
1283             'function' => \&parse_valgrind_flagged_functions,
1284             'fatal'    => $NO
1285         };
1286 }
1287
1288 my %test_keys = (
1289     'category'        => $REQUIRED,
1290     'subcategory'     => $OPTIONAL,
1291     'detail'          => $REQUIRED,
1292     'function'        => $REQUIRED,
1293     'binary'          => $OPTIONAL,
1294     'cmdline'         => $OPTIONAL,
1295     'fwknopd_cmdline' => $OPTIONAL,
1296     'fatal'           => $OPTIONAL,
1297     'exec_err'        => $OPTIONAL,
1298     'fw_rule_created' => $OPTIONAL,
1299     'fw_rule_removed' => $OPTIONAL,
1300     'server_conf'     => $OPTIONAL,
1301     'positive_output_matches' => $OPTIONAL,
1302     'negative_output_matches' => $OPTIONAL,
1303     'server_positive_output_matches' => $OPTIONAL,
1304     'server_negative_output_matches' => $OPTIONAL,
1305 );
1306
1307 if ($diff_mode) {
1308     &diff_test_results();
1309     exit 0;
1310 }
1311
1312 ### make sure everything looks as expected before continuing
1313 &init();
1314
1315 &logr("\n[+] Starting the fwknop test suite...\n\n" .
1316     "    args: @args_cp\n\n"
1317 );
1318
1319 ### save the results from any previous test suite run
1320 ### so that we can potentially compare them with --diff
1321 if ($saved_last_results) {
1322     &logr("    Saved results from previous run " .
1323         "to: ${output_dir}.last/\n\n");
1324 }
1325
1326 ### main loop through all of the tests
1327 for my $test_hr (@tests) {
1328     &run_test($test_hr);
1329 }
1330
1331 &logr("\n[+] passed/failed/executed: $passed/$failed/$executed tests\n\n");
1332
1333 copy $logfile, "$output_dir/$logfile" or die $!;
1334
1335 exit 0;
1336
1337 #===================== end main =======================
1338
1339 sub run_test() {
1340     my $test_hr = shift;
1341
1342     my $msg = "[$test_hr->{'category'}]";
1343     $msg .= " [$test_hr->{'subcategory'}]" if $test_hr->{'subcategory'};
1344     $msg .= " $test_hr->{'detail'}";
1345
1346     return unless &process_include_exclude($msg);
1347
1348     if ($list_mode) {
1349         print $msg, "\n";
1350         return;
1351     }
1352
1353     &dots_print($msg);
1354
1355     $executed++;
1356     $current_test_file  = "$output_dir/$executed.test";
1357     $server_test_file   = "$output_dir/${executed}_fwknopd.test";
1358
1359     &write_test_file("[+] TEST: $msg\n", $current_test_file);
1360     $test_hr->{'msg'} = $msg;
1361     if (&{$test_hr->{'function'}}($test_hr)) {
1362         &logr("pass ($executed)\n");
1363         $passed++;
1364     } else {
1365         &logr("fail ($executed)\n");
1366         $failed++;
1367
1368         if ($test_hr->{'fatal'} eq $YES) {
1369             die "[*] required test failed, exiting.";
1370         }
1371     }
1372
1373     return;
1374 }
1375
1376 sub process_include_exclude() {
1377     my $msg = shift;
1378
1379     ### inclusions/exclusions
1380     if (@tests_to_include) {
1381         my $found = 0;
1382         for my $test (@tests_to_include) {
1383             if ($msg =~ /$test/ or ($use_valgrind
1384                     and $msg =~ /valgrind\soutput/)) {
1385                 $found = 1;
1386                 last;
1387             }
1388         }
1389         return 0 unless $found;
1390     }
1391     if (@tests_to_exclude) {
1392         my $found = 0;
1393         for my $test (@tests_to_exclude) {
1394             if ($msg =~ /$test/) {
1395                 $found = 1;
1396                 last;
1397             }
1398         }
1399         return 0 if $found;
1400     }
1401     return 1;
1402 }
1403
1404 sub diff_test_results() {
1405
1406     $diff_dir1 = "${output_dir}.last" unless $diff_dir1;
1407     $diff_dir2 = $output_dir unless $diff_dir2;
1408
1409     die "[*] Need results from a previous run before running --diff"
1410         unless -d $diff_dir2;
1411     die "[*] Current results set does not exist." unless -d $diff_dir1;
1412
1413     my %current_tests  = ();
1414     my %previous_tests = ();
1415
1416     ### Only diff results for matching tests (parse the logfile to see which
1417     ### test numbers match across the two test cycles).
1418     &build_results_hash(\%current_tests, $diff_dir1);
1419     &build_results_hash(\%previous_tests, $diff_dir2);
1420
1421     for my $test_msg (sort {$current_tests{$a}{'num'} <=> $current_tests{$b}{'num'}}
1422                 keys %current_tests) {
1423         my $current_result = $current_tests{$test_msg}{'pass_fail'};
1424         my $current_num    = $current_tests{$test_msg}{'num'};
1425         if (defined $previous_tests{$test_msg}) {
1426             print "[+] Checking: $test_msg\n";
1427             my $previous_result = $previous_tests{$test_msg}{'pass_fail'};
1428             my $previous_num    = $previous_tests{$test_msg}{'num'};
1429             if ($current_result ne $previous_result) {
1430                 print " DIFF: **$current_result** $test_msg\n";
1431             }
1432
1433             &diff_results($previous_num, $current_num);
1434             print "\n";
1435         }
1436     }
1437
1438     exit 0;
1439 }
1440
1441 sub diff_results() {
1442     my ($previous_num, $current_num) = @_;
1443
1444     ### edit out any valgrind "==354==" prefixes
1445     my $valgrind_search_re = qr/^==\d+==\s/;
1446
1447     ### remove CMD timestamps
1448     my $cmd_search_re = qr/^\S+\s.*?\s\d{4}\sCMD\:/;
1449
1450     for my $file ("$diff_dir1/${previous_num}.test",
1451         "$diff_dir1/${previous_num}_fwknopd.test",
1452         "$diff_dir2/${current_num}.test",
1453         "$diff_dir2/${current_num}_fwknopd.test",
1454     ) {
1455         system qq{perl -p -i -e 's|$valgrind_search_re||' $file} if -e $file;
1456         system qq{perl -p -i -e 's|$cmd_search_re|CMD:|' $file} if -e $file;
1457     }
1458
1459     if (-e "$diff_dir1/${previous_num}.test"
1460             and -e "$diff_dir2/${current_num}.test") {
1461         system "diff -u $diff_dir1/${previous_num}.test " .
1462             "$diff_dir2/${current_num}.test";
1463     }
1464
1465     if (-e "$diff_dir1/${previous_num}_fwknopd.test"
1466             and -e "$diff_dir2/${current_num}_fwknopd.test") {
1467         system "diff -u $diff_dir1/${previous_num}_fwknopd.test " .
1468             "$diff_dir2/${current_num}_fwknopd.test";
1469     }
1470
1471     return;
1472 }
1473
1474 sub build_results_hash() {
1475     my ($hr, $dir) = @_;
1476
1477     open F, "< $dir/$logfile" or die $!;
1478     while (<F>) {
1479         if (/^(.*?)\.\.\..*(pass|fail)\s\((\d+)\)/) {
1480             $hr->{$1}{'pass_fail'} = $2;
1481             $hr->{$1}{'num'}       = $3;
1482         }
1483     }
1484     return;
1485 }
1486
1487 sub compile_warnings() {
1488
1489     ### 'make clean' as root
1490     return 0 unless &run_cmd('make -C .. clean',
1491         $cmd_out_tmp, $current_test_file);
1492
1493     if ($sudo_path) {
1494         my $username = getpwuid((stat($configure_path))[4]);
1495         die "[*] Could not determine $configure_path owner"
1496             unless $username;
1497
1498         return 0 unless &run_cmd("$sudo_path -u $username make -C ..",
1499             $cmd_out_tmp, $current_test_file);
1500
1501     } else {
1502
1503         return 0 unless &run_cmd('make -C ..',
1504             $cmd_out_tmp, $current_test_file);
1505
1506     }
1507
1508     ### look for compilation warnings - something like:
1509     ###     warning: ‘test’ is used uninitialized in this function
1510     return 0 if &file_find_regex([qr/\swarning:\s/, qr/gcc\:.*\sunused/],
1511         $current_test_file);
1512
1513     ### the new binaries should exist
1514     unless (-e $fwknopCmd and -x $fwknopCmd) {
1515         &write_test_file("[-] $fwknopCmd does not exist or not executable.\n",
1516             $current_test_file);
1517     }
1518     unless (-e $fwknopdCmd and -x $fwknopdCmd) {
1519         &write_test_file("[-] $fwknopdCmd does not exist or not executable.\n",
1520             $current_test_file);
1521     }
1522
1523     return 1;
1524 }
1525
1526 sub binary_exists() {
1527     my $test_hr = shift;
1528     return 0 unless $test_hr->{'binary'};
1529
1530     ### account for different libfko.so paths (e.g. libfko.so.0.3 with no
1531     ### libfko.so link on OpenBSD, and libfko.dylib path on Mac OS X)
1532
1533     if ($test_hr->{'binary'} =~ /libfko/) {
1534         unless (-e $test_hr->{'binary'}) {
1535             my $file = "$lib_dir/libfko.dylib";
1536             if (-e $file) {
1537                 $test_hr->{'binary'} = $file;
1538                 $libfko_bin = $file;
1539             } else {
1540                 for my $f (glob("$lib_dir/libfko.so*")) {
1541                     if (-e $f and -x $f) {
1542                         $test_hr->{'binary'} = $f;
1543                         $libfko_bin = $f;
1544                         last;
1545                     }
1546                 }
1547             }
1548         }
1549     }
1550
1551     return 0 unless -e $test_hr->{'binary'} and -x $test_hr->{'binary'};
1552     return 1;
1553 }
1554
1555 sub expected_code_version() {
1556     my $test_hr = shift;
1557
1558     unless (-e '../VERSION') {
1559         &write_test_file("[-] ../VERSION file does not exist.\n",
1560             $current_test_file);
1561         return 0;
1562     }
1563
1564     open F, '< ../VERSION' or die $!;
1565     my $line = <F>;
1566     close F;
1567     if ($line =~ /(\d.*\d)/) {
1568         my $version = $1;
1569         return 0 unless &run_cmd($test_hr->{'cmdline'},
1570             $cmd_out_tmp, $current_test_file);
1571         return 1 if &file_find_regex([qr/$version/], $current_test_file);
1572     }
1573     return 0;
1574 }
1575
1576 sub client_send_spa_packet() {
1577     my $test_hr = shift;
1578
1579     &write_key('fwknoptest', $local_key_file);
1580
1581     return 0 unless &run_cmd($test_hr->{'cmdline'},
1582             $cmd_out_tmp, $current_test_file);
1583     return 0 unless &file_find_regex([qr/final\spacked/i],
1584         $current_test_file);
1585
1586     return 1;
1587 }
1588
1589 sub spa_cycle() {
1590     my $test_hr = shift;
1591
1592     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1593             = &client_server_interaction($test_hr, [], $USE_CLIENT);
1594
1595     if ($test_hr->{'fw_rule_created'} eq $NEW_RULE_REQUIRED) {
1596         $rv = 0 unless $fw_rule_created;
1597     } elsif ($test_hr->{'fw_rule_created'} eq $REQUIRE_NO_NEW_RULE) {
1598         $rv = 0 if $fw_rule_created;
1599     }
1600
1601     if ($test_hr->{'fw_rule_removed'} eq $NEW_RULE_REMOVED) {
1602         $rv = 0 unless $fw_rule_removed;
1603     } elsif ($test_hr->{'fw_rule_removed'} eq $REQUIRE_NO_NEW_REMOVED) {
1604         $rv = 0 if $fw_rule_removed;
1605     }
1606
1607     if ($test_hr->{'server_positive_output_matches'}) {
1608         $rv = 0 unless &file_find_regex(
1609             $test_hr->{'server_positive_output_matches'},
1610             $server_test_file);
1611     }
1612
1613     if ($test_hr->{'server_negative_output_matches'}) {
1614         $rv = 0 if &file_find_regex(
1615             $test_hr->{'server_negative_output_matches'},
1616             $server_test_file);
1617     }
1618
1619     return $rv;
1620 }
1621
1622 sub spoof_username() {
1623     my $test_hr = shift;
1624
1625     my $rv = &spa_cycle($test_hr);
1626
1627     unless (&file_find_regex([qr/Username:\s*$spoof_user/],
1628             $current_test_file)) {
1629         $rv = 0;
1630     }
1631
1632     unless (&file_find_regex([qr/Username:\s*$spoof_user/],
1633             $server_test_file)) {
1634         $rv = 0;
1635     }
1636
1637     return $rv;
1638 }
1639
1640 sub replay_detection() {
1641     my $test_hr = shift;
1642
1643     ### do a complete SPA cycle and then parse the SPA packet out of the
1644     ### current test file and re-send
1645
1646     return 0 unless &spa_cycle($test_hr);
1647
1648     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1649
1650     unless ($spa_pkt) {
1651         &write_test_file("[-] could not get SPA packet " .
1652             "from file: $current_test_file\n",
1653             $current_test_file);
1654         return 0;
1655     }
1656
1657     my @packets = (
1658         {
1659             'proto'  => 'udp',
1660             'port'   => $default_spa_port,
1661             'dst_ip' => $loopback_ip,
1662             'data'   => $spa_pkt,
1663         },
1664     );
1665
1666     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1667         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1668
1669     $rv = 0 unless $server_was_stopped;
1670
1671     unless (&file_find_regex([qr/Replay\sdetected\sfrom\ssource\sIP/i],
1672             $server_test_file)) {
1673         $rv = 0;
1674     }
1675
1676     return $rv;
1677 }
1678
1679 sub digest_cache_structure() {
1680     my $test_hr = shift;
1681     my $rv = 1;
1682
1683     &run_cmd("file $default_digest_file", $cmd_out_tmp, $current_test_file);
1684
1685     if (&file_find_regex([qr/ASCII/i], $cmd_out_tmp)) {
1686
1687         ### the format should be:
1688         ### <digest> <proto> <src_ip> <src_port> <dst_ip> <dst_port> <time>
1689         open F, "< $default_digest_file" or
1690             die "[*] could not open $default_digest_file: $!";
1691         while (<F>) {
1692             next if /^#/;
1693             next unless /\S/;
1694             unless (m|^\S+\s+\d+\s+$ip_re\s+\d+\s+$ip_re\s+\d+\s+\d+|) {
1695                 &write_test_file("[-] invalid digest.cache line: $_",
1696                     $current_test_file);
1697                 $rv = 0;
1698                 last;
1699             }
1700         }
1701         close F;
1702     } elsif (&file_find_regex([qr/dbm/i], $cmd_out_tmp)) {
1703         &write_test_file("[+] DBM digest file format, " .
1704             "assuming this is valid.\n", $current_test_file);
1705     } else {
1706         ### don't know what kind of file the digest.cache is
1707         &write_test_file("[-] unrecognized file type for " .
1708             "$default_digest_file.\n", $current_test_file);
1709         $rv = 0;
1710     }
1711
1712     if ($rv) {
1713         &write_test_file("[+] valid digest.cache structure.\n",
1714             $current_test_file);
1715     }
1716
1717     return $rv;
1718 }
1719
1720 sub server_bpf_ignore_packet() {
1721     my $test_hr = shift;
1722
1723     my $rv = 1;
1724     my $server_was_stopped = 0;
1725     my $fw_rule_created = 0;
1726     my $fw_rule_removed = 0;
1727
1728     unless (&client_send_spa_packet($test_hr)) {
1729         &write_test_file("[-] fwknop client execution error.\n",
1730             $current_test_file);
1731         $rv = 0;
1732     }
1733
1734     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1735
1736     unless ($spa_pkt) {
1737         &write_test_file("[-] could not get SPA packet " .
1738             "from file: $current_test_file\n", $current_test_file);
1739         return 0;
1740     }
1741
1742     my @packets = (
1743         {
1744             'proto'  => 'udp',
1745             'port'   => $default_spa_port,
1746             'dst_ip' => $loopback_ip,
1747             'data'   => $spa_pkt,
1748         },
1749     );
1750
1751     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1752         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1753
1754     unless (&file_find_regex([qr/PCAP\sfilter.*\s$non_std_spa_port/],
1755             $server_test_file)) {
1756         $rv = 0;
1757     }
1758
1759     return $rv;
1760 }
1761
1762 sub altered_non_base64_spa_data() {
1763     my $test_hr = shift;
1764
1765     my $rv = 1;
1766     my $server_was_stopped = 0;
1767     my $fw_rule_created = 0;
1768     my $fw_rule_removed = 0;
1769
1770     unless (&client_send_spa_packet($test_hr)) {
1771         &write_test_file("[-] fwknop client execution error.\n",
1772             $current_test_file);
1773         $rv = 0;
1774     }
1775
1776     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1777
1778     unless ($spa_pkt) {
1779         &write_test_file("[-] could not get SPA packet " .
1780             "from file: $current_test_file\n", $current_test_file);
1781         return 0;
1782     }
1783
1784     ### alter one byte (change to a ":")
1785     $spa_pkt =~ s|^(.{3}).|$1:|;
1786
1787     my @packets = (
1788         {
1789             'proto'  => 'udp',
1790             'port'   => $default_spa_port,
1791             'dst_ip' => $loopback_ip,
1792             'data'   => $spa_pkt,
1793         },
1794     );
1795
1796     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1797         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1798
1799     $rv = 0 unless $server_was_stopped;
1800
1801     return $rv;
1802 }
1803
1804 sub altered_base64_spa_data() {
1805     my $test_hr = shift;
1806
1807     my $rv = 1;
1808     my $server_was_stopped = 0;
1809     my $fw_rule_created = 0;
1810     my $fw_rule_removed = 0;
1811
1812     unless (&client_send_spa_packet($test_hr)) {
1813         &write_test_file("[-] fwknop client execution error.\n",
1814             $current_test_file);
1815         $rv = 0;
1816     }
1817
1818     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1819
1820     unless ($spa_pkt) {
1821         &write_test_file("[-] could not get SPA packet " .
1822             "from file: $current_test_file\n", $current_test_file);
1823         return 0;
1824     }
1825
1826     $spa_pkt =~ s|^(.{3}).|AAAA|;
1827
1828     my @packets = (
1829         {
1830             'proto'  => 'udp',
1831             'port'   => $default_spa_port,
1832             'dst_ip' => $loopback_ip,
1833             'data'   => $spa_pkt,
1834         },
1835     );
1836
1837     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1838         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1839
1840     $rv = 0 unless $server_was_stopped;
1841
1842     if ($fw_rule_created) {
1843         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1844         $rv = 0;
1845     } else {
1846         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1847     }
1848
1849     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1850             $server_test_file)) {
1851         $rv = 0;
1852     }
1853
1854     return $rv;
1855 }
1856
1857 sub appended_spa_data() {
1858     my $test_hr = shift;
1859
1860     my $rv = 1;
1861     my $server_was_stopped = 0;
1862     my $fw_rule_created = 0;
1863     my $fw_rule_removed = 0;
1864
1865     unless (&client_send_spa_packet($test_hr)) {
1866         &write_test_file("[-] fwknop client execution error.\n",
1867             $current_test_file);
1868         $rv = 0;
1869     }
1870
1871     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1872
1873     unless ($spa_pkt) {
1874         &write_test_file("[-] could not get SPA packet " .
1875             "from file: $current_test_file\n", $current_test_file);
1876         return 0;
1877     }
1878
1879     $spa_pkt .= 'AAAA';
1880
1881     my @packets = (
1882         {
1883             'proto'  => 'udp',
1884             'port'   => $default_spa_port,
1885             'dst_ip' => $loopback_ip,
1886             'data'   => $spa_pkt,
1887         },
1888     );
1889
1890     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1891         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1892
1893     $rv = 0 unless $server_was_stopped;
1894
1895     if ($fw_rule_created) {
1896         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1897         $rv = 0;
1898     } else {
1899         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1900     }
1901
1902     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1903             $server_test_file)) {
1904         $rv = 0;
1905     }
1906
1907     return $rv;
1908 }
1909
1910 sub prepended_spa_data() {
1911     my $test_hr = shift;
1912
1913     my $rv = 1;
1914     my $server_was_stopped = 0;
1915     my $fw_rule_created = 0;
1916     my $fw_rule_removed = 0;
1917
1918     unless (&client_send_spa_packet($test_hr)) {
1919         &write_test_file("[-] fwknop client execution error.\n",
1920             $current_test_file);
1921         $rv = 0;
1922     }
1923
1924     my $spa_pkt = &get_spa_packet_from_file($current_test_file);
1925
1926     unless ($spa_pkt) {
1927         &write_test_file("[-] could not get SPA packet " .
1928             "from file: $current_test_file\n", $current_test_file);
1929         return 0;
1930     }
1931
1932     $spa_pkt = 'AAAA' . $spa_pkt;
1933
1934     my @packets = (
1935         {
1936             'proto'  => 'udp',
1937             'port'   => $default_spa_port,
1938             'dst_ip' => $loopback_ip,
1939             'data'   => $spa_pkt,
1940         },
1941     );
1942
1943     ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1944         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
1945
1946     $rv = 0 unless $server_was_stopped;
1947
1948     if ($fw_rule_created) {
1949         &write_test_file("[-] new fw rule created.\n", $current_test_file);
1950         $rv = 0;
1951     } else {
1952         &write_test_file("[+] new fw rule not created.\n", $current_test_file);
1953     }
1954
1955     unless (&file_find_regex([qr/Error\screating\sfko\scontext/],
1956             $server_test_file)) {
1957         $rv = 0;
1958     }
1959
1960     return $rv;
1961 }
1962
1963 sub server_start() {
1964     my $test_hr = shift;
1965
1966     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1967         = &client_server_interaction($test_hr, [], $USE_PREDEF_PKTS);
1968
1969     unless (&file_find_regex([qr/Starting\sfwknopd\smain\sevent\sloop/],
1970             $server_test_file)) {
1971         $rv = 0;
1972     }
1973
1974     $rv = 0 unless $server_was_stopped;
1975
1976     return $rv;
1977 }
1978
1979 sub server_stop() {
1980     my $test_hr = shift;
1981
1982     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
1983         = &client_server_interaction($test_hr, [], $USE_PREDEF_PKTS);
1984
1985     $rv = 0 unless $server_was_stopped;
1986
1987     return $rv;
1988 }
1989
1990 sub server_packet_limit() {
1991     my $test_hr = shift;
1992
1993     my @packets = (
1994         {
1995             'proto'  => 'udp',
1996             'port'   => $default_spa_port,
1997             'dst_ip' => $loopback_ip,
1998             'data'   => 'A'x700,
1999         },
2000     );
2001
2002     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
2003         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
2004
2005     if (&is_fwknopd_running()) {
2006         &stop_fwknopd();
2007         $rv = 0;
2008     }
2009
2010     unless (&file_find_regex([qr/count\slimit\sof\s1\sreached/],
2011             $server_test_file)) {
2012         $rv = 0;
2013     }
2014
2015     unless (&file_find_regex([qr/Shutting\sDown\sfwknopd/i],
2016             $server_test_file)) {
2017         $rv = 0;
2018     }
2019
2020     return $rv;
2021 }
2022
2023 sub server_ignore_small_packets() {
2024     my $test_hr = shift;
2025
2026     my @packets = (
2027         {
2028             'proto'  => 'udp',
2029             'port'   => $default_spa_port,
2030             'dst_ip' => $loopback_ip,
2031             'data'   => 'A'x120,  ### < MIN_SPA_DATA_SIZE
2032         },
2033     );
2034
2035     my ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed)
2036         = &client_server_interaction($test_hr, \@packets, $USE_PREDEF_PKTS);
2037
2038     sleep 2;
2039
2040     if (&is_fwknopd_running()) {
2041         &stop_fwknopd();
2042         $rv = 0;
2043     }
2044
2045     return $rv;
2046 }
2047
2048 sub client_server_interaction() {
2049     my ($test_hr, $pkts_hr, $spa_client_flag, $fw_rules_flag) = @_;
2050
2051     my $rv = 1;
2052     my $server_was_stopped = 1;
2053     my $fw_rule_created = 1;
2054     my $fw_rule_removed = 0;
2055
2056     ### start fwknopd to monitor for the SPA packet over the loopback interface
2057     my $fwknopd_parent_pid = &start_fwknopd($test_hr);
2058
2059     ### give fwknopd a chance to parse its config and start sniffing
2060     ### on the loopback interface
2061     if ($use_valgrind) {
2062         sleep 3;
2063     } else {
2064         sleep 2;
2065     }
2066
2067     ### send the SPA packet(s) to the server either manually using IO::Socket or
2068     ### with the fwknopd client
2069     if ($spa_client_flag == $USE_CLIENT) {
2070         unless (&client_send_spa_packet($test_hr)) {
2071             &write_test_file("[-] fwknop client execution error.\n",
2072                 $current_test_file);
2073             $rv = 0;
2074         }
2075     } else {
2076         &send_packets($pkts_hr);
2077     }
2078
2079     ### check to see if the SPA packet resulted in a new fw access rule
2080     my $ctr = 0;
2081     while (not &is_fw_rule_active($test_hr)) {
2082         &write_test_file("[-] new fw rule does not exist.\n",
2083             $current_test_file);
2084         $ctr++;
2085         last if $ctr == 3;
2086         sleep 1;
2087     }
2088     if ($ctr == 3) {
2089         $fw_rule_created = 0;
2090         $fw_rule_removed = 0;
2091     }
2092
2093     &time_for_valgrind() if $use_valgrind;
2094
2095     if ($fw_rule_created) {
2096         sleep 3;  ### allow time for rule time out.
2097         if (&is_fw_rule_active($test_hr)) {
2098             &write_test_file("[-] new fw rule not timed out.\n",
2099                 $current_test_file);
2100             $rv = 0;
2101         } else {
2102             &write_test_file("[+] new fw rule timed out.\n",
2103                 $current_test_file);
2104             $fw_rule_removed = 1;
2105         }
2106     }
2107
2108     if (&is_fwknopd_running()) {
2109         &stop_fwknopd();
2110         unless (&file_find_regex([qr/Got\sSIGTERM/],
2111                 $server_test_file)) {
2112             $server_was_stopped = 0;
2113         }
2114     } else {
2115         &write_test_file("[-] server is not running.\n",
2116             $current_test_file);
2117         $server_was_stopped = 0;
2118     }
2119
2120     return ($rv, $server_was_stopped, $fw_rule_created, $fw_rule_removed);
2121 }
2122
2123 sub get_spa_packet_from_file() {
2124     my $file = shift;
2125
2126     my $spa_pkt = '';
2127
2128     my $found_trigger_line = 0;
2129     open F, "< $file" or die "[*] Could not open file $file: $!";
2130     while (<F>) {
2131         if (/final\spacked/i) {
2132             $found_trigger_line = 1;
2133             next;
2134         }
2135         next unless $found_trigger_line;
2136
2137         ### the next line with non whitespace is the SPA packet
2138         if (/(\S+)/) {
2139             $spa_pkt = $1;
2140             last;
2141         }
2142     }
2143     close F;
2144
2145     return $spa_pkt;
2146 }
2147
2148 sub send_packets() {
2149     my $pkts_ar = shift;
2150
2151     open F, ">> $current_test_file" or die $!;
2152     print F "[+] send_packets(): Sending the following packets...\n";
2153     print F Dumper $pkts_ar;
2154     close F;
2155
2156     for my $pkt_hr (@$pkts_ar) {
2157         if ($pkt_hr->{'proto'} eq 'tcp' or $pkt_hr->{'proto'} eq 'udp') {
2158             my $socket = IO::Socket::INET->new(
2159                 PeerAddr => $pkt_hr->{'dst_ip'},
2160                 PeerPort => $pkt_hr->{'port'},
2161                 Proto    => $pkt_hr->{'proto'},
2162                 Timeout  => 1
2163             ) or die "[*] Could not acquire $pkt_hr->{'proto'}/$pkt_hr->{'port'} " .
2164                 "socket to $pkt_hr->{'dst_ip'}: $!";
2165
2166             $socket->send($pkt_hr->{'data'});
2167             undef $socket;
2168
2169         } elsif ($pkt_hr->{'proto'} eq 'http') {
2170             ### FIXME
2171         } elsif ($pkt_hr->{'proto'} eq 'icmp') {
2172             ### FIXME
2173         }
2174
2175         sleep $pkt_hr->{'delay'} if defined $pkt_hr->{'delay'};
2176     }
2177     return;
2178 }
2179
2180 sub generic_exec() {
2181     my $test_hr = shift;
2182
2183     my $rv = 1;
2184
2185     my $exec_rv = &run_cmd($test_hr->{'cmdline'},
2186                 $cmd_out_tmp, $current_test_file);
2187
2188     if ($test_hr->{'exec_err'} eq $YES) {
2189         $rv = 0 if $exec_rv;
2190     } else {
2191         $rv = 0 unless $exec_rv;
2192     }
2193
2194     if ($test_hr->{'positive_output_matches'}) {
2195         $rv = 0 unless &file_find_regex(
2196             $test_hr->{'positive_output_matches'},
2197             $current_test_file);
2198     }
2199
2200     if ($test_hr->{'negative_output_matches'}) {
2201         $rv = 0 if &file_find_regex(
2202             $test_hr->{'negative_output_matches'},
2203             $current_test_file);
2204     }
2205
2206     return $rv;
2207 }
2208
2209 ### check for PIE
2210 sub pie_binary() {
2211     my $test_hr = shift;
2212     return 0 unless $test_hr->{'binary'};
2213     &run_cmd("./hardening-check $test_hr->{'binary'}",
2214             $cmd_out_tmp, $current_test_file);
2215     return 0 if &file_find_regex([qr/Position\sIndependent.*:\sno/i],
2216         $current_test_file);
2217     return 1;
2218 }
2219
2220 ### check for stack protection
2221 sub stack_protected_binary() {
2222     my $test_hr = shift;
2223     return 0 unless $test_hr->{'binary'};
2224     &run_cmd("./hardening-check $test_hr->{'binary'}",
2225             $cmd_out_tmp, $current_test_file);
2226     return 0 if &file_find_regex([qr/Stack\sprotected.*:\sno/i],
2227         $current_test_file);
2228     return 1;
2229 }
2230
2231 ### check for fortified source functions
2232 sub fortify_source_functions() {
2233     my $test_hr = shift;
2234     return 0 unless $test_hr->{'binary'};
2235     &run_cmd("./hardening-check $test_hr->{'binary'}",
2236             $cmd_out_tmp, $current_test_file);
2237     return 0 if &file_find_regex([qr/Fortify\sSource\sfunctions:\sno/i],
2238         $current_test_file);
2239     return 1;
2240 }
2241
2242 ### check for read-only relocations
2243 sub read_only_relocations() {
2244     my $test_hr = shift;
2245     return 0 unless $test_hr->{'binary'};
2246     &run_cmd("./hardening-check $test_hr->{'binary'}",
2247             $cmd_out_tmp, $current_test_file);
2248     return 0 if &file_find_regex([qr/Read.only\srelocations:\sno/i],
2249         $current_test_file);
2250     return 1;
2251 }
2252
2253 ### check for immediate binding
2254 sub immediate_binding() {
2255     my $test_hr = shift;
2256     return 0 unless $test_hr->{'binary'};
2257     &run_cmd("./hardening-check $test_hr->{'binary'}",
2258             $cmd_out_tmp, $current_test_file);
2259     return 0 if &file_find_regex([qr/Immediate\sbinding:\sno/i],
2260         $current_test_file);
2261     return 1;
2262 }
2263
2264 sub specs() {
2265
2266      &run_cmd("LD_LIBRARY_PATH=$lib_dir $valgrind_str $fwknopdCmd " .
2267             "$default_server_conf_args --fw-list-all",
2268             $cmd_out_tmp, $current_test_file);
2269
2270     my $have_gpgme = 0;
2271
2272     for my $cmd (
2273         'uname -a',
2274         'uptime',
2275         'ifconfig -a',
2276         'ls -l /etc', 'if [ -e /etc/issue ]; then cat /etc/issue; fi',
2277         'if [ `which iptables` ]; then iptables -V; fi',
2278         'if [ -e /proc/cpuinfo ]; then cat /proc/cpuinfo; fi',
2279         'if [ -e /proc/config.gz ]; then zcat /proc/config.gz; fi',
2280         'if [ `which gpg` ]; then gpg --version; fi',
2281         'if [ `which tcpdump` ]; then ldd `which tcpdump`; fi',
2282         "ldd $fwknopCmd",
2283         "ldd $fwknopdCmd",
2284         "ldd $libfko_bin",
2285         'ls -l /usr/lib/*pcap*',
2286         'ls -l /usr/local/lib/*pcap*',
2287         'ls -l /usr/lib/*fko*',
2288         'ls -l /usr/local/lib/*fko*',
2289     ) {
2290         &run_cmd($cmd, $cmd_out_tmp, $current_test_file);
2291
2292         if ($cmd =~ /^ldd/) {
2293             $have_gpgme++ if &file_find_regex([qr/gpgme/], $cmd_out_tmp);
2294         }
2295     }
2296
2297     ### all three of fwknop/fwknopd/libfko must link against gpgme in order
2298     ### to enable gpg tests
2299     unless ($have_gpgme == 3) {
2300         push @tests_to_exclude, "GPG";
2301     }
2302
2303     return 1;
2304 }
2305
2306 sub time_for_valgrind() {
2307     my $ctr = 0;
2308     while (&run_cmd("ps axuww | grep LD_LIBRARY_PATH | " .
2309             "grep valgrind |grep -v perl | grep -v grep",
2310             $cmd_out_tmp, $current_test_file)) {
2311         $ctr++;
2312         last if $ctr == 5;
2313         sleep 1;
2314     }
2315     return;
2316 }
2317
2318 sub anonymize_results() {
2319     my $rv = 0;
2320     die "[*] $output_dir does not exist" unless -d $output_dir;
2321     die "[*] $logfile does not exist, has $0 been executed?"
2322         unless -e $logfile;
2323     if (-e $tarfile) {
2324         unlink $tarfile or die "[*] Could not unlink $tarfile: $!";
2325     }
2326
2327     ### remove non-loopback IP addresses
2328     my $search_re = qr/\b127\.0\.0\.1\b/;
2329     system "perl -p -i -e 's|$search_re|00MY1271STR00|g' $output_dir/*.test";
2330     $search_re = qr/\b127\.0\.0\.2\b/;
2331     system "perl -p -i -e 's|$search_re|00MY1272STR00|g' $output_dir/*.test";
2332     $search_re = qr/\b0\.0\.0\.0\b/;
2333     system "perl -p -i -e 's|$search_re|00MY0000STR00|g' $output_dir/*.test";
2334     $search_re = qr/\b(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}\b/;
2335     system "perl -p -i -e 's|$search_re|N.N.N.N|g' $output_dir/*.test";
2336     system "perl -p -i -e 's|00MY1271STR00|127.0.0.1|g' $output_dir/*.test";
2337     system "perl -p -i -e 's|00MY1272STR00|127.0.0.2|g' $output_dir/*.test";
2338     system "perl -p -i -e 's|00MY0000STR00|0.0.0.0|g' $output_dir/*.test";
2339
2340     ### remove hostname from any uname output
2341     $search_re = qr/\suname\s+\-a\s*\n\s*(\S+)\s+\S+/;
2342     system "perl -p -i -e 'undef \$/; s|$search_re" .
2343         "| uname -a\n\$1 (removed)|s' $output_dir/*.test";
2344
2345     $search_re = qr/uname=\x27(\S+)\s+\S+/;
2346     system "perl -p -i -e 's|$search_re|uname= \$1 (removed)|' $output_dir/*.test";
2347
2348     ### create tarball
2349     system "tar cvfz $tarfile $logfile $output_dir";
2350     print "[+] Anonymized test results file: $tarfile\n";
2351     if (-e $tarfile) {
2352         $rv = 1;
2353     }
2354     return $rv;
2355 }
2356
2357
2358 sub write_pid() {
2359     my $test_hr = shift;
2360
2361     open F, "> $default_pid_file" or die $!;
2362     print F "1\n";
2363     close F;
2364
2365     &server_start($test_hr);
2366
2367     open F, "< $default_pid_file" or die $!;
2368     my $pid = <F>;
2369     chomp $pid;
2370     close F;
2371
2372     if ($pid != 1) {
2373         return 1;
2374     }
2375
2376     return 0;
2377 }
2378
2379 sub start_fwknopd() {
2380     my $test_hr = shift;
2381
2382     &write_test_file("[+] TEST: $test_hr->{'msg'}\n", $server_test_file);
2383
2384     my $pid = fork();
2385     die "[*] Could not fork: $!" unless defined $pid;
2386
2387     if ($pid == 0) {
2388
2389         ### we are the child, so start fwknopd
2390         exit &run_cmd($test_hr->{'fwknopd_cmdline'},
2391             $server_cmd_tmp, $server_test_file);
2392     }
2393     return $pid;
2394 }
2395
2396 sub write_key() {
2397     my ($key, $file) = @_;
2398
2399     open K, "> $file" or die "[*] Could not open $file: $!";
2400     print K "$loopback_ip: $key\n";
2401     print K "localhost: $key\n";
2402     print K "some.host.through.proxy.com: $key\n";
2403     close K;
2404     return;
2405 }
2406
2407 sub dump_pids() {
2408     open C, ">> $current_test_file"
2409         or die "[*] Could not open $current_test_file: $!";
2410     print C "\n" . localtime() . " [+] PID dump:\n";
2411     close C;
2412     &run_cmd("ps auxww | grep knop |grep -v grep",
2413         $cmd_out_tmp, $current_test_file);
2414     return;
2415 }
2416
2417 sub run_cmd() {
2418     my ($cmd, $cmd_out, $file) = @_;
2419
2420     if (-e $file) {
2421         open F, ">> $file"
2422             or die "[*] Could not open $file: $!";
2423         print F localtime() . " CMD: $cmd\n";
2424         close F;
2425     } else {
2426         open F, "> $file"
2427             or die "[*] Could not open $file: $!";
2428         print F localtime() . " CMD: $cmd\n";
2429         close F;
2430     }
2431
2432     my $rv = ((system "$cmd > $cmd_out 2>&1") >> 8);
2433
2434     open C, "< $cmd_out" or die "[*] Could not open $cmd_out: $!";
2435     my @cmd_lines = <C>;
2436     close C;
2437
2438     open F, ">> $file" or die "[*] Could not open $file: $!";
2439     print F $_ for @cmd_lines;
2440     close F;
2441
2442     if ($rv == 0) {
2443         return 1;
2444     }
2445     return 0;
2446 }
2447
2448 sub dots_print() {
2449     my $msg = shift;
2450     &logr($msg);
2451     my $dots = '';
2452     for (my $i=length($msg); $i < $PRINT_LEN; $i++) {
2453         $dots .= '.';
2454     }
2455     &logr($dots);
2456     return;
2457 }
2458
2459 sub init() {
2460
2461     $|++; ### turn off buffering
2462
2463     $< == 0 && $> == 0 or
2464         die "[*] $0: You must be root (or equivalent ",
2465             "UID 0 account) to effectively test fwknop";
2466
2467     ### validate test hashes
2468     my $hash_num = 0;
2469     for my $test_hr (@tests) {
2470         for my $key (keys %test_keys) {
2471             if ($test_keys{$key} == $REQUIRED) {
2472                 die "[*] Missing '$key' element in hash: $hash_num"
2473                     unless defined $test_hr->{$key};
2474             } else {
2475                 $test_hr->{$key} = '' unless defined $test_hr->{$key};
2476             }
2477         }
2478         $hash_num++;
2479     }
2480
2481     if ($use_valgrind) {
2482         die "[*] $valgrindCmd exec problem, use --valgrind-path"
2483             unless -e $valgrindCmd and -x $valgrindCmd;
2484     }
2485
2486     die "[*] $conf_dir directory does not exist." unless -d $conf_dir;
2487     die "[*] $lib_dir directory does not exist." unless -d $lib_dir;
2488
2489     for my $file ($configure_path,
2490             $default_conf,
2491             $nat_conf,
2492             $default_access_conf,
2493             $no_source_match_access_conf,
2494             $ip_source_match_access_conf,
2495             $subnet_source_match_access_conf,
2496             $no_subnet_source_match_access_conf,
2497             $no_multi_source_match_access_conf,
2498             $multi_source_match_access_conf,
2499             $open_ports_access_conf,
2500             $mismatch_open_ports_access_conf,
2501             $require_user_access_conf,
2502             $mismatch_user_access_conf,
2503             $require_src_access_conf,
2504             $multi_gpg_access_conf,
2505             $multi_stanzas_access_conf,
2506             $expired_access_conf,
2507             $expired_epoch_access_conf,
2508             $future_expired_access_conf,
2509             $invalid_expire_access_conf,
2510             $force_nat_access_conf,
2511     ) {
2512         die "[*] $file does not exist" unless -e $file;
2513     }
2514
2515     if (-d $output_dir) {
2516         if (-d "${output_dir}.last") {
2517             rmtree "${output_dir}.last"
2518                 or die "[*] rmtree ${output_dir}.last $!";
2519         }
2520         mkdir "${output_dir}.last"
2521             or die "[*] ${output_dir}.last: $!";
2522         for my $file (glob("$output_dir/*.test")) {
2523             if ($file =~ m|.*/(.*)|) {
2524                 copy $file, "${output_dir}.last/$1" or die $!;
2525             }
2526         }
2527         if (-e "$output_dir/init") {
2528             copy "$output_dir/init", "${output_dir}.last/init";
2529         }
2530         if (-e $logfile) {
2531             copy $logfile, "${output_dir}.last/$logfile" or die $!;
2532         }
2533         $saved_last_results = 1;
2534     } else {
2535         mkdir $output_dir or die "[*] Could not mkdir $output_dir: $!";
2536     }
2537     unless (-d $run_dir) {
2538         mkdir $run_dir or die "[*] Could not mkdir $run_dir: $!";
2539     }
2540
2541     for my $file (glob("$output_dir/*.test")) {
2542         unlink $file or die "[*] Could not unlink($file)";
2543     }
2544     if (-e "$output_dir/init") {
2545         unlink "$output_dir/init" or die $!;
2546     }
2547
2548     if (-e $logfile) {
2549         unlink $logfile or die $!;
2550     }
2551
2552     if ($test_include) {
2553         @tests_to_include = split /\s*,\s*/, $test_include;
2554     }
2555     if ($test_exclude) {
2556         @tests_to_exclude = split /\s*,\s*/, $test_exclude;
2557     }
2558
2559     ### make sure no fwknopd instance is currently running
2560     die "[*] Please stop the running fwknopd instance."
2561         if &is_fwknopd_running();
2562
2563     unless ($enable_recompilation_warnings_check) {
2564         push @tests_to_exclude, 'recompilation';
2565     }
2566
2567     $sudo_path = &find_command('sudo');
2568
2569     unless ((&find_command('cc') or &find_command('gcc')) and &find_command('make')) {
2570         ### disable compilation checks
2571         push @tests_to_exclude, 'recompilation';
2572     }
2573
2574     open UNAME, "uname |" or die "[*] Could not execute uname: $!";
2575     while (<UNAME>) {
2576         if (/linux/i) {
2577             $platform = 'linux';
2578             last;
2579         }
2580     }
2581     close UNAME;
2582
2583     unless ($platform eq 'linux') {
2584         push @tests_to_exclude, 'NAT';
2585     }
2586
2587     return;
2588 }
2589
2590 sub identify_loopback_intf() {
2591     return if $loopback_intf;
2592
2593     ### Linux:
2594
2595     ### lo    Link encap:Local Loopback
2596     ###       inet addr:127.0.0.1  Mask:255.0.0.0
2597     ###       inet6 addr: ::1/128 Scope:Host
2598     ###       UP LOOPBACK RUNNING  MTU:16436  Metric:1
2599     ###       RX packets:534709 errors:0 dropped:0 overruns:0 frame:0
2600     ###       TX packets:534709 errors:0 dropped:0 overruns:0 carrier:0
2601     ###       collisions:0 txqueuelen:0
2602     ###       RX bytes:101110617 (101.1 MB)  TX bytes:101110617 (101.1 MB)
2603
2604     ### Freebsd:
2605
2606     ### lo0: flags=8049<UP,LOOPBACK,RUNNING,MULTICAST> metric 0 mtu 16384
2607     ###         options=3<RXCSUM,TXCSUM>
2608     ###         inet6 fe80::1%lo0 prefixlen 64 scopeid 0x2
2609     ###         inet6 ::1 prefixlen 128
2610     ###         inet 127.0.0.1 netmask 0xff000000
2611     ###         nd6 options=3<PERFORMNUD,ACCEPT_RTADV>
2612
2613     my $intf = '';
2614     my $found_loopback_intf = 0;
2615
2616     my $cmd = 'ifconfig -a';
2617     open C, "$cmd |" or die "[*] (use --loopback <name>) $cmd: $!";
2618     while (<C>) {
2619         if (/^(\S+?):?\s+.*loopback/i) {
2620             $intf = $1;
2621             next;
2622         }
2623         if (/^\S/ and $intf and not $found_loopback_intf) {
2624             ### should not happen
2625             last;
2626         }
2627         if ($intf and /\b127\.0\.0\.1\b/) {
2628             $found_loopback_intf = 1;
2629             last;
2630         }
2631     }
2632     close C;
2633
2634     die "[*] could not determine loopback interface, use --loopback <name>"
2635         unless $found_loopback_intf;
2636
2637     $loopback_intf = $intf;
2638
2639     return;
2640 }
2641
2642 sub parse_valgrind_flagged_functions() {
2643     for my $file (glob("$output_dir/*.test")) {
2644         my $type = 'server';
2645         $type = 'client' if $file =~ /\d\.test/;
2646         open F, "< $file" or die $!;
2647         while (<F>) {
2648             ### ==30969==    by 0x4E3983A: fko_set_username (fko_user.c:65)
2649             if (/^==.*\sby\s\S+\:\s(\S+)\s(.*)/) {
2650                 $valgrind_flagged_fcns{$type}{"$1 $2"}++;
2651                 $valgrind_flagged_fcns_unique{$type}{$1}++;
2652             }
2653         }
2654         close F;
2655     }
2656
2657     open F, ">> $current_test_file" or die $!;
2658     for my $type ('client', 'server') {
2659         print F "\n[+] fwknop $type functions (unique view):\n";
2660         next unless defined $valgrind_flagged_fcns_unique{$type};
2661         for my $fcn (sort {$valgrind_flagged_fcns_unique{$type}{$b}
2662                 <=> $valgrind_flagged_fcns_unique{$type}{$a}}
2663                 keys %{$valgrind_flagged_fcns_unique{$type}}) {
2664             printf F "    %5d : %s\n", $valgrind_flagged_fcns_unique{$type}{$fcn}, $fcn;
2665         }
2666         print F "\n[+] fwknop $type functions (with call line numbers):\n";
2667         for my $fcn (sort {$valgrind_flagged_fcns{$type}{$b}
2668                 <=> $valgrind_flagged_fcns{$type}{$a}} keys %{$valgrind_flagged_fcns{$type}}) {
2669             printf F "    %5d : %s\n", $valgrind_flagged_fcns{$type}{$fcn}, $fcn;
2670         }
2671         next unless defined $valgrind_flagged_fcns{$type};
2672
2673     }
2674     close F;
2675     return 1;
2676 }
2677
2678 sub is_fw_rule_active() {
2679     my $test_hr = shift;
2680
2681     my $conf_args = $default_server_conf_args;
2682
2683     if ($test_hr->{'server_conf'}) {
2684         $conf_args = "-c $test_hr->{'server_conf'} -a $default_access_conf " .
2685             "-d $default_digest_file -p $default_pid_file";
2686     }
2687
2688     return 1 if &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd " .
2689             qq{$conf_args --fw-list | grep -v "# DISABLED" |grep $fake_ip |grep _exp_},
2690             $cmd_out_tmp, $current_test_file);
2691     return 0;
2692 }
2693
2694 sub is_fwknopd_running() {
2695
2696     sleep 2 if $use_valgrind;
2697
2698     &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd $default_server_conf_args " .
2699         "--status", $cmd_out_tmp, $current_test_file);
2700
2701     return 0 if &file_find_regex([qr/no\s+running/i], $cmd_out_tmp);
2702
2703     return 1;
2704 }
2705
2706 sub stop_fwknopd() {
2707
2708     &run_cmd("LD_LIBRARY_PATH=$lib_dir $fwknopdCmd " .
2709         "$default_server_conf_args -K", $cmd_out_tmp, $current_test_file);
2710
2711     if ($use_valgrind) {
2712         &time_for_valgrind();
2713     } else {
2714         sleep 1;
2715     }
2716
2717     return;
2718 }
2719
2720 sub file_find_regex() {
2721     my ($re_ar, $file) = @_;
2722
2723     my $found_all_regexs = 1;
2724     my @write_lines = ();
2725     my @file_lines = ();
2726
2727     open F, "< $file" or die "[*] Could not open $file: $!";
2728     while (<F>) {
2729         push @file_lines, $_;
2730     }
2731     close F;
2732
2733     for my $re (@$re_ar) {
2734         my $matched = 0;
2735         for my $line (@file_lines) {
2736             if ($line =~ $re) {
2737                 push @write_lines, "[+] file_find_regex() " .
2738                     "Matched '$re' with line: $line";
2739                 $matched = 1;
2740             }
2741         }
2742         unless ($matched) {
2743             push @write_lines, "[-] file_find_regex() " .
2744                 "Did not match any regex in '@$re_ar' in file: $file\n";
2745             $found_all_regexs = 0;
2746         }
2747     }
2748
2749     for my $line (@write_lines) {
2750         &write_test_file($line, $file);
2751     }
2752
2753     return $found_all_regexs;
2754 }
2755
2756 sub find_command() {
2757     my $cmd = shift;
2758
2759     my $path = '';
2760     open C, "which $cmd |" or die "[*] Could not execute: which $cmd: $!";
2761     while (<C>) {
2762         if (m|^(/.*$cmd)$|) {
2763             $path = $1;
2764             last;
2765         }
2766     }
2767     close C;
2768     return $path;
2769 }
2770
2771 sub write_test_file() {
2772     my ($msg, $file) = @_;
2773
2774     if (-e $file) {
2775         open F, ">> $file"
2776             or die "[*] Could not open $file: $!";
2777         print F $msg;
2778         close F;
2779     } else {
2780         open F, "> $file"
2781             or die "[*] Could not open $file: $!";
2782         print F $msg;
2783         close F;
2784     }
2785     return;
2786 }
2787
2788 sub logr() {
2789     my $msg = shift;
2790     print STDOUT $msg;
2791     open F, ">> $logfile" or die $!;
2792     print F $msg;
2793     close F;
2794     return;
2795 }
2796
2797 sub usage() {
2798     print <<_HELP_;
2799
2800 [+] $0 <options>
2801
2802     -A   --Anonymize-results      - Prepare anonymized results at:
2803                                     $tarfile
2804     --diff                        - Compare the results of one test run to
2805                                     another.  By default this compares output
2806                                     in ${output_dir}.last to $output_dir
2807     --diff-dir1=<path>            - Left hand side of diff directory path,
2808                                     default is: ${output_dir}.last
2809     --diff-dir2=<path>            - Right hand side of diff directory path,
2810                                     default is: $output_dir
2811     --include=<regex>             - Specify a regex to be used over test
2812                                     names that must match.
2813     --exclude=<regex>             - Specify a regex to be used over test
2814                                     names that must not match.
2815     --enable-recompile            - Recompile fwknop sources and look for
2816                                     compilation warnings.
2817     --enable-valgrind             - Run every test underneath valgrind.
2818     --List                        - List test names.
2819     --loopback-intf=<intf>        - Specify loopback interface name (default
2820                                     depends on the OS where the test suite
2821                                     is executed).
2822     --output-dir=<path>           - Path to output directory, default is:
2823                                     $output_dir
2824     --fwknop-path=<path>          - Path to fwknop binary, default is:
2825                                     $fwknopCmd
2826     --fwknopd-path=<path>         - Path to fwknopd binary, default is:
2827                                     $fwknopdCmd
2828     --libfko-path=<path>          - Path to libfko, default is:
2829                                     $libfko_bin
2830     --valgrind-path=<path>        - Path to valgrind, default is:
2831                                     $valgrindCmd
2832     -h   --help                   - Display usage on STDOUT and exit.
2833
2834 _HELP_
2835     exit 0;
2836 }