1 # Functions for parsing ipf.conf
3 BEGIN { push(@INC, ".."); };
6 &foreign_require("net", "net-lib.pl");
8 # Get the detected ipf version
9 if (open(VERSION, "$module_config_directory/version")) {
10 chop($ipf_version = <VERSION>);
14 @actions = ( "block", "pass", "log", "count", "skip", "auth", "preauth", "call" );
15 @compare_ops = ( "=", "!=", "<", ">", "<=", ">=", "eq", "ne", "lt", "gt", "le", "ge" );
16 @icmp_codes = ( "net-unr" , "host-unr" , "proto-unr" , "port-unr" ,
17 "needfrag" , "srcfail" , "net-unk" , "host-unk" , "isolate" ,
18 "net-prohib" , "host-prohib" , "net-tos" , "host-tos" ,
19 "filter-prohib" , "host-preced" , "cutoff-preced" );
20 @log_priorities = ( "emerg" , "alert" , "crit" , "err" , "warn" , "notice" , "info" , "debug" );
21 @log_facilities = ( "kern" , "user" , "mail" , "daemon" , "auth" , "syslog" ,
22 "lpr" , "news" , "uucp" , "cron" , "ftp" , "authpriv" ,
23 "audit" , "logalert" , "local0" , "local1" , "local2" ,
24 "local3" , "local4" , "local5" , "local6" , "local7" );
25 @icmp_types = ( "unreach" , "echo" , "echorep" , "squench" , "redir" ,
26 "timex" , "paramprob" , "timest" , "timestrep" , "inforeq" ,
27 "inforep" , "maskreq" , "maskrep" );
29 $init_script = $config{'init'} || "webmin-$module_name";
31 sub missing_firewall_commands
34 foreach $cmd ("ipf", "ipfstat", "ipnat") {
35 if (!&has_command($config{$cmd})) {
43 # Parse the ipfilter config file
46 local $file = $_[0] || $config{'ipf_conf'};
47 return $get_config_cache{$file} if ($get_config_cache{$file});
52 # Read each line, splitting into words
54 if (/^\s*(#*)\s*((block|pass|log|count|skip|auth|preauth|call|\@).*)$/) {
55 # A rule, perhaps commented
57 local @w = split(/\s+/, $nocmt);
58 local @cmts = split(/\n/, $cmt);
59 local $rule = { 'index' => scalar(@rv),
61 'line' => $lnum-scalar(@cmts),
69 # There can be a special insert prefix
72 $rule->{'insert'} = shift(@w);
75 # First word is the action, possibly with an arg
76 $rule->{'action'} = shift(@w);
77 if ($rule->{'action'} eq "block") {
78 # Block can have ICMP return type parameter
79 if ($w[0] eq "return-rst") {
81 $rule->{'block-return'} = "rst";
83 elsif ($w[0] eq "return-icmp" ||
84 $w[0] eq "return-icmp-as-dest") {
85 # XXX is this correct? ie.
86 # return-icmp ( srcfail )
87 $rule->{'block-return-dest'} = 1
88 if ($w[0] eq "return-icmp-as-dest");
91 $rule->{'block-return'} = shift(@w);
95 elsif ($rule->{'action'} eq "log") {
96 # Log action can have several options
99 elsif ($rule->{'action'} eq "skip") {
100 # Skip action specifies rule number
101 $rule->{'skip'} = shift(@w);
103 elsif ($rule->{'action'} eq "call") {
104 # Call action has a function name
105 if ($w[0] eq "now") {
106 $rule->{'call-now'} = shift(@w);
108 $rule->{'call'} = shift(@w);
111 # Second is 'in' or 'out'
112 $rule->{'dir'} = shift(@w);
116 if ($w[0] eq "log") {
117 # Log option has several sub-options!
122 elsif ($w[0] eq "tag") {
125 $rule->{'tag'} = shift(@w);
127 elsif ($w[0] eq "quick") {
129 $rule->{'quick'} = 1;
131 elsif ($w[0] eq "on") {
132 # On some interface name
134 $rule->{'on'} = shift(@w);
136 elsif ($w[0] eq "dup-to") {
137 # Has interface:ip option
139 $rule->{'dup-to'} = shift(@w);
141 elsif ($w[0] eq "fastroute" || $w[0] eq "to") {
142 # Fastroute option has interface name (and IP)
144 $rule->{'fastroute'} = shift(@w);
147 $rule->{'fastroute-ip'} = shift(@w);
150 elsif ($w[0] eq "reply-to") {
151 # Replyto option has interface name (and IP)
153 $rule->{'reply-to'} = shift(@w);
156 $rule->{'reply-to-ip'} = shift(@w);
165 if ($w[0] eq "tos") {
167 $rule->{'tos'} = shift(@w);
171 if ($w[0] eq "ttl") {
173 $rule->{'ttl'} = shift(@w);
177 if ($w[0] eq "proto") {
179 $rule->{'proto'} = shift(@w);
182 # Parse from/to section
183 if ($w[0] eq "all") {
187 elsif ($w[0] eq "from") {
188 shift(@w); # skip 'from'
189 &parse_object("from");
190 shift(@w); # skip 'to'
194 if (!$rule->{'active'}) {
195 # Must actually be a comment!
196 $cmt .= "\n" if ($cmt);
200 &error("error parsing IPF line $_ at $w[0] line $lnum");
204 if ($w[0] eq "flags") {
206 $rule->{'flags1'} = shift(@w);
209 $rule->{'flags2'} = shift(@w);
212 if ($w[0] eq "with" || $w[0] eq "and") {
213 # Just store keywords till end of section
216 while(@w && $w[0] ne "keep" && $w[0] ne "icmp-type" &&
217 $w[0] ne "head" && $w[0] ne "group") {
218 push(@with, shift(@w));
220 $rule->{'with'} = \@with;
222 if ($w[0] eq "icmp-type") {
224 $rule->{'icmp-type'} = shift(@w);
225 if ($w[0] eq "code") {
227 $rule->{'icmp-type-code'} = shift(@w);
230 if ($w[0] eq "keep") {
232 $rule->{'keep'} = shift(@w);
235 # Parse group section
236 if ($w[0] eq "head") {
238 $rule->{'head'} = shift(@w);
240 elsif ($w[0] eq "group") {
242 $rule->{'group'} = shift(@w);
247 elsif (/^\s*#\s*(.*)$/) {
249 $cmt .= "\n" if ($cmt);
256 $get_config_cache{$file} = \@rv;
257 return $get_config_cache{$file};
261 # Returns all active firewall rules
264 local $livein = &get_config("$config{'ipfstat'} -i |");
265 local $liveout = &get_config("$config{'ipfstat'} -o |");
266 return [ @$livein, @$liveout ];
274 if ($w[0] eq "body") {
276 $rule->{$pfx.'-body'} = 1;
278 elsif ($w[0] eq "first") {
280 $rule->{$pfx.'-first'} = 1;
282 elsif ($w[0] eq "or-block") {
284 $rule->{$pfx.'-or-block'} = 1;
286 elsif ($w[0] eq "level") {
288 $rule->{$pfx.'-level'} = shift(@w);
296 # parse_object(direction)
304 $rule->{$dir."-not"} = 1;
308 local $addr = shift(@w);
309 if ($addr eq "any") {
310 $rule->{$dir."-any"} = 1;
312 elsif ($addr eq "<thishost>") {
313 $rule->{$dir."-thishost"} = 1;
315 elsif ($addr =~ /^(\S+)\/(\S+)$/) {
317 $rule->{$dir."-numhost"} = $1;
318 $rule->{$dir."-nummask"} = $2;
320 elsif (@w > 0 && $w[0] eq "/") {
321 # host-name [ "/" decnumber ]
322 $rule->{$dir."-numhost"} = $addr;
324 $rule->{$dir."-nummask"} = shift(@w);
327 # host-name [ "mask" ipaddr | "mask" hexnumber ]
328 $rule->{$dir."-host"} = $addr;
329 if (@w && $w[0] eq "mask") {
331 $rule->{$dir."-mask"} = shift(@w);
335 # Parse port-comp or port-range
336 if ($w[0] eq "port") {
338 if (&indexof($w[0], @compare_ops) >= 0) {
340 $rule->{$dir.'-port-comp'} = shift(@w);
341 $rule->{$dir.'-port-num'} = shift(@w);
345 $rule->{$dir.'-port-start'} = shift(@w);
346 $rule->{$dir.'-port-range'} = shift(@w);
347 $rule->{$dir.'-port-end'} = shift(@w);
352 # interface_choice(name, value, noignored, disabled?)
356 if (&foreign_check("net")) {
357 &foreign_require("net", "net-lib.pl");
358 return &net::interface_choice($_[0], $_[1],
359 $_[2] ? undef : $text{'edit_anyiface'}, $_[3]);
362 return &ui_textbox($_[0], $_[1], 6, $_[3]);
366 # parse_interface_choice(name, error)
367 sub parse_interface_choice
369 local $rv = $in{$_[0]} eq "other" ? $in{$_[0]."_other"} : $in{$_[0]};
370 $rv =~ /^[a-z]+\d*/ || &error($_[1]);
374 # describe_rule(&rule, where-mode)
375 # Returns a human-readable description for the conditions for some rule
381 push(@rv, &text('desc_proto', "<b>".uc($r->{'proto'})."</b>"));
384 push(@rv, &describe_object($r, "from"));
385 push(@rv, &describe_object($r, "to"));
387 push(@rv, &text('desc_on', "<b>".$r->{'on'}."</b>"));
389 return &describe_join(\@rv, $_[1]);
392 # describe_object(&rule, prefix)
397 if ($r->{$f.'-thishost'}) {
398 push(@rv, &text('desc_'.$f.'_thishost'));
400 elsif ($r->{$f.'-numhost'} && $r->{$f.'-nummask'} == 32) {
401 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."</b>"));
403 elsif ($r->{$f.'-numhost'} && $r->{$f.'-nummask'}) {
404 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."/".
405 $r->{$f.'-nummask'}."</b>"));
407 elsif ($r->{$f.'-numhost'}) {
408 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."</b>"));
410 elsif ($r->{$f.'-host'} && $r->{$f.'-mask'} eq "255.255.255.255") {
411 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."</b>"));
413 elsif ($r->{$f.'-host'} && $r->{$f.'-mask'}) {
414 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."/".
415 $r->{$f.'-mask'}."</b>"));
417 elsif ($r->{$f.'-host'}) {
418 push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."</b>"));
420 if ($r->{$f.'-port-comp'}) {
421 push(@rv, &text('desc_portcomp_'.$f,
422 $r->{$f.'-port-comp'},
423 "<b>".$r->{$f.'-port-num'}."</b>"));
425 elsif ($r->{$f.'-port-range'} eq '><') {
426 push(@rv, &text('desc_portrange_'.$f,
427 "<b>".$r->{$f.'-port-start'}."</b>",
428 "<b>".$r->{$f.'-port-end'}."</b>"));
430 elsif ($r->{$f.'-port-range'} eq '<>') {
431 push(@rv, &text('desc_portrangenot_'.$f,
432 "<b>".$r->{$f.'-port-start'}."</b>",
433 "<b>".$r->{$f.'-port-end'}."</b>"));
438 # describe_from(&rule, where-mode)
439 # Returns a human-readable description of the match part of a NAT rule
444 push(@rv, &text('desc_on', "<b>".$r->{'iface'}."</b>"));
446 push(@rv, &describe_object($r, "from"), &describe_object($r, "fromto"));
448 elsif ($r->{'frommask'} eq '255.255.255.255' || $r->{'frommask'} eq "32") {
449 push(@rv, &text('desc_natfromh', "<b>$r->{'fromip'}</b>"));
451 elsif ($r->{'frommask'} ne '0.0.0.0' && $r->{'frommask'} ne "0") {
452 push(@rv, &text('desc_natfrom', "<b>$r->{'fromip'}</b>",
453 "<b>$r->{'frommask'}</b>"));
455 if ($r->{'dport1'} && $r->{'dport2'}) {
456 push(@rv, &text('desc_dport2', "<b>$r->{'dport1'}</b>",
457 "<b>$r->{'dport2'}</b>"));
459 elsif ($r->{'dport1'}) {
460 push(@rv, &text('desc_dport1', "<b>$r->{'dport1'}</b>"));
462 return &describe_join(\@rv, $_[1]);
465 # describe_to(&rule, where-mode)
466 # Returns a human-readable description of the translation part of a NAT rule
469 local ($r, $where) = @_;
472 push(@rv, &text('desc_rdr', "<b>".join(" ", @{$r->{'rdrip'}})."</b>",
473 "<b>".$r->{'rdrport'}."</b>"));
475 elsif ($r->{'tostart'}) {
476 push(@rv, &text('desc_natrange', "<b>$r->{'tostart'}</b>",
477 "<b>$r->{'toend'}</b>"));
479 elsif ($r->{'toip'} eq '0.0.0.0' && $r->{'tomask'} eq "32") {
480 push(@rv, $text{'desc_nattoiface'});
482 elsif ($r->{'tomask'} eq '255.255.255.255' || $r->{'tomask'} eq "32") {
483 push(@rv, &text('desc_nattoh', "<b>$r->{'toip'}</b>"));
486 push(@rv, &text('desc_natto', "<b>$r->{'toip'}</b>",
487 "<b>$r->{'tomask'}</b>"));
489 return &text($where ? 'desc_tolc' : 'desc_touc',
490 join(" $text{'desc_and'} ", @rv));
493 # describe_join(&array, where-mode)
496 local ($rv, $where) = @_;
497 return @$rv ? &text($where ? 'desc_where' : 'desc_if',
498 join(" $text{'desc_and'} ", @$rv))
499 : $text{$where ? 'desc_all' : 'desc_always'};
503 # Add one rule to the config file, at the end
507 if ($_[0]->{'type'} eq 'ipnat') {
508 $_[0]->{'file'} ||= $config{'ipnat_conf'};
511 $_[0]->{'file'} ||= $config{'ipf_conf'};
513 local $lref = &read_file_lines($_[0]->{'file'});
514 $_[0]->{'line'} = scalar(@$lref);
515 local @lines = &rule_lines($_[0]);
516 $_[0]->{'eline'} = $_[0]->{'line'} + scalar(@lines) - 1;
517 push(@$lref, @lines);
520 # Add to config cache
521 local $conf = &config_for_rule($_[0]);
522 $_[0]->{'index'} = scalar(@$conf);
526 # insert_rule(&rule, &before)
527 # Adds one rule to the config file, before some other
531 $_[0]->{'file'} ||= $_[1]->{'file'};
532 $_[0]->{'type'} ||= $_[1]->{'type'};
533 local $lref = &read_file_lines($_[0]->{'file'});
534 $_[0]->{'line'} = $_[1]->{'line'};
535 local @lines = &rule_lines($_[0]);
536 $_[0]->{'eline'} = $_[0]->{'line'} + scalar(@lines) - 1;
537 splice(@$lref, $_[0]->{'line'}, 0, @lines);
539 # Update config cache
540 local $conf = &config_for_rule($_[0]);
541 $_[0]->{'index'} = $_[1]->{'index'};
542 foreach $c (@$conf) {
543 $c->{'index'}++ if ($c->{'index'} >= $_[0]->{'index'});
544 $c->{'line'} += scalar(@lines) if ($c->{'line'} >= $_[0]->{'line'});
545 $c->{'eline'} += scalar(@lines) if ($c->{'eline'} >= $_[0]->{'eline'});
547 splice(@$conf, $_[0]->{'index'}, 0, $_[0]);
555 local $lref = &read_file_lines($_[0]->{'file'});
556 local $len = $_[0]->{'eline'} - $_[0]->{'line'} + 1;
557 splice(@$lref, $_[0]->{'line'}, $len);
559 # Update config cache
560 local $conf = &config_for_rule($_[0]);
561 splice(@$conf, $_[0]->{'index'}, 1);
563 foreach $c (@$conf) {
564 $c->{'index'}-- if ($c->{'index'} > $_[0]->{'index'});
565 $c->{'line'} -= $len if ($c->{'line'} > $_[0]->{'line'});
566 $c->{'eline'} -= $len if ($c->{'eline'} > $_[0]->{'eline'});
575 local $lref = &read_file_lines($_[0]->{'file'});
576 local @lines = &rule_lines($_[0]);
577 local $len = $_[0]->{'eline'} - $_[0]->{'line'} + 1;
578 local $newlen = scalar(@lines);
579 splice(@$lref, $_[0]->{'line'}, $len, @lines);
581 # Update config cache
582 $_[0]->{'eline'} = $_[0]->{'line'} + $newlen - 1;
583 local $conf = &config_for_rule($_[0]);
585 foreach $c (@$conf) {
586 next if ($c eq $_[0]);
587 $c->{'line'} += $newlen - $len if ($c->{'line'} > $_[0]->{'line'});
588 $c->{'eline'} += $newlen - $len if ($c->{'eline'} > $_[0]->{'eline'});
592 # swap_rules(&rule1, &rule2)
593 # Swap two rules in the config file, which MUST be next to each other
596 local $lref = &read_file_lines($_[0]->{'file'});
597 local @lines0 = @$lref[$_[0]->{'line'} .. $_[0]->{'eline'}];
598 local @lines1 = @$lref[$_[1]->{'line'} .. $_[1]->{'eline'}];
599 if ($_[0]->{'line'} < $_[1]->{'line'}) {
600 splice(@$lref, $_[0]->{'line'}, $_[1]->{'eline'}-$_[0]->{'line'}+1,
602 $_[1]->{'line'} = $_[0]->{'line'};
603 $_[1]->{'eline'} = $_[1]->{'line'} + scalar(@lines1) - 1;
604 $_[0]->{'line'} = $_[1]->{'eline'}+1;
605 $_[0]->{'eline'} = $_[0]->{'line'} + scalar(@lines0) - 1;
608 splice(@$lref, $_[1]->{'line'}, $_[0]->{'eline'}-$_[1]->{'line'}+1,
610 $_[0]->{'line'} = $_[1]->{'line'};
611 $_[0]->{'eline'} = $_[0]->{'line'} + scalar(@lines0) - 1;
612 $_[1]->{'line'} = $_[0]->{'eline'}+1;
613 $_[1]->{'eline'} = $_[1]->{'line'} + scalar(@lines1) - 1;
616 # Update config cache
617 ($_[0]->{'index'}, $_[1]->{'index'}) = ($_[1]->{'index'}, $_[0]->{'index'});
618 local $conf = &config_for_rule($_[0]);
619 $conf->[$_[0]->{'index'}] = $_[0];
620 $conf->[$_[1]->{'index'}] = $_[1];
625 return $_[0]->{'type'} eq 'ipnat' ? &get_ipnat_config($_[0]->{'file'})
626 : &get_config($_[0]->{'file'});
629 # save_config(&rules, [file], type)
630 # Write out the entire config file
633 local $file = $_[1] || $_[0]->[0]->{'file'} ||
634 ($_[2] eq "ipnat" ? $config{'ipnat_conf'} : $config{'ipf_conf'});
635 &open_tempfile(FILE, ">$file");
639 foreach $r (@{$_[0]}) {
640 $r->{'file'} = $file;
641 $r->{'index'} = $idx++;
642 $r->{'line'} = $lnum;
643 local @lines = &rule_lines($r);
644 $lnum += scalar(@lines);
645 $r->{'eline'} = $lnum - 1;
646 &print_tempfile(FILE, map { "$_\n" } &rule_lines($r));
648 &close_tempfile(FILE);
649 if ($_[2] eq "ipnat") {
650 $get_ipnat_config_cache{$file} = $_[0];
653 $get_config_cache{$file} = $_[0];
658 # Returns the lines of text that make up a rule
662 local @rv = map { "# $_" } split(/\n/, $rule->{'cmt'});
664 push(@w, "#") if (!$_[0]->{'active'});
666 if ($rule->{'type'} ne 'ipnat') {
667 # Standard firewall rule
669 push(@w, "\@", $rule->{'insert'}) if ($rule->{'insert'} ne "");
671 # Add action and args
672 push(@w, $rule->{'action'});
673 if ($rule->{'action'} eq "block") {
674 # Add block action options
675 if ($rule->{'block-return'} eq "rst") {
676 push(@w, "return-rst");
678 elsif ($rule->{'block-return'} ne "") {
680 push(@w, $rule->{'block-return-dest'} ? "return-icmp-as-dest"
682 push(@w, "(", $rule->{'block-return'}, ")");
685 elsif ($rule->{'action'} eq "log") {
686 # Add log action options
689 elsif ($rule->{'action'} eq "skip") {
690 push(@w, $rule->{'skip'});
692 elsif ($rule->{'action'} eq "call") {
693 push(@w, "now") if ($rule->{'call-now'});
694 push(@w, $rule->{'call'});
698 push(@w, $rule->{'dir'});
701 if ($rule->{'olog'}) {
705 if ($rule->{'tag'} ne "") {
706 push(@w, "tag", $rule->{'tag'});
708 if ($rule->{'quick'}) {
711 if ($rule->{'on'} ne "") {
712 push(@w, "on", $rule->{'on'});
714 if ($rule->{'dup-to'} ne "") {
715 push(@w, "dup-to", $rule->{'dup-to'});
717 if ($rule->{'fastroute'} ne "") {
718 push(@w, "fastroute", $rule->{'fastroute'});
719 if ($rule->{'fastroute-ip'} ne "") {
720 push(@w, ":", $rule->{'fastroute-ip'});
723 if ($rule->{'reply-to'} ne "") {
724 push(@w, "reply-to", $rule->{'reply-to'});
725 if ($rule->{'reply-to-ip'} ne "") {
726 push(@w, ":", $rule->{'reply-to-ip'});
731 if ($rule->{'tos'} ne "") {
732 push(@w, "tos", $rule->{'tos'});
736 if ($rule->{'ttl'} ne "") {
737 push(@w, "ttl", $rule->{'ttl'});
741 if ($rule->{'proto'} ne "") {
742 push(@w, "proto", $rule->{'proto'});
745 # Add to/from section
746 if ($rule->{'all'}) {
751 &print_object("from");
757 if ($rule->{'flags1'} ne "") {
758 push(@w, "flags", $rule->{'flags1'});
759 if ($rule->{'flags2'} ne "") {
760 push(@w, "/", $rule->{'flags2'});
763 if ($rule->{'with'}) {
764 push(@w, "with", @{$rule->{'with'}});
766 if ($rule->{'icmp-type'} ne "") {
767 push(@w, "icmp-type", $rule->{'icmp-type'});
768 if ($rule->{'icmp-type-code'} ne "") {
769 push(@w, "code", $rule->{'icmp-type-code'});
772 if ($rule->{'keep'} ne "") {
773 push(@w, "keep", $rule->{'keep'});
777 if ($rule->{'head'} ne "") {
778 push(@w, "head", $rule->{'head'});
780 if ($rule->{'group'} ne "") {
781 push(@w, "group", $rule->{'group'});
786 push(@w, $rule->{'action'});
787 push(@w, $rule->{'iface'});
788 if ($rule->{'action'} ne 'rdr') {
789 # Mapping rule .. add the source
790 if ($rule->{'from'}) {
792 &print_object("from");
794 &print_object("fromto");
797 &print_ipmask("from");
802 # Destination address
803 if ($rule->{'tostart'}) {
804 push(@w, "range", $rule->{'tostart'}, "-",
812 if ($rule->{'portmap'}) {
813 push(@w, "portmap", $rule->{'portmap'});
814 push(@w, $rule->{'portauto'} ? "auto" :
815 $rule->{'portmapfrom'}.":".
816 $rule->{'portmapto'});
820 if ($rule->{'proxyport'}) {
821 &print_proxy("proxy");
825 if ($rule->{'proto'}) {
826 push(@w, $rule->{'proto'});
828 if ($rule->{'frag'}) {
831 if ($rule->{'age1'}) {
832 push(@w, "age", $rule->{'age1'});
833 if ($rule->{'age2'}) {
834 push(@w, "/", $rule->{'age2'});
837 if ($rule->{'mssclamp'}) {
838 push(@w, "mssclamp", $rule->{'mssclamp'});
840 if ($rule->{'oproxyport'}) {
841 &print_proxy("oproxy");
845 # Redirect rule .. add source
846 &print_ipmask("from");
848 # Add destination ports
850 push(@w, $rule->{'dport1'});
851 if ($rule->{'dport2'}) {
852 push(@w, "-", $rule->{'dport2'});
857 # Add destination IPs
858 push(@w, join(" , ", @{$rule->{'rdrip'}}));
860 # Add destination port and protocol
861 push(@w, "port", $rule->{'rdrport'}, $rule->{'rdrproto'});
864 if ($rule->{'round-robin'}) {
865 push(@w, "round-robin");
867 if ($rule->{'frag'}) {
870 if ($rule->{'age1'}) {
871 push(@w, "age", $rule->{'age1'});
872 if ($rule->{'age2'}) {
873 push(@w, "/", $rule->{'age2'});
876 if ($rule->{'mssclamp'}) {
877 push(@w, "mssclamp", $rule->{'mssclamp'});
879 if ($rule->{'proxy'}) {
880 push(@w, "proxy", $rule->{'proxy'});
885 push(@rv, join(" ", @w));
893 push(@w, "body") if ($rule->{$pfx.'-body'});
894 push(@w, "first") if ($rule->{$pfx.'-first'});
895 push(@w, "or-block") if ($rule->{$pfx.'-or-block'});
896 push(@w, "level", $rule->{$pfx.'-level'}) if($rule->{$pfx.'-level'} ne "");
903 if ($rule->{$dir."-any"}) {
906 elsif ($rule->{$dir."-thishost"}) {
907 push(@w, "<thishost>");
909 elsif ($rule->{$dir."-numhost"}) {
910 push(@w, $rule->{$dir."-numhost"}."/".$rule->{$dir."-nummask"});
913 push(@w, $rule->{$dir."-host"});
914 if ($rule->{$dir."-mask"} ne "") {
915 push(@w, "mask", $rule->{$dir."-mask"});
919 if ($rule->{$dir."-port-comp"}) {
920 push(@w, "port", $rule->{$dir.'-port-comp'},
921 $rule->{$dir.'-port-num'});
923 elsif ($rule->{$dir."-port-range"}) {
924 push(@w, "port", $rule->{$dir.'-port-start'},
925 $rule->{$dir.'-port-range'},
926 $rule->{$dir.'-port-end'});
930 # get_ipnat_config([file])
931 # Returns an array reference of ipnat rules
934 local $file = $_[0] || $config{'ipnat_conf'};
935 return $get_ipnat_config_cache{$file} if ($get_ipnat_config_cache{$file});
938 # Read each line, splitting into words
940 if (/^\s*(#*)\s*((map-block|map|bimap|mapit|rdr).*)$/) {
941 # A NAT rule, perhaps commented
943 local @w = split(/\s+/, $nocmt);
944 local @cmts = split(/\n/, $cmt);
945 local $rule = { 'index' => scalar(@rv),
947 'line' => $lnum-scalar(@cmts),
956 $rule->{'action'} = shift(@w);
958 if ($rule->{'action'} eq 'map' ||
959 $rule->{'action'} eq 'bimap' ||
960 $rule->{'action'} eq 'map-block') {
962 $rule->{'iface'} = shift(@w);
964 if ($w[0] eq "from") {
965 # A full from XXX to YYY block
966 shift(@w); # skip 'from'
968 &parse_object("from");
969 shift(@w); # skip 'to'
970 &parse_object("fromto");
973 # IP and netmask only
974 &parse_ipmask("from");
977 local $arrow = shift(@w);
979 &error("error parsing IPNAT line $_ at ".
980 "$arrow line $lnum");
982 if ($w[0] eq "range") {
983 # A destination IP range
984 shift(@w); # skip 'range'
985 $rule->{'tostart'} = shift(@w);
986 if ($rule->{'tostart'} =~ /^(\S+)\-(\S+)$/) {
987 $rule->{'tostart'} = $1;
988 $rule->{'toend'} = $2;
991 shift(@w); # skip '-'
992 $rule->{'toend'} = shift(@w);
996 # Destination IP and netmask only
1000 if ($w[0] eq "portmap") {
1001 # Parse port mapping
1002 shift(@w); # skip 'portmap'
1003 $rule->{'portmap'} = shift(@w);
1004 if ($w[0] eq "auto") {
1005 shift(@w); # skip 'auto'
1006 $rule->{'portauto'} = 1;
1009 ($rule->{'portmapfrom'},
1010 $rule->{'portmapto'}) =
1011 split(/:/, shift(@w));
1015 if ($w[0] eq "proxy") {
1017 &parse_proxy("proxy");
1021 if ($w[0] eq "tcp/udp" ||
1022 getprotobyname($w[0]) || $w[0] =~ /^\d+$/) {
1023 $rule->{'proto'} = shift(@w);
1025 if ($w[0] eq "frag") {
1026 $rule->{'frag'} = shift(@w);
1028 if ($w[0] eq "age") {
1029 shift(@w); # skip 'age'
1030 $rule->{'age1'} = shift(@w);
1032 shift(@w); # skip '/'
1033 $rule->{'age2'} = shift(@w);
1036 if ($w[0] eq "mssclamp") {
1037 shift(@w); # skip 'mssclamp'
1038 $rule->{'mssclamp'} = shift(@w);
1040 if ($w[0] eq "proxy") {
1041 # Parse proxy option
1042 &parse_proxy("oproxy");
1045 elsif ($rule->{'action'} eq 'rdr') {
1047 $rule->{'iface'} = shift(@w);
1049 # Parse IP and netmask
1050 &parse_ipmask("from");
1052 # Parse destination ports
1053 local $ports = shift(@w);
1055 &error("error parsing IPNAT line $_ at ".
1056 "$ports line $lnum");
1057 $rule->{'dport1'} = shift(@w);
1059 shift(@w); # skip '-'
1060 $rule->{'dport2'} = shift(@w);
1063 local $arrow = shift(@w);
1065 &error("error parsing IPNAT line $_ at ".
1066 "$arrow line $lnum");
1068 # Parse destination IPs
1069 $rule->{'rdrip'} = [ shift(@w) ];
1070 while($w[0] eq ",") {
1072 push(@{$rule->{'rdrip'}}, shift(@w));
1075 # Parse destination port
1076 shift(@w); # skip 'port'
1077 $rule->{'rdrport'} = shift(@w);
1080 $rule->{'rdrproto'} = shift(@w);
1082 # Parse other options
1083 if ($w[0] eq "round-robin") {
1084 $rule->{'round-robin'} = shift(@w);
1086 if ($w[0] eq "frag") {
1087 $rule->{'frag'} = shift(@w);
1089 if ($w[0] eq "age") {
1090 shift(@w); # skip 'age'
1091 $rule->{'age1'} = shift(@w);
1093 shift(@w); # skip '/'
1094 $rule->{'age2'} = shift(@w);
1097 if ($w[0] eq "mssclamp") {
1098 shift(@w); # skip 'mssclamp'
1099 $rule->{'mssclamp'} = shift(@w);
1101 if ($w[0] eq "proxy") {
1102 shift(@w); # skip 'proxy'
1103 $rule->{'proxy'} = shift(@w);
1108 elsif (/^\s*#\s*(.*)$/) {
1110 $cmt .= "\n" if ($cmt);
1117 $get_ipnat_config_cache{$file} = \@rv;
1118 return $get_ipnat_config_cache{$file};
1121 # parse_ipmask(prefix)
1125 local $ip = shift(@w);
1126 if ($ip =~ /^(\S+)\/(\S+)$/) {
1127 $rule->{$pfx."ip"} = $1;
1128 $rule->{$pfx."mask"} = $2;
1131 $rule->{$pfx."ip"} = $ip;
1132 shift(@w); # skip '/' or 'netmask'
1133 $rule->{$pfx."mask"} = shift(@w);
1137 # print_ipmask(prefix)
1141 push(@w, $rule->{$pfx."ip"}."/".$rule->{$pfx."mask"});
1144 # parse_proxy(prefix)
1148 shift(@w); # skip 'proxy'
1149 shift(@w); # skip 'port'
1150 $rule->{$pfx.'port'} = shift(@w);
1151 $rule->{$pfx.'name'} = shift(@w);
1152 if ($rule->{$pfx.'name'} =~ /^(\S+)\/(\S+)$/) {
1153 $rule->{$pfx.'name'} = $1;
1154 $rule->{$pfx.'proto'} = $2;
1157 shift(@w); # skip '/'
1158 $rule->{$pfx.'proto'} = shift(@w);
1162 # print_proxy(prefix)
1166 push(@w, "proxy", "port", $rule->{$pfx.'port'},
1167 $rule->{$pfx.'name'}."/".$rule->{$pfx.'proto'});
1171 # Returns a list of IP protocols
1174 local @stdprotos = ( 'tcp', 'udp', 'icmp' );
1176 open(PROTOS, "/etc/protocols");
1180 push(@otherprotos, $1) if (/^(\S+)\s+(\d+)/);
1183 @otherprotos = sort { lc($a) cmp lc($b) } @otherprotos;
1184 return &unique(@stdprotos, @otherprotos);
1187 # apply_configuration()
1188 # Activates the IPfilter configuration, and returns undef on success or an
1189 # error message on failure.
1190 sub apply_configuration
1193 if ($config{'apply_cmd'} && !$config{'smf'}) {
1194 $out = &backquote_logged("$config{'apply_cmd'} 2>&1 </dev/null");
1197 &system_logged("$config{'ipf'} -F a >/dev/null 2>&1");
1198 $out = &backquote_logged("$config{'ipf'} -f $config{'ipf_conf'} 2>&1 </dev/null");
1199 if (-r $config{'ipnat_conf'} && !$?) {
1200 &system_logged("$config{'ipnat'} -C -F >/dev/null 2>&1");
1201 $out = &backquote_logged("$config{'ipnat'} -f $config{'ipnat_conf'} 2>&1 </dev/null");
1204 return $? || $out =~ /error|failed|unknown/i ? "<pre>$out</pre>" : undef;
1207 # check_firewall_init()
1208 # Returns 2 if started at boot, 1 or 0 if not
1209 sub check_firewall_init
1211 if ($config{'smf'}) {
1212 # Look for SMF service
1213 &foreign_require("smf", "smf-lib.pl");
1214 local $state = &smf::svc_get_state_cmd($config{'smf'});
1215 return $state eq 'online' ? 2 :
1216 $state eq 'disabled' || $state eq 'offline' ||
1217 $state eq 'maintenance' ? 1 : 0;
1220 # Look at init script
1221 &foreign_require("init", "init-lib.pl");
1222 return &init::action_status($init_script);
1226 # create_firewall_init()
1227 # Create (if necessary) the ipfilter init script, and enable it
1228 sub create_firewall_init
1230 if ($config{'smf'}) {
1231 # Enable SMF service
1232 &foreign_require("smf", "smf-lib.pl");
1233 local $atboot = &check_firewall_init();
1234 $atboot || &error(&text('boot_esmf', "<tt>$config{'smf'}</tt>"));
1236 &smf::svc_state_cmd($smf::text{'state_enable'},
1237 [ $config{'smf'} ]);
1241 # Create or enable init script
1242 local $ipf = &has_command($config{'ipf'});
1243 local $ipfstat = &has_command($config{'ipfstat'});
1244 local $start = "$ipf -F a\n".
1245 "$ipf -f $config{'ipf_conf'}";
1246 local $stop = "$ipf -F a".
1247 &foreign_require("init", "init-lib.pl");
1248 &init::enable_at_boot($init_script, "Activate IPfilter firewall",
1253 # delete_firewall_init()
1254 # Turn off the firewall at boot time
1255 sub delete_firewall_init
1257 if ($config{'smf'}) {
1258 # Disable SMF service
1259 &foreign_require("smf", "smf-lib.pl");
1260 local $atboot = &check_firewall_init();
1261 $atboot || &error(&text('boot_esmf', "<tt>$config{'smf'}</tt>"));
1263 &smf::svc_state_cmd($smf::text{'state_disable'},
1264 [ $config{'smf'} ]);
1268 # Disable init script
1269 &foreign_require("init", "init-lib.pl");
1270 &init::disable_at_boot($init_script);
1274 # unapply_configuration()
1275 # Replace the IPfilter configuration file with active rules
1276 sub unapply_configuration
1278 local $inrules = `$config{'ipfstat'} -i 2>/dev/null </dev/null`;
1279 return $text{'unapply_ein'} if ($?);
1280 local $outrules = `$config{'ipfstat'} -o 2>/dev/null </dev/null`;
1281 return $text{'unapply_eout'} if ($?);
1283 &open_lock_tempfile(OUT, ">$config{'ipf_conf'}");
1284 &print_tempfile(OUT, $inrules);
1285 &print_tempfile(OUT, $outrules);
1286 &close_tempfile(OUT);
1291 # list_cluster_servers()
1292 # Returns a list of servers on which the firewall is managed
1293 sub list_cluster_servers
1295 &foreign_require("servers", "servers-lib.pl");
1296 local %ids = map { $_, 1 } split(/\s+/, $config{'servers'});
1297 return grep { $ids{$_->{'id'}} } &servers::list_servers();
1300 # add_cluster_server(&server)
1301 sub add_cluster_server
1303 local @sids = split(/\s+/, $config{'servers'});
1304 $config{'servers'} = join(" ", @sids, $_[0]->{'id'});
1305 &save_module_config();
1308 # delete_cluster_server(&server)
1309 sub delete_cluster_server
1311 local @sids = split(/\s+/, $config{'servers'});
1312 $config{'servers'} = join(" ", grep { $_ != $_[0]->{'id'} } @sids);
1313 &save_module_config();
1316 # server_name(&server)
1319 return $_[0]->{'desc'} ? $_[0]->{'desc'} : $_[0]->{'host'};
1322 # copy_to_cluster([force])
1323 # Copy all firewall rules from this server to those in the cluster
1326 return if (!$config{'servers'}); # no servers defined
1327 return if (!$_[0] && $config{'cluster_mode'}); # only push out when applying
1329 foreach $s (&list_cluster_servers()) {
1330 &remote_foreign_require($s, "ipfilter", "ipfilter-lib.pl");
1331 local $iconfig = &remote_foreign_config($s, "ipfilter");
1332 &remote_write($s, $config{'ipf_conf'}, $iconfig->{'ipf_conf'});
1333 if ($iconfig{'ipnat_conf'} && -r $config{'ipnat_conf'} &&
1334 $config{'cluster_nat'}) {
1335 &remote_write($s, $config{'ipnat_conf'},
1336 $iconfig->{'ipnat_conf'});
1341 # apply_cluster_configuration()
1342 # Activate the current configuration on all servers in the cluster
1343 sub apply_cluster_configuration
1345 return undef if (!$config{'servers'});
1346 if ($config{'cluster_mode'}) {
1347 ©_to_cluster(1);
1350 foreach $s (&list_cluster_servers()) {
1351 &remote_foreign_require($s, "ipfilter", "ipfilter-lib.pl");
1352 local $err = &remote_foreign_call($s, "ipfilter", "apply_configuration");
1354 return &text('apply_remote', $s->{'host'}, $err);
1360 # object_input(rule, prefix, [no-thishost])
1361 # Returns HTML for selecting an address and HTML for selecting a port
1364 local ($rule, $f, $nothis) = @_;
1368 $ft .= &ui_oneradio($f, "any", $text{'edit_any'},
1369 $rule->{$f."-any"} || $rule->{'all'})."\n";
1374 $ft .= &ui_oneradio($f, "thishost", $text{'edit_thishost'},
1375 $rule->{$f."-thishost"})."<br>\n";
1378 $ft .= &ui_oneradio($f, "host", $text{'edit_host'},
1379 $rule->{$f."-host"})."\n";
1380 $ft .= &ui_textbox($f."_host", $rule->{$f."-host"}, 20)."\n";
1381 $ft .= "$text{'edit_mask'}\n";
1382 $ft .= &ui_textbox($f."_mask", $rule->{$f."-mask"}, 13)."<br>\n";
1384 $ft .= &ui_oneradio($f, "numhost", $text{'edit_host'},
1385 $rule->{$f."-numhost"}),"\n";
1386 $ft .= &ui_textbox($f."_numhost", $rule->{$f."-numhost"}, 20)."\n";
1387 $ft .= "$text{'edit_nummask'}\n";
1388 $ft .= &ui_textbox($f."_nummask", $rule->{$f."-nummask"}, 6)."\n".
1389 "$text{'edit_opt'}<br>\n";
1394 $pt .= &ui_oneradio($f."_port", "any", $text{'edit_anyport'},
1395 !$rule->{$f."-port-comp"} &&
1396 !$rule->{$f."-port-range"})."\n";
1397 $pt .= &ui_oneradio($f."_port", "comp",
1398 &text('edit_portcomp',
1399 &ui_select($f."_portcomp", $rule->{$f."-port-comp"},
1400 [ map { [ $_ ] } @compare_ops ],
1401 0, 0, $rule->{$f."-port-comp"} ? 1 : 0),
1402 &ui_textbox($f."_portnum", $rule->{$f."-port-num"}, 6)),
1403 $rule->{$f."-port-comp"})."<br>\n";
1404 $pt .= &ui_oneradio($f."_port", "range",
1405 &text('edit_portrange',
1406 &ui_textbox($f."_portstart", $rule->{$f."-port-start"}, 6),
1407 &ui_textbox($f."_portend", $rule->{$f."-port-end"}, 6)),
1408 $rule->{$f."-port-range"} eq '><')."<br>\n";
1409 $pt .= &ui_oneradio($f."_port", "rangenot",
1410 &text('edit_portrangenot',
1411 &ui_textbox($f."_portstartnot", $rule->{$f."-port-start"}, 6),
1412 &ui_textbox($f."_portendnot", $rule->{$f."-port-end"}, 6)),
1413 $rule->{$f."-port-range"} eq '<>')."<br>\n";
1418 # parse_object_input(rule, prefix)
1419 sub parse_object_input
1421 local ($rule, $f) = @_;
1422 delete($rule->{$f."-any"});
1423 delete($rule->{$f."-thishost"});
1424 delete($rule->{$f."-host"});
1425 delete($rule->{$f."-numhost"});
1426 if ($in{$f} eq "any") {
1427 $rule->{$f."-any"} = 1;
1429 elsif ($in{$f} eq "thishost") {
1430 $rule->{$f."-thishost"} = 1;
1432 elsif ($in{$f} eq "host") {
1433 &to_ipaddress($in{$f."_host"}) ||
1434 &error($text{'save_ehost'.$f});
1435 $rule->{$f."-host"} = $in{$f."_host"};
1436 &check_ipaddress($in{$f."_mask"}) ||
1437 &error($text{'save_emask'.$f});
1438 $rule->{$f."-mask"} = $in{$f."_mask"};
1440 elsif ($in{$f} eq "numhost") {
1441 &to_ipaddress($in{$f."_numhost"}) ||
1442 &error($text{'save_ehost'.$f});
1443 $rule->{$f."-numhost"} = $in{$f."_numhost"};
1444 $in{$f."_nummask"} = "32" if ($in{$f."_nummask"} eq "");
1445 $in{$f."_nummask"} =~ /^\d+$/ &&
1446 $in{$f."_nummask"} <= 32 ||
1447 &error($text{'save_enummask'.$f});
1448 $rule->{$f."-nummask"} = $in{$f."_nummask"};
1451 # Parse port section
1452 delete($rule->{$f."-port-comp"});
1453 delete($rule->{$f."-port-range"});
1458 # parse_object_input(rule, prefix)
1459 sub parse_object_input
1461 local ($rule, $f) = @_;
1462 delete($rule->{$f."-any"});
1463 delete($rule->{$f."-thishost"});
1464 delete($rule->{$f."-host"});
1465 delete($rule->{$f."-numhost"});
1466 if ($in{$f} eq "any") {
1467 $rule->{$f."-any"} = 1;
1469 elsif ($in{$f} eq "thishost") {
1470 $rule->{$f."-thishost"} = 1;
1472 elsif ($in{$f} eq "host") {
1473 &to_ipaddress($in{$f."_host"}) ||
1474 &error($text{'save_ehost'.$f});
1475 $rule->{$f."-host"} = $in{$f."_host"};
1476 &check_ipaddress($in{$f."_mask"}) ||
1477 &error($text{'save_emask'.$f});
1478 $rule->{$f."-mask"} = $in{$f."_mask"};
1480 elsif ($in{$f} eq "numhost") {
1481 &to_ipaddress($in{$f."_numhost"}) ||
1482 &error($text{'save_ehost'.$f});
1483 $rule->{$f."-numhost"} = $in{$f."_numhost"};
1484 $in{$f."_nummask"} = "32" if ($in{$f."_nummask"} eq "");
1485 $in{$f."_nummask"} =~ /^\d+$/ &&
1486 $in{$f."_nummask"} <= 32 ||
1487 &error($text{'save_enummask'.$f});
1488 $rule->{$f."-nummask"} = $in{$f."_nummask"};
1491 # Parse port section
1492 delete($rule->{$f."-port-comp"});
1493 delete($rule->{$f."-port-range"});
1494 if ($in{$f."_port"} eq "comp") {
1495 &valid_port($in{$f."_portnum"}) ||
1496 &error($text{'save_eportnum'.$f});
1497 $rule->{$f."-port-num"} = $in{$f."_portnum"};
1498 $rule->{$f."-port-comp"} = $in{$f."_portcomp"};
1500 elsif ($in{$f."_port"} eq "range") {
1501 &valid_port($in{$f."_portstart"}) ||
1502 &error($text{'save_eportstart'.$f});
1503 &valid_port($in{$f."_portend"}) ||
1504 &error($text{'save_eportend'.$f});
1505 $rule->{$f."-port-range"} = "><";
1506 $rule->{$f."-port-start"} = $in{$f."_portstart"};
1507 $rule->{$f."-port-end"} = $in{$f."_portend"};
1509 elsif ($in{$f."_port"} eq "rangenot") {
1510 &valid_port($in{$f."_portstartnot"}) ||
1511 &error($text{'save_eportstart'.$f});
1512 &valid_port($in{$f."_portendnot"}) ||
1513 &error($text{'save_eportend'.$f});
1514 $rule->{$f."-port-range"} = "<>";
1515 $rule->{$f."-port-start"} = $in{$f."_portstartnot"};
1516 $rule->{$f."-port-end"} = $in{$f."_portendnot"};
1520 # protocol_input(name, value, add-any, add-tcp-udp)
1523 local ($name, $value, $any, $tcpudp) = @_;
1524 return &ui_select($name, $value,
1525 [ $any ? ( [ "", $text{'edit_protoany'} ] ) : ( ),
1526 $tcpudp ? ( [ "tcp/udp", $text{'edit_prototcpudp'} ] ) : ( ),
1527 map { [ $_, uc($_) ] } &list_protocols() ],
1528 0, 0, $value ? 1 : 0);
1531 # valid_port(name|number)
1532 # Returns 1 if give a valid port number or TCP or UDP name
1536 return getservbyname($n, "tcp") ||
1537 getservbyname($n, "udp") ||
1538 ($n =~ /^\d+$/ && $n > 0 && $n < 65536);
1543 return $_[0] =~ /^\d+$/ || $_[0] =~ /^0x([0-9a-f]+)$/;