Handle hostnames with upper-case letters
[webmin.git] / itsecur-firewall / iptables-lib.pl
1 # iptables-lib.pl
2 # Defines firewall functions for IPtables
3
4 @actions = ( 'accept', 'drop', 'reject', 'ignore' );
5 $save_file = "$module_config_directory/iptables.save";
6 $prerules = "$module_config_directory/prerules";
7 $postrules = "$module_config_directory/postrules";
8 $prenat = "$module_config_directory/prenat";
9 $postnat = "$module_config_directory/postnat";
10 $premangle = "$module_config_directory/premangle";
11 $postmangle = "$module_config_directory/postmangle";
12
13
14 use Time::Local;
15
16 # apply_rules()
17 # Turns the firewall configuration into an IPtables save file, and then
18 # applies it.
19 sub apply_rules
20 {
21 &deactivate_all_interfaces();   # will add those needed later
22
23 local @dayname = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" );
24
25 # Create the groups
26 open(SAVE, ">$save_file");
27 print SAVE "*filter\n";
28 print SAVE ":INPUT ACCEPT [0:0]\n";
29 print SAVE ":OUTPUT ACCEPT [0:0]\n";
30 print SAVE ":FORWARD ACCEPT [0:0]\n";
31 print SAVE ":SYN-FLOOD -\n";
32
33 # Disable bandwith monitor
34 # Have a lots of issues. 
35 # AA 2006-02-21
36
37  
38 #if ($config{'bandwidth'}) {
39 #       # Add rules for bandwidth logging
40 #       print SAVE "-A INPUT -i $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_IN: --log-level debug\n";
41 #       print SAVE "-A FORWARD -i $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_IN: --log-level debug\n";
42 #       print SAVE "-A FORWARD -o $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_OUT: --log-level debug\n";
43 #       print SAVE "-A OUTPUT -o $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_OUT: --log-level debug\n";
44 #       }
45
46 # Add rules for spoofing
47 local ($spoofiface, @nets) = &get_spoof();
48 if ($spoofiface) {
49         local $n;
50         foreach $n (@nets) {
51                 print SAVE "-A INPUT -i $spoofiface -s $n -j DROP\n";
52                 }
53         }
54
55 # Always allow established connections
56 print SAVE "-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n";
57 print SAVE "-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT\n";
58
59 # Always allow localhost
60 print SAVE "-A INPUT -i lo -j ACCEPT\n";
61 print SAVE "-A OUTPUT -o lo -j ACCEPT\n";
62
63 if ($config{'frags'}) {
64         # Drop fragments
65         print SAVE "-A INPUT -p ip -f -j DROP\n";
66         print SAVE "-A OUTPUT -p ip -f -j DROP\n";
67         print SAVE "-A FORWARD -p ip -f -j DROP\n";
68         }
69
70 # Add syn flood and spoofing rules
71 local ($flood, $spoof, $fin) = &get_syn();
72 if ($flood) {
73         # Limit number of syns / second
74         print SAVE "-A SYN-FLOOD -m limit --limit 1/s --limit-burst 4 -j RETURN\n";
75         print SAVE "-A SYN-FLOOD -j DROP\n";
76         print SAVE "-A INPUT -p tcp -m tcp --syn -j SYN-FLOOD\n";
77         }
78 if ($spoof) {
79         # Drop TCP connection starts without SYN set
80         print SAVE "-A INPUT -p tcp -m tcp ! --syn -m state --state NEW -j DROP\n";
81         }
82 if ($fin) {
83         # Drop TCP packets with both SYN and FIN
84         print SAVE "-A INPUT -p tcp -m tcp --tcp-flags SYN,FIN SYN,FIN -j DROP\n";
85         }
86
87 # Load PRErules 
88 open(STATICS, $prerules);
89 while(<STATICS>) {
90         print SAVE "$_";
91         }
92 close(STATICS);
93
94 # Add primary rules
95 local $r;
96 local @rules = &list_rules();
97 local %services = map { $_->{'name'}, $_ } &list_services();
98 local %times = map { $_->{'name'}, $_ } &list_times();
99 local @groups = &list_groups();
100 foreach $r (@rules) {
101         next if (!$r->{'enabled'});
102         next if ($r->{'sep'});
103
104         # Work out all source and destination hosts?
105         local @sources = &expand_hosts($r->{'source'}, \@groups);
106         local @dests = &expand_hosts($r->{'dest'}, \@groups);
107
108         # Work out time args
109         local $timearg;
110         if ($r->{'time'} ne "*") {
111                 local $time = $times{$r->{'time'}};
112                 $timearg .= "-m time";
113                 if ($time->{'hours'} ne "*") {
114                         local ($from, $to) = split(/\-/, $time->{'hours'});
115                         $timearg .= " --timestart $from --timestop $to";
116                         }
117                 if ($time->{'days'} ne "*") {
118                         $timearg .= " --days ".
119                                 join(",", map { $dayname[$_] }
120                                           split(/,/, $time->{'days'}));
121                         }
122                 }
123
124         # Need to output a rule for every possible combination
125         local ($source, $dest);
126         local $aarg = "-j ".uc($r->{'action'});
127         local $n = $r->{'num'};
128         local $logpfx = "--log-prefix RULE_${n}:".uc($r->{'action'}).":";
129         foreach $source (@sources) {
130                 $source =~ s/^!(\S.*)$/! $1/;
131                 local $sarg = $source eq '*' ? "" :
132                               $source =~ /^%(.*)$/ ? "-o $1" :
133                                                      "-s $source";
134                 local $me = &my_address_in($source);
135
136                 foreach $dest (@dests) {
137                         $dest =~ s/^!(\S.*)$/! $1/;
138                         local $darg = $dest eq '*' && !$config{'fw_any'} &&
139                                        $r->{'action'} eq 'accept' ? "! -d $me" :
140                                       $dest eq '*' ? "" :
141                                       $dest =~ /^%(.*)$/ ? "-i $1" :
142                                                            "-d $dest";
143
144                         if ($r->{'service'} ne '*') {
145                                 # Output one rule for each real service
146                                 local ($protos, $ports) =
147                                         &combine_services($r->{'service'},
148                                                           \%services);
149                                 for($i=0; $i<@$protos; $i++) {
150                                         local $pr = lc($protos->[$i]);
151                                         local $pt = $ports->[$i];
152                                         local $marg = $pr eq 'tcp' ||
153                                                 $pr eq 'udp' || $pr eq 'icmp' ? "-m $pr" : "";
154                                         local $prarg;
155                                         if ($pr eq "gre") {
156                                                 # handle old GRE protocols
157                                                 $pr = "ip";
158                                                 $pr = "gre";
159                                                 }
160                                         if ($pr eq "ip") {
161                                                 $prarg = "-p $pt";
162                                                 }
163                                         else {
164                                                 $prarg = "-p $pr";
165                                                 }
166                                         local $parg;
167                                         if ($pr eq "ip") {
168                                                 # No need for port number
169                                                 }
170                                         elsif ($pt =~ /^(\d+)$/ || $pt eq '*') {
171                                                 if ($pr eq 'icmp') {
172                                                         $parg = "--icmp-type $pt" if ($pt ne '*');
173                                                         }
174                                                 else {
175                                                         $parg = "--destination-port $pt";
176                                                         }
177                                                 }
178                                         elsif ($pt =~ /^(\d+)\-(\d+)$/) {
179                                                 $parg = "--dport $1:$2";
180                                                 }
181                                         else {
182                                                 $parg = "--dports ".
183                                                   join(",", split(/\s+/, $pt));
184                                                 $marg .= " -m multiport";
185                                                 }
186                                         if ($r->{'log'}) {
187                                                 if ($source !~ /^%(.*)$/) {
188                                                 #if ($dest !~ /^%(.*)$/) {
189                                                         print SAVE "-A INPUT $marg $prarg $timearg $sarg $darg $parg -j LOG $logpfx\n";
190                                                         }
191                                                 print SAVE "-A FORWARD $marg $prarg $timearg $sarg $darg $parg -j LOG $logpfx\n";
192                                                 }
193                                         if ($source !~ /^%(.*)$/) {
194                                         #if ($dest !~ /^%(.*)$/) {
195                                                 print SAVE "-A INPUT $marg $prarg $timearg $sarg $darg $parg $aarg\n";
196                                                 }
197                                                 print SAVE "-A FORWARD $marg $prarg $timearg $sarg $darg $parg $aarg\n";
198                                         }
199                                 }
200                         else {
201                                 # Single service-independent rule
202                                 if ($r->{'log'}) {
203                                         if ($source !~ /^%(.*)$/) {
204                                         #if ($dest !~ /^%(.*)$/) {
205                                                 print SAVE "-A INPUT $timearg $sarg $darg -j LOG $logpfx\n";
206                                                 }
207                                         print SAVE "-A FORWARD $timearg $sarg $darg -j LOG $logpfx\n";
208                                         }
209                                 if ($source !~ /^%(.*)$/) {
210                                 #if ($dest !~ /^%(.*)$/) {
211                                         print SAVE "-A INPUT $timearg $sarg $darg $aarg\n";
212                                         }
213                                 print SAVE "-A FORWARD $timearg $sarg $darg $aarg\n";
214                                 }
215                         }
216                 }
217         }
218 # Load POSTrules 
219 open(STATICS, $postrules);
220 while(<STATICS>) {
221         print SAVE "$_";
222         }
223 close(STATICS);
224
225
226 print SAVE "COMMIT\n";
227
228 print SAVE "*nat\n";
229 print SAVE ":PREROUTING ACCEPT [0:0]\n";
230 print SAVE ":POSTROUTING ACCEPT [0:0]\n";
231 print SAVE ":OUTPUT ACCEPT [0:0]\n";
232
233
234
235 local ($natiface, @nets) = &get_nat();
236 local @maps;
237 if ($natiface) {
238         # Add rules for NAT
239         @maps = grep { ref($_) } @nets;
240         @nets = grep { !ref($_) } @nets;
241
242         # Add rules for NAT exclusions
243         local ($e,$my_e);       
244         foreach $e (grep { $_ =~ /^\!/ } @nets) {
245                 $my_e = $e;
246                 $my_e =~ s/^\!//;
247                 local @dests = &expand_hosts("\@$my_e", \@groups);
248                 local $dest;
249
250                 foreach $dest (@dests) {
251                         $dest =~ s/^!(\S.*)$/! $1/;
252                         #print SAVE "-A POSTROUTING -o $natiface -d $dest -j RETURN\n";
253                         #print SAVE "-A PREROUTING -i $natiface -d $dest -j RETURN\n";
254                         print SAVE "-A POSTROUTING -d $dest -j RETURN\n";
255                         print SAVE "-A PREROUTING -d $dest -j RETURN\n";
256                         }
257                 }
258         #Clear the nets_copy
259         
260         # Load PREnat After Return
261         open(STATICS, $prenat);
262         while(<STATICS>) {
263                 print SAVE "$_";
264                 }
265         close(STATICS);
266
267
268         # Add rules for static NAT
269         local $m;
270         local ($intf_i,$intf_o,$option_i,$option_o);
271         
272         #               local @dests = &expand_hosts("\@$my_e", \@groups);
273         local (@tmp,$internal,$external);
274
275         
276         foreach $m (@maps) {
277                 @tmp = &expand_hosts("\@$m->[1]", \@groups);
278                 $internal=$tmp[0];
279                 #@tmp = &expand_hosts("\@$m->[0]", \@groups);           
280                 $external="$m->[0]";
281                 if ($m->[2]) {
282                         $intf_i= " -i $m->[2] ";
283                         $intf_o= " -o $m->[2] ";             
284                 } else {
285                         $intf_i= "";
286                         $intf_o= "";         
287                 }
288                 if (&check_netaddress($external)) {
289                         $option_i=" -j NETMAP ";
290                         $option_o=" -j NETMAP ";
291                 } elsif (&check_netaddress($internal)) {
292                         $option_o=" -j SNAT ";
293                         if ($m->[2]) {
294                                 &activate_interface($m->[2], $external);
295                         }                                       
296                 } else {
297                         $option_i=" -j DNAT ";
298                         $option_o=" -j SNAT ";
299                         if ($m->[2]) {
300                                 &activate_interface($m->[2], $external);
301                         }
302                 }
303                 (! &check_netaddress($internal) ) && print SAVE "-A PREROUTING $intf_i -d $external $option_i --to $internal\n";
304                 print SAVE "-A POSTROUTING $intf_o -s $internal $option_o --to $external\n";
305                 }
306
307         # Load POSTnat
308         open(STATICS, $postnat);
309         while(<STATICS>) {
310                 print SAVE "$_";
311                 }
312         close(STATICS);
313
314         # Add rules for dynamic NAT
315         
316         local $n;
317         foreach $n (grep { $_ !~ /^\!/ } @nets) {
318                 local @sources = &expand_hosts("\@$n", \@groups);
319                 local $source;
320                 foreach $source (@sources) {
321                         $source =~ s/^!(\S.*)$/! $1/;
322                         print SAVE "-A POSTROUTING -o $natiface -s $source -j MASQUERADE\n";
323                         }
324                 }
325         }
326
327 # Add rules for PAT
328 local @forwards = &get_pat();
329 local $f;
330 foreach $f (@forwards) {
331         next if (!$f->{'iface'});
332         local ($protos, $ports) = &combine_services($f->{'service'},
333                                                     \%services);
334         local $i;
335         for($i=0; $i<@$protos; $i++) {
336                 local $pr = lc($protos->[$i]);
337                 local $pt = $ports->[$i];
338                 next if ($pr ne 'tcp' && $pr ne 'udp');
339                 print SAVE "-A PREROUTING -m $pr -p $pr --dport $pt -i $f->{'iface'} -j DNAT --to-destination $f->{'host'}:$pt\n";
340                 }
341         }
342
343 print SAVE "COMMIT\n";
344
345 print SAVE "*mangle\n";
346 print SAVE ":PREROUTING ACCEPT [0:0]\n";
347 print SAVE ":OUTPUT ACCEPT [0:0]\n";
348 # Load PREmangle
349 open(STATICS, $premangle);
350 while(<STATICS>) {
351         print SAVE "$_";
352         }
353 close(STATICS);
354 # Add rules
355
356 # Load POSTmangle
357 open(STATICS, $postmangle);
358 while(<STATICS>) {
359         print SAVE "$_";
360         }
361 close(STATICS);
362 print SAVE "COMMIT\n";
363 close(SAVE);
364
365 # Apply the save file
366 local $out = `iptables-restore <$save_file 2>&1`;
367 if ($?) {
368         return "iptables-restore output : <pre>$out</pre>";
369         }
370 return undef;
371 }
372
373 # stop_rules()
374 # Cancel all firewall rules and return to the default settings (allow all)
375 sub stop_rules
376 {
377 &deactivate_all_interfaces();
378 local $table;
379 foreach $table ([ "filter", "INPUT", "OUTPUT", "FORWARD" ],
380                 [ "nat", "PREROUTING", "POSTROUTING", "OUTPUT" ],
381                 [ "mangle", "PREROUTING", "OUTPUT" ]) {
382         local ($name, @chains) = @$table;
383         local $cmd;
384         foreach $cmd ((map { "iptables -t $name -P $_ ACCEPT" } @chains),
385                       "iptables -t $name -F",
386                       "iptables -t $name -X",
387                       "iptables -t $name -Z") {
388                 local $out = `$cmd 2>&1`;
389                 if ($?) {
390                         return "$cmd output : $out";
391                         }
392                 }
393         }
394 return undef;
395 }
396
397 # enable_routing()
398 # Enable routing under Linux
399 sub enable_routing
400 {
401 system("sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1");
402 }
403
404 # disable_routing()
405 # Disable routing under Linux
406 sub disable_routing
407 {
408 system("sysctl -w net.ipv4.ip_forward=0 >/dev/null 2>&1");
409 }
410
411 sub get_log_file
412 {
413 return "/var/log/messages";
414 }
415
416 sub get_authlog_file
417 {
418 return -r "/var/log/secure" ? "/var/log/secure" :
419        -r "/var/log/security" ? "/var/log/security" :
420        -r "/var/log/authlog" ? "/var/log/authlog" :
421                                "/var/log/auth";
422 }
423
424 sub is_log_line
425 {
426 return $_[0] =~ /IN=.*OUT=/;
427 }
428
429 $time_now = time();
430 @time_now = localtime($time_now);
431 %mmap = ( 'jan' => 0, 'feb' => 1, 'mar' => 2, 'apr' => 3,
432           'may' => 4, 'jun' => 5, 'jul' => 6, 'aug' => 7,
433           'sep' => 8, 'oct' => 9, 'nov' =>10, 'dec' =>11 );
434
435 # parse_log_line(line)
436 # Parses a line into a log info structure, or returns undef
437 sub parse_log_line
438 {
439 if (&is_log_line($_[0])) {
440         local $info = { };
441         if ($_[0] =~ /RULE_(\d+):([^\s:]+)/) {
442                 $info->{'rule'} = $1;
443                 $info->{'action'} = lc($2);
444                 }
445         if ($_[0] =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)/) {
446                 local $tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]);
447                 if ($tm > $time_now + 24*60*60) {
448                         # Was really last year
449                         $tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]-1);
450                         }
451                 $info->{'time'} = $tm;
452                 }
453         $info->{'src_iface'} = $1 if ($_[0] =~ /OUT=(\S*)/);
454         $info->{'dst_iface'} = $1 if ($_[0] =~ /IN=(\S*)/);
455         $info->{'src'} = $1 if ($_[0] =~ /SRC=(\S*)/);
456         $info->{'dst'} = $1 if ($_[0] =~ /DST=(\S*)/);
457         $info->{'size'} = $1 if ($_[0] =~ /LEN=(\S*)/);
458         $info->{'proto'} = $1 if ($_[0] =~ /PROTO=(\S*)/);
459         $info->{'src_port'} = $1 if ($_[0] =~ /SPT=(\S*)/);
460         $info->{'dst_port'} = $1 if ($_[0] =~ /DPT=(\S*)/);
461         $info->{'dst_port'} = $1 if ($_[0] =~ /TYPE=(\S*)/ &&
462                                      $info->{'proto'} eq 'ICMP');
463         return $info;
464         }
465 else {
466         return undef;
467         }
468 }
469
470 sub allow_action
471 {
472 return $_[0]->{'action'} eq 'accept';
473 }
474
475 sub deny_action
476 {
477 return $_[0]->{'action'} eq 'drop';
478 }
479
480 sub default_action
481 {
482 return "drop";
483 }
484
485 sub supports_time
486 {
487 return 1;
488 }
489
490 sub supports_bandwidth
491 {
492 return &foreign_check("bandwidth");
493 }
494
495 1;
496