Handle hostnames with upper-case letters
[webmin.git] / ipfilter / ipfilter-lib.pl
1 # Functions for parsing ipf.conf
2
3 BEGIN { push(@INC, ".."); };
4 use WebminCore;
5 &init_config();
6 &foreign_require("net", "net-lib.pl");
7
8 # Get the detected ipf version
9 if (open(VERSION, "$module_config_directory/version")) {
10         chop($ipf_version = <VERSION>);
11         close(VERSION);
12         }
13
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" );
28
29 $init_script = $config{'init'} || "webmin-$module_name";
30
31 sub missing_firewall_commands
32 {
33 local $cmd;
34 foreach $cmd ("ipf", "ipfstat", "ipnat") {
35         if (!&has_command($config{$cmd})) {
36                 return $config{$cmd};
37                 }
38         }
39 return undef;
40 }
41
42 # get_config([file])
43 # Parse the ipfilter config file
44 sub get_config
45 {
46 local $file = $_[0] || $config{'ipf_conf'};
47 return $get_config_cache{$file} if ($get_config_cache{$file});
48 local @rv;
49 local $lnum = 0;
50 open(FILE, $file);
51 while(<FILE>) {
52         # Read each line, splitting into words
53         s/\r|\n//g;
54         if (/^\s*(#*)\s*((block|pass|log|count|skip|auth|preauth|call|\@).*)$/) {
55                 # A rule, perhaps commented
56                 local $nocmt = $2;
57                 local @w = split(/\s+/, $nocmt);
58                 local @cmts = split(/\n/, $cmt);
59                 local $rule = { 'index' => scalar(@rv),
60                                 'type' => 'ipf',
61                                 'line' => $lnum-scalar(@cmts),
62                                 'eline' => $lnum,
63                                 'file' => $file,
64                                 'text' => $_,
65                                 'cmt' => $cmt,
66                                 'active' => !$1 };
67                 $cmt = undef;
68
69                 # There can be a special insert prefix
70                 if ($w[0] eq "\@") {
71                         shift(@w);
72                         $rule->{'insert'} = shift(@w);
73                         }
74
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") {
80                                 shift(@w);
81                                 $rule->{'block-return'} = "rst";
82                                 }
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");
89                                 shift(@w);
90                                 shift(@w);      # skip (
91                                 $rule->{'block-return'} = shift(@w);
92                                 shift(@w);      # skip )
93                                 }
94                         }
95                 elsif ($rule->{'action'} eq "log") {
96                         # Log action can have several options
97                         &parse_log("log");
98                         }
99                 elsif ($rule->{'action'} eq "skip") {
100                         # Skip action specifies rule number
101                         $rule->{'skip'} = shift(@w);
102                         }
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);
107                                 }
108                         $rule->{'call'} = shift(@w);
109                         }
110
111                 # Second is 'in' or 'out'
112                 $rule->{'dir'} = shift(@w);
113
114                 # Parse options
115                 while(1) {
116                         if ($w[0] eq "log") {
117                                 # Log option has several sub-options!
118                                 shift(@w);
119                                 $rule->{'olog'} = 1;
120                                 &parse_log("olog");
121                                 }
122                         elsif ($w[0] eq "tag") {
123                                 # Tag has ID option
124                                 shift(@w);
125                                 $rule->{'tag'} = shift(@w);
126                                 }
127                         elsif ($w[0] eq "quick") {
128                                 shift(@w);
129                                 $rule->{'quick'} = 1;
130                                 }
131                         elsif ($w[0] eq "on") {
132                                 # On some interface name
133                                 shift(@w);
134                                 $rule->{'on'} = shift(@w);
135                                 }
136                         elsif ($w[0] eq "dup-to") {
137                                 # Has interface:ip option
138                                 shift(@w);
139                                 $rule->{'dup-to'} = shift(@w);
140                                 }
141                         elsif ($w[0] eq "fastroute" || $w[0] eq "to") {
142                                 # Fastroute option has interface name (and IP)
143                                 shift(@w);
144                                 $rule->{'fastroute'} = shift(@w);
145                                 if ($w[0] eq ":") {
146                                         shift(@w);
147                                         $rule->{'fastroute-ip'} = shift(@w);
148                                         }
149                                 }
150                         elsif ($w[0] eq "reply-to") {
151                                 # Replyto option has interface name (and IP)
152                                 shift(@w);
153                                 $rule->{'reply-to'} = shift(@w);
154                                 if ($w[0] eq ":") {
155                                         shift(@w);
156                                         $rule->{'reply-to-ip'} = shift(@w);
157                                         }
158                                 }
159                         else {
160                                 last;
161                                 }
162                         }
163
164                 # Parse TOS
165                 if ($w[0] eq "tos") {
166                         shift(@w);
167                         $rule->{'tos'} = shift(@w);
168                         }
169
170                 # Parse TTL
171                 if ($w[0] eq "ttl") {
172                         shift(@w);
173                         $rule->{'ttl'} = shift(@w);
174                         }
175
176                 # Parse protocol
177                 if ($w[0] eq "proto") {
178                         shift(@w);
179                         $rule->{'proto'} = shift(@w);
180                         }
181
182                 # Parse from/to section
183                 if ($w[0] eq "all") {
184                         shift(@w);
185                         $rule->{'all'} = 1;
186                         }
187                 elsif ($w[0] eq "from") {
188                         shift(@w);      # skip 'from'
189                         &parse_object("from");
190                         shift(@w);      # skip 'to'
191                         &parse_object("to");
192                         }
193                 else {
194                         if (!$rule->{'active'}) {
195                                 # Must actually be a comment!
196                                 $cmt .= "\n" if ($cmt);
197                                 $cmt .= $nocmt;
198                                 goto nextline;
199                                 }
200                         &error("error parsing IPF line $_ at $w[0] line $lnum");
201                         }
202
203                 # Parse ip options
204                 if ($w[0] eq "flags") {
205                         shift(@w);
206                         $rule->{'flags1'} = shift(@w);
207                         if ($w[0] eq "/") {
208                                 shift(@w);
209                                 $rule->{'flags2'} = shift(@w);
210                                 }
211                         }
212                 if ($w[0] eq "with" || $w[0] eq "and") {
213                         # Just store keywords till end of section
214                         shift(@w);
215                         local @with;
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));
219                                 }
220                         $rule->{'with'} = \@with;
221                         }
222                 if ($w[0] eq "icmp-type") {
223                         shift(@w);
224                         $rule->{'icmp-type'} = shift(@w);
225                         if ($w[0] eq "code") {
226                                 shift(@w);
227                                 $rule->{'icmp-type-code'} = shift(@w);
228                                 }
229                         }
230                 if ($w[0] eq "keep") {
231                         shift(@w);
232                         $rule->{'keep'} = shift(@w);
233                         }
234
235                 # Parse group section
236                 if ($w[0] eq "head") {
237                         shift(@w);
238                         $rule->{'head'} = shift(@w);
239                         }
240                 elsif ($w[0] eq "group") {
241                         shift(@w);
242                         $rule->{'group'} = shift(@w);
243                         }
244
245                 push(@rv, $rule);
246                 }
247         elsif (/^\s*#\s*(.*)$/) {
248                 # A comment line
249                 $cmt .= "\n" if ($cmt);
250                 $cmt .= $1;
251                 }
252         nextline:
253         $lnum++;
254         }
255 close(FILE);
256 $get_config_cache{$file} = \@rv;
257 return $get_config_cache{$file};
258 }
259
260 # get_live_config()
261 # Returns all active firewall rules
262 sub get_live_config
263 {
264 local $livein = &get_config("$config{'ipfstat'} -i |");
265 local $liveout = &get_config("$config{'ipfstat'} -o |");
266 return [ @$livein, @$liveout ];
267 }
268
269 # parse_log(suffix)
270 sub parse_log
271 {
272 local $pfx = $_[0];
273 while(1) {
274         if ($w[0] eq "body") {
275                 shift(@w);
276                 $rule->{$pfx.'-body'} = 1;
277                 }
278         elsif ($w[0] eq "first") {
279                 shift(@w);
280                 $rule->{$pfx.'-first'} = 1;
281                 }
282         elsif ($w[0] eq "or-block") {
283                 shift(@w);
284                 $rule->{$pfx.'-or-block'} = 1;
285                 }
286         elsif ($w[0] eq "level") {
287                 shift(@w);
288                 $rule->{$pfx.'-level'} = shift(@w);
289                 }
290         else {
291                 last;
292                 }
293         }
294 }
295
296 # parse_object(direction)
297 sub parse_object
298 {
299 local $dir = $_[0];
300
301 # Parse ! prefix
302 if ($w[0] eq "!") {
303         shift(@w);
304         $rule->{$dir."-not"} = 1;
305         }
306
307 # Parse addr section
308 local $addr = shift(@w);
309 if ($addr eq "any") {
310         $rule->{$dir."-any"} = 1;
311         }
312 elsif ($addr eq "<thishost>") {
313         $rule->{$dir."-thishost"} = 1;
314         }
315 elsif ($addr =~ /^(\S+)\/(\S+)$/) {
316         # host-name/number
317         $rule->{$dir."-numhost"} = $1;
318         $rule->{$dir."-nummask"} = $2;
319         }
320 elsif (@w > 0 && $w[0] eq "/") {
321         # host-name [ "/" decnumber ]
322         $rule->{$dir."-numhost"} = $addr;
323         shift(@w);
324         $rule->{$dir."-nummask"} = shift(@w);
325         }
326 else {
327         # host-name [ "mask" ipaddr | "mask" hexnumber ]
328         $rule->{$dir."-host"} = $addr;
329         if (@w && $w[0] eq "mask") {
330                 shift(@w);
331                 $rule->{$dir."-mask"} = shift(@w);
332                 }
333         }
334
335 # Parse port-comp or port-range
336 if ($w[0] eq "port") {
337         shift(@w);
338         if (&indexof($w[0], @compare_ops) >= 0) {
339                 # Must be port-comp
340                 $rule->{$dir.'-port-comp'} = shift(@w);
341                 $rule->{$dir.'-port-num'} = shift(@w);
342                 }
343         else {
344                 # Must be port-range
345                 $rule->{$dir.'-port-start'} = shift(@w);
346                 $rule->{$dir.'-port-range'} = shift(@w);
347                 $rule->{$dir.'-port-end'} = shift(@w);
348                 }
349         }
350 }
351
352 # interface_choice(name, value, noignored, disabled?)
353 sub interface_choice
354 {
355 local @ifaces;
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]);
360         }
361 else {
362         return &ui_textbox($_[0], $_[1], 6, $_[3]);
363         }
364 }
365
366 # parse_interface_choice(name, error)
367 sub parse_interface_choice
368 {
369 local $rv = $in{$_[0]} eq "other" ? $in{$_[0]."_other"} : $in{$_[0]};
370 $rv =~ /^[a-z]+\d*/ || &error($_[1]);
371 return $rv;
372 }
373
374 # describe_rule(&rule, where-mode)
375 # Returns a human-readable description for the conditions for some rule
376 sub describe_rule
377 {
378 local $r = $_[0];
379 local @rv;
380 if ($r->{'proto'}) {
381         push(@rv, &text('desc_proto', "<b>".uc($r->{'proto'})."</b>"));
382         }
383 local $f;
384 push(@rv, &describe_object($r, "from"));
385 push(@rv, &describe_object($r, "to"));
386 if ($r->{'on'}) {
387         push(@rv, &text('desc_on', "<b>".$r->{'on'}."</b>"));
388         }
389 return &describe_join(\@rv, $_[1]);
390 }
391
392 # describe_object(&rule, prefix)
393 sub describe_object
394 {
395 local ($r, $f) = @_;
396 local @rv;
397 if ($r->{$f.'-thishost'}) {
398         push(@rv, &text('desc_'.$f.'_thishost'));
399         }
400 elsif ($r->{$f.'-numhost'} && $r->{$f.'-nummask'} == 32) {
401         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."</b>"));
402         }
403 elsif ($r->{$f.'-numhost'} && $r->{$f.'-nummask'}) {
404         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."/".
405                                     $r->{$f.'-nummask'}."</b>"));
406         }
407 elsif ($r->{$f.'-numhost'}) {
408         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-numhost'}."</b>"));
409         }
410 elsif ($r->{$f.'-host'} && $r->{$f.'-mask'} eq "255.255.255.255") {
411         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."</b>"));
412         }
413 elsif ($r->{$f.'-host'} && $r->{$f.'-mask'}) {
414         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."/".
415                                     $r->{$f.'-mask'}."</b>"));
416         }
417 elsif ($r->{$f.'-host'}) {
418         push(@rv, &text('desc_'.$f, "<b>".$r->{$f.'-host'}."</b>"));
419         }
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>"));
424         }
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>"));
429         }
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>"));
434         }
435 return @rv;
436 }
437
438 # describe_from(&rule, where-mode)
439 # Returns a human-readable description of the match part of a NAT rule
440 sub describe_from
441 {
442 local ($r) = @_;
443 local @rv;
444 push(@rv, &text('desc_on', "<b>".$r->{'iface'}."</b>"));
445 if ($r->{'from'}) {
446         push(@rv, &describe_object($r, "from"), &describe_object($r, "fromto"));
447         }
448 elsif ($r->{'frommask'} eq '255.255.255.255' || $r->{'frommask'} eq "32") {
449         push(@rv, &text('desc_natfromh', "<b>$r->{'fromip'}</b>"));
450         }
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>"));
454         }
455 if ($r->{'dport1'} && $r->{'dport2'}) {
456         push(@rv, &text('desc_dport2', "<b>$r->{'dport1'}</b>",
457                                     "<b>$r->{'dport2'}</b>"));
458         }
459 elsif ($r->{'dport1'}) {
460         push(@rv, &text('desc_dport1', "<b>$r->{'dport1'}</b>"));
461         }
462 return &describe_join(\@rv, $_[1]);
463 }
464
465 # describe_to(&rule, where-mode)
466 # Returns a human-readable description of the translation part of a NAT rule
467 sub describe_to
468 {
469 local ($r, $where) = @_;
470 local @rv;
471 if ($r->{'rdrip'}) {
472         push(@rv, &text('desc_rdr', "<b>".join(" ", @{$r->{'rdrip'}})."</b>",
473                                     "<b>".$r->{'rdrport'}."</b>"));
474         }
475 elsif ($r->{'tostart'}) {
476         push(@rv, &text('desc_natrange', "<b>$r->{'tostart'}</b>",
477                                          "<b>$r->{'toend'}</b>"));
478         }
479 elsif ($r->{'toip'} eq '0.0.0.0' && $r->{'tomask'} eq "32") {
480         push(@rv, $text{'desc_nattoiface'});
481         }
482 elsif ($r->{'tomask'} eq '255.255.255.255' || $r->{'tomask'} eq "32") {
483         push(@rv, &text('desc_nattoh', "<b>$r->{'toip'}</b>"));
484         }
485 else {
486         push(@rv, &text('desc_natto', "<b>$r->{'toip'}</b>",
487                                       "<b>$r->{'tomask'}</b>"));
488         }
489 return &text($where ? 'desc_tolc' : 'desc_touc',
490              join(" $text{'desc_and'} ", @rv));
491 }
492
493 # describe_join(&array, where-mode)
494 sub describe_join
495 {
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'};
500 }
501
502 # create_rule(&rule)
503 # Add one rule to the config file, at the end
504 sub create_rule
505 {
506 # Add to file
507 if ($_[0]->{'type'} eq 'ipnat') {
508         $_[0]->{'file'} ||= $config{'ipnat_conf'};
509         }
510 else {
511         $_[0]->{'file'} ||= $config{'ipf_conf'};
512         }
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);
518 &flush_file_lines();
519
520 # Add to config cache
521 local $conf = &config_for_rule($_[0]);
522 $_[0]->{'index'} = scalar(@$conf);
523 push(@$conf, $_[0]);
524 }
525
526 # insert_rule(&rule, &before)
527 # Adds one rule to the config file, before some other
528 sub insert_rule
529 {
530 # Add to file
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);
538
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'});
546         }
547 splice(@$conf, $_[0]->{'index'}, 0, $_[0]);
548 }
549
550 # delete_rule(&rule)
551 # Remove one rule
552 sub delete_rule
553 {
554 # Update file
555 local $lref = &read_file_lines($_[0]->{'file'});
556 local $len = $_[0]->{'eline'} - $_[0]->{'line'} + 1;
557 splice(@$lref, $_[0]->{'line'}, $len);
558
559 # Update config cache
560 local $conf = &config_for_rule($_[0]);
561 splice(@$conf, $_[0]->{'index'}, 1);
562 local $c;
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'});
567         }
568 }
569
570 # modify_rule(&rule)
571 # Update one rule
572 sub modify_rule
573 {
574 # Update file
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);
580
581 # Update config cache
582 $_[0]->{'eline'} = $_[0]->{'line'} + $newlen - 1;
583 local $conf = &config_for_rule($_[0]);
584 local $c;
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'});
589         }
590 }
591
592 # swap_rules(&rule1, &rule2)
593 # Swap two rules in the config file, which MUST be next to each other
594 sub swap_rules
595 {
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,
601                (@lines1, @lines0));
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;
606         }
607 else {
608         splice(@$lref, $_[1]->{'line'}, $_[0]->{'eline'}-$_[1]->{'line'}+1,
609                (@lines0, @lines1));
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;
614         }
615
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];
621 }
622
623 sub config_for_rule
624 {
625 return $_[0]->{'type'} eq 'ipnat' ? &get_ipnat_config($_[0]->{'file'})
626                                   : &get_config($_[0]->{'file'});
627 }
628
629 # save_config(&rules, [file], type)
630 # Write out the entire config file
631 sub save_config
632 {
633 local $file = $_[1] || $_[0]->[0]->{'file'} ||
634               ($_[2] eq "ipnat" ? $config{'ipnat_conf'} : $config{'ipf_conf'});
635 &open_tempfile(FILE, ">$file");
636 local $r;
637 local $idx = 0;
638 local $lnum = 0;
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));
647         }
648 &close_tempfile(FILE);
649 if ($_[2] eq "ipnat") {
650         $get_ipnat_config_cache{$file} = $_[0];
651         }
652 else {
653         $get_config_cache{$file} = $_[0];
654         }
655 }
656
657 # rule_lines(&rule)
658 # Returns the lines of text that make up a rule
659 sub rule_lines
660 {
661 local ($rule) = @_;
662 local @rv = map { "# $_" } split(/\n/, $rule->{'cmt'});
663 local @w;
664 push(@w, "#") if (!$_[0]->{'active'});
665
666 if ($rule->{'type'} ne 'ipnat') {
667         # Standard firewall rule
668         # Add insert prefix
669         push(@w, "\@", $rule->{'insert'}) if ($rule->{'insert'} ne "");
670
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");
677                         }
678                 elsif ($rule->{'block-return'} ne "") {
679                         # XXX may be wrong
680                         push(@w, $rule->{'block-return-dest'} ? "return-icmp-as-dest"
681                                                               : "return-icmp");
682                         push(@w, "(", $rule->{'block-return'}, ")");
683                         }
684                 }
685         elsif ($rule->{'action'} eq "log") {
686                 # Add log action options
687                 &print_log("log");
688                 }
689         elsif ($rule->{'action'} eq "skip") {
690                 push(@w, $rule->{'skip'});
691                 }
692         elsif ($rule->{'action'} eq "call") {
693                 push(@w, "now") if ($rule->{'call-now'});
694                 push(@w, $rule->{'call'});
695                 }
696
697         # Add in or out
698         push(@w, $rule->{'dir'});
699
700         # Add options
701         if ($rule->{'olog'}) {
702                 push(@w, "log");
703                 &print_log("olog");
704                 }
705         if ($rule->{'tag'} ne "") {
706                 push(@w, "tag", $rule->{'tag'});
707                 }
708         if ($rule->{'quick'}) {
709                 push(@w, "quick");
710                 }
711         if ($rule->{'on'} ne "") {
712                 push(@w, "on", $rule->{'on'});
713                 }
714         if ($rule->{'dup-to'} ne "") {
715                 push(@w, "dup-to", $rule->{'dup-to'});
716                 }
717         if ($rule->{'fastroute'} ne "") {
718                 push(@w, "fastroute", $rule->{'fastroute'});
719                 if ($rule->{'fastroute-ip'} ne "") {
720                         push(@w, ":", $rule->{'fastroute-ip'});
721                         }
722                 }
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'});
727                         }
728                 }
729
730         # Add TOS
731         if ($rule->{'tos'} ne "") {
732                 push(@w, "tos", $rule->{'tos'});
733                 }
734
735         # Add TTL
736         if ($rule->{'ttl'} ne "") {
737                 push(@w, "ttl", $rule->{'ttl'});
738                 }
739
740         # Add protocol
741         if ($rule->{'proto'} ne "") {
742                 push(@w, "proto", $rule->{'proto'});
743                 }
744
745         # Add to/from section
746         if ($rule->{'all'}) {
747                 push(@w, "all");
748                 }
749         else {
750                 push(@w, "from");
751                 &print_object("from");
752                 push(@w, "to");
753                 &print_object("to");
754                 }
755
756         # Add IP options
757         if ($rule->{'flags1'} ne "") {
758                 push(@w, "flags", $rule->{'flags1'});
759                 if ($rule->{'flags2'} ne "") {
760                         push(@w, "/", $rule->{'flags2'});
761                         }
762                 }
763         if ($rule->{'with'}) {
764                 push(@w, "with", @{$rule->{'with'}});
765                 }
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'});
770                         }
771                 }
772         if ($rule->{'keep'} ne "") {
773                 push(@w, "keep", $rule->{'keep'});
774                 }
775
776         # Add group section
777         if ($rule->{'head'} ne "") {
778                 push(@w, "head", $rule->{'head'});
779                 }
780         if ($rule->{'group'} ne "") {
781                 push(@w, "group", $rule->{'group'});
782                 }
783         }
784 else {
785         # A NAT rule
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'}) {
791                         push(@w, "from");
792                         &print_object("from");
793                         push(@w, "to");
794                         &print_object("fromto");
795                         }
796                 else {
797                         &print_ipmask("from");
798                         }
799
800                 push(@w, "->");
801
802                 # Destination address
803                 if ($rule->{'tostart'}) {
804                         push(@w, "range", $rule->{'tostart'}, "-",
805                                           $rule->{'toend'});
806                         }
807                 else {
808                         &print_ipmask("to");
809                         }
810
811                 # Port mapping
812                 if ($rule->{'portmap'}) {
813                         push(@w, "portmap", $rule->{'portmap'});
814                         push(@w, $rule->{'portauto'} ? "auto" :
815                                    $rule->{'portmapfrom'}.":".
816                                    $rule->{'portmapto'});
817                         }
818
819                 # Proxy mapping
820                 if ($rule->{'proxyport'}) {
821                         &print_proxy("proxy");
822                         }
823
824                 # Add options
825                 if ($rule->{'proto'}) {
826                         push(@w, $rule->{'proto'});
827                         }
828                 if ($rule->{'frag'}) {
829                         push(@w, "frag");
830                         }
831                 if ($rule->{'age1'}) {
832                         push(@w, "age", $rule->{'age1'});
833                         if ($rule->{'age2'}) {
834                                 push(@w, "/", $rule->{'age2'});
835                                 }
836                         }
837                 if ($rule->{'mssclamp'}) {
838                         push(@w, "mssclamp", $rule->{'mssclamp'});
839                         }
840                 if ($rule->{'oproxyport'}) {
841                         &print_proxy("oproxy");
842                         }
843                 }
844         else {
845                 # Redirect rule .. add source
846                 &print_ipmask("from");
847
848                 # Add destination ports
849                 push(@w, "port");
850                 push(@w, $rule->{'dport1'});
851                 if ($rule->{'dport2'}) {
852                         push(@w, "-", $rule->{'dport2'});
853                         }
854
855                 push(@w, "->");
856
857                 # Add destination IPs
858                 push(@w, join(" , ", @{$rule->{'rdrip'}}));
859
860                 # Add destination port and protocol
861                 push(@w, "port", $rule->{'rdrport'}, $rule->{'rdrproto'});
862
863                 # Add options
864                 if ($rule->{'round-robin'}) {
865                         push(@w, "round-robin");
866                         }
867                 if ($rule->{'frag'}) {
868                         push(@w, "frag");
869                         }
870                 if ($rule->{'age1'}) {
871                         push(@w, "age", $rule->{'age1'});
872                         if ($rule->{'age2'}) {
873                                 push(@w, "/", $rule->{'age2'});
874                                 }
875                         }
876                 if ($rule->{'mssclamp'}) {
877                         push(@w, "mssclamp", $rule->{'mssclamp'});
878                         }
879                 if ($rule->{'proxy'}) {
880                         push(@w, "proxy", $rule->{'proxy'});
881                         }
882                 }
883         }
884
885 push(@rv, join(" ", @w));
886 return @rv;
887 }
888
889 # print_log(prefix)
890 sub print_log
891 {
892 local $pfx = $_[0];
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 "");
897 }
898
899 # print_object(dir)
900 sub print_object
901 {
902 local $dir = $_[0];
903 if ($rule->{$dir."-any"}) {
904         push(@w, "any");
905         }
906 elsif ($rule->{$dir."-thishost"}) {
907         push(@w, "<thishost>");
908         }
909 elsif ($rule->{$dir."-numhost"}) {
910         push(@w, $rule->{$dir."-numhost"}."/".$rule->{$dir."-nummask"});
911         }
912 else {
913         push(@w, $rule->{$dir."-host"});
914         if ($rule->{$dir."-mask"} ne "") {
915                 push(@w, "mask", $rule->{$dir."-mask"});
916                 }
917         }
918
919 if ($rule->{$dir."-port-comp"}) {
920         push(@w, "port", $rule->{$dir.'-port-comp'},
921                          $rule->{$dir.'-port-num'});
922         }
923 elsif ($rule->{$dir."-port-range"}) {
924         push(@w, "port", $rule->{$dir.'-port-start'},
925                          $rule->{$dir.'-port-range'},
926                          $rule->{$dir.'-port-end'});
927         }
928 }
929
930 # get_ipnat_config([file])
931 # Returns an array reference of ipnat rules
932 sub get_ipnat_config
933 {
934 local $file = $_[0] || $config{'ipnat_conf'};
935 return $get_ipnat_config_cache{$file} if ($get_ipnat_config_cache{$file});
936 open(FILE, $file);
937 while(<FILE>) {
938         # Read each line, splitting into words
939         s/\r|\n//g;
940         if (/^\s*(#*)\s*((map-block|map|bimap|mapit|rdr).*)$/) {
941                 # A NAT rule, perhaps commented
942                 local $nocmt = $2;
943                 local @w = split(/\s+/, $nocmt);
944                 local @cmts = split(/\n/, $cmt);
945                 local $rule = { 'index' => scalar(@rv),
946                                 'type' => 'ipnat',
947                                 'line' => $lnum-scalar(@cmts),
948                                 'eline' => $lnum,
949                                 'file' => $file,
950                                 'text' => $_,
951                                 'cmt' => $cmt,
952                                 'active' => !$1 };
953                 $cmt = undef;
954
955                 # Parse action
956                 $rule->{'action'} = shift(@w);
957
958                 if ($rule->{'action'} eq 'map' ||
959                     $rule->{'action'} eq 'bimap' ||
960                     $rule->{'action'} eq 'map-block') {
961                         # Parse interface
962                         $rule->{'iface'} = shift(@w);
963
964                         if ($w[0] eq "from") {
965                                 # A full from XXX to YYY block
966                                 shift(@w);      # skip 'from'
967                                 $rule->{'from'} = 1;
968                                 &parse_object("from");
969                                 shift(@w);      # skip 'to'
970                                 &parse_object("fromto");
971                                 }
972                         else {
973                                 # IP and netmask only
974                                 &parse_ipmask("from");
975                                 }
976
977                         local $arrow = shift(@w);
978                         $arrow eq "->" ||
979                                 &error("error parsing IPNAT line $_ at ".
980                                        "$arrow line $lnum");
981
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;
989                                         }
990                                 else {
991                                         shift(@w);      # skip '-'
992                                         $rule->{'toend'} = shift(@w);
993                                         }
994                                 }
995                         else {
996                                 # Destination IP and netmask only
997                                 &parse_ipmask("to");
998                                 }
999
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;
1007                                         }
1008                                 else {
1009                                         ($rule->{'portmapfrom'},
1010                                          $rule->{'portmapto'}) =
1011                                                 split(/:/, shift(@w));
1012                                         }
1013                                 }
1014
1015                         if ($w[0] eq "proxy") {
1016                                 # Parse proxy
1017                                 &parse_proxy("proxy");
1018                                 }
1019
1020                         # Parse options
1021                         if ($w[0] eq "tcp/udp" ||
1022                             getprotobyname($w[0]) || $w[0] =~ /^\d+$/) {
1023                                 $rule->{'proto'} = shift(@w);
1024                                 }
1025                         if ($w[0] eq "frag") {
1026                                 $rule->{'frag'} = shift(@w);
1027                                 }
1028                         if ($w[0] eq "age") {
1029                                 shift(@w);      # skip 'age'
1030                                 $rule->{'age1'} = shift(@w);
1031                                 if ($w[0] eq "/") {
1032                                         shift(@w);      # skip '/'
1033                                         $rule->{'age2'} = shift(@w);
1034                                         }
1035                                 }
1036                         if ($w[0] eq "mssclamp") {
1037                                 shift(@w);      # skip 'mssclamp'
1038                                 $rule->{'mssclamp'} = shift(@w);
1039                                 }
1040                         if ($w[0] eq "proxy") {
1041                                 # Parse proxy option
1042                                 &parse_proxy("oproxy");
1043                                 }
1044                         }
1045                 elsif ($rule->{'action'} eq 'rdr') {
1046                         # Parse redirect
1047                         $rule->{'iface'} = shift(@w);
1048
1049                         # Parse IP and netmask
1050                         &parse_ipmask("from");
1051
1052                         # Parse destination ports
1053                         local $ports = shift(@w);
1054                         $ports eq "port" ||
1055                                 &error("error parsing IPNAT line $_ at ".
1056                                        "$ports line $lnum");
1057                         $rule->{'dport1'} = shift(@w);
1058                         if ($w[0] eq "-") {
1059                                 shift(@w);      # skip '-'
1060                                 $rule->{'dport2'} = shift(@w);
1061                                 }
1062
1063                         local $arrow = shift(@w);
1064                         $arrow eq "->" ||
1065                                 &error("error parsing IPNAT line $_ at ".
1066                                        "$arrow line $lnum");
1067
1068                         # Parse destination IPs
1069                         $rule->{'rdrip'} = [ shift(@w) ];
1070                         while($w[0] eq ",") {
1071                                 shift(@w);      # skip ,
1072                                 push(@{$rule->{'rdrip'}}, shift(@w));
1073                                 }
1074
1075                         # Parse destination port
1076                         shift(@w);      # skip 'port'
1077                         $rule->{'rdrport'} = shift(@w);
1078
1079                         # Parse protocol
1080                         $rule->{'rdrproto'} = shift(@w);
1081
1082                         # Parse other options
1083                         if ($w[0] eq "round-robin") {
1084                                 $rule->{'round-robin'} = shift(@w);
1085                                 }
1086                         if ($w[0] eq "frag") {
1087                                 $rule->{'frag'} = shift(@w);
1088                                 }
1089                         if ($w[0] eq "age") {
1090                                 shift(@w);      # skip 'age'
1091                                 $rule->{'age1'} = shift(@w);
1092                                 if ($w[0] eq "/") {
1093                                         shift(@w);      # skip '/'
1094                                         $rule->{'age2'} = shift(@w);
1095                                         }
1096                                 }
1097                         if ($w[0] eq "mssclamp") {
1098                                 shift(@w);      # skip 'mssclamp'
1099                                 $rule->{'mssclamp'} = shift(@w);
1100                                 }
1101                         if ($w[0] eq "proxy") {
1102                                 shift(@w);      # skip 'proxy'
1103                                 $rule->{'proxy'} = shift(@w);
1104                                 }
1105                         }
1106                 push(@rv, $rule);
1107                 }
1108         elsif (/^\s*#\s*(.*)$/) {
1109                 # A comment line
1110                 $cmt .= "\n" if ($cmt);
1111                 $cmt .= $1;
1112                 }
1113         nextline:
1114         $lnum++;
1115         }
1116 close(FILE);
1117 $get_ipnat_config_cache{$file} = \@rv;
1118 return $get_ipnat_config_cache{$file};
1119 }
1120
1121 # parse_ipmask(prefix)
1122 sub parse_ipmask
1123 {
1124 local ($pfx) = @_;
1125 local $ip = shift(@w);
1126 if ($ip =~ /^(\S+)\/(\S+)$/) {
1127         $rule->{$pfx."ip"} = $1;
1128         $rule->{$pfx."mask"} = $2;
1129         }
1130 else {
1131         $rule->{$pfx."ip"} = $ip;
1132         shift(@w);      # skip '/' or 'netmask'
1133         $rule->{$pfx."mask"} = shift(@w);
1134         }
1135 }
1136
1137 # print_ipmask(prefix)
1138 sub print_ipmask
1139 {
1140 local ($pfx) = @_;
1141 push(@w, $rule->{$pfx."ip"}."/".$rule->{$pfx."mask"});
1142 }
1143
1144 # parse_proxy(prefix)
1145 sub parse_proxy
1146 {
1147 local ($pfx) = @_;
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;
1155         }
1156 else {
1157         shift(@w);      # skip '/'
1158         $rule->{$pfx.'proto'} = shift(@w);
1159         }
1160 }
1161
1162 # print_proxy(prefix)
1163 sub print_proxy
1164 {
1165 local ($pfx) = @_;
1166 push(@w, "proxy", "port", $rule->{$pfx.'port'},
1167          $rule->{$pfx.'name'}."/".$rule->{$pfx.'proto'});
1168 }
1169
1170 # list_protocols()
1171 # Returns a list of IP protocols
1172 sub list_protocols
1173 {
1174 local @stdprotos = ( 'tcp', 'udp', 'icmp' );
1175 local @otherprotos;
1176 open(PROTOS, "/etc/protocols");
1177 while(<PROTOS>) {
1178         s/\r|\n//g;
1179         s/#.*$//;
1180         push(@otherprotos, $1) if (/^(\S+)\s+(\d+)/);
1181         }
1182 close(PROTOS);
1183 @otherprotos = sort { lc($a) cmp lc($b) } @otherprotos;
1184 return &unique(@stdprotos, @otherprotos);
1185 }
1186
1187 # apply_configuration()
1188 # Activates the IPfilter configuration, and returns undef on success or an
1189 # error message on failure.
1190 sub apply_configuration
1191 {
1192 local $out;
1193 if ($config{'apply_cmd'} && !$config{'smf'}) {
1194         $out = &backquote_logged("$config{'apply_cmd'} 2>&1 </dev/null");
1195         }
1196 else {
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");
1202                 }
1203         }
1204 return $? || $out =~ /error|failed|unknown/i ? "<pre>$out</pre>" : undef;
1205 }
1206
1207 # check_firewall_init()
1208 # Returns 2 if started at boot, 1 or 0 if not
1209 sub check_firewall_init
1210 {
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;
1218         }
1219 else {
1220         # Look at init script
1221         &foreign_require("init", "init-lib.pl");
1222         return &init::action_status($init_script);
1223         }
1224 }
1225
1226 # create_firewall_init()
1227 # Create (if necessary) the ipfilter init script, and enable it
1228 sub create_firewall_init
1229 {
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>"));
1235         if ($atboot != 2) {
1236                 &smf::svc_state_cmd($smf::text{'state_enable'},
1237                                     [ $config{'smf'} ]);
1238                 }
1239         }
1240 else {
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",
1249                               $start, $stop);
1250         }
1251 }
1252
1253 # delete_firewall_init()
1254 # Turn off the firewall at boot time
1255 sub delete_firewall_init
1256 {
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>"));
1262         if ($atboot == 2) {
1263                 &smf::svc_state_cmd($smf::text{'state_disable'},
1264                                     [ $config{'smf'} ]);
1265                 }
1266         }
1267 else {
1268         # Disable init script
1269         &foreign_require("init", "init-lib.pl");
1270         &init::disable_at_boot($init_script);
1271         }
1272 }
1273
1274 # unapply_configuration()
1275 # Replace the IPfilter configuration file with active rules
1276 sub unapply_configuration
1277 {
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 ($?);
1282
1283 &open_lock_tempfile(OUT, ">$config{'ipf_conf'}");
1284 &print_tempfile(OUT, $inrules);
1285 &print_tempfile(OUT, $outrules);
1286 &close_tempfile(OUT);
1287
1288 return undef;
1289 }
1290
1291 # list_cluster_servers()
1292 # Returns a list of servers on which the firewall is managed
1293 sub list_cluster_servers
1294 {
1295 &foreign_require("servers", "servers-lib.pl");
1296 local %ids = map { $_, 1 } split(/\s+/, $config{'servers'});
1297 return grep { $ids{$_->{'id'}} } &servers::list_servers();
1298 }
1299
1300 # add_cluster_server(&server)
1301 sub add_cluster_server
1302 {
1303 local @sids = split(/\s+/, $config{'servers'});
1304 $config{'servers'} = join(" ", @sids, $_[0]->{'id'});
1305 &save_module_config();
1306 }
1307
1308 # delete_cluster_server(&server)
1309 sub delete_cluster_server
1310 {
1311 local @sids = split(/\s+/, $config{'servers'});
1312 $config{'servers'} = join(" ", grep { $_ != $_[0]->{'id'} } @sids);
1313 &save_module_config();
1314 }
1315
1316 # server_name(&server)
1317 sub server_name
1318 {
1319 return $_[0]->{'desc'} ? $_[0]->{'desc'} : $_[0]->{'host'};
1320 }
1321
1322 # copy_to_cluster([force])
1323 # Copy all firewall rules from this server to those in the cluster
1324 sub copy_to_cluster
1325 {
1326 return if (!$config{'servers'});                # no servers defined
1327 return if (!$_[0] && $config{'cluster_mode'});  # only push out when applying
1328 local $s;
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'});
1337                 }
1338         }
1339 }
1340
1341 # apply_cluster_configuration()
1342 # Activate the current configuration on all servers in the cluster
1343 sub apply_cluster_configuration
1344 {
1345 return undef if (!$config{'servers'});
1346 if ($config{'cluster_mode'}) {
1347         &copy_to_cluster(1);
1348         }
1349 local $s;
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");
1353         if ($err) {
1354                 return &text('apply_remote', $s->{'host'}, $err);
1355                 }
1356         }
1357 return undef;
1358 }
1359
1360 # object_input(rule, prefix, [no-thishost])
1361 # Returns HTML for selecting an address and HTML for selecting a port
1362 sub object_input
1363 {
1364 local ($rule, $f, $nothis) = @_;
1365
1366 # Address part
1367 local $ft;
1368 $ft .= &ui_oneradio($f, "any", $text{'edit_any'},
1369                     $rule->{$f."-any"} || $rule->{'all'})."\n";
1370 if ($nothis) {
1371         $ft .= "<br>\n";
1372         }
1373 else {
1374         $ft .= &ui_oneradio($f, "thishost", $text{'edit_thishost'},
1375                             $rule->{$f."-thishost"})."<br>\n";
1376         }
1377
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";
1383
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";
1390
1391 # Ports part
1392 local $pt;
1393
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";
1414
1415 return ($ft, $pt);
1416 }
1417
1418 # parse_object_input(rule, prefix)
1419 sub parse_object_input
1420 {
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;
1428         }
1429 elsif ($in{$f} eq "thishost") {
1430         $rule->{$f."-thishost"} = 1;
1431         }
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"};
1439         }
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"};
1449         }
1450
1451 # Parse port section
1452 delete($rule->{$f."-port-comp"});
1453 delete($rule->{$f."-port-range"});
1454
1455 return ($ft, $pt);
1456 }
1457
1458 # parse_object_input(rule, prefix)
1459 sub parse_object_input
1460 {
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;
1468         }
1469 elsif ($in{$f} eq "thishost") {
1470         $rule->{$f."-thishost"} = 1;
1471         }
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"};
1479         }
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"};
1489         }
1490
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"};
1499         }
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"};
1508         }
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"};
1517         }
1518 }
1519
1520 # protocol_input(name, value, add-any, add-tcp-udp)
1521 sub protocol_input
1522 {
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);
1529 }
1530
1531 # valid_port(name|number)
1532 # Returns 1 if give a valid port number or TCP or UDP name
1533 sub valid_port
1534 {
1535 local $n = $_[0];
1536 return getservbyname($n, "tcp") ||
1537        getservbyname($n, "udp") ||
1538        ($n =~ /^\d+$/ && $n > 0 && $n < 65536);
1539 }
1540
1541 sub valid_hexdec
1542 {
1543 return $_[0] =~ /^\d+$/ || $_[0] =~ /^0x([0-9a-f]+)$/;
1544 }
1545
1546 1;
1547