[PATCH] prefork: add multi-address support

Allows qpsmtpd-prefork to listen on multiple address/port combinations
simultaneously, based on the corresponding implementation in forkserver.

Signed-off-by: Robert <rspier@pobox.com>
This commit is contained in:
Devin Carraway 2009-04-14 17:57:36 -07:00 committed by Ask Bjørn Hansen
parent 6b81c68666
commit 19a0f5ded1

View File

@ -18,9 +18,9 @@ BEGIN {
# includes # includes
use IO::Socket; use IO::Socket;
use IO::Select;
use POSIX; use POSIX;
use IPC::Shareable(':all'); use IPC::Shareable(':all');
use lib 'lib';
use Qpsmtpd::TcpServer::Prefork; use Qpsmtpd::TcpServer::Prefork;
use Qpsmtpd::Constants; use Qpsmtpd::Constants;
use Getopt::Long; use Getopt::Long;
@ -62,18 +62,12 @@ my $chld_pool;
my $chld_busy; my $chld_busy;
my @children_term; # terminated children, their death pending processing my @children_term; # terminated children, their death pending processing
# by the main loop # by the main loop
my $d; # socket my $select = new IO::Select; # socket(s)
# default settings # default settings
my $pid_file; my $pid_file;
my $d_port = 25; my $d_port = 25;
my $d_addr; my @d_addr; # default applied after getopt call
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
@ -97,8 +91,10 @@ 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
--listen-address addr: Listen for connections on the address 'addr' (default: $d_addr); --listen-address addr: Listen for connections on the address 'addr' (either
synonymous with --interface an IP address or ip:port pair). Listens on all
interfaces by default; may be specified multiple
times.
--port int : TCP port daemon should listen on (default: $d_port) --port int : TCP port daemon should listen on (default: $d_port)
--max-from-ip int : Limit number of connections from single IP (default: $maxconnip, 0 to disable) --max-from-ip int : Limit number of connections from single IP (default: $maxconnip, 0 to disable)
--children int : Max number of children that can be spawned (default: $max_children) --children int : Max number of children that can be spawned (default: $max_children)
@ -118,7 +114,7 @@ GetOptions(
'quiet' => \$quiet, 'quiet' => \$quiet,
'version' => sub { print "Qpsmtpd Daemon - version $VERSION\n"; exit 0; }, 'version' => sub { print "Qpsmtpd Daemon - version $VERSION\n"; exit 0; },
'debug' => \$debug, 'debug' => \$debug,
'interface|listen-address=s' => \$d_addr, 'interface|listen-address=s' => \@d_addr,
'port=i' => \$d_port, 'port=i' => \$d_port,
'max-from-ip=i' => \$maxconnip, 'max-from-ip=i' => \$maxconnip,
'children=i' => \$max_children, 'children=i' => \$max_children,
@ -131,8 +127,20 @@ GetOptions(
'help' => \&usage, 'help' => \&usage,
) || &usage; ) || &usage;
if ($user =~ /^([\w\-]+)$/) { $user = $1 } else { &usage } if ($user && $user =~ /^([\w\-]+)$/) { $user = $1 } else { &usage }
if ($d_addr =~ /^(\[.*\]|[\w\-.]+)$/) { $d_addr = $1 } else { &usage }
if (@d_addr) {
for my $i (0..$#d_addr) {
if ($d_addr[$i] =~ /^(\[.*\]|[\d\w\-.]+)(?::(\d+))?$/) {
$d_addr[$i] = { 'addr' => $1, 'port' => $2 || $d_port };
} else {
print STDERR "Malformed listen address '$d_addr[$i]'\n";
&usage;
}
}
} else {
@d_addr = ( 'addr' => $has_ipv6 ? "[::]" : "0.0.0.0" );
}
# set max from ip to max number of children if option is set to disabled # set max from ip to max number of children if option is set to disabled
$maxconnip = $max_children if ($maxconnip == 0); $maxconnip = $max_children if ($maxconnip == 0);
@ -186,26 +194,32 @@ sub run {
endgrent; endgrent;
} }
my @Socket_opts = ( for my $addr (@d_addr) {
LocalPort => $d_port, my @Socket_opts = (
LocalAddr => $d_addr, LocalPort => $addr->{port},
Proto => 'tcp', LocalAddr => $addr->{addr},
Listen => SOMAXCONN, Proto => 'tcp',
Reuse => 1, Listen => SOMAXCONN,
); Reuse => 1,
# create new socket (used by clients to communicate with daemon) );
if ($has_ipv6) { # create new socket (used by clients to communicate with daemon)
$d = IO::Socket::INET6->new(@Socket_opts); my $s;
if ($has_ipv6) {
$s = IO::Socket::INET6->new(@Socket_opts);
}
else {
$s = IO::Socket::INET->new(@Socket_opts);
}
die "FATAL: Failed to open socket on $addr->{addr}:$addr->{port} ($@)"
. "\nIt may be necessary to wait 20 secs before starting daemon"
. " again."
unless $s;
$select->add($s);
} }
else {
$d = IO::Socket::INET->new(@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;
info("qpsmtpd-prefork daemon, version: $VERSION, staring on host: " . info("qpsmtpd-prefork daemon, version: $VERSION, staring on host: "
"$d_addr, port: $d_port (user: $user [$<])"); . join(', ', map { "$_->{addr}:$_->{port}"} @d_addr)
. " (user: $user [$<])");
# reset priority # reset priority
my $old_nice = getpriority(0, 0); my $old_nice = getpriority(0, 0);
@ -244,9 +258,8 @@ sub run {
# a notice, before the sleep below # a notice, before the sleep below
info("shutting down"); info("shutting down");
# close socket # close socket(s)
$d->close(); $_->close for $select->handles;
sleep 2;
# send signal to process group # send signal to process group
kill -$sig_num{$sig} => $$; kill -$sig_num{$sig} => $$;
@ -435,11 +448,14 @@ sub new_child {
# continue to accept connections until "old age" is reached # continue to accept connections until "old age" is reached
for (my $i = 0 ; $i < $child_lifetime ; $i++) { for (my $i = 0 ; $i < $child_lifetime ; $i++) {
# accept a connection # accept a connection
if ( $pretty ) { if ( $pretty ) {
$ENV{PROCESS} = $0 if not defined $ENV{PROCESS}; # 1st time only $ENV{PROCESS} = $0 if not defined $ENV{PROCESS}; # 1st time only
$0 = 'qpsmtpd child'; # set pretty child name in process listing $0 = 'qpsmtpd child'; # set pretty child name in process listing
} }
my ($client, $iinfo) = $d->accept() my @ready = $select->can_read();
next unless @ready;
my $socket = $ready[0];
my ($client, $iinfo) = $socket->accept()
or die or die
"failed to create new object - $!"; # wait here until client connects "failed to create new object - $!"; # wait here until client connects
info("connect from: " . $client->peerhost . ":" . $client->peerport); info("connect from: " . $client->peerhost . ":" . $client->peerport);
@ -464,7 +480,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, $iinfo, $qpsmtpd) if ($iinfo); qpsmtpd_session($socket, $client, $iinfo, $qpsmtpd) if ($iinfo);
# close connection and cleanup # close connection and cleanup
$client->shutdown(2); $client->shutdown(2);
@ -639,12 +655,14 @@ sub info {
# arg2: ref to qpsmtpd instance # arg2: ref to qpsmtpd instance
# ret0: void # ret0: void
sub qpsmtpd_session { sub qpsmtpd_session {
my $client = shift; #arg0 my $socket = shift; #arg0
my $iinfo = shift; #arg1 my $client = shift; #arg1
my $qpsmtpd = shift; #arg2 my $iinfo = shift; #arg2
my $qpsmtpd = shift; #arg3
# get local/remote hostname, port and ip address # get local/remote hostname, port and ip address
my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) = Qpsmtpd::TcpServer::lrpip($d, $client, $iinfo); my ($port, $iaddr, $lport, $laddr, $nto_iaddr, $nto_laddr) =
Qpsmtpd::TcpServer::lrpip($socket, $client, $iinfo);
# get current connected ip addresses (from shared memory) # get current connected ip addresses (from shared memory)
my %children; my %children;