[svn:qpsmtpd] r657 - in branches/0.3x: . lib/Qpsmtpd plugins

[email protected] Sun, 27 Aug 2006 16:17:38 -0700 (PDT)
Newsgroups perl.cvs.qpsmtpd
Message-ID <[email protected]>
Author: ask
Date: Sun Aug 27 16:17:33 2006
New Revision: 657

Modified:
   branches/0.3x/   (props changed)
   branches/0.3x/Changes
   branches/0.3x/lib/Qpsmtpd/Constants.pm
   branches/0.3x/plugins/check_relay
   branches/0.3x/plugins/require_resolvable_fromhost
   branches/0.3x/qpsmtpd-forkserver

Log:
 r4175@embla:  ask | 2006-08-28 01:17:10 +0200
 Experimental IPv6 support (forkserver only). (Mike Williams)


Modified: branches/0.3x/Changes
==============================================================================
--- branches/0.3x/Changes	(original)
+++ branches/0.3x/Changes	Sun Aug 27 16:17:33 2006
@@ -1,5 +1,7 @@
 0.33
 
+  Experimental IPv6 support (forkserver only). (Mike Williams)
+
   Support "module" plugins ("My::Plugin" in the config/plugins file)
 
   Enhance the spamassassin plugin to support connecting to a remote

Modified: branches/0.3x/lib/Qpsmtpd/Constants.pm
==============================================================================
--- branches/0.3x/lib/Qpsmtpd/Constants.pm	(original)
+++ branches/0.3x/lib/Qpsmtpd/Constants.pm	Sun Aug 27 16:17:33 2006
@@ -27,6 +27,24 @@
 	DONE     => 910,
 );
 
+my $has_ipv6;
+
+if (
+    eval {require Socket6;} &&
+    # INET6 prior to 2.01 will not work; sorry.
+    eval {require IO::Socket::INET6; IO::Socket::INET6->VERSION("2.00");}
+   ) {
+    import Socket6;
+    $has_ipv6=1;
+}
+else {
+    $has_ipv6=0;
+}
+
+sub has_ipv6 {
+    return $has_ipv6;
+}
+
 use vars qw(@ISA @EXPORT);
 @ISA = qw(Exporter);
 @EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");

Modified: branches/0.3x/plugins/check_relay
==============================================================================
--- branches/0.3x/plugins/check_relay	(original)
+++ branches/0.3x/plugins/check_relay	Sun Aug 27 16:17:33 2006
@@ -19,7 +19,7 @@
       $connection->relay_client(1);
       last;
     }
-    $client_ip =~ s/\d+\.?$//; # strip off another 8 bits
+    $client_ip =~ s/(\d|\w|::)+(:|\.)?$//; # strip off another 8 bits
   }
   
   return (DECLINED);

Modified: branches/0.3x/plugins/require_resolvable_fromhost
==============================================================================
--- branches/0.3x/plugins/require_resolvable_fromhost	(original)
+++ branches/0.3x/plugins/require_resolvable_fromhost	Sun Aug 27 16:17:33 2006
@@ -3,6 +3,7 @@
 use Socket;
 
 my %invalid = ();
