Handle hostnames with upper-case letters
[webmin.git] / itsecur-firewall / ipf-lib.pl
1 # ipf-lib.pl
2 # Defines firewall functions for IPF
3
4 @actions = ( "allow", "deny", "reject" );
5 $script_file = "$module_config_directory/ipf.sh";
6 $nat_conf =    "$module_config_directory/nat.conf";
7 use Time::Local;
8
9 # apply_rules(&rules, &hosts, &services)
10 # Turns the firewall configuration into an IPF script
11 sub apply_rules
12 {
13 &deactivate_all_interfaces();   # will add those needed later
14 local $ipfw = &has_command("ipfw");
15
16 # Open scripts
17 open(SCRIPT, ">$script_file");
18 print SCRIPT "#!/bin/sh\n";
19 open(NATCONF, ">$nat_conf");
20
21 # Clear existing rules
22 print SCRIPT "$ipfw -f flush\n";
23
24 # Add rules for spoofing
25 local ($spoofiface, @nets) = &get_spoof();
26 local $num = 1;
27 if ($spoofiface) {
28         local $n;
29         foreach $n (@nets) {
30                 print_ipfw("drop ip from $n to any recv $spoofiface");
31                 }
32         }
33
34 # Allow established connections
35 $num = 2;
36 print_ipfw("allow tcp from any to any established");
37
38 # Always allow localhost
39 $num = 3;
40 print_ipfw("allow ip from any to any recv lo");
41
42 if ($config{'frags'}) {
43         # Drop fragments
44         # XXX how?
45         }
46
47 # Add primary rules
48 local $r;
49 local @rules = &list_rules();
50 local %services = map { $_->{'name'}, $_ } &list_services();
51 local @groups = &list_groups();
52 foreach $r (@rules) {
53         next if (!$r->{'enabled'});
54         next if ($r->{'sep'});
55         $num = $r->{'num'}*10;
56
57         # Work out all source and destination hosts?
58         local @sources = &expand_hosts($r->{'source'}, \@groups);
59         local @dests = &expand_hosts($r->{'dest'}, \@groups);
60
61         # Need to output a rule for every possible combination
62         local ($source, $dest);
63         local $aarg = $r->{'action'};
64         local $logarg = $r->{'log'} ? "log" : "";
65         foreach $source (@sources) {
66                 $source =~ s/^!(\S.*)$/not $1/;
67                 local $sarg = $source eq '*' ? "from any" :
68                               $source =~ /^%(.*)$/ ? "from any" :
69                                                      "from $source";
70                 local $siarg = $source =~ /^%(.*)$/ ? "xmit $1" : "";
71
72                 foreach $dest (@dests) {
73                         $dest =~ s/^!(\S.*)$/! $1/;
74                         local $darg = $dest eq '*' && !$config{'fw_any'} &&
75                                        $r->{'action'} eq 'allow' ? "! -d me" :
76                                       $dest =~ /^%(.*)$/ ? "to any" :
77                                                            "to $dest";
78                         local $diarg = $dest =~ /^%(.*)$/ ? "recv $1" : "";
79
80                         if ($r->{'service'} ne '*') {
81                                 # Output one rule for each service
82                                 local ($protos, $ports) =
83                                         &combine_services($r->{'service'},
84                                                           \%services);
85                                 for($i=0; $i<@$protos; $i++) {
86                                         local $pr = lc($protos->[$i]);
87                                         local $pt = $ports->[$i];
88
89                                         local $parg;
90                                         local $opts;
91                                         local $prarg;
92                                         if ($pr eq "gre") {
93                                                 # handle old GRE protocols
94                                                 $pr = "ip";
95                                                 $pr = "gre";
96                                                 }
97                                         if ($pr eq "ip") {
98                                                 $prarg = $pt;
99                                                 }
100                                         else {
101                                                 $prarg = $pr;
102                                                 }
103                                         if ($pr eq "ip") {
104                                                 # No port for IP
105                                                 }
106                                         elsif ($pt =~ /^(\d+)$/ || $pt eq '*') {
107                                                 if ($pr eq 'icmp') {
108                                                         $opts = " icmptype $pt" if ($pt ne '*');
109                                                         }
110                                                 else {
111                                                         $parg = $pt;
112                                                         }
113                                                 }
114                                         elsif ($pt =~ /^(\d+)\-(\d+)$/) {
115                                                 $parg = "$1-$2";
116                                                 }
117                                         else {
118                                                 $parg = join(",", split(/\s+/, $pt));
119                                                 }
120                                         print_ipfw("$aarg $logarg $prarg $sarg $darg $parg $opts $siarg $diarg");
121                                         }
122                                 }
123                         else {
124                                 # Single service-independent rule
125                                 print_ipfw("$aarg $logarg ip $sarg $darg $siarg $diarg");
126                                 }
127                         }
128                 }
129         }
130
131 # Add syn flood and spoofing rules
132 local ($flood, $spoof, $fin) = &get_syn();
133 if ($flood) {
134         # Configure kernel to use syn cookies
135         print SCRIPT "sysctl net.inet.tcp.syncookies=1\n";
136         }
137 else {
138         # Configure kernel to disable syn cookies
139         print SCRIPT "sysctl net.inet.tcp.syncookies=0\n";
140         }
141 if ($spoof) {
142         # Drop TCP connection starts without SYN set
143         $num = 60000;
144         print_ipfw("allow tcp from any to any established");
145         print_ipfw("deny tcp from any to any tcpflags !syn");
146         }
147 if ($fin) {
148         # Drop TCP packets with both SYN and FIN set
149         $num = 61000;
150         print_ipfw("deny tcp from any to any tcpflags syn,fin");
151         }
152
153 local ($natiface, @nets) = &get_nat();
154 local @maps;
155 if ($natiface) {
156         # Add rules for NAT
157         @maps = grep { ref($_) } @nets;
158         @nets = grep { !ref($_) } @nets;
159         local $m;
160         foreach $m (@maps) {
161                 # Add rule for static NAT (internal to external host mapping)
162                 print NATCONF "map $natiface $m->[1]/32 -> $m->[0]/32\n";
163                 print NATCONF "map $natiface $m->[0]/32 -> $m->[1]/32\n";
164                 if ($m->[2]) {
165                         &activate_interface($m->[2], $m->[0]);
166                         }
167                 }
168         local $n;
169         foreach $n (@nets) {
170                 # Add rule for dynamic NAT
171                 local @sources = &expand_hosts("\@$n", \@groups);
172                 local $source;
173                 foreach $source (@sources) {
174                         $source =~ s/^!(\S.*)$/! $1/;
175                         print NATCONF "map $natiface $source -> 0/32\n";
176                         }
177                 }
178         }
179
180 # Add rules for PAT (external port to internal host mapping)
181 local @forwards = &get_pat();
182 local $f;
183 foreach $f (@forwards) {
184         next if (!$f->{'iface'});
185         local ($protos, $ports) = &combine_services($f->{'service'},
186                                                     \%services);
187         local $i;
188         for($i=0; $i<@$protos; $i++) {
189                 local $pr = lc($protos->[$i]);
190                 local $pt = $ports->[$i];
191                 next if ($pr ne 'tcp' && $pr ne 'udp');
192                 print NATCONF "rdr $f->{'iface'} 0/32 port $pt -> $f->{'host'} port $pt $pr\n";
193                 }
194         }
195
196 # Allow all by default
197 $num = 60001;
198 print_ipfw("allow ip from any to any");
199 close(SCRIPT);
200 chmod(0755, $script_file);
201 close(NATCONF);
202
203 # Run the script
204 #return "<pre>".`cat $script_file`."</pre>\n";
205 local $out = `cd /; $script_file 2>&1 </dev/null`;
206 if ($?) {
207         return "IPF script output : <pre>$out</pre>";
208         }
209
210 # Run the NAT config
211 $out = `cd /; ipnat -C >/dev/null ; ipnat -f $nat_conf 2>&1 </dev/null`;
212 if ($? || $out) {
213         return "ipnat command output : <pre>$out</pre>";
214         }
215
216 return undef;
217 }
218
219 sub print_ipfw
220 {
221 print SCRIPT "$ipfw add $num $_[0]\n";
222 }
223
224 # stop_rules()
225 # Allow all traffic
226 sub stop_rules
227 {
228 &deactivate_all_interfaces();
229 system("cd /; ipfw -f flush; ipfw add allow ip from any to any");
230 system("cd /; ipnat -C");
231 }
232
233 # enable_routing()
234 # Enable routing under BSD
235 sub enable_routing
236 {
237 system("sysctl net.inet.ip.forwarding=1 >/dev/null 2>&1");
238 }
239
240 # disable_routing()
241 # Disable routing under BSD
242 sub disable_routing
243 {
244 system("sysctl net.inet.ip.forwarding=0 >/dev/null 2>&1");
245 }
246
247 sub get_log_file
248 {
249 return "/var/log/security";
250 }
251
252 sub get_authlog_file
253 {
254 return "/var/log/security";
255 }
256
257 sub is_log_line
258 {
259 return $_[0] =~ /\sipfw:\s/;
260 }
261
262 $time_now = time();
263 @time_now = localtime($time_now);
264 %mmap = ( 'jan' => 0, 'feb' => 1, 'mar' => 2, 'apr' => 3,
265           'may' => 4, 'jun' => 5, 'jul' => 6, 'aug' => 7,
266           'sep' => 8, 'oct' => 9, 'nov' =>10, 'dec' =>11 );
267
268 # parse_log_line(line)
269 # Parses a line into a log info structure, or returns undef
270 sub parse_log_line
271 {
272 if (&is_log_line($_[0])) {
273         local $info = { };
274         if ($_[0] =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)/) {
275                 local $tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]);
276                 if ($tm > $time_now + 24*60*60) {
277                         # Was really last year
278                         $tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]-1);
279                         }
280                 $info->{'time'} = $tm;
281                 }
282         if ($_[0] =~ /ipfw:\s+(\d+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)\s+(in|out)\s+\S+\s+(\S+)/) {
283                 if ($1 >= 10 && $1 < 60000) {
284                         $info->{'rule'} = int($1/10);
285                         }
286                 $info->{'action'} = lc($2);
287                 $info->{'action'} = "allow" if ($info->{'action'} eq "accept");
288                 $info->{'proto'} = uc($3);
289                 if ($6 eq "in") {
290                         $info->{'dst_iface'} = $7;
291                         }
292                 else {
293                         $info->{'src_iface'} = $7;
294                         }
295                 local ($src, $dst) = ($4, $5);
296                 if ($src =~ /^(\S+):(\d+)$/) {
297                         $info->{'src'} = $1;
298                         $info->{'src_port'} = $2;
299                         }
300                 else {
301                         $info->{'src'} = $src;
302                         }
303                 if ($dst =~ /^(\S+):(\d+)$/) {
304                         $info->{'dst'} = $1;
305                         $info->{'dst_port'} = $2;
306                         }
307                 else {
308                         $info->{'dst'} = $dst;
309                         }
310                 if ($info->{'proto'} =~ /^(ICMP):(\d+)/) {
311                         $info->{'proto'} = $1;
312                         $info->{'dst_port'} = $2;
313                         }
314                 }
315         return $info;
316         }
317 else {
318         return undef;
319         }
320 }
321
322 sub allow_action
323 {
324 return $_[0]->{'action'} eq 'allow';
325 }
326
327 sub deny_action
328 {
329 return $_[0]->{'action'} eq 'deny';
330 }
331
332 sub default_action
333 {
334 return "deny";
335 }
336
337 sub supports_time
338 {
339 return 0;
340 }
341
342 sub supports_bandwidth
343 {
344 return 0;
345 }
346
347 1;
348