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