1#!/usr/local/bin/perl
2# index.cgi
3# Display current iptables firewall configuration from save file
4# unified for IPV4 and IPV6
5
6require './firewall-lib.pl';
7&ReadParse();
8
9# Load the correct library
10$ipvx_version = &get_ipvx_version();
11if ($ipvx_version == 6) {
12	require './firewall6-lib.pl';
13	}
14else {
15	require './firewall4-lib.pl';
16	}
17
18$subhead = $text{"index_title_v${ipvx}"};
19if ($ipvx_save) {
20	$subhead .= ", ".&text('index_editing', "<tt>$ipvx_save</tt>");
21	}
22&ui_print_header($subhead, $text{'index_title'}, undef,
23		 "intro", 1, 1, 0,
24		 &help_search_link("ip${ipvx}tables", "man", "doc"));
25
26# Check for iptables and iptables-restore commands
27if ($c = &missing_firewall_commands()) {
28	print "<p>",&text('index_ecommand', "<tt>$c</tt>"),"<p>\n";
29	&ui_print_footer("/", $text{'index'});
30	exit;
31	}
32
33# Check if the kernel supports iptables
34$out = &backquote_command("ip${ipvx}tables -n -t filter -L OUTPUT 2>&1");
35if ($?) {
36	print "<p>",&text('index_ekernel', "<pre>$out</pre>"),"<p>\n";
37	&ui_print_footer("/", $text{'index'});
38	exit;
39	}
40
41# Check if the distro supports iptables
42if (!$config{"direct${ipvx}"} && defined(&check_iptables) &&
43    ($err = &check_iptables())) {
44	print "<p>$err</p>\n";
45	&ui_print_footer("/", $text{'index'});
46	exit;
47	}
48
49# Check if firewall is being started at boot
50if (!$config{"direct${ipvx}"} && &foreign_check("init")) {
51	$init_support++;
52	if (defined(&started_at_boot)) {
53		$atboot = &started_at_boot();
54		}
55	else {
56		&foreign_require("init", "init-lib.pl");
57		$atboot = &init::action_status("webmin-ip${ipvx}tables") == 2;
58		}
59	}
60
61# Check if the save file exists. If not, check for any existing firewall
62# rules, and offer to create a save file from them
63@livetables = &get_iptables_save("ip${ipvx}tables-save 2>/dev/null |");
64
65# Display warnings about active external firewalls!
66&external_firewall_message(\@livetables);
67if (!$config{"direct${ipvx}"} && $in{'reset'} && $access{'setup'}) {
68	@tables = @livetables;
69	foreach $t (@tables) {
70		$rules++ if (@{$t->{'rules'}});
71		foreach $c (keys %{$t->{'defaults'}}) {
72			$chains++ if ($t->{'defaults'}->{$c} ne 'ACCEPT');
73			}
74		$hastable{$t->{'name'}}++;
75		}
76	foreach $t (@known_tables) {
77		system("ip${ipvx}tables -t $t -n -L >/dev/null") if (!$hastable{$t});
78		}
79	if (!$in{'reset'} && ($rules || $chains)) {
80		# Offer to save the current rules
81		print &ui_confirmation_form("convert.cgi",
82			&text('index_existing', $rules,
83			      "<tt>$ipvx_save</tt>"),
84			( ['version'], [${ipvx_arg}] ),
85			[ [ undef, $text{'index_saveex'} ] ],
86			$init_support && !$atboot ?
87			  &ui_checkbox("atboot", 1, $text{'index_atboot'}, 0) :
88			  "",
89			);
90
91		print &ui_table_start($text{'index_headerex'}, "width=100%", 2);
92		$out = &backquote_command("ip${ipvx}tables-save 2>/dev/null");
93		print &ui_table_row(undef,
94			"<pre>".&html_escape($out)."</pre>", 2);
95		print &ui_table_end();
96		}
97	else {
98		# Offer to set up a firewall
99		print &text($in{'reset'} ? 'index_rsetup' : 'index_setup',
100			    "<tt>$ipvx_save</tt>"),"<p>\n";
101		print &ui_form_start("setup${ipvx}.cgi");
102                print &ui_hidden("version", ${ipvx_arg});
103		print &ui_hidden("reset", $in{'reset'});
104		print "<center><table><tr><td>\n";
105		print &ui_oneradio("auto", 0, $text{'index_auto0'}, 1),"<p>\n";
106		foreach $a (1 .. 5) {
107			print &ui_oneradio("auto", $a,
108					   $text{'index_auto'.$a}, 0)." ";
109			print &interface_choice("iface".$a),"<p>\n";
110			}
111		print "</td></tr></table>\n";
112		print &ui_submit($text{'index_auto'}),"<p>\n";
113		if ($init_support && !$atboot) {
114			print &ui_checkbox("atboot", 1,
115					   $text{'index_atboot'}, 0);
116			}
117		print "</center>\n";
118		print &ui_form_end();
119		}
120	}
121else {
122	$form = 0;
123	@tables = &get_iptables_save();
124	if (!$config{"direct${ipvx}"}) {
125		# Verify that all known tables exist, and if not add them to the
126		# save file
127		foreach $t (@tables) {
128			$hastable{$t->{'name'}}++;
129			}
130		foreach $t (@known_tables) {
131			if (!$hastable{$t}) {
132				local ($missing) = &get_iptables_save(
133				    "ip${ipvx}tables-save --table $t 2>/dev/null |");
134				if ($missing) {
135					delete($missing->{'line'});
136					&save_table($missing);
137					}
138				$need_reload++;
139				}
140			}
141		@tables = &get_iptables_save() if ($need_reload);
142		}
143
144	# Check if the current config is valid
145	if (!$config{"direct${ipvx}"}) {
146		my $err = &validate_iptables_config();
147		if ($err) {
148			print "<b>",&text('index_evalid',
149					  &html_escape($err)),"</b><p>\n";
150			}
151		}
152
153	# Work out the default table
154	if (!defined($in{'table'})) {
155		foreach $t (@tables) {
156			if (@{$t->{'rules'}} && &can_edit_table($t->{'name'})) {
157				$in{'table'} = $t->{'index'};
158				last;
159				}
160			}
161		}
162	if (!defined($in{'table'})) {
163		foreach $t (@tables) {
164			if (&can_edit_table($t->{'name'})) {
165				$in{'table'} = $t->{'index'};
166				last;
167				}
168			}
169		}
170	$table = $tables[$in{'table'}];
171
172	# Allow selection of a table
173	print "<table width=100%><tr>\n";
174	print "<td>\n";
175	print "<form action=index.cgi>\n";
176	print "<input type=submit value='$text{'index_change'}'>\n";
177        print &ui_hidden("version", ${ipvx_arg});
178	print "<select name=table onChange='form.submit()'>\n";
179	foreach $t (@tables) {
180		if (&can_edit_table($t->{'name'})) {
181			printf "<option value=%s %s>%s</option>\n",
182			    $t->{'index'}, $t eq $table ? "selected" : "",
183			    &text('index_table_'.$t->{'name'}) || $t->{'name'};
184			}
185		}
186	print "</select></form>\n";
187	print "</td>\n";
188	$form++;
189
190	if ($access{'newchain'}) {
191		# Show form to create a chain
192		print "<td align=right>\n";
193		print "<form action=newchain.cgi>\n";
194		print &ui_hidden("table", $in{'table'});
195                print &ui_hidden("version", ${ipvx_arg});
196		print "<input type=submit value='$text{'index_cadd'}'>\n";
197		print "<input name=chain size=20></form>\n";
198		print "</td>\n";
199		$form++;
200		}
201	print "</tr></table>\n";
202
203        # Display a table of rules for each chain
204        CHAIN:
205        foreach $c (sort by_string_for_iptables keys %{$table->{'defaults'}}) {
206                print &ui_hr();
207                @rules = grep { lc($_->{'chain'}) eq lc($c) }
208                              @{$table->{'rules'}};
209                print "<b>",$text{"index_chain_".lc($c)} ||
210                            &text('index_chain', "<tt>$c</tt>"),"</b><br>\n";
211
212                # check if chain is filtered out
213                if ($config{'filter_chain'}) {
214                    foreach $filter (split(',', $config{'filter_chain'})) {
215                        if($c =~ /^$filter$/) {
216				# not managed by firewall, do not dispaly or modify
217                                print "<em>".$text{'index_filter_chain'}."</em><br>\n";
218                                next CHAIN;
219                            }
220                        }
221                    }
222
223                print "<form action=save_policy.cgi>\n";
224                print &ui_hidden("version", ${ipvx_arg});
225                print &ui_hidden("table", $in{'table'});
226                print &ui_hidden("chain", $c);
227
228		if (@rules > $config{'perpage'}) {
229		        # Need to show arrows
230		        print "<center>\n";
231		        $s = int($in{'start'});
232		        $e = $in{'start'} + $config{'perpage'} - 1;
233		        $e = @rules-1 if ($e >= @rules);
234		        if ($s) {
235		                print &ui_link("?start=".
236		                                ($s - $config{'perpage'}),
237		                    "<img src=/images/left.gif border=0 align=middle>");
238		                }
239		        print "<font size=+1>",&text('index_position', $s+1, $e+1,
240		                                     scalar(@rules)),"</font>\n";
241		        if ($e < @rules-1) {
242		                print &ui_link("?start=".
243		                               ($s + $config{'perpage'}),
244		                   "<img src=/images/right.gif border=0 align=middle>");
245		                }
246		        print "</center>\n";
247		        }
248		else {
249		        # Can show them all
250		        $s = 0;
251		        $e = @rules - 1;
252			}
253
254		@rules = @rules[$s..$e];
255
256		if (@rules) {
257			@links = ( &select_all_link("d", $form),
258				   &select_invert_link("d", $form) );
259			print &ui_links_row(\@links);
260
261			# Generate the header
262			local (@hcols, @tds);
263			push(@hcols, "", $text{'index_action'});
264			push(@tds, "width=5", "width=30% nowrap");
265			if ($config{'view_condition'}) {
266				push(@hcols, $text{'index_desc'});
267				push(@tds, "nowrap");
268				}
269			if ($config{'view_comment'}) {
270				push(@hcols, $text{'index_comm'});
271				push(@tds, "");
272				}
273			push(@hcols, $text{'index_move'}, $text{'index_add'});
274			push(@tds, "width=32", "width=32");
275			print &ui_columns_start(\@hcols, 100, 0, \@tds);
276
277			# Generate a row for each rule
278			foreach $r (@rules) {
279				$edit = &can_jump($r);
280				local @cols;
281				local $act =
282				  $text{"index_jump_".lc($r->{'j'}->[1])} ||
283				  &text('index_jump', $r->{'j'}->[1]);
284
285                                # check if chain jump TO is filtered out
286                                local $chain_filtered;
287                                if ($config{'filter_chain'}) {
288                                        foreach $filter (split(',', $config{'filter_chain'})) {
289                                                if($r->{'j'}->[1] =~ /^$filter$/) {
290                                                     $chain_filtered=&text('index_filter_chain');
291                                                     $act=$act."<br><em>$chain_filtered</em>";
292                                           }
293                                        }
294                                    }
295				# chain to jump to is filtered, switch of edit
296                                if ($edit && !$chain_filtered) {
297					push(@cols, &ui_link("edit_rule.cgi?version=${ipvx_arg}&table=".&urlize($in{'table'})."&idx=$r->{'index'}",$act));
298					}
299				else {
300                                        # add col for not visible checkmark
301					push(@cols, "", $act);
302					}
303				if ($config{'view_condition'}) {
304					push(@cols, &describe_rule($r));
305					}
306				if ($config{'view_comment'}) {
307					$cmt = $config{'comment_mod'} ||
308					       $r->{'comment'} ?
309					    $r->{'comment'}->[1] : $r->{'cmt'};
310					push(@cols, $cmt);
311					}
312
313				# Up/down mover
314				local $mover;
315				if ($r eq $rules[@rules-1]) {
316					$mover .= "<img src=images/gap.gif>";
317					}
318				else {
319					$mover .= "<a href='move.cgi?version=${ipvx_arg}&table=".
320					      &urlize($in{'table'}).
321					      "&idx=$r->{'index'}&".
322					      "down=1'><img src=".
323					      "images/down.gif border=0></a>";
324					}
325				if ($r eq $rules[0]) {
326					$mover .= "<img src=images/gap.gif>";
327					}
328				else {
329					$mover .= "<a href='move.cgi?version=${ipvx_arg}&table=".
330					      &urlize($in{'table'}).
331					      "&idx=$r->{'index'}&".
332					      "up=1'><img src=images/up.gif ".
333					      "border=0></a>";
334					}
335				push(@cols, $mover);
336
337				# Before / after adder
338				local $adder;
339				$adder .= "<a href='edit_rule.cgi?version=${ipvx_arg}&table=".
340				      &urlize($in{'table'}).
341				      "&chain=".&urlize($c)."&new=1&".
342				      "after=$r->{'index'}'><img src=".
343				      "images/after.gif border=0></a>";
344				$adder .= "<a href='edit_rule.cgi?version=${ipvx_arg}&table=".
345				      &urlize($in{'table'}).
346				      "&chain=".&urlize($c)."&new=1&".
347				      "before=$r->{'index'}'><img src=".
348				      "images/before.gif border=0></a>";
349                                push(@cols, $adder);
350				# chain to jump to is filtered, switch of edit
351				if ($edit && !$chain_filtered) {
352                                        print &ui_checked_columns_row(
353                                            \@cols, \@tds, "d", $r->{'index'});
354                                        }
355                                else {
356                                        print &ui_columns_row(\@cols, \@tds);
357                                        }
358                                }
359			print &ui_columns_end();
360			print &ui_links_row(\@links);
361			}
362		else {
363			print "<b>$text{'index_none'}</b><br>\n";
364			}
365
366		# Show policy changing button for chains that support it,
367		# and rule-adding button
368		print "<table width=100%><tr>\n";
369		local $d = $table->{'defaults'}->{$c};
370		if ($d ne '-') {
371			# Built-in chain
372			if ($access{'policy'}) {
373				# Change default button
374				print "<td width=33% nowrap>",
375				      &ui_submit($text{'index_policy'}),"\n";
376				print "<select name=policy>\n";
377				foreach $t ('ACCEPT','DROP','QUEUE','RETURN') {
378					printf "<option value=%s %s>%s</option>\n",
379						$t, $d eq $t ? "selected" : "",
380						$text{"index_policy_".lc($t)};
381					}
382				print "</select></td>\n";
383				}
384			else {
385				print "<td width=33%></td>\n";
386				}
387			print "<td align=center width=33%>\n";
388			if (@rules) {
389				# Delete selected button
390				print &ui_submit($text{'index_cdeletesel'},
391						 "delsel"),"\n";
392
393				# Move selected button
394				print &ui_submit($text{'index_cmovesel'},
395						 "movesel"),"\n";
396				}
397			print "</td>\n";
398			}
399		else {
400			# Custom chain
401			if ($access{'delchain'}) {
402				# Delete and rename chain buttons
403				print "<td width=33%>",
404				   &ui_submit($text{'index_cdelete'}, "delete"),
405				   "\n",
406				   &ui_submit($text{'index_crename'}, "rename"),
407				   "</td>\n";
408				}
409			print "<td align=center width=33%>\n";
410			if (@rules) {
411				# Clear chain button
412				if ($access{'delchain'}) {
413					print &ui_submit($text{'index_cclear'},
414							 "clear"),"\n";
415					}
416
417				# Delete rules button
418				print &ui_submit($text{'index_cdeletesel'},
419						 "delsel"),"\n";
420
421				# Move selected button
422				print &ui_submit($text{'index_cmovesel'},
423						 "movesel"),"\n";
424				}
425			print "</td>\n";
426			}
427		print "<td align=right width=33%>",
428		      &ui_submit($text{'index_radd'}, "add"),"</td>\n";
429		print "</tr></table></form>\n";
430		$form++;
431		}
432
433
434	# Show ipset overview if ipsets are availibe
435        # may need to check if they are used by firewall rules
436	@ipsets  = &get_ipsets_active();
437	if (@ipsets) {
438	    print &ui_hr();
439	    print "<b>$text{'index_ipset_title'}</b>";
440	    # Generate the header
441	    local (@hcols, @tds);
442	    push(@hcols, $text{'index_ipset'}, "<b>$text{'index_ipset_name'}</b>&nbsp;&nbsp;", $text{'index_ipset_type'},
443				 $text{'index_ipset_elem'}, $text{'index_ipset_maxe'}, $text{'index_ipset_size'});
444	    push(@tds, "", "", "", "", "");
445	    print &ui_columns_start(\@hcols, 100, 0, \@tds);
446	    # Generate a row for each rule
447	    foreach $s (@ipsets) {
448		local @cols;
449		local @h= split(/ /, $s->{'Header'});
450		# print matching pínet version
451		if ($h[1] =~ /inet${ipvx}$/) {
452			push(@cols, "&nbsp;&nbsp;$h[0] $h[1]", "&nbsp;&nbsp;<b>$s->{'Name'}</b>",
453					$s->{'Type'}, $s->{'Number'}, $h[5], $s->{'Size'});
454			print &ui_columns_row(\@cols, \@tds);
455			}
456                }
457	    print &ui_columns_end();
458	    }
459
460	# Display buttons for applying and un-applying the configuration,
461	# and for creating an init script if possible
462	print &ui_hr();
463	print &ui_buttons_start();
464
465	if (!$config{"direct${ipvx}"}) {
466		# Buttons to apply and reset the config
467		if (&foreign_check("servers")) {
468			@servers = &list_cluster_servers();
469			}
470		if ($access{'apply'}) {
471			print &ui_buttons_row("apply.cgi",
472				$text{'index_apply'},
473				@servers ? $text{'index_applydesc2'}
474					 : $text{'index_applydesc'},
475				[ [ "table", $in{'table'} ] ]);
476			}
477
478		if ($access{'unapply'}) {
479			print &ui_buttons_row("unapply.cgi",
480				$text{'index_unapply'},
481				$text{'index_unapplydesc'},
482				[ [ "table", $in{'table'} ] ]);
483			}
484
485		if ($init_support && $access{'bootup'}) {
486			print &ui_buttons_row("bootup.cgi",
487				$text{'index_bootup'},
488				$text{'index_bootupdesc'},
489				[ [ "table", $in{'table'} ] ],
490				&ui_yesno_radio("boot", $atboot));
491			}
492
493		if ($access{'setup'}) {
494			print &ui_buttons_row("index.cgi",
495				$text{'index_reset'}, $text{'index_resetdesc'},
496				[ [ "reset", 1 ] ]);
497			}
498		}
499	else {
500		# Button to save the live config in a file
501		if ($access{'unapply'}) {
502			print &ui_buttons_row("unapply.cgi",
503				$text{'index_unapply2'},
504				$text{'index_unapply2desc'},
505				[ [ "table", $in{'table'} ] ]);
506			}
507		}
508
509	# Show button for cluster page
510	if (&foreign_check("servers")) {
511		&foreign_require("servers", "servers-lib.pl");
512		@allservers = grep { $_->{'user'} }
513				&servers::list_servers();
514		}
515	if ($access{'cluster'} && @allservers) {
516		print &ui_buttons_row(
517			"cluster.cgi", $text{'index_cluster'},
518			$text{'index_clusterdesc'});
519		}
520
521	print &ui_buttons_end();
522	}
523
524&ui_print_footer("/", $text{'index'});
525
526sub external_firewall_message
527   {
528	local $fwname="";
529	local $fwconfig="$gconfig{'webprefix'}/config.cgi?firewall";
530
531	# detect external firewalls
532	local ($filter) = grep { $_->{'name'} eq 'filter' } @{$_[0]};
533	if ($filter->{'defaults'}->{'shorewall'}) {
534        $fwname.='shorewall ';
535        	}
536	if ($filter->{'defaults'}->{'INPUT_ZONES'}) {
537        	$fwname.='firewalld ';
538        	}
539	if ($filter->{'defaults'} =~ /^f2b-|^fail2ban-/ && !$config{'filter_chain'} ) {
540        	$fwname.='fail2ban ';
541        	}
542	# warning about not using direct
543	if($fwname && !$config{"direct${ipvx}"}) {
544                print "<b><center>",
545                &text('index_filter_nodirect', $fwconfig),
546                "</b></center><p>\n";
547           }
548        # alert about the detected firewall modules
549        foreach my $word (split ' ', $fwname) {
550                print ui_alert_box(&text("index_$word", "$gconfig{'webprefix'}/$word/", $fwconfig), 'warn');
551                }
552   }
553