Allows qpsmtpd-prefork to listen on multiple address/port combinations simultaneously, based on the corresponding implementation in forkserver. --- qpsmtpd-prefork | 108 ++++++++++++++++++++++++++++++++----------------------- 1 files changed, 63 insertions(+), 45 deletions(-)
diff --git a/qpsmtpd-prefork b/qpsmtpd-prefork index 3f23df3..798e3c4 100755 --- a/qpsmtpd-prefork +++ b/qpsmtpd-prefork @@ -18,9 +18,9 @@ BEGIN { # includes use IO::Socket; +use IO::Select; use POSIX; use IPC::Shareable(':all'); -use lib 'lib'; use Qpsmtpd::TcpServer::Prefork; use Qpsmtpd::Constants; use Getopt::Long; @@ -62,18 +62,12 @@ my $chld_pool; my $chld_busy; my @children_term; # terminated children, their death pending processing # by the main loop -my $d; # socket +my $select = new IO::Select; # socket(s) # default settings my $pid_file; my $d_port = 25; -my $d_addr; -if ($has_ipv6) { - $d_addr = "[::]"; -} -else { - $d_addr = "0.0.0.0"; -} +my @d_addr; # default applied after getopt call my $debug = 0; 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) --version : Show version information --debug : Enable debug output ---listen-address addr: Listen for connections on the address 'addr' (default: $d_addr); - synonymous with --interface +--listen-address addr: Listen for connections on the address 'addr' (either + 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) --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) @@ -118,7 +114,7 @@ GetOptions( 'quiet' => \$quiet, 'version' => sub { print "Qpsmtpd Daemon - version $VERSION\n"; exit 0; }, 'debug' => \$debug, - 'interface|listen-address=s' => \$d_addr, + 'interface|listen-address=s' => \...@d_addr, 'port=i' => \$d_port, 'max-from-ip=i' => \$maxconnip, 'children=i' => \$max_children, @@ -131,8 +127,20 @@ GetOptions( 'help' => \&usage, ) || &usage; -if ($user =~ /^([\w\-]+)$/) { $user = $1 } else { &usage } -if ($d_addr =~ /^(\[.*\]|[\w\-.]+)$/) { $d_addr = $1 } else { &usage } +if ($user && $user =~ /^([\w\-]+)$/) { $user = $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 $maxconnip = $max_children if ($maxconnip == 0); @@ -186,26 +194,32 @@ sub run { endgrent; } - 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) - if ($has_ipv6) { - $d = IO::Socket::INET6->new(@Socket_opts); - } - else { - $d = IO::Socket::INET->new(@Socket_opts); + for my $addr (@d_addr) { + my @Socket_opts = ( + LocalPort => $addr->{port}, + LocalAddr => $addr->{addr}, + Proto => 'tcp', + Listen => SOMAXCONN, + Reuse => 1, + ); + # create new socket (used by clients to communicate with daemon) + 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); } - 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: " . - "$d_addr, port: $d_port (user: $user [$<])"); + info("qpsmtpd-prefork daemon, version: $VERSION, staring on host: " + . join(', ', map { "$_->{addr}:$_->{port}"} @d_addr) + . " (user: $user [$<])"); # reset priority my $old_nice = getpriority(0, 0); @@ -244,9 +258,8 @@ sub run { # a notice, before the sleep below info("shutting down"); - # close socket - $d->close(); - sleep 2; + # close socket(s) + $_->close for $select->handles; # send signal to process group kill -$sig_num{$sig} => $$; @@ -435,11 +448,14 @@ sub new_child { # continue to accept connections until "old age" is reached for (my $i = 0 ; $i < $child_lifetime ; $i++) { # accept a connection - if ( $pretty ) { - $ENV{PROCESS} = $0 if not defined $ENV{PROCESS}; # 1st time only - $0 = 'qpsmtpd child'; # set pretty child name in process listing - } - my ($client, $iinfo) = $d->accept() + if ( $pretty ) { + $ENV{PROCESS} = $0 if not defined $ENV{PROCESS}; # 1st time only + $0 = 'qpsmtpd child'; # set pretty child name in process listing + } + my @ready = $select->can_read(); + next unless @ready; + my $socket = $ready[0]; + my ($client, $iinfo) = $socket->accept() or die "failed to create new object - $!"; # wait here until client connects info("connect from: " . $client->peerhost . ":" . $client->peerport); @@ -464,7 +480,7 @@ sub new_child { my $sigset = block_signal(SIGHUP); # 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 $client->shutdown(2); @@ -639,12 +655,14 @@ sub info { # arg2: ref to qpsmtpd instance # ret0: void sub qpsmtpd_session { - my $client = shift; #arg0 - my $iinfo = shift; #arg1 - my $qpsmtpd = shift; #arg2 + my $socket = shift; #arg0 + my $client = shift; #arg1 + my $iinfo = shift; #arg2 + my $qpsmtpd = shift; #arg3 # 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) my %children; -- 1.6.2.1