Add IPv6 support to IPtables module.
[people/stevee/guardian.git] / modules / IPtables.pm
1 package Guardian::IPtables;
2 use strict;
3 use warnings;
4
5 use Exporter qw(import);
6
7 our @EXPORT = qw(DoBlock DoUnblock DoFlush);
8
9 # Array of supported block actions.
10 my @supported_actions = ("DROP", "REJECT");
11
12 # Path where the IPtables related binaries are stored.
13 my $bindir = "/usr/sbin/";
14
15 # Hash which contains the path to the IPtables binaries,
16 # based on the used IP protocol version. IPtables is
17 # designed to use different binaries for IPv4 and IPv6.
18 my %binaries = (
19         "4" => "iptables",
20         "6" => "ip6tables",
21 );
22
23 # The used firewall chain.
24 my $chain = "INPUT";
25
26 #
27 ## The DoBlock subroutine.
28 #
29 ## This subroutine is called, when a given address should be locked by
30 ## using IPtables.
31 #
32 ## Guardian is using the "append" option from IPtables, which will add the new rule
33 ## to the end of the chain to prevent from possible race-conditions when adding/deleting
34 ## rules at the same time.
35 #
36 sub DoBlock (@) {
37         my ($address, $action) = @_;
38
39         # If no action has been given, default to "DROP".
40         unless($action) {
41                 $action = "DROP";
42         }
43
44         # Check if the given action is supported.
45         unless(&_check_action($action)) {
46                 # Abort and return an error message.
47                 return "Unsupported action: $action";
48         }
49
50         # Obtain which binary should be executed.
51         my $iptables = &_omit_binary($address);
52
53         # Abort if no suitable binary could be obtained.
54         unless ($iptables) {
55                 return "No suitable binary found.";
56         }
57
58         # Call IPtables binary to block the given address.
59         system("$iptables --wait -A $chain -s $address -j $action");
60 }
61
62 #
63 ## The DoUnblock subroutine.
64 #
65 ## This subroutine can be used to delete all firewall rules (unblock)
66 ## of a previously blocked address.
67 #
68 ## To do this a subroutine will be called, which is collecting all rule
69 ## positions in the firewall chain for the given address, which returns
70 ## them in reversed order. This list of rules will be deleted one-by-one
71 ## so multiple entries (if present) for the address will be deleted.
72 #
73 ## This approach also eliminates the exact rule argument processing again
74 ## for dropping it. So it is not neccessary to know the additional arguments
75 ## like firewall action (DROP, REJECT) etc. 
76 #
77 sub DoUnblock ($) {
78         my ($address) = @_;
79
80         # Obtain which binary should be executed.
81         my $iptables = &_omit_binary($address);
82
83         # Abort if no suitable binary could be obtained.
84         unless($iptables) {
85                 return "No suitable binary found.";
86         }
87
88         # Get rulepositions for the specified address.
89         my @rules = &_get_rules_positions_by_address($address, $iptables);
90
91         # Loop through the rules array and drop the firewall rules.
92         foreach my $rule (@rules) {
93                 # Call iptables to delete the rule.
94                 system("$iptables --wait -D $chain $rule");
95         }
96 }
97
98 #
99 ## The DoFlush subroutine.
100 #
101 ## Call this subroutine to entirely flush the IPtables chains for each
102 ## supported protocol version.
103 #
104 sub DoFlush () {
105         # Loop through the binaries hash to flush 
106         foreach my $key (keys %binaries) {
107                 # Grab binary from hash and generate the absolute path
108                 # to the binary.
109                 my $iptables = join ("/", $bindir,$binaries{$key});
110
111                 # Execute the binary if avail- and executeable.
112                 if (-x $iptables) {
113                         system("$iptables --wait -F $chain");
114                 }
115         }
116 }
117
118 #
119 ## Get rules subroutine.
120 #
121 ## This subroutine is used to get the rule position of the active
122 ## firewall rules for a given address. Those position will be collected
123 ## and returned in reversed order. 
124 #
125 sub _get_rules_positions_by_address ($$) {
126         my ($address, $iptables) = @_;
127
128         # Array to store the rule positions.
129         my @rules;
130
131         # Call iptables and list all firewall rules.
132         open(RULES, "$iptables --wait -L $chain -n -v --line-numbers |");
133
134         # Read input line by line.
135         foreach my $line (<RULES>) {
136                 # Skip descriptive line.
137                 next if ($line =~ /^Chain/);
138                 next if ($line =~ /^ pkts/);
139
140                 # Generate array, based on the line content
141                 # (seperator is a single or multiple space's)
142                 my @comps = split(/\s{1,}/, $line);
143                 my ($pos, $pkts, $bytes, $target, $prot, $opt, $in, $out, $source, $destination) = @comps;
144
145                 # Compare the current source address with the given one.
146                 # If they are equal, the rule position will be added to the
147                 # rules array.
148                 if ($address eq $source) {
149                         push(@rules, $pos);
150                 }
151         }
152
153         # Reverse the array.
154         my @reversed_rules = reverse @rules;
155
156         # Return the reversed array.
157         return @reversed_rules;
158 }
159
160 #
161 ## The _check_action function.
162 #
163 ## This private function is used to check if the given action is supported by
164 ## the firewall engine.
165 #
166 sub _check_action ($) {
167         my $action = shift;
168
169         # Check if the recieved action is part of the supported_actions array.
170         foreach my $item (@supported_actions) {
171                 # Exit the loop and return "nothing" if we found a match.
172                 if($item eq $action) {
173                         # Return "1" (True).
174                         return 1;
175                 }
176         }
177
178         # If we got here, the given action is not part of the array of supported
179         # actions. Return nothing (False).
180         return;
181 }
182
183 #
184 ## The _omit_binary function.
185 #
186 ## This private function is responsible for selecting and returning the correct
187 ## IPtables binary, based on which kind of IP address has been given.
188 #
189 sub _omit_binary ($) {
190         my $address = shift;
191
192         # Obtain used protocol version, based on the
193         # given IP address.
194         my $proto_version = &Guardian::Base::DetectIPProtocolVersion($address);
195
196         # Abort if the protocol version could not proper be detected.
197         unless ($proto_version) {
198                 # Return nothing (False).
199                 return;
200         }
201
202         # Obtain which binary is responsible, based on the detected protocol version and
203         # generate the full path to it.
204         my $binary = join("/",$bindir,$binaries{$proto_version});
205
206         # Abort if the obtained binary is not avail- or executeable.
207         unless (-x $binary) {
208                 # Return nothing (False).
209                 return;
210         }
211
212         # Return the detected and validated binary.
213         return $binary;
214 }
215
216 1;