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> ", $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, " $h[0] $h[1]", " <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