Files
webmin/itsecur-firewall/iptables-lib.pl
2017-11-08 16:43:37 +08:00

497 lines
12 KiB
Perl

# iptables-lib.pl
# Defines firewall functions for IPtables
@actions = ( 'accept', 'drop', 'reject', 'ignore' );
$save_file = "$module_config_directory/iptables.save";
$prerules = "$module_config_directory/prerules";
$postrules = "$module_config_directory/postrules";
$prenat = "$module_config_directory/prenat";
$postnat = "$module_config_directory/postnat";
$premangle = "$module_config_directory/premangle";
$postmangle = "$module_config_directory/postmangle";
use Time::Local;
# apply_rules()
# Turns the firewall configuration into an IPtables save file, and then
# applies it.
sub apply_rules
{
&deactivate_all_interfaces(); # will add those needed later
local @dayname = ( "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" );
# Create the groups
open(SAVE, ">$save_file");
print SAVE "*filter\n";
print SAVE ":INPUT ACCEPT [0:0]\n";
print SAVE ":OUTPUT ACCEPT [0:0]\n";
print SAVE ":FORWARD ACCEPT [0:0]\n";
print SAVE ":SYN-FLOOD -\n";
# Disable bandwidth monitor
# Have a lots of issues.
# AA 2006-02-21
#if ($config{'bandwidth'}) {
# # Add rules for bandwidth logging
# print SAVE "-A INPUT -i $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_IN: --log-level debug\n";
# print SAVE "-A FORWARD -i $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_IN: --log-level debug\n";
# print SAVE "-A FORWARD -o $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_OUT: --log-level debug\n";
# print SAVE "-A OUTPUT -o $config{'bandwidth'} -j LOG --log-prefix BANDWIDTH_OUT: --log-level debug\n";
# }
# Add rules for spoofing
local ($spoofiface, @nets) = &get_spoof();
if ($spoofiface) {
local $n;
foreach $n (@nets) {
print SAVE "-A INPUT -i $spoofiface -s $n -j DROP\n";
}
}
# Always allow established connections
print SAVE "-A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT\n";
print SAVE "-A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT\n";
# Always allow localhost
print SAVE "-A INPUT -i lo -j ACCEPT\n";
print SAVE "-A OUTPUT -o lo -j ACCEPT\n";
if ($config{'frags'}) {
# Drop fragments
print SAVE "-A INPUT -p ip -f -j DROP\n";
print SAVE "-A OUTPUT -p ip -f -j DROP\n";
print SAVE "-A FORWARD -p ip -f -j DROP\n";
}
# Add syn flood and spoofing rules
local ($flood, $spoof, $fin) = &get_syn();
if ($flood) {
# Limit number of syns / second
print SAVE "-A SYN-FLOOD -m limit --limit 1/s --limit-burst 4 -j RETURN\n";
print SAVE "-A SYN-FLOOD -j DROP\n";
print SAVE "-A INPUT -p tcp -m tcp --syn -j SYN-FLOOD\n";
}
if ($spoof) {
# Drop TCP connection starts without SYN set
print SAVE "-A INPUT -p tcp -m tcp ! --syn -m state --state NEW -j DROP\n";
}
if ($fin) {
# Drop TCP packets with both SYN and FIN
print SAVE "-A INPUT -p tcp -m tcp --tcp-flags SYN,FIN SYN,FIN -j DROP\n";
}
# Load PRErules
open(STATICS, $prerules);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
# Add primary rules
local $r;
local @rules = &list_rules();
local %services = map { $_->{'name'}, $_ } &list_services();
local %times = map { $_->{'name'}, $_ } &list_times();
local @groups = &list_groups();
foreach $r (@rules) {
next if (!$r->{'enabled'});
next if ($r->{'sep'});
# Work out all source and destination hosts?
local @sources = &expand_hosts($r->{'source'}, \@groups);
local @dests = &expand_hosts($r->{'dest'}, \@groups);
# Work out time args
local $timearg;
if ($r->{'time'} ne "*") {
local $time = $times{$r->{'time'}};
$timearg .= "-m time";
if ($time->{'hours'} ne "*") {
local ($from, $to) = split(/\-/, $time->{'hours'});
$timearg .= " --timestart $from --timestop $to";
}
if ($time->{'days'} ne "*") {
$timearg .= " --days ".
join(",", map { $dayname[$_] }
split(/,/, $time->{'days'}));
}
}
# Need to output a rule for every possible combination
local ($source, $dest);
local $aarg = "-j ".uc($r->{'action'});
local $n = $r->{'num'};
local $logpfx = "--log-prefix RULE_${n}:".uc($r->{'action'}).":";
foreach $source (@sources) {
$source =~ s/^!(\S.*)$/! $1/;
local $sarg = $source eq '*' ? "" :
$source =~ /^%(.*)$/ ? "-o $1" :
"-s $source";
local $me = &my_address_in($source);
foreach $dest (@dests) {
$dest =~ s/^!(\S.*)$/! $1/;
local $darg = $dest eq '*' && !$config{'fw_any'} &&
$r->{'action'} eq 'accept' ? "! -d $me" :
$dest eq '*' ? "" :
$dest =~ /^%(.*)$/ ? "-i $1" :
"-d $dest";
if ($r->{'service'} ne '*') {
# Output one rule for each real service
local ($protos, $ports) =
&combine_services($r->{'service'},
\%services);
for($i=0; $i<@$protos; $i++) {
local $pr = lc($protos->[$i]);
local $pt = $ports->[$i];
local $marg = $pr eq 'tcp' ||
$pr eq 'udp' || $pr eq 'icmp' ? "-m $pr" : "";
local $prarg;
if ($pr eq "gre") {
# handle old GRE protocols
$pr = "ip";
$pr = "gre";
}
if ($pr eq "ip") {
$prarg = "-p $pt";
}
else {
$prarg = "-p $pr";
}
local $parg;
if ($pr eq "ip") {
# No need for port number
}
elsif ($pt =~ /^(\d+)$/ || $pt eq '*') {
if ($pr eq 'icmp') {
$parg = "--icmp-type $pt" if ($pt ne '*');
}
else {
$parg = "--destination-port $pt";
}
}
elsif ($pt =~ /^(\d+)\-(\d+)$/) {
$parg = "--dport $1:$2";
}
else {
$parg = "--dports ".
join(",", split(/\s+/, $pt));
$marg .= " -m multiport";
}
if ($r->{'log'}) {
if ($source !~ /^%(.*)$/) {
#if ($dest !~ /^%(.*)$/) {
print SAVE "-A INPUT $marg $prarg $timearg $sarg $darg $parg -j LOG $logpfx\n";
}
print SAVE "-A FORWARD $marg $prarg $timearg $sarg $darg $parg -j LOG $logpfx\n";
}
if ($source !~ /^%(.*)$/) {
#if ($dest !~ /^%(.*)$/) {
print SAVE "-A INPUT $marg $prarg $timearg $sarg $darg $parg $aarg\n";
}
print SAVE "-A FORWARD $marg $prarg $timearg $sarg $darg $parg $aarg\n";
}
}
else {
# Single service-independent rule
if ($r->{'log'}) {
if ($source !~ /^%(.*)$/) {
#if ($dest !~ /^%(.*)$/) {
print SAVE "-A INPUT $timearg $sarg $darg -j LOG $logpfx\n";
}
print SAVE "-A FORWARD $timearg $sarg $darg -j LOG $logpfx\n";
}
if ($source !~ /^%(.*)$/) {
#if ($dest !~ /^%(.*)$/) {
print SAVE "-A INPUT $timearg $sarg $darg $aarg\n";
}
print SAVE "-A FORWARD $timearg $sarg $darg $aarg\n";
}
}
}
}
# Load POSTrules
open(STATICS, $postrules);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
print SAVE "COMMIT\n";
print SAVE "*nat\n";
print SAVE ":PREROUTING ACCEPT [0:0]\n";
print SAVE ":POSTROUTING ACCEPT [0:0]\n";
print SAVE ":OUTPUT ACCEPT [0:0]\n";
local ($natiface, @nets) = &get_nat();
local @maps;
if ($natiface) {
# Add rules for NAT
@maps = grep { ref($_) } @nets;
@nets = grep { !ref($_) } @nets;
# Add rules for NAT exclusions
local ($e,$my_e);
foreach $e (grep { $_ =~ /^\!/ } @nets) {
$my_e = $e;
$my_e =~ s/^\!//;
local @dests = &expand_hosts("\@$my_e", \@groups);
local $dest;
foreach $dest (@dests) {
$dest =~ s/^!(\S.*)$/! $1/;
#print SAVE "-A POSTROUTING -o $natiface -d $dest -j RETURN\n";
#print SAVE "-A PREROUTING -i $natiface -d $dest -j RETURN\n";
print SAVE "-A POSTROUTING -d $dest -j RETURN\n";
print SAVE "-A PREROUTING -d $dest -j RETURN\n";
}
}
#Clear the nets_copy
# Load PREnat After Return
open(STATICS, $prenat);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
# Add rules for static NAT
local $m;
local ($intf_i,$intf_o,$option_i,$option_o);
# local @dests = &expand_hosts("\@$my_e", \@groups);
local (@tmp,$internal,$external);
foreach $m (@maps) {
@tmp = &expand_hosts("\@$m->[1]", \@groups);
$internal=$tmp[0];
#@tmp = &expand_hosts("\@$m->[0]", \@groups);
$external="$m->[0]";
if ($m->[2]) {
$intf_i= " -i $m->[2] ";
$intf_o= " -o $m->[2] ";
} else {
$intf_i= "";
$intf_o= "";
}
if (&check_netaddress($external)) {
$option_i=" -j NETMAP ";
$option_o=" -j NETMAP ";
} elsif (&check_netaddress($internal)) {
$option_o=" -j SNAT ";
if ($m->[2]) {
&activate_interface($m->[2], $external);
}
} else {
$option_i=" -j DNAT ";
$option_o=" -j SNAT ";
if ($m->[2]) {
&activate_interface($m->[2], $external);
}
}
(! &check_netaddress($internal) ) && print SAVE "-A PREROUTING $intf_i -d $external $option_i --to $internal\n";
print SAVE "-A POSTROUTING $intf_o -s $internal $option_o --to $external\n";
}
# Load POSTnat
open(STATICS, $postnat);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
# Add rules for dynamic NAT
local $n;
foreach $n (grep { $_ !~ /^\!/ } @nets) {
local @sources = &expand_hosts("\@$n", \@groups);
local $source;
foreach $source (@sources) {
$source =~ s/^!(\S.*)$/! $1/;
print SAVE "-A POSTROUTING -o $natiface -s $source -j MASQUERADE\n";
}
}
}
# Add rules for PAT
local @forwards = &get_pat();
local $f;
foreach $f (@forwards) {
next if (!$f->{'iface'});
local ($protos, $ports) = &combine_services($f->{'service'},
\%services);
local $i;
for($i=0; $i<@$protos; $i++) {
local $pr = lc($protos->[$i]);
local $pt = $ports->[$i];
next if ($pr ne 'tcp' && $pr ne 'udp');
print SAVE "-A PREROUTING -m $pr -p $pr --dport $pt -i $f->{'iface'} -j DNAT --to-destination $f->{'host'}:$pt\n";
}
}
print SAVE "COMMIT\n";
print SAVE "*mangle\n";
print SAVE ":PREROUTING ACCEPT [0:0]\n";
print SAVE ":OUTPUT ACCEPT [0:0]\n";
# Load PREmangle
open(STATICS, $premangle);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
# Add rules
# Load POSTmangle
open(STATICS, $postmangle);
while(<STATICS>) {
print SAVE "$_";
}
close(STATICS);
print SAVE "COMMIT\n";
close(SAVE);
# Apply the save file
local $out = `iptables-restore <$save_file 2>&1`;
if ($?) {
return "iptables-restore output : <pre>$out</pre>";
}
return undef;
}
# stop_rules()
# Cancel all firewall rules and return to the default settings (allow all)
sub stop_rules
{
&deactivate_all_interfaces();
local $table;
foreach $table ([ "filter", "INPUT", "OUTPUT", "FORWARD" ],
[ "nat", "PREROUTING", "POSTROUTING", "OUTPUT" ],
[ "mangle", "PREROUTING", "OUTPUT" ]) {
local ($name, @chains) = @$table;
local $cmd;
foreach $cmd ((map { "iptables -t $name -P $_ ACCEPT" } @chains),
"iptables -t $name -F",
"iptables -t $name -X",
"iptables -t $name -Z") {
local $out = `$cmd 2>&1`;
if ($?) {
return "$cmd output : $out";
}
}
}
return undef;
}
# enable_routing()
# Enable routing under Linux
sub enable_routing
{
system("sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1");
}
# disable_routing()
# Disable routing under Linux
sub disable_routing
{
system("sysctl -w net.ipv4.ip_forward=0 >/dev/null 2>&1");
}
sub get_log_file
{
return "/var/log/messages";
}
sub get_authlog_file
{
return -r "/var/log/secure" ? "/var/log/secure" :
-r "/var/log/security" ? "/var/log/security" :
-r "/var/log/authlog" ? "/var/log/authlog" :
"/var/log/auth";
}
sub is_log_line
{
return $_[0] =~ /IN=.*OUT=/;
}
$time_now = time();
@time_now = localtime($time_now);
%mmap = ( 'jan' => 0, 'feb' => 1, 'mar' => 2, 'apr' => 3,
'may' => 4, 'jun' => 5, 'jul' => 6, 'aug' => 7,
'sep' => 8, 'oct' => 9, 'nov' =>10, 'dec' =>11 );
# parse_log_line(line)
# Parses a line into a log info structure, or returns undef
sub parse_log_line
{
if (&is_log_line($_[0])) {
local $info = { };
if ($_[0] =~ /RULE_(\d+):([^\s:]+)/) {
$info->{'rule'} = $1;
$info->{'action'} = lc($2);
}
if ($_[0] =~ /^(\S+)\s+(\d+)\s+(\d+):(\d+):(\d+)/) {
local $tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]);
if ($tm > $time_now + 24*60*60) {
# Was really last year
$tm = timelocal($5, $4, $3, $2, $mmap{lc($1)}, $time_now[5]-1);
}
$info->{'time'} = $tm;
}
$info->{'src_iface'} = $1 if ($_[0] =~ /OUT=(\S*)/);
$info->{'dst_iface'} = $1 if ($_[0] =~ /IN=(\S*)/);
$info->{'src'} = $1 if ($_[0] =~ /SRC=(\S*)/);
$info->{'dst'} = $1 if ($_[0] =~ /DST=(\S*)/);
$info->{'size'} = $1 if ($_[0] =~ /LEN=(\S*)/);
$info->{'proto'} = $1 if ($_[0] =~ /PROTO=(\S*)/);
$info->{'src_port'} = $1 if ($_[0] =~ /SPT=(\S*)/);
$info->{'dst_port'} = $1 if ($_[0] =~ /DPT=(\S*)/);
$info->{'dst_port'} = $1 if ($_[0] =~ /TYPE=(\S*)/ &&
$info->{'proto'} eq 'ICMP');
return $info;
}
else {
return undef;
}
}
sub allow_action
{
return $_[0]->{'action'} eq 'accept';
}
sub deny_action
{
return $_[0]->{'action'} eq 'drop';
}
sub default_action
{
return "drop";
}
sub supports_time
{
return 1;
}
sub supports_bandwidth
{
return &foreign_check("bandwidth");
}
1;