More progress on Webmin IPv6 support
authorJamie Cameron <jcameron@webmin.com>
Sat, 30 Oct 2010 18:58:21 +0000 (11:58 -0700)
committerJamie Cameron <jcameron@webmin.com>
Sat, 30 Oct 2010 18:58:21 +0000 (11:58 -0700)
miniserv.pl
webmin/change_bind.cgi
webmin/edit_bind.cgi
webmin/lang/en
webmin/webmin-lib.pl

index c6b22be..fe31010 100755 (executable)
@@ -421,14 +421,15 @@ if ($config{'inetd'}) {
        (undef, $locala) = &get_socket_ip(SOCK, 0);
 
        print DEBUG "main: Starting handle_request loop pid=$$\n";
-       while(&handle_request($peera, $locala)) { }
+       while(&handle_request($peera, $locala, 0)) { }
        print DEBUG "main: Done handle_request loop pid=$$\n";
        close(SOCK);
        exit;
        }
 
 # Build list of sockets to listen on
-if ($config{"bind"} && $config{"bind"} ne "*") {
+$config{'bind'} = '' if ($config{'bind'} eq '*');
+if ($config{'bind'}) {
        # Listening on a specific IP
        if (&check_ip6address($config{'bind'})) {
                # IP is v6
@@ -446,31 +447,31 @@ if ($config{"bind"} && $config{"bind"} ne "*") {
        }
 else {
        # Listening on all IPs
+       push(@sockets, [ INADDR_ANY, $config{'port'}, PF_INET ]);
        if ($use_ipv6) {
-               # Accept IPv6 and v4 connections
+               # Also IPv6
                push(@sockets, [ in6addr_any(), $config{'port'},
                                 Socket6::PF_INET6() ]);
                }
-       else {
-               # Accept IPv4 only
-               push(@sockets, [ INADDR_ANY, $config{'port'}, PF_INET ]);
-               }
        }
 foreach $s (split(/\s+/, $config{'sockets'})) {
        if ($s =~ /^(\d+)$/) {
                # Just listen on another port on the main IP
                push(@sockets, [ $sockets[0]->[0], $s, $sockets[0]->[2] ]);
+               if ($use_ipv6 && !$config{'bind'}) {
+                       # Also listen on that port on the main IPv6 address
+                       push(@sockets, [ $sockets[1]->[0], $s,
+                                        $sockets[1]->[2] ]);
+                       }
                }
        elsif ($s =~ /^\*:(\d+)$/) {
                # Listening on all IPs on some port
+               push(@sockets, [ INADDR_ANY, $config{'port'},
+                                PF_INET ]);
                if ($use_ipv6) {
                        push(@sockets, [ in6addr_any(), $config{'port'},
                                         Socket6::PF_INET6() ]);
                        }
-               else {
-                       push(@sockets, [ INADDR_ANY, $config{'port'},
-                                        PF_INET ]);
-                       }
                }
        elsif ($s =~ /^(\S+):(\d+)$/) {
                # Listen on a specific port and IP
@@ -510,11 +511,13 @@ for($i=0; $i<@sockets; $i++) {
        socket($fh, $sockets[$i]->[2], SOCK_STREAM, $proto) ||
                die "Failed to open socket family $sockets[$i]->[2] : $!";
        setsockopt($fh, SOL_SOCKET, SO_REUSEADDR, pack("l", 1));
-       $pack = $sockets[$i]->[2] eq PF_INET ?
-                       pack_sockaddr_in($sockets[$i]->[1],
-                                        $sockets[$i]->[0]) :
-                       pack_sockaddr_in6($sockets[$i]->[1],
-                                          $sockets[$i]->[0]);
+       if ($sockets[$i]->[2] eq PF_INET) {
+               $pack = pack_sockaddr_in($sockets[$i]->[1], $sockets[$i]->[0]);
+               }
+       else {
+               $pack = pack_sockaddr_in6($sockets[$i]->[1], $sockets[$i]->[0]);
+               setsockopt($fh, 41, 26, pack("l", 1));  # IPv6 only
+               }
        for($j=0; $j<5; $j++) {
                last if (bind($fh, $pack));
                sleep(1);
@@ -863,10 +866,12 @@ while(1) {
                                &close_all_sockets();
                                close(LISTEN);
 
-                               # XXX IPv6 - refactor to not resolve IP again
                                print DEBUG
                                  "main: Starting handle_request loop pid=$$\n";
-                               while(&handle_request($peera, $locala)) { }
+                               while(&handle_request($peera, $locala,
+                                                     $ipv6fhs{$s})) {
+                                       # Loop until keepalive stops
+                                       }
                                print DEBUG
                                  "main: Done handle_request loop pid=$$\n";
                                shutdown(SOCK, 1);
@@ -1168,14 +1173,14 @@ while(1) {
        @passout = grep { defined($_) } @passout;
        }
 
-# handle_request(remoteaddress, localaddress)
+# handle_request(remoteaddress, localaddress, ipv6-flag)
 # Where the real work is done
 sub handle_request
 {
-local ($acptip, $localip) = @_;
-print DEBUG "handle_request: from $acptip to $localip\n";
+local ($acptip, $localip, $ipv6) = @_;
+print DEBUG "handle_request: from $acptip to $localip ipv6=$ipv6\n";
 if ($config{'loghost'}) {
-       $acpthost = gethostbyaddr(inet_aton($acptip), AF_INET);
+       $acpthost = &to_hostname($acptip);
        $acpthost = $acptip if (!$acpthost);
        }
 else {
@@ -2155,6 +2160,7 @@ if (&get_type($full) eq "internal/cgi" && $validated != 4) {
        $ENV{"SERVER_PORT"} = $port;
        $ENV{"REMOTE_HOST"} = $acpthost;
        $ENV{"REMOTE_ADDR"} = $acptip;
+       $ENV{"REMOTE_ADDR_PROTOCOL"} = $ipv6 ? 6 : 4;
        $ENV{"REMOTE_USER"} = $authuser;
        $ENV{"BASE_REMOTE_USER"} = $authuser ne $baseauthuser ?
                                        $baseauthuser : undef;
@@ -2547,13 +2553,20 @@ sub b64decode
 sub ip_match
 {
 local(@io, @mo, @ms, $i, $j, $hn, $needhn);
-@io = split(/\./, $_[0]);
+@io = &check_ip6address($_[0]) ? split(/:/, $_[0])
+                              : split(/\./, $_[0]);
 for($i=2; $i<@_; $i++) {
        $needhn++ if ($_[$i] =~ /^\*(\S+)$/);
        }
 if ($needhn && !defined($hn = $ip_match_cache{$_[0]})) {
-       $hn = gethostbyaddr(inet_aton($_[0]), AF_INET);
-       $hn = "" if (&to_ipaddress($hn) ne $_[0]);
+       # Reverse-lookup hostname if any rules match based on it
+       $hn = &to_hostname($_[0]);
+       if (&check_ip6address($_[0])) {
+               $hn = "" if (&to_ip6address($hn) ne $_[0]);
+               }
+       else {
+               $hn = "" if (&to_ipaddress($hn) ne $_[0]);
+               }
        $ip_match_cache{$_[0]} = $hn;
        }
 for($i=2; $i<@_; $i++) {
@@ -2564,6 +2577,7 @@ for($i=2; $i<@_; $i++) {
                }
        if ($_[$i] =~ /^(\S+)\/(\S+)$/) {
                # Compare with network/mask
+               # XXX IPv6 support
                @mo = split(/\./, $1); @ms = split(/\./, $2);
                for($j=0; $j<4; $j++) {
                        if ((int($io[$j]) & int($ms[$j])) != int($mo[$j])) {
@@ -2577,6 +2591,7 @@ for($i=2; $i<@_; $i++) {
                }
        elsif ($_[$i] eq 'LOCAL') {
                # Compare with local network
+               # XXX IPv6 support
                local @lo = split(/\./, $_[1]);
                if ($lo[0] < 128) {
                        $mismatch = 1 if ($lo[0] != $io[0]);
@@ -2591,12 +2606,8 @@ for($i=2; $i<@_; $i++) {
                                          $lo[2] != $io[2]);
                        }
                }
-       elsif ($_[$i] !~ /^[0-9\.]+$/) {
-               # Compare with hostname
-               $mismatch = 1 if ($_[0] ne &to_ipaddress($_[$i]));
-               }
-       else {
-               # Compare with IP or network
+       elsif ($_[$i] =~ /^[0-9\.]+$/) {
+               # Compare with IPv4 address or network
                @mo = split(/\./, $_[$i]);
                while(@mo && !$mo[$#mo]) { pop(@mo); }
                for($j=0; $j<@mo; $j++) {
@@ -2605,6 +2616,20 @@ for($i=2; $i<@_; $i++) {
                                }
                        }
                }
+       elsif ($_[$i] =~ /^[a-f0-9:]+$/) {
+               # Compare with IPv6 address or network
+               @mo = split(/:/, $_[$i]);
+               while(@mo && !$mo[$#mo]) { pop(@mo); }
+               for($j=0; $j<@mo; $j++) {
+                       if ($mo[$j] != $io[$j]) {
+                               $mismatch = 1;
+                               }
+                       }
+               }
+       elsif ($_[$i] !~ /^[0-9\.]+$/) {
+               # Compare with hostname
+               $mismatch = 1 if ($_[0] ne &to_ipaddress($_[$i]));
+               }
        return 1 if (!$mismatch);
        }
 return 0;
@@ -2659,17 +2684,65 @@ sub trigger_reload
 $need_reload = 1;
 }
 
+# to_ipaddress(address, ...)
 sub to_ipaddress
 {
 local (@rv, $i);
 foreach $i (@_) {
        if ($i =~ /(\S+)\/(\S+)/ || $i =~ /^\*\S+$/ ||
-           $i eq 'LOCAL' || $i =~ /^[0-9\.]+$/) { push(@rv, $i); }
-       else { push(@rv, join('.', unpack("CCCC", inet_aton($i)))); }
+           $i eq 'LOCAL' || $i =~ /^[0-9\.]+$/ || $i =~ /^[a-f0-9:]+$/) {
+               # A pattern or IP, not a hostname, so don't change
+               push(@rv, $i);
+               }
+       else {
+               # Lookup IP address
+               push(@rv, join('.', unpack("CCCC", inet_aton($i))));
+               }
        }
 return wantarray ? @rv : $rv[0];
 }
 
+# to_ip6address(address, ...)
+sub to_ip6address
+{
+local (@rv, $i);
+foreach $i (@_) {
+       if ($i =~ /(\S+)\/(\S+)/ || $i =~ /^\*\S+$/ ||
+           $i eq 'LOCAL' || $i =~ /^[0-9\.]+$/ || $i =~ /^[a-f0-9:]+$/) {
+               # A pattern, not a hostname, so don't change
+               push(@rv, $i);
+               }
+       else {
+               # Lookup IPv6 address
+               local ($inaddr, $addr);
+               (undef, undef, undef, $inaddr) =
+                   getaddrinfo($i, undef, Socket6::AF_INET6(), SOCK_STREAM);
+               if ($inaddr) {
+                       push(@rv, undef);
+                       }
+               else {
+                       (undef, $addr) = unpack_sockaddr_in6($inaddr);
+                       push(@rv, inet_ntop(Socket6::AF_INET6(), $addr));
+                       }
+               }
+       }
+return wantarray ? @rv : $rv[0];
+}
+
+# to_hostname(ipv4|ipv6-address)
+# Reverse-resolves an IPv4 or 6 address to a hostname
+sub to_hostname
+{
+local ($addr) = @_;
+if (&check_ip6address($_[0])) {
+       return gethostbyaddr(inet_pton(Socket6::AF_INET6(), $addr),
+                            Socket6::AF_INET6());
+       }
+else {
+       return gethostbyaddr(inet_aton($addr), AF_INET);
+       }
+}
+
 # read_line(no-wait, no-limit)
 # Reads one line from SOCK or SSL
 sub read_line
@@ -3517,7 +3590,7 @@ if ($ipv6) {
        }
 else {
        local ($p, $b) = unpack_sockaddr_in($sn);
-       return ($b, inet_ntoa($myaddr), $p);
+       return ($b, inet_ntoa($b), $p);
        }
 }
 
index 2657d35..5b19d97 100755 (executable)
@@ -16,7 +16,9 @@ for($i=0; defined($in{"ip_def_$i"}); $i++) {
                }
        else {
                $ip = $in{"ip_$i"};
-               &check_ipaddress($ip) || &error(&text('bind_eip2', $ip));
+               &check_ipaddress($ip) ||
+                   $in{'ipv6'} && &check_ip6address($ip) ||
+                       &error(&text('bind_eip2', $ip));
                }
        if ($in{"port_def_$i"} == 1) {
                $port = $in{"port_$i"};
@@ -32,6 +34,10 @@ for($i=0; defined($in{"ip_def_$i"}); $i++) {
 $in{'listen_def'} || $in{'listen'} =~ /^\d+$/ || &error($text{'bind_elisten'});
 $in{'hostname_def'} || $in{'hostname'} =~ /^[a-z0-9\.\-]+$/i ||
        &error($text{'bind_ehostname'});
+if ($in{'ipv6'}) {
+       eval "use Socket6";
+       @$ && &error(&text('bind_eipv6', "<tt>Socket6</tt>"));
+       }
 
 # Update config file
 &lock_file($ENV{'MINISERV_CONFIG'});
@@ -44,6 +50,7 @@ else {
        $miniserv{'bind'} = $first->[0];
        }
 $miniserv{'sockets'} = join(" ", map { "$_->[0]:$_->[1]" } @sockets);
+$miniserv{'ipv6'} = $in{'ipv6'};
 if ($in{'listen_def'}) {
        delete($miniserv{'listen'});
        }
@@ -89,6 +96,7 @@ if ($tconfig{'inframe'}) {
        # Theme uses frames, so we need to redirect the whole frameset
        $url .= ":$miniserv{'port'}";
        &ui_print_header(undef, $text{'bind_title'}, "");
+       print $text{'bind_redirecting'},"<p>\n";
        print "<script>\n";
        print "top.location = '$url';\n";
        print "</script>\n";
index 060783e..2015672 100755 (executable)
@@ -38,7 +38,11 @@ foreach $s (@sockets, [ undef, "*" ]) {
 $stable .= &ui_columns_end();
 print &ui_table_row($text{'bind_sockets'}, $stable);
 
-# Show listen address
+# IPv6 enabled?
+print &ui_table_row($text{'bind_ipv6'},
+       &ui_yesno_radio("ipv6", $miniserv{'ipv6'}));
+
+# Show UDP listen address
 print &ui_table_row($text{'bind_listen'},
     &ui_radio("listen_def", $miniserv{"listen"} ? 0 : 1,
        [ [ 1, $text{'bind_none'} ],
index abd1314..54bb73b 100644 (file)
@@ -54,6 +54,9 @@ bind_erestart=An error occurring starting Webmin with the new address and port s
 bind_elisten=Missing or invalid port to listen for UDP broadcasts on
 bind_ehostname=Missing or invalid web server hostname
 bind_resolv_myname=Reverse-resolve connected IP address?
+bind_ipv6=Accept IPv6 connections?
+bind_eipv6=IPv6 cannot be enabled unless the $1 Perl module is installed
+bind_redirecting=Redirecting to new URL ..
 
 log_title=Logging
 log_desc=Webmin can be configured to write a log of web server hits, in the standard CLF log file format. If logging is enabled, you can also choose whether IP addresses or hostnames are recorded, and how often the log file is cleared. When enabled, logs are written to the file $1.
index 58ebe2c..940a287 100755 (executable)
@@ -787,7 +787,8 @@ foreach $s (split(/\s+/, $_[0]->{'sockets'})) {
                # Listen on a specific port and IP
                push(@sockets, [ $1, $2 ]);
                }
-       elsif ($s =~ /^([0-9\.]+):\*$/ || $s =~ /^([0-9\.]+)$/) {
+       elsif ($s =~ /^([0-9\.]+):\*$/ || $s =~ /^([0-9\.]+)$/ ||
+              $s =~ /^([a-f0-9:]+):\*$/ || $s =~ /^([a-f0-9:]+)$/) {
                # Listen on the main port on another IP
                push(@sockets, [ $1, "*" ]);
                }