root/fwknop/tags/fwknop-0.9.7/knoptm

Revision 373, 11.3 kB (checked in by mbr, 3 years ago)

version 0.9.5

  • Property svn:eol-style set to native
  • Property svn:executable set to *
  • Property svn:keywords set to Author Date Id Revision
Line 
1 #!/usr/bin/perl -w
2 #
3 #############################################################################
4 #
5 # File: knoptm
6 #
7 # Purpose: This daemon will remove entries from the iptables chain(s) to
8 #          which fwknop has added access for certain IP addresses.  It uses
9 #          the file /var/log/fwknop/knoptm.cache file in order to determine
10 #          when access should be removed.  The format of the entries in this
11 #          is as follows:
12 #
13 #   <rule timestamp> <timeout> <ip> <proto> <port> <table> <chain> <target>
14 #
15 # Author: Michael Rash (mbr@cipherdyne.org)
16 #
17 # Version: 0.9.5
18 #
19 # Copyright (C) 2004 Michael Rash (mbr@cipherdyne.org)
20 #
21 # License (GNU Public License):
22 #
23 #    This program is distributed in the hope that it will be useful,
24 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
25 #    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
26 #    GNU General Public License for more details.
27 #
28 #    You should have received a copy of the GNU General Public License
29 #    along with this program; if not, write to the Free Software
30 #    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
31 #    USA
32 #
33 #############################################################################
34 #
35 # $Id$
36 #
37
38 use lib '/usr/lib/fwknop';
39 use IPTables::Parse;
40 use IPTables::ChainMgr;
41 use Unix::Syslog qw(:subs :macros);
42 use Net::IPv4Addr qw(ipv4_in_network);
43 use IO::Socket;
44 use IO::Handle;
45 use File::Copy;
46 use Data::Dumper;
47 use POSIX;
48 use Getopt::Long;
49 use strict;
50
51 my $config_file = '/etc/fwknop/fwknop.conf';
52 my $alerting_config_file = '/etc/fwknop/alert.conf';
53 my $user_rc_file = '';
54
55 my $version = '0.9.5';
56 my $print_help = 0;
57 my $debug      = 0;
58 my $die_msg    = '';
59 my $warn_msg   = '';
60 my $timeout_sock = '';
61
62 my %config = ();
63 my %cmds   = ();
64 my %timeout_cache = ();
65
66 my $ip_re = '(?:\d{1,3}\.){3}\d{1,3}';
67
68 ### make Getopts case sensitive
69 Getopt::Long::Configure('no_ignore_case');
70 &usage(1) unless (GetOptions(
71     'config=s' => \$config_file,
72     'help'     => \$print_help
73 ));
74
75 &usage(0) if $print_help;
76
77 ### set things up, deal with pid's, and import config
78 &knoptm_init();
79
80 print STDERR "[+] Opening $config{'KNOPTM_IP_TIMEOUT_SOCK'} socket, ",
81     "and entering main loop.\n" if $debug;
82
83 $timeout_sock = IO::Socket::UNIX->new(
84     Type   => SOCK_STREAM,
85     Local  => $config{'KNOPTM_IP_TIMEOUT_SOCK'},
86     Listen => SOMAXCONN,
87     Timeout => .1
88 ) or die "[*] Could not acquire auto-response domain socket: $!";
89
90 for (;;) {
91     my @fw_cache_entries = ();
92
93     my $fwknop_connection = $timeout_sock->accept();
94     if ($fwknop_connection) {
95         @fw_cache_entries = <$fwknop_connection>;
96
97         ### add new entries to the cache
98         &build_timeout_cache(\@fw_cache_entries) if @fw_cache_entries;
99     }
100
101     ### always check to see if any fw rules need to be removed
102     &timeout_cache_entries();
103
104     &append_die_msg()  if $die_msg;
105     &append_warn_msg() if $warn_msg;
106
107     sleep 1;
108 }
109 close $timeout_sock;
110 exit 0;
111 #============================ end main ==============================
112
113 sub build_timeout_cache() {
114     my $cache_entries_aref = shift;
115     LINE: for my $line (@$cache_entries_aref) {
116         if ($line =~ /^\s*\d+\s+\d+\s+\S+
117                 \s+\S+\s+\d+\s+\S+\s+\S+\s+\S+/x) {
118
119             $timeout_cache{$line} = '';
120         }
121     }
122     return;
123 }
124
125 sub timeout_cache_entries() {
126
127     my @del_keys = ();
128     for my $line (keys %timeout_cache) {
129         if ($line =~ /^\s*(\d+)\s+(\d+)\s+(\S+)
130                 \s+(\S+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)/x) {
131             my $rule_timestamp = $1;
132             my $timeout        = $2;
133             my $ip             = $3;
134             my $proto          = $4;
135             my $port           = $5;
136             my $table          = $6;
137             my $chain          = $7;
138             my $target         = $8;
139
140             ### see if the rule is still active, and remove if necessary
141             if (&rm_fw_rule($rule_timestamp, $timeout, $ip, $proto,
142                     $port, $table, $chain, $target)) {
143
144                 ### delete the entry from the in-memory cache now that
145                 ### Netfilter rule has been removed
146                 push @del_keys, $line;
147             }
148         }
149     }
150     if (@del_keys) {
151         for my $key (@del_keys) {
152             delete $timeout_cache{$key};
153         }
154     }
155     return;
156 }
157
158 sub rm_fw_rule() {
159     my ($rule_timestamp, $timeout, $ip, $proto,
160         $port, $table, $chain, $target) = @_;
161
162     return 0 unless (time() - $rule_timestamp > $timeout);
163
164     my $removed_rule = 0;
165     my $ipt = new IPTables::ChainMgr(
166         'iptables' => $cmds{'iptables'}
167     ) or die '[*] Could not acquire IPTables::ChainMgr object.';
168
169     if ($ipt->find_ip_rule($ip, '0.0.0.0/0', $table,
170             $chain, $target, {'protocol' => $proto,
171             'd_port' => $port})) {
172
173         my ($rv, $status_msg) = $ipt->delete_ip_rule($ip,
174             '0.0.0.0/0', $table, $chain, $target,
175             {'protocol' => $proto, 'd_port' => $port});
176
177         if ($rv) {
178             &logr('[+]', "removed iptables $chain ACCEPT rule " .
179                 "for $ip -> $proto/$port, $timeout " .
180                 "second timeout exceeded", 1);
181             $removed_rule = 1;
182         } else {
183             &logr('[-]', $status_msg, 0);
184         }
185     }
186     return $removed_rule;
187 }
188
189 sub import_config() {
190     my $config_file = shift;
191     open C, "< $config_file" or die "[*] Could not open ",
192         "config file $config_file: $!";
193     my @lines = <C>;
194     close C;
195     for my $line (@lines) {
196         chomp $line;
197         next if ($line =~ /^\s*#/);
198         if ($line =~ /^(\S+)\s+(.*?)\;/) {
199             my $varname = $1;
200             my $val     = $2;
201             if ($val =~ m|/.+| && $varname =~ /^(\w+)Cmd$/) {
202                 ### found a command
203                 $cmds{$1} = $val;
204             } else {
205                 $config{$varname} = $val;
206             }
207         }
208     }
209     return;
210 }
211
212 ### check paths to commands and attempt to correct if any are wrong.
213 sub check_commands() {
214     my @path = qw(
215         /bin
216         /sbin
217         /usr/bin
218         /usr/sbin
219         /usr/local/bin
220         /usr/local/sbin
221     );
222     for my $cmd (keys %cmds) {
223         unless (-x $cmds{$cmd}) {
224             my $found = 0;
225             PATH: for my $dir (@path) {
226                 if (-x "${dir}/${cmd}") {
227                     $cmds{$cmd} = "${dir}/${cmd}";
228                     $found = 1;
229                     last PATH;
230                 }
231             }
232             unless ($found) {
233                 die "[*] Could not find $cmd anywhere!!!  Please edit the\n",
234                     "config section in $config_file to include the path to\n",
235                     "$cmd.";
236             }
237         }
238         unless (-x $cmds{$cmd}) {
239             die "[*] Command $cmd is located at $cmds{$cmd}, but ",
240                 "is not executable by uid: $<";
241         }
242     }
243     return;
244 }
245
246 sub sendmail() {
247     my $subject = shift;
248     open MAIL, "| $cmds{'mail'} -s \"$subject\" $config{'EMAIL_ADDRESSES'} " .
249         "> /dev/null" or die "[*] Could not send mail: $cmds{'mail'} -s " .
250         "$subject\" $config{'EMAIL_ADDRESSES'}: $!";
251     close MAIL;
252     return;
253 }
254
255 sub uniquepid() {
256     if (-e $config{'KNOPTM_PID_FILE'}) {
257         my $caller = $0;
258         open PIDFILE, "< $config{'KNOPTM_PID_FILE'}";
259         my $pid = <PIDFILE>;
260         close PIDFILE;
261         chomp $pid;
262         if (kill 0, $pid) {  # knoptm is already running
263             die "[*] knoptm (pid: $pid) is already running!  Exiting.\n";
264         }
265     }
266     return;
267 }
268
269 sub writepid() {
270     open P, "> $config{'KNOPTM_PID_FILE'}" or die "[*] Could not open ",
271         "$config{'KNOPTM_PID_FILE'}: $!";
272     print P $$, "\n";
273     close P;
274     chmod 0600, $config{'KNOPTM_PID_FILE'};
275     return;
276 }
277
278 sub knoptm_init() {
279
280     ### import config
281     &import_config($config_file);
282
283     ### import alerting config (for ALERTING_METHODS keyword)
284     &import_config($alerting_config_file);
285
286     ### make sure all the vars we need are actually in the config file.
287     &required_vars();
288
289     ### validate config
290     &validate_config();
291
292     ### make sure there is not another knoptm process already running.
293     &uniquepid();
294
295     ### make sure command paths are correct
296     &check_commands();
297
298     unless ($debug) {
299         my $pid = fork();
300         exit 0 if $pid;
301         die "[*] $0: Couldn't fork: $!" unless defined $pid;
302         POSIX::setsid() or die "[*] $0: Can't start a new session: $!";
303     }
304
305     ### write our pid out to disk
306     &writepid();
307
308     ### Install signal handlers for debugging and for reaping zombie
309     ### whois processes.
310     $SIG{'__WARN__'} = \&warn_handler;
311     $SIG{'__DIE__'}  = \&die_handler;
312     $SIG{'CHLD'}     = \&REAPER;
313
314     unlink $config{'KNOPTM_IP_TIMEOUT_SOCK'}
315         if -e $config{'KNOPTM_IP_TIMEOUT_SOCK'};
316
317     return;
318 }
319
320 ### write a message to syslog (leaves off $prefix, which assigns a
321 ### "type" to the message, when writing syslog; might add it later
322 sub logr() {
323     my ($prefix, $msg, $send_email) = @_;
324     if ($debug) {
325         print STDERR "$prefix $msg\n";
326     } else {
327         unless ($config{'ALERTING_METHODS'} =~ /no.?syslog/i) {
328             ### write a message to syslog
329             openlog 'knoptm', LOG_DAEMON, LOG_LOCAL7;
330             syslog LOG_INFO, $msg;
331             closelog();
332         }
333
334         ### see if we need to send an email
335         if ($send_email and $config{'ALERTING_METHODS'} !~ /noe?mail/i) {
336             &sendmail("$prefix knoptm: $msg");
337         }
338     }
339     return;
340 }
341
342 sub required_vars() {
343     for my $var qw(KNOPTM_PID_FILE FWKNOP_DIR EMAIL_ADDRESSES AUTH_MODE
344             KNOPTM_IP_TIMEOUT_SOCK ALERTING_METHODS) {
345         unless (defined $config{$var}) {
346             die "[*] Variable $var is not defined in $config_file";
347         }
348     }
349     return;
350 }
351
352 sub validate_config() {
353
354     die qq([*] Invalid EMAIL_ADDRESSES value: "$config{'EMAIL_ADDRESSES'}")
355         unless $config{'EMAIL_ADDRESSES'} =~ /\S+\@\S+/;
356
357     ### translate commas into spaces
358     $config{'EMAIL_ADDRESSES'} =~ s/\s*\,\s/ /g;
359
360     unless ($config{'AUTH_MODE'} eq 'KNOCK'
361             or $config{'AUTH_MODE'} eq 'ULOG_PCAP'
362             or $config{'AUTH_MODE'} eq 'PCAP') {
363         die "[*] AUTH_MODE must be either KNOCK, ULOG_PCAP, or PCAP";
364     }
365     return;
366 }
367
368 sub die_handler() {
369     $die_msg = shift;
370     return;
371 }
372
373 ### write all warnings to a logfile
374 sub warn_handler() {
375     $warn_msg = shift;
376     return;
377 }
378
379 sub REAPER {
380     my $pid;
381     $pid = waitpid(-1, WNOHANG);
382 #   if (WIFEXITED($?)) {
383 #          print STDERR "[+] **  Process $pid exited.\n";
384 #      }
385     $SIG{'CHLD'} = \&REAPER;
386     return;
387 }
388
389 sub append_die_msg() {
390     open D, ">> $config{'FWKNOP_DIR'}/knoptm.die" or
391         die "[*] Could not open $config{'FWKNOP_DIR'}/knoptm.die: $!";
392     print D scalar localtime(), " $die_msg";
393     close D;
394     $die_msg = '';
395     return;
396 }
397
398 sub append_warn_msg() {
399     open D, ">> $config{'FWKNOP_DIR'}/knoptm.warn" or
400         die "[*] Could not open $config{'FWKNOP_DIR'}/knoptm.warn: $!";
401     print D scalar localtime(), " $warn_msg";
402     close D;
403     $warn_msg = '';
404     return;
405 }
406
407 sub usage() {
408     my $exit_status = shift;
409     print <<_HELP_;
410 knoptm
411     version: $version, by Michael Rash (mbr\@cipherdyne.org)
412
413 Usage: knoptm [-c <config file>]
414
415 Options:
416     -c, --config <file>        - Specify path to config file instead of using
417                                  the default $config_file.  This
418                                  file is used only when knoptm is run as a
419                                  daemon.
420 _HELP_
421     exit $exit_status;
422 }
Note: See TracBrowser for help on using the browser.