root/fwknop/tags/fwknop-1.9.3/knoptm

Revision 1046, 25.4 kB (checked in by mbr, 8 months ago)

version 1.9.3

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: knoptm
6 #
7 # Purpose: This daemon will remove firewall rules created by fwknopd (after
8 #          receiving a valid SPA packet).  The fwknopd daemon communicates
9 #          with knoptm via the /var/run/fwknop/knoptm_ip_timeout.sock UNIX
10 #          domain socket whenever new rules are added, and knoptm removes
11 #          them after the associated timer expires.
12 #
13 #          The format of the rules communicated to knoptm by fwknopd are as
14 #          follows:
15 #
16 #   <rule timestamp> <timeout> <src> <sport> <dst> <dport> <proto> \
17 #   <table> <chain> <target> <direction> <nat_ip> <nat_port>
18 #
19 # Author: Michael Rash (mbr@cipherdyne.org)
20 #
21 # Version: 1.9.3
22 #
23 # Copyright (C) 2004-2007 Michael Rash (mbr@cipherdyne.org)
24 #
25 # License (GNU Public License):
26 #
27 #    This program is distributed in the hope that it will be useful,
28 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
29 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
30 #    GNU General Public License for more details.
31 #
32 #    You should have received a copy of the GNU General Public License
33 #    along with this program; if not, write to the Free Software
34 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
35 #    USA
36 #
37 #############################################################################
38 #
39 # $Id$
40 #
41
42 use lib '/usr/lib/fwknop';
43 use Unix::Syslog qw(:subs :macros);
44 use Net::IPv4Addr qw(ipv4_in_network);
45 use IO::Socket;
46 use IO::Handle;
47 use File::Copy;
48 use Data::Dumper;
49 use POSIX;
50 use Getopt::Long;
51 use strict;
52
53 my $config_file  = '/etc/fwknop/fwknop.conf';
54 my $user_rc_file = '';
55
56 my $version = '1.9.3';
57 my $revision_svn = '$Revision$';
58 my $rev_num = '1';
59 ($rev_num) = $revision_svn =~ m|\$Rev.*:\s+(\S+)|;
60
61 my $print_help = 0;
62 my $print_ver  = 0;
63 my $debug      = 0;
64 my $die_msg    = '';
65 my $warn_msg   = '';
66 my $fw_type    = '';
67 my $no_logs    = 0;
68 my $timeout_sock = '';
69 my $max_timeout_tries  = 20;
70 my $no_voluntary_exits = 0;
71 my $imported_iptables_modules = 0;
72 my $voluntary_exit_timestamp  = 0;
73
74 my @fw_cache_entries = ();
75 my %config = ();
76 my %cmds   = ();
77 my %timeout_cache = ();
78
79 my $ip_re = qr|(?:[0-2]?\d{1,2}\.){3}[0-2]?\d{1,2}|;
80 my $zero_ip_re = qr|(?:0\.){3}0|;
81
82 my $SEND_MAIL = 1;
83 my $NO_MAIL   = 0;
84
85 ### make Getopts case sensitive
86 Getopt::Long::Configure('no_ignore_case');
87 exit 1 unless (GetOptions(
88     'config=s'  => \$config_file,
89     'debug'     => \$debug,
90     'Version'   => \$print_ver,
91     'fw-type=s' => \$fw_type,
92     'no-voluntary-exits' => \$no_voluntary_exits,
93     'no-logs'   => \$no_logs,
94     'help'      => \$print_help
95 ));
96
97 ### Print the version number and exit if -V given on the command line.
98 if ($print_ver) {
99     print
100 "[+] knoptm v$version (part of the fwknop project), by Michael Rash\n",
101 "    <mbr\@cipherdyne.org>\n";
102     exit 0;
103 }
104
105 &usage(0) if $print_help;
106
107 ### set things up, deal with pid's, and import config
108 &knoptm_init();
109
110 print STDERR "[+] Opening $config{'KNOPTM_IP_TIMEOUT_SOCK'} socket, ",
111     "and entering main loop.\n" if $debug;
112
113 $timeout_sock = IO::Socket::UNIX->new(
114     Type    => SOCK_STREAM,
115     Local   => $config{'KNOPTM_IP_TIMEOUT_SOCK'},
116     Listen  => SOMAXCONN,
117     Timeout => .1
118 ) or die "[*] Could not acquire auto-response domain socket: $!";
119
120 ### main loop
121 for (;;) {
122
123     my $fwknop_connection = $timeout_sock->accept();
124     if ($fwknop_connection) {
125         @fw_cache_entries = <$fwknop_connection>;
126
127         ### add new entries to the cache
128         &build_timeout_cache() if @fw_cache_entries;
129     }
130
131     ### always check to see if any fw rules need to be removed
132     &timeout_cache_entries();
133
134     &append_die_msg()  if $die_msg;
135     &append_warn_msg() if $warn_msg;
136
137     ### see if knoptm should voluntarily exit so that it can be
138     ### restarted by knopwatchd
139     &check_voluntary_exits();
140
141     @fw_cache_entries = ();
142
143     sleep 1;
144 }
145 close $timeout_sock;
146 exit 0;
147 #============================ end main ==============================
148
149 sub build_timeout_cache() {
150
151     ### line format:
152     ### rule_timeout timeout src sport dst dport \
153     ### proto table chain target direction nat_ip \
154     ### nat_port
155
156     ### 1201982858 5 127.0.0.2 0 0.0.0.0/0 22 tcp filter FWKNOP_INPUT \
157     ### ACCEPT src 0.0.0.0/0 0
158     for my $line (@fw_cache_entries) {
159
160         my @ar = split /\s+/, $line;
161         next unless $#ar == 12;
162         next unless &is_digit($ar[0]);
163         next unless &is_digit($ar[1]);
164         next unless $ar[2] =~ /$ip_re/;
165         next unless &is_digit($ar[3]);
166         next unless $ar[4] =~ /$ip_re/;
167         next unless &is_digit($ar[5]);
168         next unless $ar[6] =~ /\w+/;
169         next unless $ar[7] =~ /\w+/;
170         next unless $ar[8] =~ /\w+/;
171         next unless $ar[9] =~ /\w+/;
172         next unless $ar[10] =~ /\w+/;
173         next unless $ar[11] =~ /$ip_re/;
174         next unless &is_digit($ar[12]);
175
176         ### the number represents the number of times we attempt to
177         ### delete the rule
178         $timeout_cache{$line} = 0;
179
180         print STDERR "[+] Received valid line: $line" if $debug;
181     }
182     return;
183 }
184
185 sub timeout_cache_entries() {
186
187     my @del_keys = ();
188     for my $line (keys %timeout_cache) {
189
190         my @ar = split /\s+/, $line;
191
192         my $rule_timestamp = $ar[0];
193         my $timeout        = $ar[1];
194         my $src            = $ar[2];
195         my $sport          = $ar[3];
196         my $dst            = $ar[4];
197         my $dport          = $ar[5];
198         my $proto          = $ar[6];
199         my $table          = $ar[7];
200         my $chain          = $ar[8];
201         my $target         = $ar[9];
202         my $direction      = $ar[10];
203         my $nat_ip         = $ar[11];
204         my $nat_port       = $ar[12];
205
206         if ((time() - $rule_timestamp) > $timeout) {
207
208             print STDERR "[+] Expiring rule: $line" if $debug;
209             ### see if the rule is still active, and remove if necessary
210             if (&rm_fw_rule($rule_timestamp, $timeout, $src, $sport, $dst,
211                     $dport, $proto, $table, $chain, $target, $direction,
212                     $nat_ip, $nat_port)) {
213
214                 ### delete the entry from the in-memory cache now that
215                 ### the firewall rule has been removed
216                 push @del_keys, $line;
217
218             }
219             $timeout_cache{$line}++;
220             if ($timeout_cache{$line} > $max_timeout_tries) {
221                 ### it seems the rule has been lost (perhaps manually
222                 ### deleted) so remove it from the cache since it is
223                 ### past the timeout anyway
224                 my $str = "$src -> $dst($proto/$dport)";
225                 if ($direction eq 'dst') {
226                     $str = "$src($proto/$sport) -> $dst";
227                 }
228                 &logr('[-]', "exceeded max removal tries for $str, " .
229                     "deleting from cache", $NO_MAIL);
230                 push @del_keys, $line;
231             }
232         }
233     }
234     if (@del_keys) {
235         for my $key (@del_keys) {
236             delete $timeout_cache{$key};
237         }
238     }
239     return;
240 }
241
242 sub rm_fw_rule() {
243     my ($rule_timestamp, $timeout, $src, $sport, $dst, $dport,
244         $proto, $table, $chain, $target, $direction, $nat_ip,
245         $nat_port) = @_;
246
247     if ($config{'FIREWALL_TYPE'} eq 'iptables') {
248
249         return &rm_ipt_rule($timeout, $src, $sport, $dst, $dport,
250                     $proto, $table, $chain, $target, $direction,
251                     $nat_ip, $nat_port);
252
253     } elsif ($config{'FIREWALL_TYPE'} eq 'ipfw') {
254
255         return &rm_ipfw_rule($timeout, $src, $dst, $proto, $dport);
256     }
257
258     return 0;
259 }
260
261 sub rm_ipt_rule() {
262     my ($timeout, $src, $sport, $dst, $dport, $proto,
263         $table, $chain, $target, $direction, $nat_ip, $nat_port) = @_;
264
265     my $removed_rule = 0;
266
267     my %ipt_opts = (
268         'iptables' => $cmds{'iptables'},
269         'iptout'   => $config{'KNOPTM_IPT_OUTPUT_FILE'},
270         'ipterr'   => $config{'KNOPTM_IPT_ERROR_FILE'}
271     );
272     $ipt_opts{'debug'} = 1 if $debug;
273
274     my $ipt = new IPTables::ChainMgr(%ipt_opts)
275         or die '[*] Could not acquire IPTables::ChainMgr object.';
276
277     my %extended_info = ('protocol' => $proto);
278     if ($sport) {
279         $extended_info{'s_port'} = $sport;
280     }
281     if ($dport) {
282         $extended_info{'d_port'} = $dport;
283     }
284     if ($nat_ip !~ /$zero_ip_re/ and $nat_port > 0) {
285         $extended_info{'to_ip'}   = $nat_ip;
286         $extended_info{'to_port'} = $nat_port;
287     }
288
289     my $out_aref = [];
290     my $err_aref = [];
291
292     my ($rv, $num_chain_rules) = $ipt->find_ip_rule($src, $dst,
293             $table, $chain, $target, \%extended_info);
294
295     if ($rv) {
296         my ($rv, $out_aref, $err_aref) = $ipt->delete_ip_rule($src,
297             $dst, $table, $chain, $target, \%extended_info);
298
299         my $str = "$src -> $dst($proto/$dport)";
300         if ($direction eq 'dst') {
301             $str = "$src($proto/$sport) -> $dst";
302         }
303         if (defined $extended_info{'to_ip'}) {
304             $str = "$src -> $extended_info{'to_ip'}" .
305                 "($proto/$extended_info{'to_port'})";
306         }
307
308         if ($rv) {
309             &logr('[+]', "removed iptables $chain $target rule " .
310                 "for $str, $timeout sec timeout exceeded", $SEND_MAIL);
311             $removed_rule = 1;
312         } else {
313             my $msg = "could not delete $target rule for $str";
314             &logr('[-]', $msg, $NO_MAIL);
315             &psyslog_errs($err_aref);
316         }
317     }
318     return $removed_rule;
319 }
320
321 sub rm_ipfw_rule() {
322     my ($timeout, $src, $dst, $proto, $port) = @_;
323
324     my $removed_rule = 0;
325
326     $src = 'any' if $src =~ /$zero_ip_re/;
327     $dst = 'any' if $dst =~ /$zero_ip_re/;
328
329     ### FIXME, need to add specific destination IP (inspired from
330     ### the FORWARD_ACCESS capability for iptables firewalls
331     my $rulenum = &ipfw_find_ip_rule($src, $dst, $proto, $port);
332
333     if ($rulenum) {
334         if (&ipfw_delete_ip_rule($rulenum)) {
335
336             &logr('[+]', "removed ipfw allow " .
337                     "rule for $src -> " .
338                     "$proto/$port, $timeout " .
339                     "second timeout exceeded", $SEND_MAIL);
340             $removed_rule = 1;
341         } else {
342             my $msg = "could not delete ipfw allow rule for $src " .
343                 "-> $proto/$port";
344             &logr('[-]', $msg, $NO_MAIL);
345         }
346     }
347
348     return $removed_rule;
349 }
350
351 sub ipfw_find_ip_rule() {
352     my ($src, $dst, $proto, $port) = @_;
353
354     my $rulenum = 0;
355
356     open LIST, "$cmds{'ipfw'} list |" or
357         die "[*] Could not execute 'ipfw list'";
358     while (<LIST>) {
359         if ($proto eq 'tcp' or $proto eq 'udp') {
360             ### 00002 allow tcp from 1.1.1.1 to any dst-port 22 keep-state
361             if (/^\s*(\d+)\s+allow\s+$proto\s+from\s+$src\s+to\s+
362                         $dst\s+dst-port\s+$port\s+keep-state/x) {
363                 $rulenum = $1;
364                 last;
365             }
366         } else### icmp
367             if (/^\s*(\d+)\s+allow\s+$proto\s+from\s+$src\s+to\s+$dst/x) {
368                 $rulenum = $1;
369                 last;
370             }
371         }
372     }
373     close LIST;
374
375     if ($rulenum) {
376         ### remove any leading zeros from the rule number
377         $rulenum =~ s/^0{1,4}//g;
378     }
379
380     return $rulenum;
381 }
382
383 sub ipfw_delete_ip_rule() {
384     my $rulenum = shift;
385
386     open IPFW, "| $cmds{'ipfw'} delete $rulenum" or die "[*] Could not ",
387         "execute $cmds{'ipfw'} delete $rulenum";
388     close IPFW;
389
390     return 1;
391 }
392
393 sub import_config() {
394     open C, "< $config_file" or die "[*] Could not open ",
395         "config file $config_file: $!";
396     my @lines = <C>;
397     close C;
398     for my $line (@lines) {
399         chomp $line;
400         next if ($line =~ /^\s*#/);
401         if ($line =~ /^(\S+)\s+(.*?)\;/) {
402             my $varname = $1;
403             my $val     = $2;
404             if ($val =~ m|/.+| && $varname =~ /^(\w+)Cmd$/) {
405                 ### found a command
406                 $cmds{$1} = $val;
407             } else {
408                 $config{$varname} = $val;
409             }
410         }
411     }
412     return;
413 }
414
415 sub expand_vars() {
416
417     my $has_sub_var = 1;
418     my $resolve_ctr = 0;
419
420     while ($has_sub_var) {
421         $resolve_ctr++;
422         $has_sub_var = 0;
423         if ($resolve_ctr >= 20) {
424             die "[*] Exceeded maximum variable resolution counter.";
425         }
426         for my $hr (\%config, \%cmds) {
427             for my $var (keys %$hr) {
428                 my $val = $hr->{$var};
429                 if ($val =~ m|\$(\w+)|) {
430                     my $sub_var = $1;
431                     die "[*] sub-ver $sub_var not allowed within same ",
432                         "variable $var" if $sub_var eq $var;
433                     if (defined $config{$sub_var}) {
434                         $val =~ s|\$$sub_var|$config{$sub_var}|;
435                         $hr->{$var} = $val;
436                     } else {
437                         die "[*] sub-var \"$sub_var\" not defined in ",
438                             "config for var: $var."
439                     }
440                     $has_sub_var = 1;
441                 }
442             }
443         }
444     }
445     return;
446 }
447
448 ### check paths to commands and attempt to correct if any are wrong.
449 sub check_commands() {
450     my @path = qw(
451         /bin
452         /sbin
453         /usr/bin
454         /usr/sbin
455         /usr/local/bin
456         /usr/local/sbin
457     );
458     for my $cmd (keys %cmds) {
459
460         if ($cmd eq 'iptables') {
461             next unless $config{'FIREWALL_TYPE'} eq 'iptables';
462         } elsif ($cmd eq 'ipfw') {
463             next unless $config{'FIREWALL_TYPE'} eq 'ipfw';
464         }
465         unless (-x $cmds{$cmd}) {
466             my $found = 0;
467             PATH: for my $dir (@path) {
468                 if (-x "${dir}/${cmd}") {
469                     $cmds{$cmd} = "${dir}/${cmd}";
470                     $found = 1;
471                     last PATH;
472                 }
473             }
474             unless ($found) {
475                 die "[*] Could not find $cmd anywhere!!!  Please edit the\n",
476                     "config section in $config_file to include the path to\n",
477                     "$cmd.";
478             }
479         }
480         unless (-x $cmds{$cmd}) {
481             die "[*] Command $cmd is located at $cmds{$cmd}, but ",
482                 "is not executable by uid: $<";
483         }
484     }
485     return;
486 }
487
488 sub sendmail() {
489     my $subject = shift;
490     open MAIL, "| $cmds{'mail'} -s \"$subject\" $config{'EMAIL_ADDRESSES'} " .
491         "> /dev/null" or die "[*] Could not send mail: $cmds{'mail'} -s " .
492         "$subject\" $config{'EMAIL_ADDRESSES'}: $!";
493     close MAIL;
494     return;
495 }
496
497 sub uniquepid() {
498     if (-e $config{'KNOPTM_PID_FILE'}) {
499         my $caller = $0;
500         open PIDFILE, "< $config{'KNOPTM_PID_FILE'}";
501         my $pid = <PIDFILE>;
502         close PIDFILE;
503         chomp $pid;
504         if (kill 0, $pid) {  # knoptm is already running
505             die "[*] knoptm (pid: $pid) is already running!  Exiting.\n";
506         }
507     }
508     return;
509 }
510
511 sub writepid() {
512     open P, "> $config{'KNOPTM_PID_FILE'}" or die "[*] Could not open ",
513         "$config{'KNOPTM_PID_FILE'}: $!";
514     print P $$, "\n";
515     close P;
516     chmod 0600, $config{'KNOPTM_PID_FILE'};
517     return;
518 }
519
520 sub knoptm_init() {
521
522     ### import config
523     &import_config();
524
525     &expand_vars();
526
527     ### make sure all the vars we need are actually in the config file.
528     &required_vars();
529
530     ### validate config
531     &validate_config();
532
533     &import_ipt_modules() if $config{'FIREWALL_TYPE'} eq 'iptables';
534
535     ### make sure there is not another knoptm process already running.
536     &uniquepid();
537
538     ### make sure command paths are correct
539     &check_commands();
540
541     unless ($debug) {
542         my $pid = fork();
543         exit 0 if $pid;
544         die "[*] $0: Couldn't fork: $!" unless defined $pid;
545         POSIX::setsid() or die "[*] $0: Can't start a new session: $!";
546     }
547
548     ### write our pid out to disk
549     &writepid();
550
551     ### Install signal handlers for debugging and for reaping zombie
552     ### whois processes.
553     $SIG{'__WARN__'} = \&warn_handler;
554     $SIG{'__DIE__'}  = \&die_handler;
555     $SIG{'CHLD'}     = \&REAPER;
556
557     unlink $config{'KNOPTM_IP_TIMEOUT_SOCK'}
558         if -e $config{'KNOPTM_IP_TIMEOUT_SOCK'};
559
560     if ($config{'ENABLE_VOLUNTARY_EXITS'} eq 'Y') {
561         $voluntary_exit_timestamp = time();
562     }
563
564     return;
565 }
566
567 ### write a message to syslog (leaves off $prefix, which assigns a
568 ### "type" to the message, when writing syslog; might add it later
569 sub logr() {
570     my ($prefix, $msg, $send_email) = @_;
571
572     return if $no_logs;
573
574     if ($debug) {
575         print STDERR "$prefix $msg\n";
576         return;
577     }
578
579     ### see if we need to send an email
580     if ($send_email and $config{'ALERTING_METHODS'} !~ /noe?mail/i) {
581         &sendmail("$prefix $config{'HOSTNAME'} knoptm: $msg");
582     }
583
584     return if $config{'ALERTING_METHODS'} =~ /no.?syslog/i;
585
586     ### this is an ugly hack to avoid the 'can't use string as subroutine'
587     ### error because of 'use strict'
588     if ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL7/i) {
589         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL7());
590     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL6/i) {
591         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL6());
592     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL5/i) {
593         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL5());
594     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL4/i) {
595         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL4());
596     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL3/i) {
597         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL3());
598     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL2/i) {
599         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL2());
600     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL1/i) {
601         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL1());
602     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL0/i) {
603         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL0());
604     }
605
606     if ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_INFO/i) {
607         syslog(&LOG_INFO(), $msg);
608     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_DEBUG/i) {
609         syslog(&LOG_DEBUG(), $msg);
610     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_NOTICE/i) {
611         syslog(&LOG_NOTICE(), $msg);
612     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_WARNING/i) {
613         syslog(&LOG_WARNING(), $msg);
614     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_ERR/i) {
615         syslog(&LOG_ERR(), $msg);
616     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_CRIT/i) {
617         syslog(&LOG_CRIT(), $msg);
618     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_ALERT/i) {
619         syslog(&LOG_ALERT(), $msg);
620     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_EMERG/i) {
621         syslog(&LOG_EMERG(), $msg);
622     }
623
624     closelog();
625
626     return;
627 }
628
629 sub psyslog_errs() {
630     my $aref = shift;
631     return if $config{'ALERTING_METHODS'} =~ /no.?syslog/i;
632
633     ### this is an ugly hack to avoid the 'can't use string as subroutine'
634     ### error because of 'use strict'
635     if ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL7/i) {
636         openlog($config{'KNOPTM_SYSLOG_IDENTITY'},&LOG_DAEMON(), &LOG_LOCAL7());
637     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL6/i) {
638         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL6());
639     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL5/i) {
640         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL5());
641     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL4/i) {
642         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL4());
643     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL3/i) {
644         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL3());
645     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL2/i) {
646         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL2());
647     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL1/i) {
648         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL1());
649     } elsif ($config{'KNOPTM_SYSLOG_FACILITY'} =~ /LOG_LOCAL0/i) {
650         openlog($config{'KNOPTM_SYSLOG_IDENTITY'}, &LOG_DAEMON(), &LOG_LOCAL0());
651     }
652
653     if ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_INFO/i) {
654         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
655             syslog(&LOG_INFO(), $aref->[$i]);
656         }
657     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_DEBUG/i) {
658         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
659             syslog(&LOG_DEBUG(), $aref->[$i]);
660         }
661     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_NOTICE/i) {
662         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
663             syslog(&LOG_NOTICE(), $aref->[$i]);
664         }
665     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_WARNING/i) {
666         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
667             syslog(&LOG_WARNING(), $aref->[$i]);
668         }
669     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_ERR/i) {
670         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
671             syslog(&LOG_ERR(), $aref->[$i]);
672         }
673     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_CRIT/i) {
674         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
675             syslog(&LOG_CRIT(), $aref->[$i]);
676         }
677     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_ALERT/i) {
678         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
679             syslog(&LOG_ALERT(), $aref->[$i]);
680         }
681     } elsif ($config{'KNOPTM_SYSLOG_PRIORITY'} =~ /LOG_EMERG/i) {
682         for (my $i=0; $i<5 && $i<=$#$aref; $i++) {
683             syslog(&LOG_EMERG(), $aref->[$i]);
684         }
685     }
686
687     closelog();
688     return;
689 }
690
691 sub check_voluntary_exits() {
692
693     return unless $config{'ENABLE_VOLUNTARY_EXITS'} eq 'Y';
694     return if $no_voluntary_exits;
695
696     if ((time() - $voluntary_exit_timestamp) > $config{'EXIT_INTERVAL'}*60) {
697
698         ### EXIT_INTERVAL is in minutes
699         &logr('[+]', "voluntary exit timer expired, knopwatchd will restart",
700             $SEND_MAIL);
701         &logr('[+]', "stopping fwknopd daemon, knopwatchd will restart",
702             $SEND_MAIL);
703
704         &stop_daemon($config{'FWKNOP_PID_FILE'});
705
706         exit 0;
707     }
708
709     return;
710 }
711
712 sub stop_daemon() {
713     my $pidfile = shift;
714     return unless -e $pidfile;
715     open PID, "< $pidfile" or die "[*] Could not open $pidfile: $!";
716     my $pid = <PID>;
717     close PID;
718     chomp $pid;
719     if (kill 0, $pid) {
720         if (kill 15, $pid) {
721             unlink $pidfile;
722         } else {
723             kill 9, $pid;
724         }
725     } else {
726         unlink $pidfile;
727     }
728     return;
729 }
730
731 sub required_vars() {
732     for my $var qw(KNOPTM_PID_FILE FWKNOP_DIR FWKNOP_ERR_DIR
733             EMAIL_ADDRESSES AUTH_MODE KNOPTM_IP_TIMEOUT_SOCK
734             ALERTING_METHODS FIREWALL_TYPE KNOPTM_SYSLOG_IDENTITY
735             KNOPTM_SYSLOG_FACILITY KNOPTM_SYSLOG_PRIORITY
736             ENABLE_VOLUNTARY_EXITS EXIT_INTERVAL FWKNOP_PID_FILE) {
737
738         die "[*] Variable $var is not defined in $config_file"
739             unless defined $config{$var};
740     }
741     return;
742 }
743
744 sub validate_config() {
745
746     die qq([*] Invalid EMAIL_ADDRESSES value: "$config{'EMAIL_ADDRESSES'}")
747         unless $config{'EMAIL_ADDRESSES'} =~ /\S+\@\S+/;
748
749     ### translate commas into spaces
750     $config{'EMAIL_ADDRESSES'} =~ s/\s*\,\s/ /g;
751
752     if ($fw_type) {
753         die "[*] --fw-type must be 'iptables' or 'ipfw'"
754             unless $fw_type eq 'iptables' or $fw_type eq 'ipfw';
755         $config{'FIREWALL_TYPE'} = $fw_type if $fw_type;
756     }
757
758     unless ($config{'AUTH_MODE'} eq 'KNOCK'
759             or $config{'AUTH_MODE'} eq 'ULOG_PCAP'
760             or $config{'AUTH_MODE'} eq 'FILE_PCAP'
761             or $config{'AUTH_MODE'} eq 'PCAP') {
762         die "[*] AUTH_MODE must be either KNOCK, ULOG_PCAP, FILE_PCAP or PCAP";
763     }
764     return;
765 }
766
767 sub import_ipt_modules() {
768
769     unless ($imported_iptables_modules) {
770
771         require IPTables::Parse;
772         require IPTables::ChainMgr;
773
774         $imported_iptables_modules = 1;
775     }
776
777     return;
778 }
779
780 sub die_handler() {
781     $die_msg = shift;
782     return;
783 }
784
785 ### write all warnings to a logfile
786 sub warn_handler() {
787     $warn_msg = shift;
788     return;
789 }
790
791 sub REAPER {
792     my $pid;
793     $pid = waitpid(-1, WNOHANG);
794 #   if (WIFEXITED($?)) {
795 #          print STDERR "[+] **  Process $pid exited.\n";
796 #      }
797     $SIG{'CHLD'} = \&REAPER;
798     return;
799 }
800
801 sub is_digit() {
802     my $str = shift;
803     return 1 if $str =~ /^\d+$/;
804     return 0;
805 }
806
807 sub append_die_msg() {
808     open D, ">> $config{'FWKNOP_ERR_DIR'}/knoptm.die" or
809         die "[*] Could not open $config{'FWKNOP_DIR'}/knoptm.die: $!";
810     print D scalar localtime(), " knoptm v$version (file " .
811         "rev: $rev_num) pid: $$ $die_msg";
812     close D;
813     $die_msg = '';
814     return;
815 }
816
817 sub append_warn_msg() {
818     open D, ">> $config{'FWKNOP_ERR_DIR'}/knoptm.warn" or
819         die "[*] Could not open $config{'FWKNOP_DIR'}/knoptm.warn: $!";
820     print D scalar localtime(), " knoptm v$version (file " .
821         "rev: $rev_num) pid: $$ $warn_msg";
822     close D;
823     $warn_msg = '';
824     return;
825 }
826
827 sub usage() {
828     my $exit_status = shift;
829     print <<_HELP_;
830
831 knoptm; Access timeout daemon for fwknop
832
833 [+] Version: $version, by Michael Rash (mbr\@cipherdyne.org)
834     URL: http://www.cipherdyne.org/fwknop/
835
836 Usage: knoptm [options]
837
838 Options:
839     -c, --config <file>&n