mirror of
https://github.com/webmin/webmin.git
synced 2026-02-06 23:42:21 +00:00
497 lines
12 KiB
Perl
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;
|
|
|