IPv6 support from issue #7.

git-svn-id: https://svn.perl.org/qpsmtpd/trunk@740 958fd67b-6ff1-0310-b445-bb7760255be9
This commit is contained in:
Matt Sergeant 2007-05-17 22:16:27 +00:00
parent 2db48784fe
commit ccf990e032
6 changed files with 164 additions and 83 deletions

View File

@ -29,24 +29,6 @@ my %return_codes = (
YIELD => 911, YIELD => 911,
); );
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); use vars qw(@ISA @EXPORT);
@ISA = qw(Exporter); @ISA = qw(Exporter);
@EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level"); @EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");

View File

@ -8,6 +8,23 @@ use strict;
use POSIX (); use POSIX ();
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;
}
my $first_0; my $first_0;
sub start_connection { sub start_connection {
@ -104,4 +121,43 @@ sub disconnect {
exit; exit;
} }
# local/remote port and ip address
sub lrpip {
my ($server, $client, $hisaddr) = @_;
my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr));
my $localsockaddr = getsockname($client);
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 $nto_laddr = ($server->sockdomain == AF_INET) ? (inet_ntoa($laddr)) : (inet_ntop(AF_INET6(), $laddr));
$nto_iaddr =~ s/::ffff://;
$nto_laddr =~ s/::ffff://;
return ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr);
}
sub tcpenv {
my ($nto_laddr, $nto_iaddr, $no_rdns) = @_;
my $TCPLOCALIP = $nto_laddr;
my $TCPREMOTEIP = $nto_iaddr;
if ($no_rdns) {
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEIP ? "[$ENV{TCPREMOTEIP}]" : "[noip!]");
}
my $res = new Net::DNS::Resolver;
$res->tcp_timeout(3);
$res->udp_timeout(3);
my $query = $res->query($nto_iaddr);
my $TCPREMOTEHOST;
if($query) {
foreach my $rr ($query->answer) {
next unless $rr->type eq "PTR";
$TCPREMOTEHOST = $rr->ptrdname;
}
}
return ($TCPLOCALIP, $TCPREMOTEIP, $TCPREMOTEHOST || "Unknown");
}
1; 1;

View File

@ -2,25 +2,70 @@
# $ENV{RELAYCLIENT} to see if relaying is allowed. # $ENV{RELAYCLIENT} to see if relaying is allowed.
# #
use Net::IP qw(:PROC);
sub hook_connect { sub hook_connect {
my ($self, $transaction) = @_; my ($self, $transaction) = @_;
my $connection = $self->qp->connection; my $connection = $self->qp->connection;
# Check if this IP is allowed to relay # Check if this IP is allowed to relay
my @relay_clients = $self->qp->config("relayclients");
my $more_relay_clients = $self->qp->config("morerelayclients", "map");
my %relay_clients = map { $_ => 1 } @relay_clients;
my $client_ip = $self->qp->connection->remote_ip; my $client_ip = $self->qp->connection->remote_ip;
while ($client_ip) {
if (exists($ENV{RELAYCLIENT}) or # @crelay... for comparing, @srelay... for stripping
exists($relay_clients{$client_ip}) or my (@crelay_clients, @srelay_clients);
exists($more_relay_clients->{$client_ip}))
{ my @relay_clients = $self->qp->config("relayclients");
$connection->relay_client(1); for (@relay_clients) {
last; my ($range_ip, $range_prefix) = ip_splitprefix($_);
if($range_prefix){
# has a prefix, so due for comparing
push @crelay_clients, $_;
}
else {
# has no prefix, so due for splitting
push @srelay_clients, $_;
} }
$client_ip =~ s/(\d|\w|::)+(:|\.)?$//; # strip off another 8 bits
} }
if (@crelay_clients){
my ($range_ip, $range_prefix, $rversion, $begin, $end, $bin_client_ip);
my $cversion = ip_get_version($client_ip);
for (@crelay_clients) {
# Get just the IP from the CIDR range, to get the IP version, so we can
# get the start and end of the range
($range_ip, $range_prefix) = ip_splitprefix($_);
$rversion = ip_get_version($range_ip);
($begin, $end) = ip_normalize($_, $rversion);
# expand the client address (zero pad it) before converting to binary
$bin_client_ip = ip_iptobin(ip_expand_address($client_ip, $cversion), $cversion);
if (ip_bincomp($bin_client_ip, 'gt', ip_iptobin($begin, $rversion))
&& ip_bincomp($bin_client_ip, 'lt', ip_iptobin($end, $rversion)))
{
$connection->relay_client(1);
last;
}
}
}
# If relay_client is already set, no point checking again
if (@srelay_clients && !$connection->relay_client) {
my $more_relay_clients = $self->qp->config("morerelayclients", "map");
my %srelay_clients = map { $_ => 1 } @srelay_clients;
$client_ip =~ s/::/:/;
($connection->relay_client(1) && undef($client_ip)) if $client_ip eq ":1";
while ($client_ip) {
if (exists($ENV{RELAYCLIENT}) or
exists($srelay_clients{$client_ip}) or
exists($more_relay_clients->{$client_ip}))
{
$connection->relay_client(1);
last;
}
$client_ip =~ s/(\d|\w)+(:|\.)?$//; # strip off another 8 bits
}
}
return (DECLINED); return (DECLINED);
} }

