Handle hostnames with upper-case letters
[webmin.git] / ipfw / ipfw-lib.pl
1 # Functions for managing an ipfw firewall.
2 # Works on a file as generated by ipfw list and read by ipfw /path/name,
3 # rather than a script.
4 # XXX some thing are not supported by ipfw1
5
6 BEGIN { push(@INC, ".."); };
7 use WebminCore;
8 &init_config();
9 if (&foreign_check("net")) {
10         &foreign_require("net", "net-lib.pl");
11         if (defined(&net::get_rc_conf)) {
12                 $has_net_lib = 1;
13                 }
14         }
15
16 # Work out save file
17 $ipfw_file = "$module_config_directory/ipfw.rules";
18 if ($config{'save_file'}) {
19         $ipfw_file = $config{'save_file'};
20         }
21 elsif ($has_net_lib) {
22         # Use entry in rc.conf, if set
23         local %rc = &net::get_rc_conf();
24         if ($rc{'firewall_type'} =~ /^\//) {
25                 $ipfw_file = $rc{'firewall_type'};
26                 }
27         }
28
29 @actions = ( "allow", "deny", "reject", "reset", "skipto", "fwd", "check-state",
30              "count", "divert", "pipe", "queue", "tee", "unreach" );
31
32 @unreaches = ( "net", "host", "protocol", "port", "needfrag", "srcfail",
33                "net-unknown", "host-unknown", "isolated", "net-prohib",
34                "host-prohib", "tosnet", "toshost", "filter-prohib",
35                "host-precedence", "precedence-cutoff" );
36
37 @options = ( "bridged", "established", "frag", "in", "out",
38              "keep-state", "setup" );
39
40 @one_options = ( "gid", "uid", "icmptypes", "recv", "xmit",
41                  "via", "tcpflags" );
42
43 @two_options = ( "limit", "mac" );
44
45 @multi_options = ( "dst-port", "src-port" );
46
47 @icmptypes = ( "echo-reply", undef, undef, "destination-unreachable",
48                "source-quench", "redirect", undef, undef, "echo-request",
49                "router-advertisement", "router-solicitation", "ttl-exceeded",
50                "ip-header-bad", "timestamp-request", "timestamp-reply",
51                "information-request", "information-reply",
52                "address-mask-request", "address-mask-reply" );
53
54 @tcpflags = ( "fin", "syn", "rst", "psh", "ack", "urg" );
55
56 # Get the detected ipfw version
57 if (open(VERSION, "$module_config_directory/version")) {
58         chop($ipfw_version = <VERSION>);
59         close(VERSION);
60         }
61
62 # get_config([file], [&output])
63 # Returns a list of rules from the firewall file
64 sub get_config
65 {
66 local $file = $_[0] || $ipfw_file;
67 local $fmt = &get_ipfw_format();
68 if ($_[0] =~ /\|$/) {
69         # When getting from command, there is never an 'add'
70         $fmt = 0;
71         }
72 local @rv;
73 local $cmt;
74 local $lnum = -1;
75 open(LIST, $file);
76 while(<LIST>) {
77         ${$_[1]} .= $_ if ($_[1]);
78         $lnum++;
79         if ($fmt == 1 && !/^add\s+/ && !/^#/) {
80                 # Expecting 'add' suffixes, but found some other directive
81                 local $rule = { 'index' => scalar(@rv),
82                                 'line' => $lnum-scalar(@cmts),
83                                 'eline' => $lnum,
84                                 'other' => 1,
85                                 'text' => $_ };
86                 $cmt = undef;
87                 push(@rv, $rule);
88                 }
89         elsif (/^(add\s+)?(\d+)\s+(.*)/) {
90                 # an ipfw rule
91                 local @cmts = split(/\n/, $cmt);
92                 local $rule = { 'index' => scalar(@rv),
93                                 'line' => $lnum-scalar(@cmts),
94                                 'eline' => $lnum,
95                                 'num' => $2,
96                                 'text' => $3,
97                                 'cmd' => $1,
98                                 'cmt' => $cmt };
99                 $cmt = undef;
100                 local @w = &split_quoted_string($3);
101                 $rule->{'cmd'} =~ s/\s+$//;
102
103                 # Parse counts, if given
104                 if ($w[0] =~ /^\d+$/) {
105                         $rule->{'count1'} = shift(@w);
106                         $rule->{'count2'} = shift(@w);
107                         }
108
109                 # parse the set number
110                 if ($w[0] eq "set") {
111                         shift(@w);
112                         $rule->{'set'} = shift(@w);
113                         }
114
115                 # parse the probability of match
116                 if ($w[0] eq "prob") {
117                         shift(@w);
118                         $rule->{'prob'} = shift(@w);
119                         }
120
121                 # Parse the action
122                 $rule->{'action'} = shift(@w);
123                 if ($rule->{'action'} =~ /divert|fwd|forward|pipe|queue|skipto|tee|unreach/) {
124                         # Action has an arg
125                         $rule->{'aarg'} = shift(@w);
126                         }
127
128                 # Parse the log section
129                 if ($w[0] eq "log") {
130                         $rule->{'log'} = 1;
131                         shift(@w);
132                         if ($w[0] eq "logamount") {
133                                 shift(@w);
134                                 $rule->{'logamount'} = shift(@w);
135                                 }
136                         }
137
138                 # Parse the protocol
139                 local $hasproto;
140                 if ($w[0] eq "{" || $w[0] eq "(") {
141                         $rule->{'proto'} = &words_to_orblock(\@w);
142                         }
143                 else {
144                         $rule->{'proto'} = shift(@w);
145                         $hasproto++ if ($rule->{'proto'} ne "ip" &&
146                                         $rule->{'proto'} ne "any");
147                         }
148
149                 # Parse the source and destination sections
150                 local $s;
151                 foreach $s ("from", "to") {
152                         local $sn = shift(@w);
153                         next if ($sn ne $s);
154
155                         # Parse IP address
156                         if ($w[0] eq "not") {
157                                 $rule->{$s."_not"} = 1;
158                                 shift(@w);
159                                 }
160                         if ($w[0] eq "{" || $w[0] eq "(") {
161                                 $rule->{$s} = &words_to_orblock(\@w);
162                                 }
163                         else {
164                                 $rule->{$s} = shift(@w);
165                                 }
166
167                         # Parse ports
168                         local $pr = $rule->{'proto'};
169                         if ($w[0] eq "not" && @w > 1 &&
170                             ($w[1] =~ /^\d+$/ || $w[1] =~ /,/ ||
171                              $w[1] =~ /\-/ ||
172                              defined(getservbyname($w[1], $rule->{'proto'})))) {
173                                 shift(@w);
174                                 $rule->{$s."_ports_not"} = 1;
175                                 }
176                         if ($w[0] =~ /^\d+$/ || $w[0] =~ /,/ ||
177                             ($w[0] =~ /^(\S+)\-(\S+)$/ &&
178                              &valid_port($1, $pr) &&
179                              &valid_port($2, $pr)) ||
180                             &valid_port($w[0], $pr)) {
181                                 $rule->{$s."_ports"} = shift(@w);
182                                 }
183                         }
184
185                 # Parse any options
186                 if ($w[0] eq "{" || $w[0] eq "(") {
187                         # XXX can be an or-block!
188                         $rule->{'options'} = &words_to_orblock(\@w);
189                         }
190                 else {
191                         local $nextnot = 0;
192                         while(@w) {
193                                 local $o = lc(shift(@w));
194                                 $o = "icmptypes" if ($o eq "icmptype");
195                                 if ($o eq "not") {
196                                         $nextnot = 1;
197                                         }
198                                 else {
199                                         if (&indexof($o, @options) >= 0) {
200                                                 # Stand-alone option
201                                                 $rule->{$o}++;
202                                                 $rule->{$o."_not"} = $nextnot;
203                                                 }
204                                         elsif (&indexof($o, @one_options) >= 0) {
205                                                 # Option with one value
206                                                 $rule->{$o} = shift(@w);
207                                                 $rule->{$o."_not"} = $nextnot;
208                                                 }
209                                         elsif (&indexof($o, @two_options) >= 0) {
210                                                 $rule->{$o} = [ shift(@w), shift(@w) ];
211                                                 $rule->{$o."_not"} = $nextnot;
212                                                 }
213                                         elsif (&indexof($o, @multi_options) >= 0) {
214                                                 $rule->{$o} = [ ];
215                                                 while(@w && $w[0] =~ /^\d+$/) {
216                                                         push(@{$rule->{$o}}, shift(@w));
217                                                         }
218                                                 $rule->{$o."_not"} = $nextnot;
219                                                 }
220                                         else {
221                                                 # Unknown option!!
222                                                 push(@{$rule->{'unknown'}}, "not") if ($nextnot);
223                                                 push(@{$rule->{'unknown'}}, $o);
224                                                 }
225                                         $nextnot = 0;
226                                         }
227                                 }
228                         }
229
230                 push(@rv, $rule);
231                 }
232         elsif (/^#\s*(.*)/) {
233                 # A comment, which applies to the next rule
234                 $cmt .= "\n" if ($cmt);
235                 $cmt .= $1;
236                 }
237         }
238 close(LIST);
239 return \@rv;
240 }
241
242 # valid_port(text, protocol)
243 sub valid_port
244 {
245 return 1 if ($_[0] =~ /^\d+$/);
246 return 1 if (defined(getservbyname($_[0], $_[1])));
247 return 0;
248 }
249
250 # save_config(&rules)
251 # Updates the firewall file with a list of rules
252 sub save_config
253 {
254 open(LIST, ">$ipfw_file");
255 foreach $r (@{$_[0]}) {
256         local @lines = &rule_lines($r);
257         local $l;
258         foreach $l (@lines) {
259                 print LIST $l,"\n";
260                 }
261         }
262 close(LIST);
263 }
264
265 # rule_lines(&rule, [no-comment], [no-add])
266 # Returns the lines of text to make up a rule
267 sub rule_lines
268 {
269 local ($rule, $nocmt, $noadd) = @_;
270 local @cmts = $nocmt ? ( ) : map { "# $_" } split(/\n/, $rule->{'cmt'});
271 local $fmt = &get_ipfw_format();
272 if ($rule->{'other'}) {
273         # Some other line (non-add) that never changes
274         return (@cmts, $rule->{'text'});
275         }
276 elsif (defined($rule->{'text'})) {
277         # A rule line that has not changed
278         if ($fmt && !$rule->{'cmd'}) {
279                 $rule->{'cmd'} = 'add';
280                 }
281         return (@cmts, ($rule->{'cmd'} ? $rule->{'cmd'}." " : "").
282                        (defined($rule->{'num'}) ? $rule->{'num'}." " : "").
283                        $rule->{'text'});
284         }
285 else {
286         # Need to construct
287         local @w;
288
289         # Add the basic rule parameters
290         if ($fmt == 1 && !$noadd) {
291                 push(@w, "add");
292                 }
293         push(@w, $rule->{'num'});
294         push(@w, "set", $rule->{'set'}) if (defined($rule->{'set'}));
295         push(@w, "prob", $rule->{'prob'}) if (defined($rule->{'prob'}));
296         push(@w, $rule->{'action'});
297         push(@w, $rule->{'aarg'}) if (defined($rule->{'aarg'}));
298         if ($rule->{'log'}) {
299                 push(@w, "log");
300                 push(@w, "logamount", $rule->{'logamount'})
301                         if (defined($rule->{'logamount'}));
302                 }
303         push(@w, &orblock_to_words($rule->{'proto'}));
304
305         # Add the from and to sections
306         local $s;
307         foreach $s ("from", "to") {
308                 push(@w, $s);
309                 push(@w, "not") if ($rule->{$s."_not"});
310                 push(@w, &orblock_to_words($rule->{$s}));
311                 if (defined($rule->{$s."_ports"})) {
312                         push(@w, "not") if ($rule->{$s."_ports_not"});
313                         push(@w, $rule->{$s."_ports"});
314                         }
315                 }
316
317         # Add the options
318         if (ref($rule->{'options'})) {
319                 push(@w, &orblock_to_words($rule->{'options'}));
320                 }
321         else {
322                 local $o;
323                 foreach $o (@options) {
324                         if ($rule->{$o}) {
325                                 push(@w, "not") if ($rule->{$o."_not"});
326                                 push(@w, $o);
327                                 }
328                         }
329                 foreach $o (@one_options) {
330                         if (defined($rule->{$o})) {
331                                 push(@w, "not") if ($rule->{$o."_not"});
332                                 push(@w, $o);
333                                 push(@w, $rule->{$o});
334                                 }
335                         }
336                 foreach $o (@two_options, @multi_options) {
337                         if (defined($rule->{$o})) {
338                                 push(@w, "not") if ($rule->{$o."_not"});
339                                 push(@w, $o);
340                                 push(@w, @{$rule->{$o}});
341                                 }
342                         }
343                 push(@w, @{$rule->{'unknown'}});
344                 }
345
346         # Create the resulting rule string
347         local @w = map { $_ =~ /\(|\)/ ? "\"$_\"" : $_ } @w;
348         return (@cmts, join(" ", @w));
349         }
350 }
351
352 sub describe_rule
353 {
354 local $r = $_[0];
355 local @rv;
356 if ($r->{'proto'} ne 'all' && $r->{'proto'} ne 'ip') {
357         push(@rv, &text($r->{'proto_not'} ? 'desc_proto_not' : 'desc_proto',
358                         "<b>".uc($r->{'proto'})."</b>"));
359         }
360 if ($r->{'from'} ne 'any') {
361         push(@rv, &text($r->{'from_not'} ? 'desc_from_not' : 'desc_from',
362                 $r->{'from'} eq 'me' ? $text{'desc_me'} : "<b>$r->{'from'}</b>"));
363         }
364 if ($r->{'from_ports'} ne '') {
365         push(@rv, &text($r->{'from_ports_not'} ? 'desc_from_ports_not'
366                                                : 'desc_from_ports',
367                         "<b>$r->{'from_ports'}</b>"));
368         }
369 if ($r->{'to'} ne 'any') {
370         push(@rv, &text($r->{'to_not'} ? 'desc_to_not' : 'desc_to',
371                 $r->{'to'} eq 'me' ? $text{'desc_me'} : "<b>$r->{'to'}</b>"));
372         }
373 if ($r->{'to_ports'} ne '') {
374         push(@rv, &text($r->{'to_ports_not'} ? 'desc_to_ports_not'
375                                                : 'desc_to_ports',
376                         "<b>$r->{'to_ports'}</b>"));
377         }
378 push(@rv, $text{'desc_in'}) if ($r->{'in'});
379 push(@rv, $text{'desc_out'}) if ($r->{'out'});
380 local $o;
381 foreach $o (@options) {
382         if ($r->{$o} && $r->{$o."_not"}) {
383                 push(@rv, $text{'desc_'.$o.'_not'});
384                 }
385         elsif ($r->{$o}) {
386                 push(@rv, $text{'desc_'.$o});
387                 }
388         }
389 foreach $o (@one_options) {
390         local $v = $r->{$o};
391         if ($o eq "icmptypes") {
392                 $v = join(",", map { $icmptypes[$_] || $_ }
393                                 split(/,/, $v));
394                 }
395         if ($r->{$o} && $r->{$o."_not"}) {
396                 push(@rv, &text('desc_'.$o.'_not', "<b>$v</b>"));
397                 }
398         elsif ($r->{$o}) {
399                 push(@rv, &text('desc_'.$o, "<b>$v</b>"));
400                 }
401         }
402 if ($r->{'mac'}) {
403         if ($r->{'mac'}->[0] eq "any") {
404                 push(@rv, &text('desc_mac1', "<b>$r->{'mac'}->[1]</b>"));
405                 }
406         elsif ($r->{'mac'}->[1] eq "any") {
407                 push(@rv, &text('desc_mac2', "<b>$r->{'mac'}->[0]</b>"));
408                 }
409         else {
410                 push(@rv, &text('desc_mac', "<b>$r->{'mac'}->[0]</b>",
411                                             "<b>$r->{'mac'}->[1]</b>"));
412                 }
413         }
414 if ($r->{'limit'}) {
415         $limit = &text('desc_limit', $text{'desc_'.$r->{'limit'}->[0]}, $r->{'limit'}->[1]);
416         }
417 if ($r->{'dst-port'}) {
418         push(@rv, &text('desc_dstport', join(", ", @{$r->{'dst-port'}})));
419         }
420 if ($r->{'src-port'}) {
421         push(@rv, &text('desc_srcport', join(", ", @{$r->{'src-port'}})));
422         }
423 return @rv ? &text($_[1] ? 'desc_where' : 'desc_if',
424                    join(" $text{'desc_and'} ", @rv)).$limit
425            : $text{$_[1] ? 'desc_all' : 'desc_always'}.$limit;
426 }
427
428 # words_to_orblock(&words)
429 sub words_to_orblock
430 {
431 local $st = shift(@{$_[0]});
432 while($_[0]->[0] ne $st) {
433         push(@or, shift(@{$_[0]}));
434         }
435 shift(@{$_[0]});
436 return \@or;
437 }
438
439 # orblock_to_words(&block)
440 sub orblock_to_words
441 {
442 if (ref($_[0])) {
443         return ( "{", @{$_[0]}, "}" ); 
444         }
445 else {
446         return ( $_[0] );
447         }
448 }
449
450 # real_action(name)
451 # Returns the proper name for some action
452 sub real_action
453 {
454 return $_[0] =~ /accept|pass|permit/ ? "allow" :
455        $_[0] =~ /drop/ ? "deny" :
456        $_[0] =~ /forward/ ? "fwd" : $_[0];
457 }
458
459 sub list_protocols
460 {
461 local @stdprotos = ( 'tcp', 'udp', 'icmp' );
462 local @otherprotos;
463 open(PROTOS, "/etc/protocols");
464 while(<PROTOS>) {
465         s/\r|\n//g;
466         s/#.*$//;
467         push(@otherprotos, $1) if (/^(\S+)\s+(\d+)/);
468         }
469 close(PROTOS);
470 @otherprotos = sort { lc($a) cmp lc($b) } @otherprotos;
471 return &unique(@stdprotos, @otherprotos);
472 }
473
474 # apply_rules([&rules])
475 # Apply the supplied firewall rules
476 sub apply_rules
477 {
478 local $conf = $_[0];
479 $conf ||= &get_config();
480 local $dir = &get_current_dir();
481 chdir("/");
482 local $fmt = &get_ipfw_format();
483 &system_logged("$config{'ipfw'} -f flush >/dev/null 2>&1");
484 if ($fmt == 0) {
485         # Apply each rule in turn
486         local $r;
487         foreach $r (@$conf) {
488                 if (!$r->{'other'} &&
489                     $r->{'num'} != 65535) {     # skip auto-added final rule
490                         local ($line) = &rule_lines($r, 1, 1);
491                         local $cmd = "$config{'ipfw'} add $line";
492                         $out = &backquote_logged("$cmd 2>&1 </dev/null");
493                         return "<tt>$cmd</tt> failed : <tt>$out</tt>" if ($?);
494                         }
495                 }
496         }
497 else {
498         # The ipfw command can apply the whole file
499         local $out = &backquote_logged(
500                 "$config{'ipfw'} ".quotemeta($ipfw_file)." 2>&1 </dev/null");
501         return "<tt>$config{'ipfw'} $ipfw_file</tt> failed : <tt>$out</tt>"
502                 if ($?);
503         }
504 chdir($dir);
505 return undef;
506 }
507
508 # disable_rules()
509 # Returns the system to an 'accept all' state
510 sub disable_rules
511 {
512 local $dir = `pwd`;
513 chop($dir);
514 chdir("/");
515 &system_logged("$config{'ipfw'} -f flush >/dev/null 2>&1");
516 &system_logged("$config{'ipfw'} add allow ip from any to any >/dev/null 2>&1");
517 chdir($dir);
518 return undef;
519 }
520
521 # interface_choice(name, value, noignored)
522 sub interface_choice
523 {
524 local @ifaces;
525 if ($has_net_lib) {
526         return &net::interface_choice($_[0], $_[1],
527                 $_[2] ? undef : "&lt;$text{'edit_ignored'}&gt;");
528         }
529 else {
530         return "<input name=$_[0] size=6 value='$_[1]'>";
531         }
532 }
533
534 sub create_firewall_init
535 {
536 &foreign_require("init", "init-lib.pl");
537 &foreign_require("cron", "cron-lib.pl");
538 &cron::create_wrapper("$module_config_directory/start.pl",
539                       $module_name, "start.pl");
540 &cron::create_wrapper("$module_config_directory/stop.pl",
541                       $module_name, "stop.pl");
542 &init::enable_at_boot($module_name,
543                       "Start firewall",
544                       "$module_config_directory/start.pl",
545                       "$module_config_directory/stop.pl");
546 }
547
548 # list_cluster_servers()
549 # Returns a list of servers on which the firewall is managed
550 sub list_cluster_servers
551 {
552 &foreign_require("servers", "servers-lib.pl");
553 local %ids = map { $_, 1 } split(/\s+/, $config{'servers'});
554 return grep { $ids{$_->{'id'}} } &servers::list_servers();
555 }
556
557 # add_cluster_server(&server)
558 sub add_cluster_server
559 {
560 local @sids = split(/\s+/, $config{'servers'});
561 $config{'servers'} = join(" ", @sids, $_[0]->{'id'});
562 &save_module_config();
563 }
564
565 # delete_cluster_server(&server)
566 sub delete_cluster_server
567 {
568 local @sids = split(/\s+/, $config{'servers'});
569 $config{'servers'} = join(" ", grep { $_ != $_[0]->{'id'} } @sids);
570 &save_module_config();
571 }
572
573 # server_name(&server)
574 sub server_name
575 {
576 return $_[0]->{'desc'} ? $_[0]->{'desc'} : $_[0]->{'host'};
577 }
578
579 # copy_to_cluster([force])
580 # Copy all firewall rules from this server to those in the cluster
581 sub copy_to_cluster
582 {
583 return if (!$config{'servers'});                # no servers defined
584 return if (!$_[0] && $config{'cluster_mode'});  # only push out when applying
585 local $s;
586 foreach $s (&list_cluster_servers()) {
587         &remote_foreign_require($s, "ipfw", "ipfw-lib.pl");
588         local $rfile = &remote_eval($s, "ipfw", "\$ipfw_file");
589         &remote_write($s, $ipfw_file, $rfile);
590         }
591 }
592
593 # apply_cluster_configuration()
594 # Activate the current configuration on all servers in the cluster
595 sub apply_cluster_configuration
596 {
597 return undef if (!$config{'servers'});
598 if ($config{'cluster_mode'}) {
599         &copy_to_cluster(1);
600         }
601 local $s;
602 foreach $s (&list_cluster_servers()) {
603         &remote_foreign_require($s, "ipfw", "ipfw-lib.pl");
604         local $err = &remote_foreign_call($s, "ipfw", "apply_rules");
605         if ($err) {
606                 return &text('apply_remote', $s->{'host'}, $err);
607                 }
608         }
609 return undef;
610 }
611
612 # check_boot()
613 # Returns 1 if enabled at boot via an init script, 2 if enabled via rc.conf,
614 # -1 if a different file is enabled at boot, 0 otherwise
615 sub check_boot
616 {
617 &foreign_require("init", "init-lib.pl");
618 local $atboot = &init::action_status($module_name);
619 if ($atboot == 2) {
620         return 1;
621         }
622 if ($has_net_lib && defined(&net::get_rc_conf)) {
623         local %rc = &net::get_rc_conf();
624         if ($rc{'firewall_enable'} ne 'YES') {
625                 # Disabled
626                 return 0;
627                 }
628         elsif ($rc{'firewall_type'} eq $ipfw_file) {
629                 return 2;
630                 }
631         elsif ($rc{'firewall_type'}) {
632                 # A *different* file is enabled
633                 return -1;
634                 }
635         }
636 return 0;
637 }
638
639 # enable_boot()
640 # Make sure ipfw gets started at boot. Uses rc.conf if possible
641 sub enable_boot
642 {
643 return 0 if (&check_boot());    # Already on
644 if ($has_net_lib && defined(&net::get_rc_conf) && &get_ipfw_format() == 1) {
645         # Add to rc.conf
646         local %rc = &net::get_rc_conf();
647         &lock_file("/etc/rc.conf");
648         &net::save_rc_conf('firewall_type', $ipfw_file);
649         &net::save_rc_conf('firewall_enable', 'YES');
650         &net::save_rc_conf('firewall_quiet', 'YES');
651         &unlock_file("/etc/rc.conf");
652         return 2;
653         }
654 else {
655         # Create init script
656         &create_firewall_init();
657         }
658 return 1;
659 }
660
661 sub disable_boot
662 {
663 local $mode = &check_boot();
664 return 0 if ($mode <= 0);
665 if ($mode == 1) {
666         # Turn off init script
667         &init::disable_at_boot($module_name);
668         }
669 elsif ($mode == 2) {
670         # Take out rc.conf entry
671         &lock_file("/etc/rc.conf");
672         &net::save_rc_conf('firewall_enable', 'NO');
673         &unlock_file("/etc/rc.conf");
674         }
675 return $mode;
676 }
677
678 # get_ipfw_format()
679 # Works out the IPFW file format we should use. Returns 1 for with 'add' at the
680 # start, vs 0 for without.
681 sub get_ipfw_format
682 {
683 if (defined($get_ipfw_format_cache)) {
684         return $get_ipfw_format_cache;
685         }
686 local $fmt;
687 if (open(FILE, $ipfw_file)) {
688         # Check existing format
689         while(<FILE>) {
690                 if (/^(\d+)\s/) {
691                         # Numeric line
692                         $fmt = 0;
693                         last;
694                         }
695                 elsif (/\S/ && !/^\#/) {
696                         # Add or other directive line
697                         $fmt = 1;
698                         last;
699                         }
700                 }
701         close(FILE);
702         }
703 if (!defined($fmt)) {
704         if (-r "/etc/rc.conf") {
705                 # FreeBSD - use it's format
706                 $fmt = 1;
707                 }
708         else {
709                 # Assume numeric format
710                 $fmt = 0;
711                 }
712         }
713 $get_ipfw_format_cache = $fmt;
714 return $fmt;
715 }
716
717 1;