1# Functions for managing firewalld 2 3BEGIN { push(@INC, ".."); }; 4use strict; 5use warnings; 6use WebminCore; 7&init_config(); 8do 'md5-lib.pl'; 9our ($module_root_directory, %text, %config, %gconfig); 10our %access = &get_module_acl(); 11 12# check_firewalld() 13# Returns an error message if firewalld is not installed, undef if all is OK 14sub check_firewalld 15{ 16&has_command($config{'firewall_cmd'}) || 17 return &text('check_ecmd', "<tt>".$config{'firewall_cmd'}."</tt>"); 18return undef; 19} 20 21# is_firewalld_running() 22# Returns 1 if the server is running, 0 if not 23sub is_firewalld_running 24{ 25my $ex = system("$config{'firewall_cmd'} --state >/dev/null 2>&1 </dev/null"); 26return $ex ? 0 : 1; 27} 28 29# list_firewalld_zones([active-only]) 30# Returns an array of firewalld zones, each of which is a hash ref with fields 31# like services and ports 32sub list_firewalld_zones 33{ 34my ($active) = @_; 35my @rv; 36my $out = &backquote_command("$config{'firewall_cmd'} --list-all-zones ". 37 ($active ? "" : "--permanent ")."</dev/null 2>&1"); 38if ($?) { 39 &error("Failed to list zones : $out"); 40 } 41my $default_zone = backquote_command( 42 "$config{'firewall_cmd'} --get-default-zone </dev/null 2>&1"); 43chomp($default_zone); 44my $zone; 45my $lo; 46foreach my $l (split(/\r?\n/, $out)) { 47 if ($l =~ /^(\S+)(\s+\(.*\))?/) { 48 # New zone 49 $zone = { 'name' => $1, 50 'default' => $default_zone eq $1 ? 1 : 0 }; 51 $lo = undef; 52 push(@rv, $zone); 53 } 54 elsif ($l =~ /^ (\S+):\s*(.*)/ && $zone) { 55 # Option in some zone 56 $lo = $1; 57 $zone->{$1} = [ split(/\s+/, $2) ]; 58 } 59 elsif ($l =~ /^\t(\S.*)/ && $zone && $lo) { 60 # Continued option 61 push(@{$zone->{$lo}}, split(/\s+/, $1)); 62 } 63 } 64return @rv; 65} 66 67# list_firewalld_services() 68# Returns an array of known service names 69sub list_firewalld_services 70{ 71my $out = &backquote_command("$config{'firewall_cmd'} --get-services </dev/null 2>&1"); 72if ($?) { 73 &error("Failed to list services : $out"); 74 } 75$out =~ s/\r|\n//g; 76return split(/\s+/, $out); 77} 78 79# list_firewalld_services_with_ports() 80# Returns an array of service names and descriptions 81sub list_firewalld_services_with_ports 82{ 83my @rv; 84foreach my $s (&list_firewalld_services()) { 85 my @n = getservbyname($s, "tcp"); 86 if (!@n) { 87 @n = getservbyname($s, "udp"); 88 } 89 if (@n) { 90 push(@rv, [ $s, $s." (".$n[2]." ".uc($n[3]).")" ]); 91 } 92 else { 93 push(@rv, [ $s, $s ]); 94 } 95 } 96return @rv; 97} 98 99# create_firewalld_port(&zone, port|range, proto) 100# Adds a new allowed port to a zone. Returns undef on success or an error 101# message on failure 102sub create_firewalld_port 103{ 104my ($zone, $port, $proto) = @_; 105my $out = &backquote_logged("$config{'firewall_cmd'} ". 106 "--zone ".quotemeta($zone->{'name'})." ". 107 "--permanent --add-port ". 108 quotemeta($port)."/".quotemeta($proto)." 2>&1"); 109return $? ? $out : undef; 110} 111 112# delete_firewalld_port(&zone, port|range, proto) 113# Delete one existing port from a zone. Returns undef on success or an error 114# message on failure 115sub delete_firewalld_port 116{ 117my ($zone, $port, $proto) = @_; 118my $out = &backquote_logged("$config{'firewall_cmd'} ". 119 "--zone ".quotemeta($zone->{'name'})." ". 120 "--permanent --remove-port ". 121 quotemeta($port)."/".quotemeta($proto)." 2>&1"); 122return $? ? $out : undef; 123} 124 125# create_firewalld_service(&zone, service) 126# Adds a new allowed service to a zone. Returns undef on success or an error 127# message on failure 128sub create_firewalld_service 129{ 130my ($zone, $service) = @_; 131my $out = &backquote_logged("$config{'firewall_cmd'} ". 132 "--zone ".quotemeta($zone->{'name'})." ". 133 "--permanent --add-service ". 134 quotemeta($service)." 2>&1"); 135return $? ? $out : undef; 136} 137 138# delete_firewalld_service(&zone, service) 139# Delete one existing service from a zone. Returns undef on success or an error 140# message on failure 141sub delete_firewalld_service 142{ 143my ($zone, $service) = @_; 144my $out = &backquote_logged("$config{'firewall_cmd'} ". 145 "--zone ".quotemeta($zone->{'name'})." ". 146 "--permanent --remove-service ". 147 quotemeta($service)." 2>&1"); 148return $? ? $out : undef; 149} 150 151# create_firewalld_forward(&zone, src-port, src-proto, dst-port, dst-addr) 152# Create a new forwarding rule in some zone. Returns undef on success or an 153# error message on failure 154sub create_firewalld_forward 155{ 156my ($zone, $srcport, $srcproto, $dstport, $dstaddr) = @_; 157my $out = &backquote_logged( 158 $config{'firewall_cmd'}." ". 159 "--zone ".quotemeta($zone->{'name'})." ". 160 "--permanent ". 161 "--add-forward-port=port=$srcport:proto=$srcproto". 162 ($dstport ? ":toport=$dstport" : ""). 163 ($dstaddr ? ":toaddr=$dstaddr" : ""). 164 " 2>&1"); 165return $? ? $out : undef; 166} 167 168# delete_firewalld_forward(&zone, src-port, src-proto, dst-port, dst-addr) 169# Deletes a forwarding rule in some zone. Returns undef on success or an 170# error message on failure 171sub delete_firewalld_forward 172{ 173my ($zone, $srcport, $srcproto, $dstport, $dstaddr) = @_; 174my $out = &backquote_logged( 175 $config{'firewall_cmd'}." ". 176 "--zone ".quotemeta($zone->{'name'})." ". 177 "--permanent ". 178 "--remove-forward-port=port=$srcport:proto=$srcproto". 179 ($dstport ? ":toport=$dstport" : ""). 180 ($dstaddr ? ":toaddr=$dstaddr" : ""). 181 " 2>&1"); 182return $? ? $out : undef; 183} 184 185# parse_firewalld_forward(str) 186# Parses a forward string into port, proto, dstport and dstaddr 187sub parse_firewalld_forward 188{ 189my ($str) = @_; 190my %w = map { split(/=/, $_) } split(/:/, $str); 191return ($w{'port'}, $w{'proto'}, $w{'toport'}, $w{'toaddr'}); 192} 193 194# apply_firewalld() 195# Make the current saved config active 196sub apply_firewalld 197{ 198my $out = &backquote_logged("$config{'firewall_cmd'} --reload 2>&1"); 199return $? ? $out : undef; 200} 201 202# stop_firewalld() 203# Shut down the firewalld service 204sub stop_firewalld 205{ 206&foreign_require("init"); 207my ($ok, $err) = &init::stop_action($config{'init_name'}); 208return $ok ? undef : $err; 209} 210 211# start_firewalld() 212# Shut down the firewalld service 213sub start_firewalld 214{ 215&foreign_require("init"); 216my ($ok, $err) = &init::start_action($config{'init_name'}); 217return $ok ? undef : $err; 218} 219 220# list_system_interfaces() 221# Returns the list of all interfaces on the system 222sub list_system_interfaces 223{ 224&foreign_require("net"); 225my @rv = map { $_->{'name'} } &net::active_interfaces(); 226push(@rv, map { $_->{'name'} } &net::boot_interfaces()); 227return &unique(@rv); 228} 229 230# update_zone_interfaces(&zone, &interface-list) 231# Update the interfaces a zone applies to 232sub update_zone_interfaces 233{ 234my ($zone, $newifaces) = @_; 235my $oldifaces = $zone->{'interfaces'}; 236foreach my $i (&list_system_interfaces()) { 237 my $inold = &indexof($i, @$oldifaces) >= 0; 238 my $innew = &indexof($i, @$newifaces) >= 0; 239 my $args; 240 if ($inold && !$innew) { 241 # Remove from this zone 242 $args = "--remove-interface ".quotemeta($i); 243 } 244 elsif (!$inold && $innew) { 245 # Add to from this zone 246 $args = "--add-interface ".quotemeta($i); 247 } 248 else { 249 next; 250 } 251 my $cmd = "$config{'firewall_cmd'} ". 252 "--zone ".quotemeta($zone->{'name'})." ". 253 "--permanent ".$args; 254 my $out = &backquote_logged($cmd." 2>&1 </dev/null"); 255 return $out if ($?); 256 } 257return undef; 258} 259 260# create_firewalld_zone(name) 261# Add a new zone with the given name 262sub create_firewalld_zone 263{ 264my ($name) = @_; 265my $cmd = "$config{'firewall_cmd'} --permanent --new-zone ".quotemeta($name); 266my $out = &backquote_logged($cmd." 2>&1 </dev/null"); 267return $? ? $out : undef; 268} 269 270# delete_firewalld_zone(&zone) 271# Removes the specified zone 272sub delete_firewalld_zone 273{ 274my ($zone) = @_; 275my $cmd = "$config{'firewall_cmd'} --permanent --delete-zone ". 276 quotemeta($zone->{'name'}); 277my $out = &backquote_logged($cmd." 2>&1 </dev/null"); 278return $? ? $out : undef; 279} 280 281# default_firewalld_zone(&zone) 282# Makes the specified zone the default 283sub default_firewalld_zone 284{ 285my ($zone) = @_; 286my $cmd = "$config{'firewall_cmd'} --set-default-zone ". 287 quotemeta($zone->{'name'}); 288my $out = &backquote_logged($cmd." 2>&1 </dev/null"); 289return $? ? $out : undef; 290} 291 292# parse_port_field(&in, name) 293# Either returns a port expression, or calls error 294sub parse_port_field 295{ 296my ($in, $pfx) = @_; 297if ($in->{$pfx.'mode'} == 0) { 298 $in->{$pfx.'port'} =~ /^\d+$/ && 299 $in->{$pfx.'port'} > 0 && $in->{$pfx.'port'} < 65536 || 300 getservbyname($in->{$pfx.'port'}, $in->{'proto'}) || 301 &error($text{'port_eport'}); 302 return $in->{$pfx.'port'}; 303 } 304elsif ($in->{$pfx.'mode'} == 1) { 305 $in->{$pfx.'portlow'} =~ /^\d+$/ && 306 $in->{$pfx.'portlow'} > 0 && $in->{$pfx.'portlow'} < 65536 || 307 &error($text{'port_eportlow'}); 308 $in->{$pfx.'porthigh'} =~ /^\d+$/ && 309 $in->{$pfx.'porthigh'} > 0 && $in->{$pfx.'porthigh'} < 65536 || 310 &error($text{'port_eporthigh'}); 311 $in->{$pfx.'portlow'} < $in->{$pfx.'porthigh'} || 312 &error($text{'port_eportrange'}); 313 return $in->{$pfx.'portlow'}."-".$in->{$pfx.'porthigh'}; 314 } 315else { 316 # No port chosen 317 return undef; 318 } 319} 320 3211; 322