+my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
 
 sub hook_mail {
   my ($self, $transaction, $sender, %param) = @_;
@@ -38,6 +39,7 @@
 
 sub check_dns {
   my ($self, $host) = @_;
+  my @host_answers;
 
   # for stuff where we can't even parse a hostname out of the address
   return 0 unless $host;
@@ -53,15 +55,24 @@
   }
   my $query = $res->search($host);
   if ($query) {
-    foreach my $rr ($query->answer) {
-      if ($rr->type eq "A") {
-        return is_valid($rr->address);
-      }
-      elsif ($rr->type eq "MX") {
-        return mx_valid($self, $rr->exchange, $host);
+    foreach my $rrA ($query->answer) {
+      push(@host_answers, $rrA);
+    }
+  }
+  if ($has_ipv6) {
+    my $query = $res->search($host, 'AAAA');
+    if ($query) {
+      foreach my $rrAAAA ($query->answer) {
+        push(@host_answers, $rrAAAA);
       }
     }
   } 
+  if (@host_answers) {
+    foreach my $rr (@host_answers) {
+      return is_valid($rr->address) if $rr->type eq "A" or $rr->type eq "AAAA";
+      return mx_valid($self, $rr->exchange, $host) if $rr->type eq "MX";
+    }
+  }
   else {
     $self->log(LOGWARN, "$$ query for $host failed: ", $res->errorstring)
       unless $res->errorstring eq "NXDOMAIN";
@@ -88,10 +99,24 @@
 sub mx_valid {
   my ($self, $name, $host) = @_;
   my $res   = new Net::DNS::Resolver;
-  my $query = $res->search($name);
+  my @mx_answers;
+  my $query = $res->search($name, 'A');
   if ($query) {
-    foreach my $rr ($query->answer) {
-      next unless $rr->type eq "A";
+    foreach my $rrA ($query->answer) {
+      push(@mx_answers, $rrA);
+    }
+  }
+  if ($has_ipv6) {
+    my $query = $res->search($name, 'AAAA');
+    if ($query) {
+      foreach my $rrAAAA ($query->answer) {
+        push(@mx_answers, $rrAAAA);
+      }
+    }
+  }
+  if (@mx_answers) {
+    foreach my $rr (@mx_answers) {
+      next unless $rr->type eq "A" or $rr->type eq "AAAA";
       return is_valid($rr->address);
     }
   }

Modified: branches/0.3x/qpsmtpd-forkserver
==============================================================================
--- branches/0.3x/qpsmtpd-forkserver	(original)
+++ branches/0.3x/qpsmtpd-forkserver	Sun Aug 27 16:17:33 2006
@@ -17,6 +17,12 @@
 use strict;
 $| = 1;
 
+my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
+
+if ($has_ipv6) {
+  use Socket6;
+}
+
 # Configuration
 my $MAXCONN   = 15;                  # max simultaneous connections
 my @PORT;                            # port number(s)
@@ -54,12 +60,17 @@
           ) || &usage;
 
 # detaint the commandline
-@LOCALADDR = ( '0.0.0.0' ) if !@LOCALADDR;
+if ($has_ipv6) {
+  @LOCALADDR = ( '[::]' ) if !@LOCALADDR;
+}
+else {
+  @LOCALADDR = ( '0.0.0.0' ) if !@LOCALADDR;
+}
 @PORT = ( 2525 ) if !@PORT;
 
 my @LISTENADDR;
 for (0..$#LOCALADDR) {
-  if ($LOCALADDR[$_] =~ /^([\d\w\-.]+)(?::(\d+))?$/) {
+  if ($LOCALADDR[$_] =~ /^(\[.*\]|[\d\w\-.]+)(?::(\d+))?$/) {
     if ( defined $2 ) {
       push @LISTENADDR, { 'addr' => $1, 'port' => $2 };
     } else {
@@ -106,16 +117,24 @@
 $SIG{TERM} = \&HUNTSMAN;
 
 my $select = new IO::Select;
+my $server;
 
 # establish SERVER socket(s), bind and listen.
 for my $listen_addr (@LISTENADDR) {
-  my $server = IO::Socket::INET->new(LocalPort => $listen_addr->{'port'},
+  my @Socket_opts = (LocalPort => $listen_addr->{'port'},
                                      LocalAddr => $listen_addr->{'addr'},
                                      Proto     => 'tcp',
                                      Reuse     => 1,
                                      Blocking  => 0,
-                                     Listen    => SOMAXCONN )
-    or die "Creating TCP socket $listen_addr->{'addr'}:$listen_addr->{'port'}: $!\n";
+                                     Listen    => SOMAXCONN);
+  if ($has_ipv6) {
+    $server = IO::Socket::INET6->new(@Socket_opts)
+      or die "Creating TCP socket $listen_addr->{'addr'}:$listen_addr->{'port'}: $!\n";
+  }
+  else {
+    $server = IO::Socket::INET->new(@Socket_opts)
+      or die "Creating TCP socket $listen_addr->{'addr'}:$listen_addr->{'port'}: $!\n";
+  }
   IO::Handle::blocking($server, 0);
   $select->add($server);
 }
@@ -208,14 +227,19 @@
       next;
     }
     IO::Handle::blocking($client, 1);
-    my ($port, $iaddr) = sockaddr_in($hisaddr);
+    my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr));
     my $localsockaddr = getsockname($client);
-    my ($lport, $laddr) = sockaddr_in($localsockaddr);
+    my ($lport, $laddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($localsockaddr)) : (sockaddr_in6($localsockaddr));
+    my $nto_iaddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($iaddr)) : (inet_ntop(AF_INET6, $iaddr));
+    my $ton_iaddr = ($server->sockdomain == AF_INET) ? (inet_aton($iaddr)) : (inet_pton(AF_INET6, $iaddr));
+    my $nto_laddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($laddr)) : (inet_ntop(AF_INET6, $laddr));
+    $nto_iaddr =~ s/::ffff://;
+    $nto_laddr =~ s/::ffff://;
 
     my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
-                                         remote_ip    => inet_ntoa($iaddr),
+                                         remote_ip    => $nto_iaddr,
                                          remote_port  => $port,
-                                         local_ip     => inet_ntoa($laddr),
+                                         local_ip     => $nto_laddr,
                                          local_port   => $lport,
                                          max_conn_ip  => $MAXCONNIP,
                                          child_addrs  => [values %childstatus],
@@ -259,11 +283,18 @@
        ::log(LOGINFO, "Connection Timed Out"); 
        exit; };
   
-    $ENV{TCPLOCALIP} = inet_ntoa($laddr);
+    $ENV{TCPLOCALIP} = $nto_laddr;
     # my ($port, $iaddr) = sockaddr_in($hisaddr);
-    $ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
-    $ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
-  
+    $ENV{TCPREMOTEIP} = $nto_iaddr;
+
+    if ($server->sockdomain == AF_INET) {
+      $ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
+    }
+    else {
+      my ($family, $socktype, $proto, $saddr, $canonname, @res) = getaddrinfo($iaddr, $port, AF_UNSPEC);
+      $ENV{TCPREMOTEHOST} = $canonname || "Unknown";
+    }
+
     # don't do this!
     #$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";