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:
parent
2db48784fe
commit
ccf990e032
@ -29,24 +29,6 @@ my %return_codes = (
|
||||
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);
|
||||
@ISA = qw(Exporter);
|
||||
@EXPORT = (keys(%return_codes), keys(%log_levels), "return_code", "log_level");
|
||||
|
@ -8,6 +8,23 @@ use strict;
|
||||
|
||||
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;
|
||||
|
||||
sub start_connection {
|
||||
@ -104,4 +121,43 @@ sub disconnect {
|
||||
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;
|
||||
|
@ -2,25 +2,70 @@
|
||||
# $ENV{RELAYCLIENT} to see if relaying is allowed.
|
||||
#
|
||||
|
||||
use Net::IP qw(:PROC);
|
||||
|
||||
sub hook_connect {
|
||||
my ($self, $transaction) = @_;
|
||||
my $connection = $self->qp->connection;
|
||||
|
||||
# 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;
|
||||
while ($client_ip) {
|
||||
if (exists($ENV{RELAYCLIENT}) or
|
||||
exists($relay_clients{$client_ip}) or
|
||||
exists($more_relay_clients->{$client_ip}))
|
||||
{
|
||||
$connection->relay_client(1);
|
||||
last;
|
||||
|
||||
# @crelay... for comparing, @srelay... for stripping
|
||||
my (@crelay_clients, @srelay_clients);
|
||||
|
||||
my @relay_clients = $self->qp->config("relayclients");
|
||||
for (@relay_clients) {
|
||||
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);
|
||||
}
|
||||
|
@ -1,9 +1,10 @@
|
||||
use Qpsmtpd::DSN;
|
||||
use Net::DNS qw(mx);
|
||||
use Socket;
|
||||
use Net::IP qw(:PROC);
|
||||
|
||||
my %invalid = ();
|
||||
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
|
||||
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||
|
||||
sub hook_mail {
|
||||
my ($self, $transaction, $sender, %param) = @_;
|
||||
@ -99,6 +100,9 @@ sub is_valid {
|
||||
sub mx_valid {
|
||||
my ($self, $name, $host) = @_;
|
||||
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 $query = $res->search($name, 'A');
|
||||
if ($query) {
|
||||
|
@ -18,7 +18,7 @@ use Net::DNS::Header;
|
||||
use strict;
|
||||
$| = 1;
|
||||
|
||||
my $has_ipv6 = Qpsmtpd::Constants::has_ipv6;
|
||||
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||
|
||||
if ($has_ipv6) {
|
||||
eval 'use Socket6';
|
||||
@ -40,8 +40,10 @@ sub usage {
|
||||
print <<"EOT";
|
||||
usage: qpsmtpd-forkserver [ options ]
|
||||
-l, --listen-address addr : listen on specific address(es); can be specified
|
||||
multiple times for multiple bindings. Default is
|
||||
0.0.0.0 (all interfaces).
|
||||
multiple times for multiple bindings. IPv6
|
||||
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
|
||||
specified multiple times for multiple bindings.
|
||||
-c, --limit-connections N : limit concurrent connections to N; default 15
|
||||
@ -234,14 +236,8 @@ while (1) {
|
||||
next;
|
||||
}
|
||||
IO::Handle::blocking($client, 1);
|
||||
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 $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://;
|
||||
# get local/remote hostname, port and ip address
|
||||
my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($server, $client, $hisaddr);
|
||||
|
||||
my ($rc, @msg) = $qpsmtpd->run_hooks("pre-connection",
|
||||
remote_ip => $nto_iaddr,
|
||||
@ -293,28 +289,9 @@ while (1) {
|
||||
::log(LOGINFO, "Connection Timed Out");
|
||||
exit; };
|
||||
|
||||
$ENV{TCPLOCALIP} = $nto_laddr;
|
||||
# my ($port, $iaddr) = sockaddr_in($hisaddr);
|
||||
$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;
|
||||
}
|
||||
|
||||
# set enviroment variables
|
||||
($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
|
||||
|
||||
# don't do this!
|
||||
#$0 = "qpsmtpd-forkserver: $ENV{TCPREMOTEIP} / $ENV{TCPREMOTEHOST}";
|
||||
|
||||
|
@ -19,6 +19,12 @@ use Qpsmtpd::TcpServer::Prefork;
|
||||
use Qpsmtpd::Constants;
|
||||
use Getopt::Long;
|
||||
|
||||
my $has_ipv6 = Qpsmtpd::TcpServer::has_ipv6;
|
||||
|
||||
if ($has_ipv6) {
|
||||
use Socket6;
|
||||
}
|
||||
|
||||
#use Time::HiRes qw(gettimeofday tv_interval);
|
||||
|
||||
# secure shell
|
||||
@ -47,7 +53,14 @@ my $d; # socket
|
||||
my $pid_path = '/var/run/qpsmtpd/';
|
||||
my $PID = $pid_path . "/qpsmtpd.pid";
|
||||
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 $max_children = 15; # max number of child processes to spawn
|
||||
my $idle_children = 5; # number of idle child processes to spawn
|
||||
@ -67,7 +80,7 @@ sub usage {
|
||||
print <<"EOT";
|
||||
Usage: qpsmtpd-prefork [ options ]
|
||||
--quiet : Be quiet (even errors are suppressed)
|
||||
--version : Show version information
|
||||
--version : Show version information
|
||||
--debug : Enable debug output
|
||||
--interface addr : Interface daemon should listen on (default: $d_addr)
|
||||
--port int : TCP port daemon should listen on (default: $d_port)
|
||||
@ -134,15 +147,20 @@ sub run {
|
||||
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)
|
||||
$d =
|
||||
new IO::Socket::INET(
|
||||
LocalPort => $d_port,
|
||||
LocalAddr => $d_addr,
|
||||
Proto => 'tcp',
|
||||
Listen => SOMAXCONN,
|
||||
Reuse => 1,
|
||||
);
|
||||
if ($has_ipv6) {
|
||||
$d = IO::Socket::INET6->new(@Socket_opts);
|
||||
}
|
||||
else {
|
||||
$d = IO::Socket::INET(@Socket_opts);
|
||||
}
|
||||
die "FATAL: Failed to start daemon.\nReason: $!\n(It may be nessesary to "
|
||||
. "wait 20 secs before starting daemon again)\n"
|
||||
unless $d;
|
||||
@ -361,7 +379,7 @@ sub new_child {
|
||||
my $sigset = block_signal(SIGHUP);
|
||||
|
||||
# start a session if connection looks valid
|
||||
qpsmtpd_session($client, $qpsmtpd) if ($iinfo);
|
||||
qpsmtpd_session($client, $iinfo, $qpsmtpd) if ($iinfo);
|
||||
|
||||
# close connection and cleanup
|
||||
$client->shutdown(2);
|
||||
@ -512,15 +530,16 @@ sub info {
|
||||
|
||||
# start qpmstpd session
|
||||
# arg0: ref to socket object
|
||||
# arg1: ref to qpsmtpd instance
|
||||
# arg1: ref to socket object
|
||||
# arg2: ref to qpsmtpd instance
|
||||
# ret0: void
|
||||
sub qpsmtpd_session {
|
||||
my $client = shift; #arg0
|
||||
my $qpsmtpd = shift; #arg1
|
||||
my $iinfo = shift; #arg1
|
||||
my $qpsmtpd = shift; #arg2
|
||||
|
||||
# get local/remote hostname, port and ip address
|
||||
my ($port, $iaddr) = sockaddr_in(getpeername($client)); #remote
|
||||
my ($lport, $laddr) = sockaddr_in(getsockname($client)); #local
|
||||
my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($d, $client, $iinfo);
|
||||
|
||||
# get current connected ip addresses (from shared memory)
|
||||
my %children;
|
||||
@ -529,9 +548,9 @@ sub qpsmtpd_session {
|
||||
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 %children],
|
||||
@ -574,9 +593,7 @@ sub qpsmtpd_session {
|
||||
};
|
||||
|
||||
# set enviroment variables
|
||||
$ENV{TCPLOCALIP} = inet_ntoa($laddr);
|
||||
$ENV{TCPREMOTEIP} = inet_ntoa($iaddr);
|
||||
$ENV{TCPREMOTEHOST} = gethostbyaddr($iaddr, AF_INET) || "Unknown";
|
||||
($ENV{TCPLOCALIP}, $ENV{TCPREMOTEIP}, $ENV{TCPREMOTEHOST}) = Qpsmtpd::TcpServer::tcpenv($nto_laddr, $nto_iaddr);
|
||||
|
||||
# run qpmsptd functions
|
||||
$SIG{__DIE__} = 'DEFAULT';
|
||||
|
Loading…
Reference in New Issue
Block a user