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