2 # Functions for parsing iptables-save format files
5 BEGIN { push(@INC, ".."); };
8 if ($config{'save_file'}) {
9 # Force use of a different save file, and webmin's functions
10 $iptables_save_file = $config{'save_file'};
13 if (-r "$module_root_directory/$gconfig{'os_type'}-lib.pl") {
14 # Use the operating system's save file and functions
15 do "$gconfig{'os_type'}-lib.pl";
18 if (!$iptables_save_file) {
19 # Use webmin's own save file
20 $iptables_save_file = "$module_config_directory/iptables.save";
24 %access = &get_module_acl();
26 @known_tables = ( "filter", "mangle", "nat" );
27 @known_args = ('-p', '-m', '-s', '-d', '-i', '-o', '-f',
28 '--dport', '--sport', '--tcp-flags', '--tcp-option',
29 '--icmp-type', '--mac-source', '--limit', '--limit-burst',
30 '--ports', '--uid-owner', '--gid-owner',
31 '--pid-owner', '--sid-owner', '--state', '--tos', '-j',
32 '--to-ports', '--to-destination', '--to-source',
33 '--reject-with', '--dports', '--sports',
35 '--physdev-is-bridged',
41 # get_iptables_save([file])
42 # Parse the iptables save file into a list of tables
45 # :chain defaultpolicy
51 local (@rv, $table, %got);
53 open(FILE, $_[0] || ($config{'direct'} ? "iptables-save 2>/dev/null |"
54 : $iptables_save_file));
60 $cmt .= " " if ($cmt);
65 # Start of a new table
67 push(@rv, $table = { 'line' => $lnum,
73 elsif (/^:(\S+)\s+(\S+)/) {
74 # Default policy definition
75 $table->{'defaults'}->{$1} = $2;
77 elsif (/^(\[[^\]]*\]\s+)?-N\s+(\S+)(.*)/) {
78 # New chain definition
79 $table->{'defaults'}->{$2} = '-';
81 elsif (/^(\[[^\]]*\]\s+)?-(A|I)\s+(\S+)(.*)/) {
83 local $rule = { 'line' => $lnum,
85 'index' => scalar(@{$table->{'rules'}}),
90 unshift(@{$table->{'rules'}}, $rule);
93 push(@{$table->{'rules'}}, $rule);
97 foreach $a (@known_args) {
99 while($rule->{'args'} =~
100 s/\s+(!?)\s*($a)\s+(!?)\s*("[^"]*")(\s+|$)/ / ||
102 s/\s+(!?)\s*($a)\s+(!?)\s*(([^ \-!]\S*(\s+|$))+)/ / ||
104 s/\s+(!?)\s*($a)()(\s+|$)/ /) {
105 push(@vl, [ $1 || $3, &split_quoted_string($4) ]);
107 local ($aa = $a); $aa =~ s/^-+//;
109 $rule->{$aa} = \@vl if (@vl);
112 $rule->{$aa} = $vl[0];
117 # Marks end of a table
118 $table->{'eline'} = $lnum;
121 &error(&text('eiptables', "<tt>$_</tt>"));
124 if (! defined($read_comment)) { $cmt=undef; }
127 @rv = sort { $a->{'name'} cmp $b->{'name'} } @rv;
129 map { $_->{'index'} = $i++ } @rv;
134 # Updates an existing IPtable in the save file
138 if ($config{'direct'}) {
139 # Read in the current iptables-save output
140 $lref = &read_file_lines("iptables-save 2>/dev/null |");
143 # Updating the save file
144 $lref = &read_file_lines($iptables_save_file);
146 local @lines = ( "*$_[0]->{'name'}" );
148 foreach $d (keys %{$_[0]->{'defaults'}}) {
149 push(@lines, ":$d $_[0]->{'defaults'}->{$d} [0:0]");
151 foreach $r (@{$_[0]->{'rules'}}) {
153 $line = "# $r->{'cmt'}\n" if ($r->{'cmt'});
154 $line .= "-A $r->{'chain'}";
155 foreach $a (@known_args) {
156 local ($aa = $a); $aa =~ s/^-+//;
158 local @al = ref($r->{$aa}->[0]) ?
159 @{$r->{$aa}} : ( $r->{$aa} );
161 local $n = shift(@$ag);
162 local @w = ( $n ? ( $n ) : (), $a, @$ag );
163 @w = map { $_ =~ /\s/ ? "\"$_\"" : $_ } @w;
164 $line .= " ".join(" ", @w);
168 $line .= " $r->{'args'}" if ($r->{'args'} =~ /\S/);
171 push(@lines, "COMMIT");
172 if (defined($_[0]->{'line'})) {
174 splice(@$lref, $_[0]->{'line'}, $_[0]->{'eline'} - $_[0]->{'line'} + 1,
178 # Append new table to file
179 push(@$lref, "# Generated by webmin", @lines, "# Completed");
181 if ($config{'direct'}) {
182 # Pass new lines to iptables-restore
183 open(SAVE, "| iptables-restore");
184 print SAVE map { $_."\n" } @$lref;
193 # describe_rule(&rule)
194 # Returns a human-readable description of some rule conditions
198 foreach $d ('p', 's', 'd', 'i', 'o', 'f', 'dport',
199 'sport', 'tcp-flags', 'tcp-option',
200 'icmp-type', 'mac-source', 'limit', 'limit-burst',
201 'ports', 'uid-owner', 'gid-owner',
202 'pid-owner', 'sid-owner', 'state', 'tos',
203 'dports', 'sports', 'physdev-in', 'physdev-out') {
205 local ($n, @v) = @{$_[0]->{$d}};
206 @v = map { uc($_) } @v if ($d eq 'p');
207 local $txt = &text("desc_$d$n", map { "<b>$_</b>" } @v);
208 push(@c, $txt) if ($txt);
213 $rv = &text('desc_conds', join(" $text{'desc_and'} ", @c));
216 $rv = $text{'desc_always'};
221 # create_firewall_init()
222 # Do whatever is needed to have the firewall started at boot time
223 sub create_firewall_init
225 if (defined(&enable_at_boot)) {
226 # Use distro's function
230 # May need to create init script
231 &create_webmin_init();
235 # create_webmin_init()
236 # Create (if necessary) the Webmin iptables init script
237 sub create_webmin_init
239 local $res = &has_command("iptables-restore");
240 local $ipt = &has_command("iptables");
241 local $start = "$res <$iptables_save_file";
242 local $stop = "$ipt -t filter -F\n".
244 "$ipt -t mangle -F\n".
245 "$ipt -t filter -P INPUT ACCEPT\n".
246 "$ipt -t filter -P OUTPUT ACCEPT\n".
247 "$ipt -t filter -P FORWARD ACCEPT\n".
248 "$ipt -t nat -P PREROUTING ACCEPT\n".
249 "$ipt -t nat -P POSTROUTING ACCEPT\n".
250 "$ipt -t nat -P OUTPUT ACCEPT\n".
251 "$ipt -t mangle -P PREROUTING ACCEPT\n".
252 "$ipt -t mangle -P OUTPUT ACCEPT";
253 &foreign_require("init", "init-lib.pl");
254 &init::enable_at_boot("webmin-iptables", "Load IPtables save file",
258 # interface_choice(name, value)
262 if (&foreign_check("net")) {
263 &foreign_require("net", "net-lib.pl");
264 return &net::interface_choice($_[0], $_[1], undef, 0, 1);
267 return "<input name=$_[0] size=6 value='$_[1]'>";
274 for ($i=0;$i<$max;$i++)
276 if ($n eq $p[$i]){return 1}
281 sub by_string_for_iptables
283 my @p=("PREROUTING","INPUT","FORWARD","OUTPUT","POSTROUTING");
285 for ($i=0;$i<@p;$i++)
288 if (&check_previous(@p,$i,$b)){return -1;}
291 if (&check_previous(@p,$i,$b)){return 1;}
298 sub missing_firewall_commands
301 foreach $c ("iptables", "iptables-restore", "iptables-save") {
302 return $c if (!&has_command($c));
308 # Activates the current firewall rules, and returns any error
311 local $out = &backquote_logged("cd / ; iptables-restore <$iptables_save_file 2>&1");
312 return $? ? "<pre>$out</pre>" : undef;
316 # Saves the active firewall rules, and returns any error
319 local $out = &backquote_logged("iptables-save >$iptables_save_file 2>&1");
320 return $? ? "<pre>$out</pre>" : undef;
323 # can_edit_table(name)
326 return $access{$_[0]};
329 # can_jump(jump|&rule)
332 return 1 if (!$access{'jumps'});
333 if (!%can_jumps_cache) {
334 %can_jumps_cache = map { lc($_), 1 } split(/\s+/, $access{'jumps'});
336 local $j = ref($_[0]) ? $_[0]->{'j'}->[1] : $_[0];
337 return 1 if (!$j); # always allow 'do nothing'
338 return $can_jumps_cache{lc($j)};
341 # run_before_command()
342 # Runs the before-saving command, if any
343 sub run_before_command
345 if ($config{'before_cmd'}) {
346 &system_logged("($config{'before_cmd'}) </dev/null >/dev/null 2>&1");
350 # run_after_command()
351 # Runs the after-saving command, if any
352 sub run_after_command
354 if ($config{'after_cmd'}) {
355 &system_logged("($config{'after_cmd'}) </dev/null >/dev/null 2>&1");
359 # run_before_apply_command()
360 # Runs the before-applying command, if any. If it failes, returns the error
362 sub run_before_apply_command
364 if ($config{'before_apply_cmd'}) {
365 local $out = &backquote_logged("($config{'before_apply_cmd'}) </dev/null 2>&1");
371 # run_after_apply_command()
372 # Runs the after-applying command, if any
373 sub run_after_apply_command
375 if ($config{'after_apply_cmd'}) {
376 &system_logged("($config{'after_apply_cmd'}) </dev/null >/dev/null 2>&1");
380 # apply_configuration()
381 # Calls all the appropriate apply functions and programs, and returns an error
382 # message if anything fails
383 sub apply_configuration
385 local $err = &run_before_apply_command();
386 return $err if ($err);
387 if (defined(&apply_iptables)) {
388 # Call distro's apply command
389 $err = &apply_iptables();
392 # Manually run iptables-restore
393 $err = &iptables_restore();
395 return $err if ($err);
396 &run_after_apply_command();
400 # list_cluster_servers()
401 # Returns a list of servers on which the firewall is managed
402 sub list_cluster_servers
404 &foreign_require("servers", "servers-lib.pl");
405 local %ids = map { $_, 1 } split(/\s+/, $config{'servers'});
406 return grep { $ids{$_->{'id'}} } &servers::list_servers();
409 # add_cluster_server(&server)
410 sub add_cluster_server
412 local @sids = split(/\s+/, $config{'servers'});
413 $config{'servers'} = join(" ", @sids, $_[0]->{'id'});
414 &save_module_config();
417 # delete_cluster_server(&server)
418 sub delete_cluster_server
420 local @sids = split(/\s+/, $config{'servers'});
421 $config{'servers'} = join(" ", grep { $_ != $_[0]->{'id'} } @sids);
422 &save_module_config();
425 # server_name(&server)
428 return $_[0]->{'desc'} ? $_[0]->{'desc'} : $_[0]->{'host'};
431 # copy_to_cluster([force])
432 # Copy all firewall rules from this server to those in the cluster
435 return if (!$config{'servers'}); # no servers defined
436 return if (!$_[0] && $config{'cluster_mode'}); # only push out when applying
439 if ($config{'direct'}) {
440 # Dump current configuration
441 $ltemp = &transname();
442 system("iptables-save >$ltemp 2>/dev/null");
444 foreach $s (&list_cluster_servers()) {
445 &remote_foreign_require($s, "firewall", "firewall-lib.pl");
446 if ($config{'direct'}) {
447 # Directly activate on remote server!
448 local $rtemp = &remote_write($s, $ltemp);
450 local $err = &remote_eval($s, "firewall",
451 "\$out = `iptables-restore <$rtemp 2>&1`; [ \$out, \$? ]");
452 &remote_eval($s, "firewall", "unlink('$rtemp')");
453 &error(&text('apply_remote', $s->{'host'}, $err->[0]))
457 # Can just copy across save file
458 local $rfile = &remote_eval($s, "firewall",
459 "\$iptables_save_file");
460 &remote_write($s, $iptables_save_file, $rfile);
465 # apply_cluster_configuration()
466 # Activate the current configuration on all servers in the cluster
467 sub apply_cluster_configuration
469 return undef if (!$config{'servers'});
470 if ($config{'cluster_mode'}) {
474 foreach $s (&list_cluster_servers()) {
475 &remote_foreign_require($s->{'host'}, "firewall", "firewall-lib.pl");
476 local $err = &remote_foreign_call($s->{'host'}, "firewall", "apply_configuration");
478 return &text('apply_remote', $s->{'host'}, $err);