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