4 # Common functions for all firewall types
5 # XXX only backup firewall module users?
7 BEGIN { push(@INC, ".."); };
10 do "$config{'type'}-lib.pl";
12 @opts = ( 'rules', 'services', 'groups', 'nat','nat2', 'pat', 'spoof', 'syn', 'logs',
15 &supports_time() ? ('times') : (),
19 # &supports_bandwidth() ? ('bandwidth') : (),
20 @backup_opts = grep { $_ ne 'logs' && $_ ne 'backup' && $_ ne 'restore' }
21 (@opts, 'ipsec', 'searches', 'config');
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";
40 $searches_dir = "$module_config_directory/searches";
42 @config_files = ( $groups_file, $services_file,
43 $rules_file, $nat_file, $nat2_file, $pat_file, $spoof_file,
44 $syn_file, $times_file );
46 %access = &get_module_acl();
47 if (defined($access{'edit'})) {
48 if ($access{'edit'}) {
49 @edit_access = @read_access = split(/\s+/, $access{'features'});
52 @read_access = split(/\s+/, $access{'features'});
56 @edit_access = split(/\s+/, $access{'features'});
57 @read_access = split(/\s+/, $access{'rfeatures'});
59 %edit_access = map { $_, 1 } @edit_access;
60 %read_access = map { $_, 1 } @read_access;
62 $cron_cmd = "$module_config_directory/backup.pl";
65 # Returns a list of groups. Each has a name and zero or more member hosts,
66 # IP addresses, networks or other groups.
70 open(GROUPS, $_[0] || $groups_file);
73 if (/^(\S+)\t+(.*)$/) {
74 local $group = { 'name' => $1,
75 'members' => [ split(/\t+/, $2) ],
76 'index' => scalar(@rv) };
84 # save_groups(group, ...)
85 # Updates the groups list
91 push(@SortGroups,$g->{'name'}."\t".join("\t", @{$g->{'members'}})."\n");
93 open(GROUPS, ">$groups_file");
94 print GROUPS sort { lc($a) cmp lc($b) } @SortGroups;
99 # list_services([file])
100 # Returns a list of services, each of which has a name and multiple
105 #if (!-r $standard_services_file) {
106 # system("cp $module_root_directory/standard-services $standard_services_file");
108 foreach $sf ($_[0] || $services_file, $standard_services_file) {
115 if (/^(\S+)\t+(.*)$/) {
116 local $serv = { 'name' => $1,
118 ($sf eq $standard_services_file),
119 'index' => scalar(@frv) };
120 local @pps = split(/\s*\t+\s*/, $2);
122 for($i=0; $i<@pps; $i+=2) {
123 if ($pps[$i] eq "other") {
124 push(@{$serv->{'others'}}, $pps[$i+1]);
127 push(@{$serv->{'protos'}}, $pps[$i]);
128 push(@{$serv->{'ports'}}, $pps[$i+1]);
135 if ($sf eq $standard_services_file) {
136 push(@rv, sort { lc($a->{'name'}) cmp lc($b->{'name'}) } @frv);
145 # combine_services(comma-list, &services-hash)
146 # Returns lists of protocols and port numbers taken from a comma-separated list
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);
159 return (\@protos, \@ports);
162 # save_services(service, ...)
165 #open(SERVS, ">$services_file");
170 next if ($serv->{'standard'});
171 $data=$serv->{'name'};
173 for($i=0; $i<@{$serv->{'protos'}}; $i++) {
174 $data = $data . "\t" . $serv->{'protos'}->[$i] . "\t" . $serv->{'ports'}->[$i];
176 for($i=0; $i<@{$serv->{'others'}}; $i++) {
177 if ( $serv->{'others'}->[$i] ne $serv->{'name'}) {
178 $data = $data . "\tother\t".$serv->{'others'}->[$i];
182 push(@SortGroups,$data);
186 open(SERVS, ">$services_file");
187 print SERVS sort { lc($a) cmp lc($b) } @SortGroups;
193 # Returns a list of rules, each of which has a source, destination, service,
194 # action and log-flag
198 open(RULES, $_[0] || $rules_file);
202 if (/^(#*)([^\t]+)\t+([^\t]+)\t+(\S+)\t+(\S+)\t+(\d+)(\t+(\S+))?(\t+(\S+))?$/) {
203 local $rule = { 'enabled' => !$1,
210 'desc' => &un_urlize($10 || "*"),
211 'index' => scalar(@rv),
216 local $sep = { 'sep' => 1,
217 'desc' => &un_urlize($1),
218 'index' => scalar(@rv) };
226 # save_rules(rule, ...)
229 open(RULES, ">$rules_file");
232 if ($rule->{'sep'}) {
233 print RULES &urlize($rule->{'desc'}),"\n";
236 print RULES ($rule->{'enabled'} ? "" : "#"),
237 $rule->{'source'},"\t",
238 $rule->{'dest'},"\t",
239 $rule->{'service'},"\t",
240 $rule->{'action'},"\t",
242 $rule->{'time'},"\t",
243 $rule->{'desc'} eq "*" ? "*"
244 : &urlize($rule->{'desc'}),"\n";
250 # group_name(string, [direction])
251 # Given a source or destination name that may be a group, makes it nice
254 if ($_[0] =~ /^\@(.*)$/) {
256 return "<font color=#0000ff>$1</font>";
258 elsif ($_[0] =~ /^\!\@(.*)$/) {
260 return "<font color=#0000ff>".&text('not', "$1")."</font>";
262 elsif ($_[0] =~ /^\%(.*)$/) {
264 return "<font color=#C0C0C0>".&text('iface', "$1")."</font>";
266 elsif ($_[0] =~ /^\!\%(.*)$/) {
268 return "<font color=#C0C0C0>".&text('iface_not', "$1")."</font>";
270 elsif ($_[0] eq '*') {
272 return $text{'anywhere'};
274 elsif ($_[0] eq '!*') {
276 return $text{'nowhere'};
278 elsif ($_[0] =~ /^\!(.*\/.*)$/) {
279 # Negated network address
280 return &text('not', "<tt><font color=#008800>$1</font></tt>");
282 elsif ($_[0] =~ /^\!([0-9\.]+)\-([0-9]+)$/) {
283 # Negated address range
284 return &text('not', "<tt><font color=#ffff00>$1-$2</font></tt>");
286 elsif ($_[0] =~ /^\!(.*)$/) {
287 # Negated hostname or IP
288 return &text('not', "<tt>$1</tt>");
290 elsif ($_[0] =~ /^(.*\/.*)$/) {
292 return "<tt><font color=#008800>$_[0]</font></tt>";
294 elsif ($_[0] =~ /^([0-9\.]+)\-([0-9]+)$/) {
296 return "<tt><font color=#ffff00>$1-$2</font></tt>";
300 return "<tt>$_[0]</tt>";
304 # group_names(string)
307 return join(", ", map { &group_name($_) } split(/\s+/, $_[0]));
310 # group_names_link(dest, [from], [direction])
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>");
321 push(@rv, &group_name($g, $_[2]));
324 return join(", ", @rv);
327 # group_input(name, [value], [blankoption], [multiple])
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";
335 $rv .= sprintf "<option value='' %s>%s\n",
336 $_[1] ? "" : "selected", $_[2] == 2 ? $text{'other'} : " ";
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" : "",
345 $rv .= "</select>\n";
349 # service_input(name, value, [blankoption], [multiple], [norange])
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";
357 $rv .= sprintf "<option value='' %s>%s\n",
358 $_[1] ? "" : "selected", $_[2] == 2 ? $text{'other'} : " ";
361 foreach $s (@servs) {
363 local @up = &unique(@{$s->{'protos'}});
366 $desc = uc($up[0])." ".join(", ", @{$s->{'ports'}});
369 for($i=0; $i<@{$s->{'protos'}}; $i++) {
370 $desc .= ", " if ($desc);
371 $desc .= uc($s->{'protos'}->[$i])."/".
375 for($i=0; $i<@{$s->{'others'}}; $i++) {
376 $desc .= ", " if ($desc);
377 $desc .= $s->{'others'}->[$i];
379 $rv .= sprintf "<option value=%s %s>%s%s\n",
380 $s->{'name'}, $got{$s->{'name'}} ? "selected" : "",
381 $s->{'name'}, $_[4] ? "" : " ($desc)";
383 $rv .= "</select>\n";
387 # action_input(name, value, [select-mode])
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" : "",
399 $rv .= "</select>\n";
402 foreach $a (@actions) {
403 $rv .= sprintf "<input type=radio name=%s value=%s %s>%s\n",
404 $_[0], $a, $_[1] eq $a ? "checked" : "",
412 # protocol_input(name, value)
415 local @protos = ( 'tcp', 'udp', 'icmp', 'ip' );
416 #open(PROTOS, "/etc/protocols");
420 # push(@protos, $1) if (/^(\S+)\s+(\d+)/);
424 local $rv = "<select name=$_[0]>\n";
425 $rv .= sprintf "<option value='' %s> \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" : "",
432 $rv .= "</select>\n";
439 if (&check_ipaddress($_[0])) {
442 elsif (gethostbyname($_[0])) {
445 elsif (&check_netaddress($_[0])) {
446 #$_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/) {
449 elsif ($_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\-(\d+)$/) {
457 # iface_input(name, value, [realonly], [nother], [nonemode])
461 if (&foreign_check("net")) {
462 &foreign_require("net", "net-lib.pl");
464 foreach $i (&net::active_interfaces(), &net::boot_interfaces()) {
465 push(@ifaces, $i->{'fullname'})
466 if (!$_[2] || $i->{'virtual'} eq '');
468 @ifaces = &unique(@ifaces);
471 local $rv = "<select name=$_[0]>\n";
474 $rv .= sprintf "<option value='' %s>%s\n",
475 $_[1] eq "" ? "selected" : "", "<None>";
476 $found++ if ($_[1] eq "");
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);
484 $rv .= "<option value=$_[1] selected>$_[1]\n" if (!$found && $_[1]);
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] : "";
496 return "<input name=$_[0] size=6 value='$_[1]'>";
500 # time_input(name, [value])
503 local @times = &list_times();
504 return undef if (!@times);
505 local $rv = "<select name=$_[0]>\n";
507 foreach $t (@times) {
508 $rv .= sprintf "<option value=%s %s>%s\n",
509 $t->{'name'}, $t->{'name'} eq $_[1] ? "selected" : "",
512 $rv .= "</select>\n";
519 local ($iface, @nets, @maps);
520 open(NAT, $_[0] || $nat_file) || return ( );
521 chop($iface = <NAT>);
527 elsif (/^(\S+)\t+(\S+)\t+(\S+)$/) {
528 push(@maps, [ $1, $2, $3 ]);
530 elsif (/^(\S+)\t+(\S+)$/) {
531 push(@maps, [ $1, $2 ]);
535 return ( $iface, @nets, @maps );
538 # save_nat(iface, net, ..)
541 open(NAT, ">$nat_file");
542 print NAT shift(@_),"\n";
546 print NAT join("\t", @$n),"\n";
557 open(NAT, ">$nat2_file");
558 print NAT shift(@_),"\n";
562 print NAT join("\t", @$n),"\n";
575 local ($defiface, @forwards);
576 open(PAT, $_[0] || $pat_file) || return ( );
577 chop($defiface = <PAT>);
580 if (/^(\S+)\t+(\S+)\t+(\S+)$/) {
581 push(@forwards, { 'service' => $1,
585 elsif (/^(\S+)\t+(\S+)$/) {
586 push(@forwards, { 'service' => $1,
588 'iface' => $defiface });
595 # save_pat(forward, ...)
598 open(PAT, ">$pat_file");
599 print PAT (@_ ? $_[0]->{'iface'} : ""),"\n";
603 print PAT "$f->{'service'}\t$f->{'host'}\t$f->{'iface'}\n";
606 print PAT "$f->{'service'}\t$f->{'host'}\n";
615 local ($iface, @nets);
616 open(PAT, $_[0] || $spoof_file) || return ( );
617 chop($iface = <PAT>);
625 return ( $iface, @nets );
628 # save_spoof(iface, net, ...)
631 open(PAT, ">$spoof_file");
632 print PAT shift(@_),"\n";
643 local ($flood, $spoof, $fin);
644 open(SYN, $_[0] || $syn_file) || return ( );
645 chop($flood = <SYN>);
646 chop($spoof = <SYN>);
649 return ($flood, $spoof, $fin);
652 # save_syn(flood, spoof, fin)
655 open(SYN, ">$syn_file");
656 print SYN int($_[0]),"\n";
657 print SYN int($_[1]),"\n";
658 print SYN int($_[2]),"\n";
663 # Returns a list of all time ranges
667 open(TIMES, $_[0] || $times_file);
670 local @t = split(/\t/, $_);
672 local $time = { 'index' => scalar(@rv),
683 # save_times(time, ...)
684 # Updates the time ranges list
687 open(TIMES, ">$times_file");
690 print TIMES $t->{'name'},"\t",
697 # activate_interface(base, ip)
698 sub activate_interface
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
707 foreach $a (@active) {
708 $vmax = $a->{'virtual'}
709 if ($a->{'name'} eq $base->{'name'} &&
710 $a->{'virtual'} > $vmax);
714 $virt = { 'address' => $_[1],
715 'netmask' => $base->{'netmask'},
716 'broadcast' => $base->{'broadcast'},
717 'name' => $base->{'name'},
718 'virtual' => $vmax+1,
720 $virt->{'fullname'} = $virt->{'name'}.":".$virt->{'virtual'};
721 &net::activate_interface($virt);
724 open(ACTIVE, ">>$active_interfaces");
725 print ACTIVE "$virt->{'fullname'}\t$virt->{'address'}\n";
730 # deactivate_all_interfaces()
731 # Shuts down all interfaces activated by the above function
732 sub deactivate_all_interfaces
734 &foreign_require("net", "net-lib.pl");
735 open(ACTIVE, $active_interfaces);
737 if (/^(\S+)\s+(\S+)/) {
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);
747 unlink($active_interfaces);
752 if (&can_edit("apply")) {
753 return "<a href='apply.cgi?return=1'>$text{'apply_button'}</a>";
760 # expand_hosts(names, &groups)
761 # Give a list of host or group names, expands them to hosts
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]));
774 elsif ($e =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\-(\d+)$/) {
775 # Expand to all IPs in range
776 push(@rv, map { "$1.$2.$3.$_" } ($4 .. $5) );
779 # Just a single IP, host or network
789 return 1 if ($read_access{'*'} || $edit_access{'*'});
790 return $read_access{$_[0]} || $edit_access{$_[0]};
796 return 0 if (!&can_use($_[0]));
797 return $edit_access{'*'} || $edit_access{$_[0]};
800 # can_use_error(feature)
803 &can_use($_[0]) || &error($text{$_[0]."_ecannot"} ||
804 &text('ecannot', $text{$_[0]."_title"}));
807 # can_edit_error(feature)
810 &can_edit($_[0]) || &error($text{$_[0]."_ecannot"} ||
811 &text('ecannot', $text{$_[0]."_title"}));
814 # can_edit_disable(feature)
817 if (!&can_edit($_[0])) {
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";
830 # protocol_name(proto, port)
833 local $pr = uc($_[0]);
836 return "<font color=#0000ff>$pr/$pt</font>";
838 elsif ($pr eq "UDP") {
839 return "<font color=#008800>$pr/$pt</font>";
841 elsif ($pr eq "ICMP") {
842 return "<font color=#f00000>$pr/$pt</font>";
849 # protocol_names(comma-list, [&services])
853 return $text{'rule_any'};
856 local %sn = map { $_->{'name'}, $_ }
857 ( $_[1] ? @{$_[1]} : &list_services() );
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'}."'>";
870 local $pr = @{$serv->{'protos'}} == 1 ? uc($serv->{'protos'}->[0]) : undef;
872 push(@rv, "$editServO<font color=#0000ff>$sn</font>$editServC");
874 elsif ($pr eq "UDP") {
875 push(@rv, "$editServO<font color=#008800>$sn</font>$editServC");
877 elsif ($pr eq "ICMP") {
878 push(@rv, "$editServO<font color=#f00000>$sn</font>$editServC");
881 push(@rv, "$editServO $sn $editServC");
884 return join(", ", @rv);
888 # my_address_in(address/network)
889 # Returns this system's IP address in some network
894 if ($net =~ /[a-z]/i) {
895 $net = &to_ipaddress($net);
897 $net =~ s/^(\d+\.\d+\.\d+).*$/$1/;
898 &foreign_require("net", "net-lib.pl");
899 local @ifaces = &net::active_interfaces();
902 foreach $i (@ifaces) {
904 if (!$primary && $i->{'fullname'} !~ /^lo/) {
905 $primary = $i->{'address'};
907 local $addr = $i->{'address'};
908 $addr =~ s/^(\d+\.\d+\.\d+).*$/$1/;
910 return $i->{'address'};
919 return &foreign_installed("ipsec", 1);
922 # backup_firewall(&what, file, [password])
923 # Backs up the firewall to some file
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";
933 local (@files, @delfiles);
934 foreach $w (@{$_[0]}) {
936 # Copy the ipsec.conf files
938 system("cp $ipsec::config{'file'} $ipsec_tmp");
939 system("cp $ipsec::config{'secrets'} $secrets_tmp");
940 push(@delfiles, "ipsec.conf", "ipsec.secrets");
943 elsif ($w eq "users") {
944 # Copy all Webmin users
945 opendir(DIR, $module_config_directory);
946 push(@files, grep { /\.acl$/ } readdir(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");
953 push(@files, $w) if (-r "$module_config_directory/$w");
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);
964 # Send to destination
968 &ftp_upload($dest[2], $dest[3], $file, \$err, undef, $dest[0], $dest[1]);
970 return $err if ($err);
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" ],
981 [ 'Subject', "Firewall backup" ] ],
983 [ { 'headers' => [ [ 'Content-type', 'text/plain' ] ],
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); };
994 &foreign_require("sendmail", "sendmail-lib.pl");
995 &foreign_require("sendmail", "boxes-lib.pl");
996 eval { &sendmail::send_mail($mail); };
1006 &has_command("zip") && &has_command("unzip") ||
1007 &error($text{'backup_ezipcmd'});
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;
1019 sub parse_backup_dest
1021 if ($_[0] =~ /^mailto:(.*)/) {
1024 elsif ($_[0] =~ /^ftp:\/\/([^:]*):([^@]*)@([^\/]+)(\/.*)$/) {
1025 return (2, $1, $2, $3, $4);
1027 elsif ($_[0] =~ /^\//) {
1035 # ftp_upload(host, file, srcfile, [&error], [&callback], [user, pass])
1036 # Download data from a local file to an FTP site
1040 local $cbfunc = $_[4];
1042 $download_timed_out = undef;
1043 local $SIG{ALRM} = "download_timeout";
1046 # connect to host and login
1047 &open_socket($_[0], 21, "SOCK", $_[3]) || return 0;
1049 if ($download_timed_out) {
1050 if ($_[3]) { ${$_[3]} = $download_timed_out; return 0; }
1051 else { &error($download_timed_out); }
1053 &ftp_command("", 2, $_[3]) || return 0;
1055 # Login as supplied user
1056 local @urv = &ftp_command("USER $_[5]", [ 2, 3 ], $_[3]);
1058 if (int($urv[1]/100) == 3) {
1059 &ftp_command("PASS $_[6]", 2, $_[3]) || return 0;
1063 # Login as anonymous
1064 local @urv = &ftp_command("USER anonymous", [ 2, 3 ], $_[3]);
1066 if (int($urv[1]/100) == 3) {
1067 &ftp_command("PASS root\@".&get_system_hostname(), 2,
1071 &$cbfunc(1, 0) if ($cbfunc);
1073 &ftp_command("TYPE I", 2, $_[3]) || return 0;
1075 # get the file size and tell the callback
1076 local @st = stat($_[2]);
1078 &$cbfunc(2, $st[7]);
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;
1092 while(read(PFILE, $buf, 1024) > 0) {
1094 $got += length($buf);
1095 &$cbfunc(3, $got) if ($cbfunc);
1099 if ($got != $st[7]) {
1100 if ($_[3]) { ${$_[3]} = "Upload incomplete"; return 0; }
1101 else { &error("Upload incomplete"); }
1103 &$cbfunc(4) if ($cbfunc);
1106 &ftp_command("", 2, $_[3]) || return 0;
1107 &ftp_command("QUIT", 2, $_[3]) || return 0;
1113 # lock_itsecur_files()
1114 # Lock all firewall config files
1115 sub lock_itsecur_files
1118 foreach $f (@config_files) {
1123 # unlock_itsecur_files()
1124 # Unlock all firewall config files
1125 sub unlock_itsecur_files
1128 foreach $f (@config_files) {
1133 sub remote_webmin_log
1135 if ($config{'remote_log'} && !fork()) {
1136 # Disconnect from TTY
1144 # Send log to remote host
1145 &remote_foreign_require($config{'remote_log'}, $module_name,
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'});
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(),
1158 $ENV{'REMOTE_HOST'});
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
1170 return if (!$config{'auto_dir'} || !-d $config{'auto_dir'});
1172 # Backup to a temp file
1173 local $temp = &tempname();
1174 local $err = &backup_firewall(\@backup_opts, $temp, undef);
1180 # Make sure this backup is actually different from the last
1181 local $linkfile = "$config{'auto_dir'}/latest.zip";
1183 local $out = `diff '$config{'auto_dir'}/latest.zip' '$temp' 2>&1`;
1191 # Copy to directory, and update latest link
1193 local $newfile = strftime "$config{'auto_dir'}/firewall.%Y-%m-%d-%H:%M:%S.zip",
1195 system("mv '$temp' '$newfile'");
1197 symlink($newfile, $linkfile);
1202 # parse_all_logs([base-only])
1203 # Returns a list of all log structures, newest first
1206 local $baselog = $config{'log'} || &get_log_file();
1208 foreach $log ($config{'all_files'} && !$_[0] ? &all_log_files($baselog)
1210 if ($log =~ /\.gz$/i) {
1211 open(LOG, "gunzip -c ".quotemeta($log)." |");
1213 elsif ($log =~ /\.Z$/i) {
1214 open(LOG, "uncompress -c ".quotemeta($log)." |");
1220 local $info = &parse_log_line($_);
1221 push(@rv, $info) if ($info);
1225 return reverse(@rv);
1228 # all_log_files(file)
1231 $_[0] =~ /^(.*)\/([^\/]+)$/;
1236 foreach $f (readdir(DIR)) {
1237 if ($f =~ /^\Q$base\E/ && -f "$dir/$f") {
1238 push(@rv, "$dir/$f");
1245 @search_fields = ("src", "dst", "dst_iface", "proto", "src_port", "dst_port",
1246 "first", "last", "action", "rule");
1248 # filter_logs(&logs, &in, [&searchvars])
1251 local @logs = @{$_[0]};
1252 local %in = %{$_[1]};
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
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);
1267 for($i=0; $i<@$protos; $i++) {
1268 if ($ports->[$i] =~ /^(\d+)\-(\d+)$/) {
1270 foreach $p ($1 .. $2) {
1271 $matches{lc($protos->[$i]),$p}++;
1275 $matches{lc($protos->[$i]),$ports->[$i]}++;
1279 elsif (($f eq "src_port" || $f eq "dst_port") && !$in{$f."_what"}) {
1280 # One specified port number
1281 $matches{$in{$f."_other"}}++;
1283 elsif (($f eq "src" || $f eq "dst") && $in{$f."_what"}) {
1285 local @hosts = &expand_hosts(
1286 '@'.$in{$f."_what"}, \@groups);
1288 foreach $h (@hosts) {
1290 foreach $eh (&expand_net($h)) {
1295 elsif (($f eq "src" || $f eq "dst") && !$in{$f."_what"}) {
1298 foreach $eh (&expand_net($in{$f."_other"})) {
1302 elsif ($f eq "first" || $f eq "last") {
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); };
1310 $matches{lc($in{$f."_what"})}++;
1313 if ($f eq "first" && $tm) {
1314 # Find those after start minute
1315 @logs = grep { $_->{'time'} >= $tm } @logs;
1317 elsif ($f eq "last" && $tm) {
1318 # Find those before end minute
1319 @logs = grep { $_->{'time'} < $tm+60 } @logs;
1321 elsif ($in{$f."_mode"} == 1) {
1322 # Find matching entries
1324 $matches{lc($_->{$f})} ||
1325 $matches{lc($_->{'proto'}),lc($_->{$f})} }
1328 elsif ($in{$f."_mode"} == 2) {
1329 # Find non-matching entries
1331 !($matches{lc($_->{$f})} ||
1332 $matches{lc($_->{'proto'}),lc($_->{$f})}) }
1337 foreach $e ("mode", "what", "other", "day",
1339 if ($in{$f."_".$e} ne "") {
1340 push(@{$_[2]}, $f."_".$e."=".
1341 &urlize($in{$f."_".$e}));
1350 # expand_net(network)
1351 # Given a network address, hostname or IP address, returns a list of all
1352 # IP addresses it contains
1355 if ($_[0] =~ /^(\d+)\.(\d+)\.(\d+)\.(\d+)\/(\d+)$/) {
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,
1364 push(@rv, join(".", @ip));
1369 return &to_ipaddress($_[0]);
1374 # Returns a list of all saved searches
1378 opendir(DIR, $searches_dir);
1380 while($f = readdir(DIR)) {
1381 if ($f ne "." && $f ne "..") {
1382 local $search = &get_search($f);
1383 push(@rv, $search) if ($search);
1393 if (&read_file("$searches_dir/$_[0]", \%search)) {
1401 # save_search(&search)
1404 mkdir($searches_dir, 0755);
1405 &write_file("$searches_dir/$_[0]->{'save_name'}", $_[0]);
1409 # Returns the webmin servers object used for remote logging, or undef
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;
1419 # save_remote(server, port, username, password, test, save)
1422 local ($host, $port, $user, $pass, $test, $save) = @_;
1423 &foreign_require("servers", "servers-lib.pl");
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'});
1435 elsif ($newserver && !$server) {
1437 $server = $newserver;
1439 elsif (!$newserver && $server) {
1440 # Can just stick to old server
1444 $server = { 'id' => time(),
1447 'desc' => 'Firewall logging server',
1448 'type' => 'unknown',
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'};
1459 # Try a test connection
1460 &remote_error_setup(\&test_error);
1462 $SIG{'ALRM'} = sub { die "alarm\n" };
1464 &remote_foreign_require($server->{'host'}, "webmin",
1469 &error(&text('remote_econnect', $text{'remote_etimeout'}));
1471 elsif ($test_error_msg) {
1472 &error(&text('remote_econnect', $test_error_msg));
1478 delete($config{'remote_log'});
1481 &lock_file($module_config_file);
1482 &write_file($module_config_file, \%config);
1483 &unlock_file($module_config_file);
1489 $test_error_msg = join("", @_);
1492 sub check_netaddress
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;
1504 local @groups = &list_groups();
1505 local @rv=&expand_hosts($_[0], \@groups);