Handle hostnames with upper-case letters
[webmin.git] / itsecur-firewall / itsecur-lib.pl
1 # itsecure-lib.pl
2 # Version
3 # ITsecur
4 # Common functions for all firewall types
5 # XXX only backup firewall module users?
6
7 BEGIN { push(@INC, ".."); };
8 use WebminCore;
9 &init_config();
10 do "$config{'type'}-lib.pl";
11
12 @opts = ( 'rules', 'services', 'groups', 'nat','nat2', 'pat', 'spoof', 'syn', 'logs',
13           'authlogs', 'report',
14           'users',
15           &supports_time() ? ('times') : (),
16           'backup', 'restore',
17           'remote', 'import' );
18 # Take out to test
19 #       &supports_bandwidth() ? ('bandwidth') : (),
20 @backup_opts = grep { $_ ne 'logs' && $_ ne 'backup' && $_ ne 'restore' }
21                     (@opts, 'ipsec', 'searches', 'config');
22
23 $groups_file = "$module_config_directory/groups";
24 $standard_services_file = "$module_root_directory/standard-services";
25 $services_file = "$module_config_directory/services";
26 $rules_file = "$module_config_directory/rules";
27 $nat_file = "$module_config_directory/nat";
28 $nat2_file = "$module_config_directory/nat2";
29 $pat_file = "$module_config_directory/pat";
30 $spoof_file = "$module_config_directory/spoof";
31 $syn_file = "$module_config_directory/syn";
32 $times_file = "$module_config_directory/times";
33 $active_interfaces = "$module_config_directory/active";
34 $prerules = "$module_config_directory/prerules";
35 $postrules = "$module_config_directory/postrules";
36 $prenat = "$module_config_directory/prenat";
37 $postnat = "$module_config_directory/postnat";
38 $debug_file = "$module_config_directory/debug";
39
40 $searches_dir = "$module_config_directory/searches";
41
42 @config_files = ( $groups_file, $services_file,
43                   $rules_file, $nat_file, $nat2_file, $pat_file, $spoof_file,
44                   $syn_file, $times_file );
45
46 %access = &get_module_acl();
47 if (defined($access{'edit'})) {
48         if ($access{'edit'}) {
49                 @edit_access = @read_access = split(/\s+/, $access{'features'});
50                 }
51         else {
52                 @read_access = split(/\s+/, $access{'features'});
53                 }
54         }
55 else {
56         @edit_access = split(/\s+/, $access{'features'});
57         @read_access = split(/\s+/, $access{'rfeatures'});
58         }
59 %edit_access = map { $_, 1 } @edit_access;
60 %read_access = map { $_, 1 } @read_access;
61
62 $cron_cmd = "$module_config_directory/backup.pl";
63
64 # list_groups([file])
65 # Returns a list of groups. Each has a name and zero or more member hosts,
66 # IP addresses, networks or other groups.
67 sub list_groups
68 {
69 local @rv;
70 open(GROUPS, $_[0] || $groups_file);
71 while(<GROUPS>) {
72         s/\r|\n//g;
73         if (/^(\S+)\t+(.*)$/) {
74                 local $group = { 'name' => $1,
75                                  'members' => [ split(/\t+/, $2) ],
76                                  'index' => scalar(@rv) };
77                 push(@rv, $group);
78                 }
79         }
80 close(GROUPS);
81 return @rv;
82 }
83
84 # save_groups(group, ...)
85 # Updates the groups list
86 sub save_groups
87 {
88 local $g;
89 local @SortGroups=();
90 foreach $g (@_) {
91         push(@SortGroups,$g->{'name'}."\t".join("\t", @{$g->{'members'}})."\n"); 
92         }
93 open(GROUPS, ">$groups_file");
94 print GROUPS sort { lc($a) cmp lc($b) } @SortGroups;
95 close(GROUPS);
96 &automatic_backup();
97 }
98
99 # list_services([file])
100 # Returns a list of services, each of which has a name and multiple
101 # protocols and port
102 sub list_services
103 {
104 local ($sf, @rv);
105 #if (!-r $standard_services_file) {
106 #       system("cp $module_root_directory/standard-services $standard_services_file");
107 #       }
108 foreach $sf ($_[0] || $services_file, $standard_services_file) {
109         local @frv;
110         open(SERVS, $sf);
111         while(<SERVS>) {
112                 s/\r|\n//g;
113                 s/#.*$//;
114                 s/\s+$//;
115                 if (/^(\S+)\t+(.*)$/) {
116                         local $serv = { 'name' => $1,
117                                         'standard' =>
118                                             ($sf eq $standard_services_file),
119                                         'index' => scalar(@frv) };
120                         local @pps = split(/\s*\t+\s*/, $2);
121                         local $i;
122                         for($i=0; $i<@pps; $i+=2) {
123                                 if ($pps[$i] eq "other") {
124                                         push(@{$serv->{'others'}}, $pps[$i+1]);
125                                         }
126                                 else {
127                                         push(@{$serv->{'protos'}}, $pps[$i]);
128                                         push(@{$serv->{'ports'}}, $pps[$i+1]);
129                                         }
130                                 }
131                         push(@frv, $serv);
132                         }
133                 }
134         close(SERVS);
135         if ($sf eq $standard_services_file) {
136                 push(@rv, sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @frv);
137                 }
138         else {
139                 push(@rv, @frv);
140                 }
141         }
142 return @rv;
143 }
144
145 # combine_services(comma-list, &services-hash)
146 # Returns lists of protocols and port numbers taken from a comma-separated list
147 # of service names
148 sub combine_services
149 {
150 local (@protos, @ports);
151 foreach $sn (split(/,/, $_[0])) {
152         local $serv = $_[1]->{$sn};
153         push(@protos, @{$serv->{'protos'}});
154         push(@ports, @{$serv->{'ports'}});
155         local ($cprotos, $cports) = &combine_services(join(",", @{$serv->{'others'}}), $_[1]);
156         push(@protos, @$cprotos);
157         push(@ports, @$cports);
158         }
159 return (\@protos, \@ports);
160 }
161
162 # save_services(service, ...)
163 sub save_services
164 {
165 #open(SERVS, ">$services_file");
166
167 local @SortGroups;
168 local $data;
169 foreach $serv (@_) {
170         next if ($serv->{'standard'});
171         $data=$serv->{'name'};
172         local $i;
173         for($i=0; $i<@{$serv->{'protos'}}; $i++) {
174                 $data = $data . "\t" . $serv->{'protos'}->[$i] . "\t" . $serv->{'ports'}->[$i];
175                 }
176         for($i=0; $i<@{$serv->{'others'}}; $i++) {
177                 if ( $serv->{'others'}->[$i] ne $serv->{'name'}) {      
178                         $data = $data . "\tother\t".$serv->{'others'}->[$i];
179                         }
180                 }
181         $data=$data . "\n";
182         push(@SortGroups,$data);
183         }
184
185
186 open(SERVS, ">$services_file");
187 print SERVS sort { lc($a) cmp lc($b) } @SortGroups;
188 close(SERVS);
189
190 }
191
192 # list_rules([file])
193 # Returns a list of rules, each of which has a source, destination, service,
194 # action and log-flag
195 sub list_rules
196 {
197 local @rv;
198 open(RULES, $_[0] || $rules_file);
199 local $rn = 1;
200 while(<RULES>) {
201         s/\r|\n//g;
202         if (/^(#*)([^\t]+)\t+([^\t]+)\t+(\S+)\t+(\S+)\t+(\d+)(\t+(\S+))?(\t+(\S+))?$/) {
203                 local $rule = { 'enabled' => !$1,
204                                 'source' => $2,
205                                 'dest' => $3,
206                                 'service' => $4,
207                                 'action' => $5,
208                                 'log' => $6,
209                                 'time' => $8 || "*",
210                                 'desc' => &un_urlize($10 || "*"),
211                                 'index' => scalar(@rv),
212                                 'num' => $rn++ };
213                 push(@rv, $rule);
214                 }
215         elsif (/^(\S+)$/) {
216                 local $sep = { 'sep' => 1,
217                                'desc' => &un_urlize($1),
218                                 'index' => scalar(@rv) };
219                 push(@rv, $sep);
220                 }
221         }
222 close(RULES);
223 return @rv;
224 }
225
226 # save_rules(rule, ...)
227 sub save_rules
228 {
229 open(RULES, ">$rules_file");
230 local $rule;
231 foreach $rule (@_) {
232         if ($rule->{'sep'}) {
233                 print RULES &urlize($rule->{'desc'}),"\n";
234                 }
235         else {
236                 print RULES ($rule->{'enabled'} ? "" : "#"),
237                             $rule->{'source'},"\t",
238                             $rule->{'dest'},"\t",
239                             $rule->{'service'},"\t",
240                             $rule->{'action'},"\t",
241                             $rule->{'log'},"\t",
242                             $rule->{'time'},"\t",
243                             $rule->{'desc'} eq "*" ? "*"
244                                 : &urlize($rule->{'desc'}),"\n";
245                 }
246         }
247 close(RULES);
248 }
249
250 # group_name(string, [direction])
251 # Given a source or destination name that may be a group, makes it nice
252 sub group_name
253 {
254 if ($_[0] =~ /^\@(.*)$/) {
255         # Host group
256         return "<font color=#0000ff>$1</font>";
257         }
258 elsif ($_[0] =~ /^\!\@(.*)$/) {
259         # Negated host group
260         return "<font color=#0000ff>".&text('not', "$1")."</font>";
261         }
262 elsif ($_[0] =~ /^\%(.*)$/) {
263         # Interface
264         return "<font color=#C0C0C0>".&text('iface', "$1")."</font>";
265         }
266 elsif ($_[0] =~ /^\!\%(.*)$/) {
267         # Negated interface
268         return "<font color=#C0C0C0>".&text('iface_not', "$1")."</font>";
269         }
270 elsif ($_[0] eq '*') {
271         # Anywhere
272         return $text{'anywhere'};
273         }
274 elsif ($_[0] eq '!*') {
275         # Nowhere
276         return $text{'nowhere'};
277         }
278 elsif ($_[0] =~ /^\!(.*\/.*)$/) {
279         # Negated network address
280         return &text('not', "<tt><font color=#008800>$1</font></tt>");
281         }
282 elsif ($_[0] =~ /^\!([0-9\.]+)\-([0-9]+)$/) {
283         # Negated address range
284         return &text('not', "<tt><font color=#ffff00>$1-$2</font></tt>");
285         }
286 elsif ($_[0] =~ /^\!(.*)$/) {
287         # Negated hostname or IP
288         return &text('not', "<tt>$1</tt>");
289         }
290 elsif ($_[0] =~ /^(.*\/.*)$/) {
291         # Network address
292         return "<tt><font color=#008800>$_[0]</font></tt>";
293         }
294 elsif ($_[0] =~ /^([0-9\.]+)\-([0-9]+)$/) {
295         # Address range
296         return "<tt><font color=#ffff00>$1-$2</font></tt>";
297         }
298 else {
299         # Hostname or IP
300         return "<tt>$_[0]</tt>";
301         }
302 }
303
304 # group_names(string)
305 sub group_names
306 {
307 return join(", ", map { &group_name($_) } split(/\s+/, $_[0]));
308 }
309
310 # group_names_link(dest, [from], [direction])
311 sub group_names_link
312 {
313 local $g;
314 local @rv;
315 foreach $g (split(/\s+/, $_[0])) {
316         if ($g =~ /^\@(.*)$/ || $g =~ /^\!\@(.*)$/) {
317                 push(@rv, "<a href='edit_group.cgi?name=$1&from=$_[1]'>".
318                           &group_name($g, $_[2])."</a>");
319                 }
320         else {
321                 push(@rv, &group_name($g, $_[2]));
322                 }
323         }
324 return join(", ", @rv);
325 }
326
327 # group_input(name, [value], [blankoption], [multiple])
328 sub group_input
329 {
330 local @groups = &list_groups();
331 return undef if (!@groups);
332 local $rv = $_[3] ? "<select name=$_[0] size=5 multiple>"
333                   : "<select name=$_[0]>\n";
334 if ($_[2]) {
335         $rv .= sprintf "<option value='' %s>%s\n",
336                 $_[1] ? "" : "selected", $_[2] == 2 ? $text{'other'} : "&nbsp;";
337         }
338 local $g;
339 local %vals = map { $_, 1 } split(/\s+/, $_[1]);
340 foreach $g (@groups) {
341         $rv .= sprintf "<option value=%s %s>%s\n",
342                 $g->{'name'}, $vals{$g->{'name'}} ? "selected" : "",
343                 $g->{'name'};
344         }
345 $rv .= "</select>\n";
346 return $rv;
347 }
348
349 # service_input(name, value, [blankoption], [multiple], [norange])
350 sub service_input
351 {
352 local @servs = &list_services();
353 local %got = map { $_, 1 } split(/,/, $_[1]);
354 local $rv = $_[3] ? "<select name=$_[0] size=5 multiple>"
355                   : "<select name=$_[0]>\n";
356 if ($_[2]) {
357         $rv .= sprintf "<option value='' %s>%s\n",
358                 $_[1] ? "" : "selected", $_[2] == 2 ? $text{'other'} : "&nbsp;";
359         }
360 local $s;
361 foreach $s (@servs) {
362         local $desc;
363         local @up = &unique(@{$s->{'protos'}});
364         local $i;
365         if (@up == 1) {
366                 $desc = uc($up[0])." ".join(", ", @{$s->{'ports'}});
367                 }
368         else {
369                 for($i=0; $i<@{$s->{'protos'}}; $i++) {
370                         $desc .= ", " if ($desc);
371                         $desc .= uc($s->{'protos'}->[$i])."/".  
372                                  $s->{'ports'}->[$i];
373                         }
374                 }
375         for($i=0; $i<@{$s->{'others'}}; $i++) {
376                 $desc .= ", " if ($desc);
377                 $desc .= $s->{'others'}->[$i];
378                 }
379         $rv .= sprintf "<option value=%s %s>%s%s\n",
380                 $s->{'name'}, $got{$s->{'name'}} ? "selected" : "",
381                 $s->{'name'}, $_[4] ? "" : " ($desc)";
382         }
383 $rv .= "</select>\n";
384 return $rv;
385 }
386
387 # action_input(name, value, [select-mode])
388 sub action_input
389 {
390 local $rv;
391 local $a;
392 if ($_[2]) {
393         $rv .= "<select name=$_[0]>\n";
394         foreach $a (@actions) {
395                 $rv .= sprintf "<option value=%s %s>%s\n",
396                         $a, $_[1] eq $a ? "selected" : "",
397                         $text{"rule_".$a};
398                 }
399         $rv .= "</select>\n";
400         }
401 else {
402         foreach $a (@actions) {
403                 $rv .= sprintf "<input type=radio name=%s value=%s %s>%s\n",
404                         $_[0], $a, $_[1] eq $a ? "checked" : "",
405                         $text{"rule_".$a};
406                 }
407         }
408 return $rv;
409 }
410
411
412 # protocol_input(name, value)
413 sub protocol_input
414 {
415 local @protos = ( 'tcp', 'udp', 'icmp', 'ip' );
416 #open(PROTOS, "/etc/protocols");
417 #while(<PROTOS>) {
418 #        s/\r|\n//g;
419 #        s/#.*$//;
420 #        push(@protos, $1) if (/^(\S+)\s+(\d+)/);
421 #        }
422 #close(PROTOS);
423 local $p;
424 local $rv = "<select name=$_[0]>\n";
425 $rv .= sprintf "<option value='' %s>&nbsp;\n",
426                 $_[1] eq '' ? "selected" : "";
427 foreach $p (&unique(@protos)) {
428         $rv .= sprintf "<option value='%s' %s>%s\n",
429                         $p, $_[1] eq $p && $p ? "selected" : "",
430                         uc($p) || "-------";
431         }
432 $rv .= "</select>\n";
433 return $rv;
434 }
435
436 sub valid_host
437 {
438
439 if (&check_ipaddress($_[0])) {
440         return 1;
441         }
442 elsif (gethostbyname($_[0])) {
443         return 2;
444         }
445 elsif (&check_netaddress($_[0])) {
446         #$_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/) {
447         return 3;
448         }
449 elsif ($_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\-(\d+)$/) {
450         return 4;
451         }
452 else {
453         return 0;
454         }
455 }
456
457 # iface_input(name, value, [realonly], [nother], [nonemode])
458 sub iface_input
459 {
460 local @ifaces;
461 if (&foreign_check("net")) {
462         &foreign_require("net", "net-lib.pl");
463         local $i;
464         foreach $i (&net::active_interfaces(), &net::boot_interfaces()) {
465                 push(@ifaces, $i->{'fullname'})
466                         if (!$_[2] || $i->{'virtual'} eq '');
467                 }
468         @ifaces = &unique(@ifaces);
469         }
470 if (@ifaces) {
471         local $rv = "<select name=$_[0]>\n";
472         local ($i, $found);
473         if ($_[4]) {
474                 $rv .= sprintf "<option value='' %s>%s\n",
475                         $_[1] eq "" ? "selected" : "", "&lt;None&gt;";
476                 $found++ if ($_[1] eq "");
477                 }
478         foreach $i (@ifaces) {
479                 $rv .= sprintf "<option value=%s %s>%s\n",
480                         $i, $_[1] eq $i ? "selected" : "", $i;
481                 $found++ if ($_[1] eq $i);
482                 }
483         if ($_[3]) {
484                 $rv .= "<option value=$_[1] selected>$_[1]\n" if (!$found && $_[1]);
485                 }
486         else {
487                 $rv .= sprintf "<option value='' %s> %s\n",
488                                 !$found && $_[1] ? "selected" : "", $text{'rule_oifc'};
489                 $rv .= "</select>\n";
490                 $rv .= sprintf "<input name=$_[0]_other size=6 value='%s'>\n",
491                                 !$found ? $_[1] : "";
492                 }
493         return $rv;
494         }
495 else {
496         return "<input name=$_[0] size=6 value='$_[1]'>";
497         }
498 }
499
500 # time_input(name, [value])
501 sub time_input
502 {
503 local @times = &list_times();
504 return undef if (!@times);
505 local $rv = "<select name=$_[0]>\n";
506 local $t;
507 foreach $t (@times) {
508         $rv .= sprintf "<option value=%s %s>%s\n",
509                 $t->{'name'}, $t->{'name'} eq $_[1] ? "selected" : "",
510                 $t->{'name'};
511         }
512 $rv .= "</select>\n";
513 return $rv;
514 }
515
516 # get_nat([file])
517 sub get_nat
518 {
519 local ($iface, @nets, @maps);
520 open(NAT, $_[0] || $nat_file) || return ( );
521 chop($iface = <NAT>);
522 while(<NAT>) {
523         s/\r|\n//g;
524         if (/^(\S+)$/) {
525                 push(@nets, $_);
526                 }
527         elsif (/^(\S+)\t+(\S+)\t+(\S+)$/) {
528                 push(@maps, [ $1, $2, $3 ]);
529                 }
530         elsif (/^(\S+)\t+(\S+)$/) {
531                 push(@maps, [ $1, $2 ]);
532                 }
533         }
534 close(NAT);
535 return ( $iface, @nets, @maps );
536 }
537
538 # save_nat(iface, net, ..)
539 sub save_nat
540 {
541 open(NAT, ">$nat_file");
542 print NAT shift(@_),"\n";
543 local $n;
544 foreach $n (@_) {
545         if (ref($n)) {
546                 print NAT join("\t", @$n),"\n";
547                 }
548         else {
549                 print NAT $n,"\n";
550                 }
551         }
552 close(NAT);
553 }
554
555 sub save_nat2
556 {
557 open(NAT, ">$nat2_file");
558 print NAT shift(@_),"\n";
559 local $n;
560 foreach $n (@_) {
561         if (ref($n)) {
562                 print NAT join("\t", @$n),"\n";
563                 }
564         else {
565                 print NAT $n,"\n";
566                 }
567         }
568 close(NAT);
569 }
570
571
572 # get_pat([file])
573 sub get_pat
574 {
575 local ($defiface, @forwards);
576 open(PAT, $_[0] || $pat_file) || return ( );
577 chop($defiface = <PAT>);
578 while(<PAT>) {
579         s/\r|\n//g;
580         if (/^(\S+)\t+(\S+)\t+(\S+)$/) {
581                 push(@forwards, { 'service' => $1,
582                                   'host' => $2,
583                                   'iface' => $3 });
584                 }
585         elsif (/^(\S+)\t+(\S+)$/) {
586                 push(@forwards, { 'service' => $1,
587                                   'host' => $2,
588                                   'iface' => $defiface });
589                 }
590         }
591 close(PAT);
592 return @forwards;
593 }
594
595 # save_pat(forward, ...)
596 sub save_pat
597 {
598 open(PAT, ">$pat_file");
599 print PAT (@_ ? $_[0]->{'iface'} : ""),"\n";
600 local $f;
601 foreach $f (@_) {
602         if ($f->{'iface'}) {
603                 print PAT "$f->{'service'}\t$f->{'host'}\t$f->{'iface'}\n";
604                 }
605         else {
606                 print PAT "$f->{'service'}\t$f->{'host'}\n";
607                 }
608         }
609 close(PAT);
610 }
611
612 # get_spoof([file])
613 sub get_spoof
614 {
615 local ($iface, @nets);
616 open(PAT, $_[0] || $spoof_file) || return ( );
617 chop($iface = <PAT>);
618 while(<PAT>) {
619         s/\r|\n//g;
620         if (/^(\S+)$/) {
621                 push(@nets, $_);
622                 }
623         }
624 close(PAT);
625 return ( $iface, @nets );
626 }
627
628 # save_spoof(iface, net, ...)
629 sub save_spoof
630 {
631 open(PAT, ">$spoof_file");
632 print PAT shift(@_),"\n";
633 local $s;
634 foreach $s (@_) {
635         print PAT "$s\n";
636         }
637 close(PAT);
638 }
639
640 # get_syn([file])
641 sub get_syn
642 {
643 local ($flood, $spoof, $fin);
644 open(SYN, $_[0] || $syn_file) || return ( );
645 chop($flood = <SYN>);
646 chop($spoof = <SYN>);
647 chop($fin = <SYN>);
648 close(SYN);
649 return ($flood, $spoof, $fin);
650 }
651
652 # save_syn(flood, spoof, fin)
653 sub save_syn
654 {
655 open(SYN, ">$syn_file");
656 print SYN int($_[0]),"\n";
657 print SYN int($_[1]),"\n";
658 print SYN int($_[2]),"\n";
659 close(SYN);
660 }
661
662 # list_times([file])
663 # Returns a list of all time ranges
664 sub list_times
665 {
666 local @rv;
667 open(TIMES, $_[0] || $times_file);
668 while(<TIMES>) {
669         s/\r|\n//g;
670         local @t = split(/\t/, $_);
671         if (@t >= 3) {
672                 local $time = { 'index' => scalar(@rv),
673                                 'name' => $t[0],
674                                 'hours' => $t[1],
675                                 'days' => $t[2] };
676                 push(@rv, $time);
677                 }
678         }
679 close(TIMES);
680 return @rv;
681 }
682
683 # save_times(time, ...)
684 # Updates the time ranges list
685 sub save_times
686 {
687 open(TIMES, ">$times_file");
688 local $t;
689 foreach $t (@_) {
690         print TIMES $t->{'name'},"\t",
691                     $t->{'hours'},"\t",
692                     $t->{'days'},"\n";
693         }
694 close(TIMES);
695 }
696
697 # activate_interface(base, ip)
698 sub activate_interface
699 {
700 &foreign_require("net", "net-lib.pl");
701 local @active = &net::active_interfaces();
702 local ($base) = grep { $_->{'fullname'} eq $_[0] } @active;
703 local ($already) = grep { $_->{'address'} eq $_[1] } @active;
704 if ($base && !$already) {
705         # Work out an interface number
706         local $vmax = 0;
707         foreach $a (@active) {
708                 $vmax = $a->{'virtual'}
709                         if ($a->{'name'} eq $base->{'name'} &&
710                             $a->{'virtual'} > $vmax);
711                 }
712
713         # Activate now
714         $virt = { 'address' => $_[1],
715                   'netmask' => $base->{'netmask'},
716                   'broadcast' => $base->{'broadcast'},
717                   'name' => $base->{'name'},
718                   'virtual' => $vmax+1,
719                   'up' => 1 };
720         $virt->{'fullname'} = $virt->{'name'}.":".$virt->{'virtual'};
721         &net::activate_interface($virt);
722
723         # Save for later
724         open(ACTIVE, ">>$active_interfaces");
725         print ACTIVE "$virt->{'fullname'}\t$virt->{'address'}\n";
726         close(ACTIVE);
727         }
728 }
729
730 # deactivate_all_interfaces()
731 # Shuts down all interfaces activated by the above function
732 sub deactivate_all_interfaces
733 {
734 &foreign_require("net", "net-lib.pl");
735 open(ACTIVE, $active_interfaces);
736 while(<ACTIVE>) {
737         if (/^(\S+)\s+(\S+)/) {
738                 local $addr = $2;
739                 local @active = &net::active_interfaces();
740                 local ($virt) = grep { $_->{'address'} eq $addr } @active;
741                 if ($virt && $virt->{'virtual'} ne '') {
742                         &net::deactivate_interface($virt);
743                         }
744                 }
745         }
746 close(ACTIVE);
747 unlink($active_interfaces);
748 }
749
750 sub apply_button
751 {
752 if (&can_edit("apply")) {
753         return "<a href='apply.cgi?return=1'>$text{'apply_button'}</a>";
754         }
755 else {
756         return undef;
757         }
758 }
759
760 # expand_hosts(names, &groups)
761 # Give a list of host or group names, expands them to hosts
762 sub expand_hosts
763 {
764 local ($e, @rv);
765 local %groups = map { $_->{'name'}, $_ } @{$_[1]};
766 foreach $e (split(/\s+/, $_[0])) {
767         if ($e =~ /^\@(.*)$/) {
768                 # Expand to all group members
769                 local $group = $groups{$1};
770                 foreach $m (@{$group->{'members'}}) {
771                         push(@rv, &expand_hosts($m, $_[1]));
772                         }
773                 }
774         elsif ($e =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\-(\d+)$/) {
775                 # Expand to all IPs in range
776                 push(@rv, map { "$1.$2.$3.$_" } ($4 .. $5) );
777                 }
778         else {
779                 # Just a single IP, host or network
780                 push(@rv, $e);
781                 }
782         }
783 return @rv;
784 }
785
786 # can_use(feature)
787 sub can_use
788 {
789 return 1 if ($read_access{'*'} || $edit_access{'*'});
790 return $read_access{$_[0]} || $edit_access{$_[0]};
791 }
792
793 # can_edit(feature)
794 sub can_edit
795 {
796 return 0 if (!&can_use($_[0]));
797 return $edit_access{'*'} || $edit_access{$_[0]};
798 }
799
800 # can_use_error(feature)
801 sub can_use_error
802 {
803 &can_use($_[0]) || &error($text{$_[0]."_ecannot"} ||
804                           &text('ecannot', $text{$_[0]."_title"}));
805 }
806
807 # can_edit_error(feature)
808 sub can_edit_error
809 {
810 &can_edit($_[0]) || &error($text{$_[0]."_ecannot"} ||
811                           &text('ecannot', $text{$_[0]."_title"}));
812 }
813
814 # can_edit_disable(feature)
815 sub can_edit_disable
816 {
817 if (!&can_edit($_[0])) {
818         print "<script>\n";
819         print "l = document.forms[0].elements;\n";
820         print "for(i=0; i<l.length; i++) {\n";
821         print "    if (l[i].name != \"burn\" && l[i].name != \"test\" &&\n";
822         print "        l[i].type != \"hidden\") {\n";
823         print "        l[i].disabled = true;\n";
824         print "    }\n";
825         print "}\n";
826         print "</script>\n";
827         }
828 }
829
830 # protocol_name(proto, port)
831 sub protocol_name
832 {
833 local $pr = uc($_[0]);
834 local $pt = $_[1];
835 if ($pr eq "TCP") {
836         return "<font color=#0000ff>$pr/$pt</font>";
837         }
838 elsif ($pr eq "UDP") {
839         return "<font color=#008800>$pr/$pt</font>";
840         }
841 elsif ($pr eq "ICMP") {
842         return "<font color=#f00000>$pr/$pt</font>";
843         }
844 else {
845         return "$pr/$pt";
846         }
847 }
848
849 # protocol_names(comma-list, [&services])
850 sub protocol_names
851 {
852 if ($_[0] eq "*") {
853         return $text{'rule_any'};
854         }
855 else {
856         local %sn = map { $_->{'name'}, $_ }
857                         ( $_[1] ? @{$_[1]} : &list_services() );
858         local $sn;
859         local @rv;
860         local ($editServO,$editServC);
861         foreach $sn (split(/,/, $_[0])) {
862                 local $serv = $sn{$sn};
863                 if (!$serv->{'standard'}){
864                                 $editServO="<a href='edit_service.cgi?name=".$serv->{'name'}."'>";
865                                 $editServC="</a>";                              
866                         } else {
867                                 $editServO="";
868                                 $editServC="";                                                  
869                         }
870                 local $pr = @{$serv->{'protos'}} == 1 ? uc($serv->{'protos'}->[0]) : undef;
871                 if ($pr eq "TCP") {
872                         push(@rv, "$editServO<font color=#0000ff>$sn</font>$editServC");
873                         }
874                 elsif ($pr eq "UDP") {
875                         push(@rv, "$editServO<font color=#008800>$sn</font>$editServC");
876                         }
877                 elsif ($pr eq "ICMP") {
878                         push(@rv, "$editServO<font color=#f00000>$sn</font>$editServC");
879                         }
880                 else {
881                         push(@rv, "$editServO $sn $editServC");
882                         }
883                 }
884         return join(", ", @rv);
885         }
886 }
887
888 # my_address_in(address/network)
889 # Returns this system's IP address in some network
890 sub my_address_in
891 {
892 local $net = $_[0];
893 $net =~ s/^\!\s+//;
894 if ($net =~ /[a-z]/i) {
895         $net = &to_ipaddress($net);
896         }
897 $net =~ s/^(\d+\.\d+\.\d+).*$/$1/;
898 &foreign_require("net", "net-lib.pl");
899 local @ifaces = &net::active_interfaces();
900 local $i;
901 local $primary;
902 foreach $i (@ifaces) {
903         if ($i->{'up'}) {
904                 if (!$primary && $i->{'fullname'} !~ /^lo/) {
905                         $primary = $i->{'address'};
906                         }
907                 local $addr = $i->{'address'};
908                 $addr =~ s/^(\d+\.\d+\.\d+).*$/$1/;
909                 if ($addr eq $net) {
910                         return $i->{'address'};
911                         }
912                 }
913         }
914 return $primary;
915 }
916
917 sub has_ipsec
918 {
919 return &foreign_installed("ipsec", 1);
920 }
921
922 # backup_firewall(&what, file, [password])
923 # Backs up the firewall to some file
924 sub backup_firewall
925 {
926 local ($mode, @dest) = &parse_backup_dest($_[1]);
927 local $file = $mode == 1 ? $dest[0] : &tempname();
928 local $ipsec_tmp = "$module_config_directory/ipsec.conf";
929 local $secrets_tmp = "$module_config_directory/ipsec.secrets";
930 local $users_tmp = "$module_config_directory/miniserv.users";
931 local $acl_tmp = "$module_config_directory/webmin.acl";
932 local $w;
933 local (@files, @delfiles);
934 foreach $w (@{$_[0]}) {
935         if ($w eq "ipsec") {
936                 # Copy the ipsec.conf files
937                 if (&has_ipsec()) {
938                         system("cp $ipsec::config{'file'} $ipsec_tmp");
939                         system("cp $ipsec::config{'secrets'} $secrets_tmp");
940                         push(@delfiles, "ipsec.conf", "ipsec.secrets");
941                         }
942                 }
943         elsif ($w eq "users") {
944                 # Copy all Webmin users
945                 opendir(DIR, $module_config_directory);
946                 push(@files, grep { /\.acl$/ } readdir(DIR));
947                 closedir(DIR);
948                 system("cp $config_directory/miniserv.users $users_tmp");
949                 system("cp $config_directory/webmin.acl $acl_tmp");
950                 push(@delfiles, "miniserv.users", "webmin.acl");
951                 }
952         else {
953                 push(@files, $w) if (-r "$module_config_directory/$w");
954                 }
955         }
956 push(@files, @delfiles);
957 local $what = join(" ", @files);
958 return $text{'backup_ewhat2'} if (!$what);
959 local $pass = $_[2] ? "-P '$_[2]'" : "";
960 local $out = &backquote_logged("(cd $module_config_directory ; zip -r $pass '$file' $what) 2>&1");
961 return "<pre>$out</pre>" if ($?);
962 unlink(map { "$module_config_directory/$_" } @delfiles);
963
964 # Send to destination
965 if ($mode == 2) {
966         # FTP somewhere
967         local $err;
968         &ftp_upload($dest[2], $dest[3], $file, \$err, undef, $dest[0], $dest[1]);
969         unlink($file);
970         return $err if ($err);
971         }
972 elsif ($mode == 3) {
973         # Email somewhere
974         $data = `cat $file`;
975         unlink($file);
976         $host = &get_system_hostname();
977         $body = "The backup of the firewall configuration on $host is attached to this email.\n";
978         local $mail = { 'headers' =>
979                         [ [ 'From', $config{'from'} || "webmin\@$host" ],
980                           [ 'To', $dest[0] ],
981                           [ 'Subject', "Firewall backup" ] ],
982                         'attach' =>
983                         [ { 'headers' => [ [ 'Content-type', 'text/plain' ] ],
984                             'data' => $body },
985                           { 'headers' => [ [ 'Content-type', 'application/zip' ],
986                                            [ 'Content-Transfer-Encoding', 'base64' ] ],
987                             'data' => $data } ] };
988         $main::errors_must_die = 1;
989         if (&foreign_check("mailboxes")) {
990                 &foreign_require("mailboxes", "mailboxes-lib.pl");
991                 eval { &mailboxes::send_mail($mail); };
992                 }
993         else {
994                 &foreign_require("sendmail", "sendmail-lib.pl");
995                 &foreign_require("sendmail", "boxes-lib.pl");
996                 eval { &sendmail::send_mail($mail); };
997                 }
998         return $@ if ($@);
999         }
1000
1001 return undef;
1002 }
1003
1004 sub check_zip
1005 {
1006 &has_command("zip") && &has_command("unzip") ||
1007         &error($text{'backup_ezipcmd'});
1008 }
1009
1010 sub find_backup_job
1011 {
1012 &foreign_require("cron", "cron-lib.pl");
1013 local @jobs = &cron::list_cron_jobs();
1014 local ($job) = grep { $_->{'user'} eq 'root' &&
1015                       $_->{'command'} eq $cron_cmd } @jobs;
1016 return $job;
1017 }
1018
1019 sub parse_backup_dest
1020 {
1021 if ($_[0] =~ /^mailto:(.*)/) {
1022         return (3, $1);
1023         }
1024 elsif ($_[0] =~ /^ftp:\/\/([^:]*):([^@]*)@([^\/]+)(\/.*)$/) {
1025         return (2, $1, $2, $3, $4);
1026         }
1027 elsif ($_[0] =~ /^\//) {
1028         return (1, $_[0]);
1029         }
1030 else {
1031         return (0);
1032         }
1033 }
1034
1035 # ftp_upload(host, file, srcfile, [&error], [&callback], [user, pass])
1036 # Download data from a local file to an FTP site
1037 sub ftp_upload
1038 {
1039 local($buf, @n);
1040 local $cbfunc = $_[4];
1041
1042 $download_timed_out = undef;
1043 local $SIG{ALRM} = "download_timeout";
1044 alarm(60);
1045
1046 # connect to host and login
1047 &open_socket($_[0], 21, "SOCK", $_[3]) || return 0;
1048 alarm(0);
1049 if ($download_timed_out) {
1050         if ($_[3]) { ${$_[3]} = $download_timed_out; return 0; }
1051         else { &error($download_timed_out); }
1052         }
1053 &ftp_command("", 2, $_[3]) || return 0;
1054 if ($_[5]) {
1055         # Login as supplied user
1056         local @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
1057         @urv || return 0;
1058         if (int($urv[1]/100) == 3) {
1059                 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
1060                 }
1061         }
1062 else {
1063         # Login as anonymous
1064         local @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
1065         @urv || return 0;
1066         if (int($urv[1]/100) == 3) {
1067                 &ftp_command("PASS root\@".&get_system_hostname(), 2,
1068                              $_[3]) || return 0;
1069                 }
1070         }
1071 &$cbfunc(1, 0) if ($cbfunc);
1072
1073 &ftp_command("TYPE I", 2, $_[3]) || return 0;
1074
1075 # get the file size and tell the callback
1076 local @st = stat($_[2]);
1077 if ($cbfunc) {
1078         &$cbfunc(2, $st[7]);
1079         }
1080
1081 # send the file
1082 local $pasv = &ftp_command("PASV", 2, $_[3]);
1083 defined($pasv) || return 0;
1084 $pasv =~ /\(([0-9,]+)\)/;
1085 @n = split(/,/ , $1);
1086 &open_socket("$n[0].$n[1].$n[2].$n[3]", $n[4]*256 + $n[5], "CON", $_[3]) || return 0;
1087 &ftp_command("STOR $_[1]", 1, $_[3]) || return 0;
1088
1089 # transfer data
1090 local $got;
1091 open(PFILE, $_[2]);
1092 while(read(PFILE, $buf, 1024) > 0) {
1093         print CON $buf;
1094         $got += length($buf);
1095         &$cbfunc(3, $got) if ($cbfunc);
1096         }
1097 close(PFILE);
1098 close(CON);
1099 if ($got != $st[7]) {
1100         if ($_[3]) { ${$_[3]} = "Upload incomplete"; return 0; }
1101         else { &error("Upload incomplete"); }
1102         }
1103 &$cbfunc(4) if ($cbfunc);
1104
1105 # finish off..
1106 &ftp_command("", 2, $_[3]) || return 0;
1107 &ftp_command("QUIT", 2, $_[3]) || return 0;
1108 close(SOCK);
1109
1110 return 1;
1111 }
1112
1113 # lock_itsecur_files()
1114 # Lock all firewall config files
1115 sub lock_itsecur_files
1116 {
1117 local $f;
1118 foreach $f (@config_files) {
1119         &lock_file($f);
1120         }
1121 }
1122
1123 # unlock_itsecur_files()
1124 # Unlock all firewall config files
1125 sub unlock_itsecur_files
1126 {
1127 local $f;
1128 foreach $f (@config_files) {
1129         &unlock_file($f);
1130         }
1131 }
1132
1133 sub remote_webmin_log
1134 {
1135 if ($config{'remote_log'} && !fork()) {
1136         # Disconnect from TTY
1137         untie(*STDIN);
1138         untie(*STDOUT);
1139         untie(*STDERR);
1140         close(STDIN);
1141         close(STDOUT);
1142         close(STDERR);
1143
1144         # Send log to remote host
1145         &remote_foreign_require($config{'remote_log'}, $module_name,
1146                                 "itsecur-lib.pl");
1147         local $d;
1148         foreach $d (@main::locked_file_diff) {
1149                 &remote_foreign_call($config{'remote_log'}, $module_name,
1150                                      "additional_log", $d->{'type'},
1151                                      $d->{'object'}, $d->{'data'});
1152                 }
1153         local $script_name = $0 =~ /([^\/]+)$/ ? $1 : '';
1154         &remote_foreign_call($config{'remote_log'}, $module_name,
1155                              "webmin_log", @_[0..3], $module_name,
1156                              &get_system_hostname(),
1157                              $script_name,
1158                              $ENV{'REMOTE_HOST'});
1159
1160         exit(0);
1161         }
1162 &webmin_log(@_);
1163 }
1164
1165 # automatic_backup()
1166 # If a change has been made and an automatic backup directory set, save the
1167 # module's configuration
1168 sub automatic_backup
1169 {
1170 return if (!$config{'auto_dir'} || !-d $config{'auto_dir'});
1171
1172 # Backup to a temp file
1173 local $temp = &tempname();
1174 local $err = &backup_firewall(\@backup_opts, $temp, undef);
1175 if ($err) {
1176         unlink($temp);
1177         return 0;
1178         }
1179
1180 # Make sure this backup is actually different from the last
1181 local $linkfile = "$config{'auto_dir'}/latest.zip";
1182 if (-r $linkfile) {
1183         local $out = `diff '$config{'auto_dir'}/latest.zip' '$temp' 2>&1`;
1184         if ($? == 0) {
1185                 # No change!
1186                 unlink($temp);
1187                 return 0;
1188                 }
1189         }
1190
1191 # Copy to directory, and update latest link
1192 use POSIX;
1193 local $newfile = strftime "$config{'auto_dir'}/firewall.%Y-%m-%d-%H:%M:%S.zip",
1194                         localtime(time());
1195 system("mv '$temp' '$newfile'");
1196 unlink($linkfile);
1197 symlink($newfile, $linkfile);
1198
1199 return 1;
1200 }
1201
1202 # parse_all_logs([base-only])
1203 # Returns a list of all log structures, newest first
1204 sub parse_all_logs
1205 {
1206 local $baselog = $config{'log'} || &get_log_file();
1207 local @rv;
1208 foreach $log ($config{'all_files'} && !$_[0] ? &all_log_files($baselog)
1209                                              : ($baselog)) {
1210         if ($log =~ /\.gz$/i) {
1211                 open(LOG, "gunzip -c ".quotemeta($log)." |");
1212                 }
1213         elsif ($log =~ /\.Z$/i) {
1214                 open(LOG, "uncompress -c ".quotemeta($log)." |");
1215                 }
1216         else {
1217                 open(LOG, $log);
1218                 }
1219         while(<LOG>) {
1220                 local $info = &parse_log_line($_);
1221                 push(@rv, $info) if ($info);
1222                 }
1223         close(LOG);
1224         }
1225 return reverse(@rv);
1226 }
1227
1228 # all_log_files(file)
1229 sub all_log_files
1230 {
1231 $_[0] =~ /^(.*)\/([^\/]+)$/;
1232 local $dir = $1;
1233 local $base = $2;
1234 local ($f, @rv);
1235 opendir(DIR, $dir);
1236 foreach $f (readdir(DIR)) {
1237         if ($f =~ /^\Q$base\E/ && -f "$dir/$f") {
1238                 push(@rv, "$dir/$f");
1239                 }
1240         }
1241 closedir(DIR);
1242 return @rv;
1243 }
1244
1245 @search_fields = ("src", "dst", "dst_iface", "proto", "src_port", "dst_port",
1246                   "first", "last", "action", "rule");
1247
1248 # filter_logs(&logs, &in, [&searchvars])
1249 sub filter_logs
1250 {
1251 local @logs = @{$_[0]};
1252 local %in = %{$_[1]};
1253 local $f;
1254 local @servs = &list_services();
1255 local @groups = &list_groups();
1256 local %servs = map { $_->{'name'}, $_ } @servs;
1257 foreach $f (@search_fields) {
1258         if ($in{$f."_mode"}) {
1259                 # This search applies .. find all suitable match types
1260                 local %matches;
1261                 local $tm;
1262                 if (($f eq "src_port" || $f eq "dst_port") && $in{$f."_what"}) {
1263                         # Lookup all ports and protocols
1264                         local ($protos, $ports) =
1265                                 &combine_services($in{$f."_what"}, \%servs);
1266                         local $i;
1267                         for($i=0; $i<@$protos; $i++) {
1268                                 if ($ports->[$i] =~ /^(\d+)\-(\d+)$/) {
1269                                         local $p;
1270                                         foreach $p ($1 .. $2) {
1271                                                 $matches{lc($protos->[$i]),$p}++;
1272                                                 }
1273                                         }
1274                                 else {
1275                                         $matches{lc($protos->[$i]),$ports->[$i]}++;
1276                                         }
1277                                 }
1278                         }
1279                 elsif (($f eq "src_port" || $f eq "dst_port") && !$in{$f."_what"}) {
1280                         # One specified port number
1281                         $matches{$in{$f."_other"}}++;
1282                         }
1283                 elsif (($f eq "src" || $f eq "dst") && $in{$f."_what"}) {
1284                         # Lookup all hosts
1285                         local @hosts = &expand_hosts(
1286                                 '@'.$in{$f."_what"}, \@groups);
1287                         local $h;
1288                         foreach $h (@hosts) {
1289                                 local $eh;
1290                                 foreach $eh (&expand_net($h)) {
1291                                         $matches{$eh}++;
1292                                         }
1293                                 }
1294                         }
1295                 elsif (($f eq "src" || $f eq "dst") && !$in{$f."_what"}) {
1296                         # One other host
1297                         local $eh;
1298                         foreach $eh (&expand_net($in{$f."_other"})) {
1299                                 $matches{$eh}++;
1300                                 }
1301                         }
1302                 elsif ($f eq "first" || $f eq "last") {
1303                         # A time range
1304                         eval { $tm = timelocal(
1305                                 0, $in{$f."_min"}, $in{$f."_hour"},
1306                                 $in{$f."_day"}, $in{$f."_month"}-1,
1307                                 $in{$f."_year"}-1900); };
1308                         }
1309                 else {
1310                         $matches{lc($in{$f."_what"})}++;
1311                         }
1312
1313                 if ($f eq "first" && $tm) {
1314                         # Find those after start minute
1315                         @logs = grep { $_->{'time'} >= $tm } @logs;
1316                         }
1317                 elsif ($f eq "last" && $tm) {
1318                         # Find those before end minute
1319                         @logs = grep { $_->{'time'} < $tm+60 } @logs;
1320                         }
1321                 elsif ($in{$f."_mode"} == 1) {
1322                         # Find matching entries
1323                         @logs = grep {
1324                                 $matches{lc($_->{$f})} ||
1325                                 $matches{lc($_->{'proto'}),lc($_->{$f})} }
1326                                      @logs;
1327                         }
1328                 elsif ($in{$f."_mode"} == 2) {
1329                         # Find non-matching entries
1330                         @logs = grep {
1331                                 !($matches{lc($_->{$f})} ||
1332                                   $matches{lc($_->{'proto'}),lc($_->{$f})}) }
1333                                      @logs;
1334                         }
1335                 if ($_[2]) {
1336                         local $e;
1337                         foreach $e ("mode", "what", "other", "day",
1338                                     "month", "year") {
1339                                 if ($in{$f."_".$e} ne "") {
1340                                         push(@{$_[2]}, $f."_".$e."=".
1341                                              &urlize($in{$f."_".$e}));
1342                                         }
1343                                 }
1344                         }
1345                 }
1346         }
1347 return @logs;
1348 }
1349
1350 # expand_net(network)
1351 # Given a network address, hostname or IP address, returns a list of all
1352 # IP addresses it contains
1353 sub expand_net
1354 {
1355 if ($_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/) {
1356         local @rv;
1357         local $first = ($1<<24) + ($2<<16) + ($3<<8) + ($4);
1358         local $last = $first + (1<<(32-$5)) - 1;
1359         for($ipnum=$first; $ipnum<=$last; $ipnum++) {
1360                 local @ip = ( ($ipnum>>24)&0xff,
1361                               ($ipnum>>16)&0xff,
1362                               ($ipnum>>8)&0xff,
1363                               ($ipnum)&0xff );
1364                 push(@rv, join(".", @ip));
1365                 }
1366         return @rv;
1367         }
1368 else {
1369         return &to_ipaddress($_[0]);
1370         }
1371 }
1372
1373 # list_searches()
1374 # Returns a list of all saved searches
1375 sub list_searches
1376 {
1377 local @rv;
1378 opendir(DIR, $searches_dir);
1379 local $f;
1380 while($f = readdir(DIR)) {
1381         if ($f ne "." && $f ne "..") {
1382                 local $search = &get_search($f);
1383                 push(@rv, $search) if ($search);
1384                 }
1385         }
1386 closedir(DIR);
1387 return @rv;
1388 }
1389
1390 sub get_search
1391 {
1392 local %search;
1393 if (&read_file("$searches_dir/$_[0]", \%search)) {
1394         return \%search;
1395         }
1396 else {
1397         return undef;
1398         }
1399 }
1400
1401 # save_search(&search)
1402 sub save_search
1403 {
1404 mkdir($searches_dir, 0755);
1405 &write_file("$searches_dir/$_[0]->{'save_name'}", $_[0]);
1406 }
1407
1408 # get_remote()
1409 # Returns the webmin servers object used for remote logging, or undef
1410 sub get_remote
1411 {
1412 return undef if (!$config{'remote_log'});
1413 &foreign_require("servers", "servers-lib.pl");
1414 local @servers = &servers::list_servers();
1415 local ($server) = grep { $_->{'host'} eq $config{'remote_log'} } @servers;
1416 return $server;
1417 }
1418
1419 # save_remote(server, port, username, password, test, save)
1420 sub save_remote
1421 {
1422 local ($host, $port, $user, $pass, $test, $save) = @_;
1423 &foreign_require("servers", "servers-lib.pl");
1424 if ($host) {
1425         # Enabling or updating
1426         local @servers = &servers::list_servers();
1427         local ($newserver) = grep { $_->{'host'} eq $host } @servers;
1428         local $server = &get_remote();
1429         if ($newserver && $server) {
1430                 if ($newserver ne $server) {
1431                         # Re-name would cause clash, so delete it
1432                         &servers::delete_server($newserver->{'id'});
1433                         }
1434                 }
1435         elsif ($newserver && !$server) {
1436                 # Re-naming server
1437                 $server = $newserver;
1438                 }
1439         elsif (!$newserver && $server) {
1440                 # Can just stick to old server
1441                 }
1442         else {
1443                 # Totally new
1444                 $server = { 'id' => time(),
1445                             'port' => $port,
1446                             'ssl' => 0,
1447                             'desc' => 'Firewall logging server',
1448                             'type' => 'unknown',
1449                             'fast' => 0 };
1450                 }
1451         $server->{'host'} = $host;
1452         $server->{'port'} = $port;
1453         $server->{'user'} = $user;
1454         $server->{'pass'} = $pass;
1455         &servers::save_server($server);
1456         $config{'remote_log'} = $server->{'host'};
1457
1458         if ($test) {
1459                 # Try a test connection
1460                 &remote_error_setup(\&test_error);
1461                 eval {
1462                         $SIG{'ALRM'} = sub { die "alarm\n" };
1463                         alarm(10);
1464                         &remote_foreign_require($server->{'host'}, "webmin",
1465                                                 "webmin-lib.pl");
1466                         alarm(0);
1467                         };
1468                 if ($@) {
1469                         &error(&text('remote_econnect', $text{'remote_etimeout'}));
1470                         }
1471                 elsif ($test_error_msg) {
1472                         &error(&text('remote_econnect', $test_error_msg));
1473                         }
1474                 }
1475         }
1476 else {
1477         # Disabling
1478         delete($config{'remote_log'});
1479         }
1480 if ($save) {
1481         &lock_file($module_config_file);
1482         &write_file($module_config_file, \%config);
1483         &unlock_file($module_config_file);
1484         }
1485 }
1486
1487 sub test_error
1488 {
1489 $test_error_msg = join("", @_);
1490 }
1491
1492 sub check_netaddress
1493 {
1494 return $_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/ &&
1495         $1 >= 0 && $1 <= 255 &&
1496         $2 >= 0 && $2 <= 255 &&
1497         $3 >= 0 && $3 <= 255 &&
1498         $4 >= 0 && $4 <= 255 &&
1499         $5 >= 0 && $5 <= 32;
1500 }
1501
1502 sub is_one_host
1503 {
1504 local @groups = &list_groups();
1505 local @rv=&expand_hosts($_[0], \@groups);
1506 return $#rv;
1507 }
1508
1509 1;
1510