Forkserver can accept a --listen-address switch to listen on a particular address/interface instead of 0.0.0.0, but only one. This expands the handling of that switch so as to listen on any number of local addresses, using IO::Select to manage them. The default is a single socket on 0.0.0.0, as usual.
Tested lightly; it works fine on my spam-only secondary MX, and isn't perturbed by having interfaces dropped or similar surprises, but I haven't subjected it to much stress yet. And since I was one line away -- is there any remaining good reason why the default port for forkserver is 2525? This has the look of development or testing leftovers. -- Devin \ aqua(at)devin.com, IRC:Requiem; http://www.devin.com Carraway \ 1024D/E9ABFCD2: 13E7 199E DD1E 65F0 8905 2E43 5395 CA0D E9AB FCD2
Index: qpsmtpd-forkserver =================================================================== --- qpsmtpd-forkserver (revision 460) +++ qpsmtpd-forkserver (working copy) @@ -10,6 +10,7 @@ use Qpsmtpd::TcpServer; use Qpsmtpd::Constants; use IO::Socket; +use IO::Select; use Socket; use Getopt::Long; use POSIX qw(:sys_wait_h :errno_h :signal_h); @@ -19,7 +20,7 @@ # Configuration my $MAXCONN = 15; # max simultaneous connections my $PORT = 2525; # port number -my $LOCALADDR = '0.0.0.0'; # ip address to bind to +my @LOCALADDR; # ip address(es) to bind to my $USER = 'smtpd'; # user to suid to my $MAXCONNIP = 5; # max simultaneous connections from one IP my $PID_FILE = '/var/run/qpsmtpd.pid'; @@ -27,7 +28,9 @@ sub usage { print <<"EOT"; usage: qpsmtpd-forkserver [ options ] - -l, --listen-address addr : listen on a specific address; default 0.0.0.0 + -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). -p, --port P : listen on a specific port; default 2525 -c, --limit-connections N : limit concurrent connections to N; default 15 -u, --user U : run as a particular user (default 'smtpd') @@ -38,7 +41,7 @@ } GetOptions('h|help' => \&usage, - 'l|listen-address=s' => \$LOCALADDR, + 'l|listen-address=s' => [EMAIL PROTECTED], 'c|limit-connections=i' => \$MAXCONN, 'm|max-from-ip=i' => \$MAXCONNIP, 'p|port=i' => \$PORT, @@ -48,7 +51,14 @@ # detaint the commandline if ($PORT =~ /^(\d+)$/) { $PORT = $1 } else { &usage } -if ($LOCALADDR =~ /^([\d\w\-.]+)$/) { $LOCALADDR = $1 } else { &usage } [EMAIL PROTECTED] = ( '0.0.0.0' ) if [EMAIL PROTECTED]; +for (0..$#LOCALADDR) { + if ($LOCALADDR[$_] =~ /^([\d\w\-.]+)$/) { + $LOCALADDR[$_] = $1; + } else { + &usage; + } +} if ($USER =~ /^([\w\-]+)$/) { $USER = $1 } else { &usage } if ($MAXCONN =~ /^(\d+)$/) { $MAXCONN = $1 } else { &usage } if ($PID_FILE =~ m#^(/[\w\d/\-.]+)$#) { $PID_FILE = $1 } else { &usage } @@ -77,14 +87,19 @@ $SIG{INT} = \&HUNTSMAN; $SIG{TERM} = \&HUNTSMAN; -# establish SERVER socket, bind and listen. -my $server = IO::Socket::INET->new(LocalPort => $PORT, - LocalAddr => $LOCALADDR, - Proto => 'tcp', - Reuse => 1, - Listen => SOMAXCONN ) - or die "Creating TCP socket $LOCALADDR:$PORT: $!\n"; +my $select = new IO::Select; +# establish SERVER socket(s), bind and listen. +for my $listen_addr (@LOCALADDR) { + my $server = IO::Socket::INET->new(LocalPort => $PORT, + LocalAddr => $listen_addr, + Proto => 'tcp', + Reuse => 1, + Listen => SOMAXCONN ) + or die "Creating TCP socket $listen_addr:$PORT: $!\n"; + $select->add($server); +} + if (-e $PID_FILE) { open PID, "+<$PID_FILE" or die "open pid_file: $!\n"; @@ -139,9 +154,16 @@ sleep(1) ; $running = scalar keys %childstatus; } - my $hisaddr = accept(my $client, $server); - if (!$hisaddr) { - # possible something condition... + + my @ready = $select->can_read(); + next if [EMAIL PROTECTED]; + # accept one connection per loop + my $server = shift @ready; + + my ($client, $hisaddr) = $server->accept; + + if (!$client) { + ::log(LOGERROR,"Error accepting new connection: $!"); next; } my ($port, $iaddr) = sockaddr_in($hisaddr); @@ -181,7 +203,7 @@ # all children should have different seeds, to prevent conflicts srand( time ^ ($$ + ($$ << 15)) ); - close($server); + $_->close for $select->handles; $SIG{$_} = 'DEFAULT' for keys %SIG; $SIG{ALRM} = sub {