You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spamassassin.apache.org by jm...@apache.org on 2006/11/03 19:31:01 UTC
svn commit: r470934 - in /spamassassin/trunk: MANIFEST
lib/Mail/SpamAssassin/SpamdForkScaling.pm spamd/spamd.raw
t/spamd_unix_and_tcp.t
Author: jm
Date: Fri Nov 3 10:31:00 2006
New Revision: 470934
URL: http://svn.apache.org/viewvc?view=rev&rev=470934
Log:
bug 3991: spamd can now listen on UNIX domain, TCP, and SSL sockets simultaneously. Command-line semantics extended slightly, although fully backwards compatibly; add the --ssl-port switch to allow TCP and SSL listening at the same time
Added:
spamassassin/trunk/t/spamd_unix_and_tcp.t (with props)
Modified:
spamassassin/trunk/MANIFEST
spamassassin/trunk/lib/Mail/SpamAssassin/SpamdForkScaling.pm
spamassassin/trunk/spamd/spamd.raw
Modified: spamassassin/trunk/MANIFEST
URL: http://svn.apache.org/viewvc/spamassassin/trunk/MANIFEST?view=diff&rev=470934&r1=470933&r2=470934
==============================================================================
--- spamassassin/trunk/MANIFEST (original)
+++ spamassassin/trunk/MANIFEST Fri Nov 3 10:31:00 2006
@@ -540,3 +540,4 @@
t/spamc_y.t
t/spamd_kill_restart.t
t/spamd_kill_restart_rr.t
+t/spamd_unix_and_tcp.t
Modified: spamassassin/trunk/lib/Mail/SpamAssassin/SpamdForkScaling.pm
URL: http://svn.apache.org/viewvc/spamassassin/trunk/lib/Mail/SpamAssassin/SpamdForkScaling.pm?view=diff&rev=470934&r1=470933&r2=470934
==============================================================================
--- spamassassin/trunk/lib/Mail/SpamAssassin/SpamdForkScaling.pm (original)
+++ spamassassin/trunk/lib/Mail/SpamAssassin/SpamdForkScaling.pm Fri Nov 3 10:31:00 2006
@@ -187,9 +187,16 @@
###########################################################################
sub set_server_fh {
- my ($self, $fh) = @_;
- $self->{server_fh} = $fh;
- $self->{server_fileno} = $fh->fileno();
+ my ($self, @fhs) = @_;
+
+ $self->{server_fh} = [];
+ $self->{server_fileno} = [];
+
+ foreach my $fh (@fhs) {
+ next unless defined $fh;
+ push @{$self->{server_fh}}, $fh;
+ push @{$self->{server_fileno}}, $fh->fileno();
+ }
}
sub main_server_poll {
@@ -199,7 +206,7 @@
if ($self->{overloaded}) {
# don't select on the server fh -- we already KNOW that's ready,
# since we're overloaded
- vec($rin, $self->{server_fileno}, 1) = 0;
+ $self->vec_all(\$rin, $self->{server_fileno}, 0);
}
my ($rout, $eout, $nfound, $timeleft, $selerr);
@@ -265,7 +272,7 @@
# errors on the handle?
# return them immediately, they may be from a SIGHUP restart signal
- if (vec ($eout, $self->{server_fileno}, 1)) {
+ if ($self->vec_all(\$eout, $self->{server_fileno})) {
warn "prefork: select returned error on server filehandle: $selerr $!\n";
return;
}
@@ -283,7 +290,7 @@
}
# were the kids ready, or did we get signal?
- if (vec ($rout, $self->{server_fileno}, 1)) {
+ if ($self->vec_all(\$rout, $self->{server_fileno})) {
# dbg("prefork: server fh ready");
# the server socket: new connection from a client
if (!$self->order_idle_child_to_accept()) {
@@ -765,6 +772,20 @@
kill 'INT' => $pid;
dbg("prefork: adjust: decreasing, too many idle children ($num_idle > $self->{max_idle}), killed $pid");
+}
+
+sub vec_all {
+ my ($self, $bitsref, $fhs, $value) = @_;
+ my $ret = 0;
+ foreach my $fh (@{$fhs}) {
+ next unless defined $fh;
+ if (defined $value) {
+ vec($$bitsref, $fh, 1) = $value;
+ } else {
+ $ret |= vec($$bitsref, $fh, 1);
+ }
+ }
+ return $ret;
}
1;
Modified: spamassassin/trunk/spamd/spamd.raw
URL: http://svn.apache.org/viewvc/spamassassin/trunk/spamd/spamd.raw?view=diff&rev=470934&r1=470933&r2=470934
==============================================================================
--- spamassassin/trunk/spamd/spamd.raw (original)
+++ spamassassin/trunk/spamd/spamd.raw Fri Nov 3 10:31:00 2006
@@ -244,6 +244,7 @@
'socketpath=s' => \$opt{'socketpath'},
'sql-config!' => \$opt{'sql-config'},
'ssl' => \$opt{'ssl'},
+ 'ssl-port=s' => \$opt{'ssl-port'},
'syslog-socket=s' => \$opt{'syslog-socket'},
'syslog|s=s' => \$opt{'syslog'},
'timeout-tcp|T=i' => \$opt{'timeout-tcp'},
@@ -311,20 +312,39 @@
) if ( $opt{$opt} );
}
-# sanity checking on parameters: if --socketpath is used, it means that we're using
-# UNIX domain sockets, none of the IP params are allowed. The code would probably
-# work ok if we didn't check it, but it's better if we detect the error and report
-# it lest the admin find surprises.
+# sanity checking on parameters: if --socketpath is used, and --port or
+# --ssl-port is NOT specified, it means that we're using UNIX domain sockets,
+# none of the IP params are allowed. The code would probably work ok if we
+# didn't check it, but it's better if we detect the error and report it lest
+# the admin find surprises.
+
+my $listen_unix = 1;
+my $listen_inet = 1;
+my $listen_ssl = 1;
+if (!defined $opt{'socketpath'}) {
+ $listen_unix = 0;
+} else {
+ $listen_inet = $listen_ssl = 0;
+}
+if (defined $opt{'ssl'} && !defined $opt{'ssl-port'}) {
+ $listen_inet = 0;
+}
if (
- defined $opt{'socketpath'}
- and ( ( @{ $opt{'allowed-ip'} } > 0 )
- or defined $opt{'ssl'}
+ ( @{ $opt{'allowed-ip'} } > 0 )
or defined $opt{'auth-ident'}
- or defined $opt{'port'} )
+ or defined $opt{'port'}
)
{
- print_usage_and_exit("ERROR: --socketpath mutually exclusive with --allowed-ip/--ssl/--port params");
+ $listen_inet = 1;
+}
+
+if (
+ defined $opt{'ssl'}
+ or defined $opt{'ssl-port'}
+ )
+{
+ $listen_ssl = 1;
}
if (
@@ -549,13 +569,9 @@
# Do whitelist later in tmp dir. Side effect: this will be done as -u user.
-my ( $port, $addr, $proto );
-my ($listeninfo); # just for reporting
+my ( $sslport, $inetport, $addr, $proto );
-if ( defined $opt{'socketpath'} ) {
- $listeninfo = "UNIX domain socket " . $opt{'socketpath'};
-}
-else {
+if ( $listen_inet || $listen_ssl ) {
$proto = getprotobyname('tcp') or die "getprotobyname(tcp): $!";
$addr = $opt{'listen-ip'};
@@ -571,16 +587,37 @@
else {
$addr = '127.0.0.1';
}
+}
+
+my @listeninfo = ();
+
+if ( $listen_unix ) {
+ push @listeninfo, "UNIX domain socket " . $opt{'socketpath'};
+}
+
+if ( $listen_ssl ) {
+ $sslport = $opt{'ssl-port'} || $opt{'port'} || 783;
+ if ($sslport !~ /^(\d+)$/ ) {
+ $sslport = ( getservbyname($sslport, 'tcp') )[2];
+ die "spamd: invalid ssl-port: $opt{'port'}\n" unless $sslport;
+ }
+
+ push @listeninfo, "SSL port $sslport/tcp";
+}
- $port = $opt{'port'} || 783;
- if ($port !~ /^(\d+)$/ ) {
- $port = ( getservbyname($port, 'tcp') )[2];
- die "spamd: invalid port: $opt{'port'}\n" unless $port;
+if ( $listen_inet ) {
+ $inetport = $opt{'port'} || 783;
+ if ($inetport !~ /^(\d+)$/ ) {
+ $inetport = ( getservbyname($inetport, 'tcp') )[2];
+ die "spamd: invalid port: $opt{'port'}\n" unless $inetport;
}
- $listeninfo = "port $port/tcp";
+ push @listeninfo, "port $inetport/tcp";
}
+# just for reporting at startup
+my $listeninfo = join ', ', @listeninfo;
+
my $backchannel = Mail::SpamAssassin::SubProcBackChannel->new();
my $scaling;
if (!$opt{'round-robin'})
@@ -604,9 +641,13 @@
});
}
-# Be a well-behaved daemon
-my $server;
-if ( $opt{'socketpath'} ) {
+my ( $server_inet, $server_unix, $server_ssl );
+my ( $fd_inet, $fd_unix, $fd_ssl );
+my $have_multiple_server_socks;
+my $server_select_mask;
+
+# Create the sockets
+if ( $listen_unix ) {
my $path = $opt{'socketpath'};
#---------------------------------------------------------------------
@@ -642,15 +683,15 @@
Listen => SOMAXCONN,
);
dbg("spamd: creating UNIX socket:\n" . join("\n", map { " $_: " . (defined $socket{$_} ? $socket{$_} : "(undef)") } sort keys %socket));
- $server = IO::Socket::UNIX->new(%socket);
+ $server_unix = IO::Socket::UNIX->new(%socket);
# sanity check! cf. bug 3490
- if (not $server or not -S $path) {
- unless ($server) {
+ if (not $server_unix or not -S $path) {
+ unless ($server_unix) {
dbg "spamd: socket path might have been truncated due to system limits\n";
die "spamd: could not create UNIX socket on $path: $!\n";
}
- my $hostpath = $server->hostpath();
+ my $hostpath = $server_unix->hostpath();
if ($hostpath ne $path) {
warn "spamd: socket path was truncated at position " . length($hostpath) . "\n";
warn "spamd: leaving stale socket at $hostpath\n" if -S $hostpath;
@@ -692,10 +733,11 @@
die "spamd: could not chmod $path to $mode: $!";
}
}
-elsif ( $opt{'ssl'} ) {
+
+if ( $listen_ssl ) {
my %socket = (
LocalAddr => $addr,
- LocalPort => $port,
+ LocalPort => $sslport,
Proto => $proto,
Type => SOCK_STREAM,
ReuseAddr => 1,
@@ -705,23 +747,27 @@
SSL_cert_file => $opt{'server-cert'}
);
dbg("spamd: creating SSL socket:\n" . join("\n", map { " $_: " . (defined $socket{$_} ? $socket{$_} : "(undef)") } sort keys %socket));
- $server = new IO::Socket::SSL(%socket)
- || die "spamd: could not create SSL socket on $addr:$port: $!\n";
+ $server_ssl = new IO::Socket::SSL(%socket)
+ || die "spamd: could not create SSL socket on $addr:$sslport: $!\n";
}
-else {
+
+if ( $listen_inet ) {
my %socket = (
LocalAddr => $addr,
- LocalPort => $port,
+ LocalPort => $inetport,
Proto => $proto,
Type => SOCK_STREAM,
ReuseAddr => 1,
Listen => SOMAXCONN
);
dbg("spamd: creating INET socket:\n" . join("\n", map { " $_: " . (defined $socket{$_} ? $socket{$_} : "(undef)") } sort keys %socket));
- $server = new_io_socket_inetx(%socket)
- || die "spamd: could not create INET socket on $addr:$port: $!\n";
+ $server_inet = new_io_socket_inetx(%socket)
+ || die "spamd: could not create INET socket on $addr:$inetport: $!\n";
}
+# for select() purposes: make a map of the server socket FDs
+map_server_sockets();
+
if ( defined $opt{'pidfile'} ) {
$opt{'pidfile'} =
Mail::SpamAssassin::Util::untaint_file_path( $opt{'pidfile'} );
@@ -793,10 +839,6 @@
my $current_msgid = "(none)";
$SIG{USR2} = \&backtrace_handler;
-my $select_mask = '';
-vec($select_mask, $server->fileno, 1) = 1;
-$backchannel->set_selector(\$select_mask);
-
# log server started, but processes watching the log to wait for connect
# should wait until they see the pid, after signal handlers are in place
# FIXME: two calls are one too much
@@ -826,7 +868,7 @@
}
if ($scaling) {
- $scaling->set_server_fh($server);
+ $scaling->set_server_fh($server_inet, $server_unix, $server_ssl);
}
while (1) {
@@ -1020,8 +1062,37 @@
}
}
+sub accept_from_any_server_socket {
+
+ my $fdvec = $server_select_mask;
+
+ if ($have_multiple_server_socks) {
+ # determine which of our server FDs is ready using select().
+ # We only need to do this if we have more than one server
+ # socket supported, since otherwise there can only be one socket
+ # with a client waiting.
+ # (TODO: we could extend the prefork protocol to pass this data)
+ my $nfound = select($fdvec, undef, undef, 0.1);
+ die "oops? accept_a_conn: no fds ready" unless $nfound;
+ }
+
+ if ($fd_inet && vec $fdvec, $fd_inet, 1) {
+ $client = $server_inet->accept;
+ }
+ elsif ($fd_unix && vec $fdvec, $fd_unix, 1) {
+ $client = $server_unix->accept;
+ }
+ elsif ($fd_ssl && vec $fdvec, $fd_ssl, 1) {
+ $client = $server_ssl->accept;
+ }
+ else {
+ die "accept_a_conn: no fds ready by vec: $fdvec";
+ }
+ return $client;
+}
+
sub accept_a_conn {
- $client = $server->accept();
+ $client = accept_from_any_server_socket();
if ($scaling) {
$scaling->update_child_status_busy();
@@ -2023,7 +2094,9 @@
sub kill_handler {
my ($sig) = @_;
info("spamd: server killed by SIG$sig, shutting down");
- $server->close;
+ $server_inet and $server_inet->close;
+ $server_unix and $server_unix->close;
+ $server_ssl and $server_ssl->close;
if (defined($opt{'pidfile'})) {
unlink($opt{'pidfile'}) || warn "spamd: cannot unlink $opt{'pidfile'}: $!\n";
@@ -2094,16 +2167,25 @@
}
%children = ();
- unless ( $server->eof ) {
- $server->shutdown(2);
- $server->close;
+ unless ( !$server_inet || $server_inet->eof ) {
+ $server_inet->shutdown(2);
+ $server_inet->close;
+ info("spamd: server INET socket closed");
+ }
- # the UNIX domain socket
+ unless ( !$server_unix || $server_unix->eof ) {
+ $server_unix->shutdown(2);
+ $server_unix->close;
if (defined($opt{'socketpath'})) {
unlink($opt{'socketpath'}) || warn "spamd: cannot unlink $opt{'socketpath'}: $!\n";
}
+ info("spamd: server UNIX socket closed");
+ }
- info("spamd: server socket closed");
+ unless ( !$server_ssl || $server_ssl->eof ) {
+ $server_ssl->shutdown(2);
+ $server_ssl->close;
+ info("spamd: server SSL socket closed");
}
$got_sighup = 1;
}
@@ -2203,6 +2285,26 @@
}
}
+sub map_server_sockets {
+ $fd_inet = $server_inet ? $server_inet->fileno : undef;
+ $fd_unix = $server_unix ? $server_unix->fileno : undef;
+ $fd_ssl = $server_ssl ? $server_ssl->fileno : undef;
+
+ $server_select_mask = '';
+ $server_inet and vec($server_select_mask, $fd_inet, 1) = 1;
+ $server_unix and vec($server_select_mask, $fd_unix, 1) = 1;
+ $server_ssl and vec($server_select_mask, $fd_ssl, 1) = 1;
+
+ my $back_selector = $server_select_mask;
+ $backchannel->set_selector(\$back_selector);
+
+ # and set a boolean indicating whether or not we have > 1 server socket
+ $have_multiple_server_socks =
+ ((defined $fd_inet ? 1 : 0) +
+ (defined $fd_unix ? 1 : 0) +
+ (defined $fd_ssl ? 1 : 0)) > 1;
+}
+
__DATA__
=head1 NAME
@@ -2257,6 +2359,7 @@
-P, --paranoid Die upon user errors
-H [dir], --helper-home-dir[=dir] Specify a different HOME directory
--ssl Run an SSL server
+ --ssl-port port Listen on port for SSL connections
--server-key keyfile Specify an SSL keyfile
--server-cert certfile Specify an SSL certificate
--socketpath=path Listen on given UNIX domain socket
@@ -2349,6 +2452,13 @@
Optionally specifies the port number for the server to listen on (default: 783).
+If the B<--ssl> switch is used, and B<--ssl-port> is not supplied, then this
+port will be used to accept SSL connections instead of unencrypted connections.
+If the B<--ssl> switch is used, and B<--ssl-port> is set, then unencrypted
+connections will be accepted on the B<--port> at the same time as encrypted
+connections are accepted at B<--ssl-port>.
+
+
=item B<-q>, B<--sql-config>
Turn on SQL lookups even when per-user config files have been disabled
@@ -2654,6 +2764,18 @@
Accept only SSL connections. The B<IO::Socket::SSL> perl module must be
installed.
+If the B<--ssl> switch is used, and B<--ssl-port> is not supplied, then
+B<--port> port will be used to accept SSL connections instead of unencrypted
+connections. If the B<--ssl> switch is used, and B<--ssl-port> is set, then
+unencrypted connections will be accepted on the B<--port>, at the same time as
+encrypted connections are accepted at B<--ssl-port>.
+
+=item B<--ssl-port>=I<port>
+
+Optionally specifies the port number for the server to listen on for
+SSL connections (default: whatever --port uses). See B<--ssl> for
+more details.
+
=item B<--server-key> I<keyfile>
Specify the SSL key file to use for SSL connections.
@@ -2671,6 +2793,10 @@
you see a 'could not find newly-created UNIX socket' error message, and the
path appears truncated, this may be the cause. Try using a shorter path
to the socket.
+
+By default, use of B<--socketpath> will inhibit SSL connections and unencrypted
+TCP connections. To enable them, specify B<--port> and/or B<--ssl-port>
+explicitly.
=item B<--socketowner> I<name>
Added: spamassassin/trunk/t/spamd_unix_and_tcp.t
URL: http://svn.apache.org/viewvc/spamassassin/trunk/t/spamd_unix_and_tcp.t?view=auto&rev=470934
==============================================================================
--- spamassassin/trunk/t/spamd_unix_and_tcp.t (added)
+++ spamassassin/trunk/t/spamd_unix_and_tcp.t Fri Nov 3 10:31:00 2006
@@ -0,0 +1,45 @@
+#!/usr/bin/perl
+
+use lib '.'; use lib 't';
+use SATest; sa_t_init("spamd_unix_and_tcp");
+
+use Test; BEGIN { plan tests => ((!$SKIP_SPAMD_TESTS && !$RUNNING_ON_WINDOWS)? 10 : 0) };
+
+exit unless (!$SKIP_SPAMD_TESTS && !$RUNNING_ON_WINDOWS);
+
+# ---------------------------------------------------------------------------
+
+my $sockpath = mk_safe_tmpdir()."/spamd.sock";
+start_spamd("-D -L --socketpath=$sockpath --port $spamdport");
+%patterns = (
+ q{ Subject: There yours for FREE!}, 'subj',
+ q{ X-Spam-Flag: YES}, 'flag',
+);
+ok (spamcrun ("-U $sockpath < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+clear_pattern_counters();
+%patterns = (
+ q{ GTUBE }, 'gtube',
+);
+ok (spamcrun ("< data/spam/gtube.eml", \&patterns_run_cb));
+ok_all_patterns();
+
+clear_pattern_counters();
+%patterns = (
+ q{ Subject: There yours for FREE!}, 'subj',
+ q{ X-Spam-Flag: YES}, 'flag',
+);
+ok (spamcrun ("-U $sockpath < data/spam/001", \&patterns_run_cb));
+ok_all_patterns();
+
+clear_pattern_counters();
+%patterns = (
+ q{ GTUBE }, 'gtube',
+);
+ok (spamcrun ("< data/spam/gtube.eml", \&patterns_run_cb));
+ok_all_patterns();
+
+stop_spamd();
+cleanup_safe_tmpdir();
+
Propchange: spamassassin/trunk/t/spamd_unix_and_tcp.t
------------------------------------------------------------------------------
svn:executable = *