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