Well, that was surprisingly easy.

I moved the check for Socket6 and IO::Socket::INET6 to 
lib/Qpsmtpd/Constants.pm and added a has_ipv6 function to return the status 
of support.
Constants seemed the most universally used module.

require_resolvable_fromhost had it's own check for IPv6 removed, and is 
included in this patch.

I'm not fully convinced check_relay is good enough.
It appears to work fine. However I guess IPv6 addresses could come in in 
either upper or lower case.

I use forkserver, so that's what I modified.
IPv6 address must be specified within [].
It can listen on both v6 and v4 address, at the same time.
The LOCALADDR regexp should be better.
I can't get getaddrinfo to work, it always seems to return gibberish, so 
unless qpsmtpd is listening on a v4 address TCPREMOTEHOST will always be 
Unknown.

So far I've run several tests against it, sending mails from v4 addresses to 
v4 and v6 ([::]) servers, and from v6 to v6 ([::] and [2001:...]) servers.
dns_bl doesn't have any issue, but there isn't much point in running that on 
v6 addresses.
I'm not yet confident enough to run it in production :) Although I am going to 
relay this via my local copy over IPv6 to our main server over v4

The patch is against branches/0.3x.
Ignore the SWALLOW in Constants.pm, it's not relevant to IPv6.

-- 
Mike Williams
Index: plugins/require_resolvable_fromhost
===================================================================
--- plugins/require_resolvable_fromhost	(revision 656)
+++ plugins/require_resolvable_fromhost	(working copy)
@@ -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);
+    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);
       }
-      elsif ($rr->type eq "MX") {
-        return mx_valid($self, $rr->exchange, $host);
-      }
     }
   } 
+  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);
     }
   }
Index: plugins/check_relay
===================================================================
--- plugins/check_relay	(revision 656)
+++ plugins/check_relay	(working copy)
@@ -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);
Index: lib/Qpsmtpd/Constants.pm
===================================================================
--- lib/Qpsmtpd/Constants.pm	(revision 656)
+++ lib/Qpsmtpd/Constants.pm	(working copy)
@@ -23,10 +23,28 @@
 	DENYHARD => 903,   # 550 + disconnect  (deprecated in 0.29)
 	DENY_DISCONNECT     => 903, # 550 + disconnect
 	DENYSOFT_DISCONNECT => 904, # 450 + disconnect
+	SWALLOW  => 905, # Lie, say it was queued, without queuing
 	DECLINED => 909,
 	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");
Index: qpsmtpd-forkserver
===================================================================
--- qpsmtpd-forkserver	(revision 656)
+++ qpsmtpd-forkserver	(working copy)
@@ -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
[EMAIL PROTECTED] = ( '0.0.0.0' ) if [EMAIL PROTECTED];
+if ($has_ipv6) {
+  @LOCALADDR = ( '[::]' ) if [EMAIL PROTECTED];
+}
+else {
+  @LOCALADDR = ( '0.0.0.0' ) if [EMAIL PROTECTED];
+}
 @PORT = ( 2525 ) if [EMAIL PROTECTED];
 
 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}";
   

Reply via email to