View File

@ -1,9 +1,10 @@
use Qpsmtpd::DSN; use Qpsmtpd::DSN;
use Net::DNS qw(mx); use Net::DNS qw(mx);
use Socket; use Socket;
use Net::IP qw(:PROC);
my %invalid = (); my %invalid = ();
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6; my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
sub hook_mail { sub hook_mail {
my ($self, $transaction, $sender, %param) = @_; my ($self, $transaction, $sender, %param) = @_;
@ -99,6 +100,9 @@ sub is_valid {
sub mx_valid { sub mx_valid {
my ($self, $name, $host) = @_; my ($self, $name, $host) = @_;
my $res = new Net::DNS::Resolver; my $res = new Net::DNS::Resolver;
# IP in MX
return is_valid($name) if ip_is_ipv4($name) or ip_is_ipv6($name);
my @mx_answers; my @mx_answers;
my $query = $res->search($name, 'A'); my $query = $res->search($name, 'A');
if ($query) { if ($query) {

View File

@ -18,7 +18,7 @@ use Net::DNS::Header;
use strict; use strict;
$| = 1; $| = 1;
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6; my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
if ($has_ipv6) { if ($has_ipv6) {
eval 'use Socket6'; eval 'use Socket6';
@ -40,8 +40,10 @@ sub usage {
print <<"EOT"; print <<"EOT";
usage: qpsmtpd-forkserver [ options ] usage: qpsmtpd-forkserver [ options ]
-l, --listen-address addr : listen on specific address(es); can be specified -l, --listen-address addr : listen on specific address(es); can be specified
multiple times for multiple bindings. Default is multiple times for multiple bindings. IPv6
0.0.0.0 (all interfaces). addresses must be inside square brackets [], and
don't need to be zero padded.
Default is [::] (if has_ipv6) or 0.0.0.0 (if not)
-p, --port P : listen on a specific port; default 2525; can be -p, --port P : listen on a specific port; default 2525; can be
specified multiple times for multiple bindings. specified multiple times for multiple bindings.
-c, --limit-connections N : limit concurrent connections to N; default 15 -c, --limit-connections N : limit concurrent connections to N; default 15
@ -234,14 +236,8 @@ while (1) {
next; next;
} }
IO::Handle::blocking($client, 1); IO::Handle::blocking($client, 1);
my ($port, $iaddr) = ($server->sockdomain == AF_INET) ? (sockaddr_in($hisaddr)) : (sockaddr_in6($hisaddr)); # get local/remote hostname, port and ip address
my $localsockaddr = getsockname($client); my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($server, $client, $hisaddr);
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", my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
remote_ip => $nto_iaddr, remote_ip => $nto_iaddr,
@ -293,28 +289,9 @@ while (1) {
::log(LOGINFO, "Connection Timed Out"); ::log(LOGINFO, "Connection Timed Out");
exit; }; exit; };
$ENV{TCPLOCALIP} = $nto_laddr; # set enviroment variables
# my ($port, $iaddr) = sockaddr_in($hisaddr); ($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
$ENV{TCPREMOTEIP} = $nto_iaddr;
if ($NORDNS) {
$ENV{TCPREMOTEHOST} = $ENV{TCPREMOTEIP} ? "[$ENV{TCPREMOTEIP}]" : "[noip!]";
}
else {
my $zero = $0;
$0 = "$zero (gethostbyname $ENV{TCPREMOTEIP})";
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";
}
$0 = $zero;
}
# don't do this! # don't do this!
#$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}"; #$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";

View File

@ -19,6 +19,12 @@ use Qpsmtpd::TcpServer::Prefork;
use Qpsmtpd::Constants; use Qpsmtpd::Constants;
use Getopt::Long; use Getopt::Long;
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
if ($has_ipv6) {
use Socket6;
}
#use Time::HiRes qw(gettimeofday tv_interval); #use Time::HiRes qw(gettimeofday tv_interval);
# secure shell # secure shell
@ -47,7 +53,14 @@ my $d; # socket
my $pid_path = '/var/run/qpsmtpd/'; my $pid_path = '/var/run/qpsmtpd/';
my $PID = $pid_path . "/qpsmtpd.pid"; my $PID = $pid_path . "/qpsmtpd.pid";
my $d_port = 25; my $d_port = 25;
my $d_addr = "0.0.0.0"; my $d_addr;
if ($has_ipv6) {
$d_addr = "[::]";
}
else {
$d_addr = "0.0.0.0";
}
my $debug = 0; my $debug = 0;
my $max_children = 15; # max number of child processes to spawn my $max_children = 15; # max number of child processes to spawn
my $idle_children = 5; # number of idle child processes to spawn my $idle_children = 5; # number of idle child processes to spawn
@ -67,7 +80,7 @@ sub usage {
print <<"EOT"; print <<"EOT";
Usage: qpsmtpd-prefork [ options ] Usage: qpsmtpd-prefork [ options ]
--quiet : Be quiet (even errors are suppressed) --quiet : Be quiet (even errors are suppressed)
--version : Show version information --version : Show version information
--debug : Enable debug output --debug : Enable debug output
--interface addr : Interface daemon should listen on (default: $d_addr) --interface addr : Interface daemon should listen on (default: $d_addr)
--port int : TCP port daemon should listen on (default: $d_port) --port int : TCP port daemon should listen on (default: $d_port)
@ -134,15 +147,20 @@ sub run {
if (!$uuid || !$ugid); if (!$uuid || !$ugid);
} }
my @Socket_opts = (
LocalPort => $d_port,
LocalAddr => $d_addr,
Proto => 'tcp',
Listen => SOMAXCONN,
Reuse => 1,
);
# create new socket (used by clients to communicate with daemon) # create new socket (used by clients to communicate with daemon)
$d = if ($has_ipv6) {
new IO::Socket::INET( $d = IO::Socket::INET6->new(@Socket_opts);
LocalPort => $d_port, }
LocalAddr => $d_addr, else {
Proto => 'tcp', $d = IO::Socket::INET(@Socket_opts);
Listen => SOMAXCONN, }
Reuse => 1,
);
die "FATAL: Failed to start daemon.\nReason: $!\n(It may be nessesary to " die "FATAL: Failed to start daemon.\nReason: $!\n(It may be nessesary to "
. "wait 20 secs before starting daemon again)\n" . "wait 20 secs before starting daemon again)\n"
unless $d; unless $d;
@ -361,7 +379,7 @@ sub new_child {
my $sigset = block_signal(SIGHUP); my $sigset = block_signal(SIGHUP);
# start a session if connection looks valid # start a session if connection looks valid
qpsmtpd_session($client, $qpsmtpd) if ($iinfo); qpsmtpd_session($client, $iinfo, $qpsmtpd) if ($iinfo);
# close connection and cleanup # close connection and cleanup
$client->shutdown(2); $client->shutdown(2);
@ -512,15 +530,16 @@ sub info {
# start qpmstpd session # start qpmstpd session
# arg0: ref to socket object # arg0: ref to socket object
# arg1: ref to qpsmtpd instance # arg1: ref to socket object
# arg2: ref to qpsmtpd instance
# ret0: void # ret0: void
sub qpsmtpd_session { sub qpsmtpd_session {
my $client = shift; #arg0 my $client = shift; #arg0
my $qpsmtpd = shift; #arg1 my $iinfo = shift; #arg1
my $qpsmtpd = shift; #arg2
# get local/remote hostname, port and ip address # get local/remote hostname, port and ip address
my ($port, $iaddr) = sockaddr_in(getpeername($client)); #remote my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($d, $client, $iinfo);
my ($lport, $laddr) = sockaddr_in(getsockname($client)); #local
# get current connected ip addresses (from shared memory) # get current connected ip addresses (from shared memory)
my %children; my %children;
@ -529,9 +548,9 @@ sub qpsmtpd_session {
my ($rc, @msg) = my ($rc, @msg) =
$qpsmtpd->run_hooks( $qpsmtpd->run_hooks(
"pre-connection", "pre-connection",
remote_ip => inet_ntoa($iaddr), remote_ip => $nto_iaddr,
remote_port => $port, remote_port => $port,
local_ip => inet_ntoa($laddr), local_ip => $nto_laddr,
local_port => $lport, local_port => $lport,
max_conn_ip => $maxconnip, max_conn_ip => $maxconnip,
child_addrs => [values %children], child_addrs => [values %children],
@ -574,9 +593,7 @@ sub qpsmtpd_session {
}; };
# set enviroment variables # set enviroment variables
$ENV{TCPLOCALIP} = inet_ntoa($laddr); ($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
$ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
$ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
# run qpmsptd functions # run qpmsptd functions
$SIG{__DIE__} = 'DEFAULT'; $SIG{__DIE__} = 'DEFAULT';