Mail-SpamAssassin
view release on metacpan or search on metacpan
spamd/spamd.raw view on Meta::CPAN
use IO::Pipe;
use IO::File ();
use Mail::SpamAssassin;
use Mail::SpamAssassin::NetSet;
use Mail::SpamAssassin::SubProcBackChannel;
use Mail::SpamAssassin::SpamdForkScaling qw(:pfstates);
use Mail::SpamAssassin::Logger qw(:DEFAULT log_message);
use Mail::SpamAssassin::Util qw(untaint_var untaint_file_path secure_tmpdir
exit_status_str am_running_on_windows
get_user_groups force_die);
use Mail::SpamAssassin::Timeout;
use Getopt::Long;
use POSIX qw(:sys_wait_h);
use POSIX qw(locale_h setsid sigprocmask);
use Errno;
use Fcntl qw(:flock);
use Cwd ();
use File::Spec 0.8;
use File::Path;
use Carp ();
use Time::HiRes qw(time);
use constant RUNNING_ON_MACOS => ($^O =~ /^darwin/oi);
# Check to make sure the script version and the module version matches.
# If not, die here! Also, deal with unchanged VERSION macro.
if ($Mail::SpamAssassin::VERSION ne '@@VERSION@@' && '@@VERSION@@' ne "\@\@VERSION\@\@") {
die 'spamd: spamd script is v@@VERSION@@, but using modules v'.$Mail::SpamAssassin::VERSION."\n";
}
# Bug 3062: SpamAssassin should be "locale safe"
POSIX::setlocale(LC_TIME,'C');
my %resphash = (
EX_OK => 0, # no problems
EX_USAGE => 64, # command line usage error
EX_DATAERR => 65, # data format error
EX_NOINPUT => 66, # cannot open input
EX_NOUSER => 67, # addressee unknown
EX_NOHOST => 68, # host name unknown
EX_UNAVAILABLE => 69, # service unavailable
EX_SOFTWARE => 70, # internal software error
EX_OSERR => 71, # system error (e.g., can't fork)
EX_OSFILE => 72, # critical OS file missing
EX_CANTCREAT => 73, # can't create (user) output file
EX_IOERR => 74, # input/output error
EX_TEMPFAIL => 75, # temp failure; user is invited to retry
EX_PROTOCOL => 76, # remote error in protocol
EX_NOPERM => 77, # permission denied
EX_CONFIG => 78, # configuration error
EX_TIMEOUT => 79, # read timeout
);
sub print_version {
printf("SpamAssassin Server version %s\n", Mail::SpamAssassin::Version());
printf(" running on Perl %s\n",
join(".", map( 0+($_||0), ($] =~ /(\d)\.(\d{3})(\d{3})?/) )));
eval { require IO::Socket::SSL; };
printf(" with SSL support (%s %s)\n", "IO::Socket::SSL", $IO::Socket::SSL::VERSION) unless ($@);
eval { require Compress::Zlib; };
printf(" with zlib support (%s %s)\n", "Compress::Zlib", $Compress::Zlib::VERSION) unless ($@);
}
sub print_usage_and_exit {
my ( $message, $respnam ) = (@_);
$respnam ||= 'EX_USAGE';
if ($respnam eq 'EX_OK' ) {
print_version();
print("\n");
}
require Pod::Usage;
Pod::Usage->import;
pod2usage(
-verbose => 0,
-message => $message,
-exitval => $resphash{$respnam},
);
}
# defaults
my %opt = (
'user-config' => 1,
# scaling settings; some of these aren't actually settable via cmdline
'server-scale-period' => 2, # how often to scale the # of kids, secs
'min-children' => 1, # min kids to have running
'min-spare' => 1, # min kids that must be spare
'max-spare' => 2, # max kids that should be spare
'pre' => [], # extra .pre lines
'cf' => [], # extra config lines
);
# bug 1725, 2192:
# Untaint all command-line options and ENV vars, since spamd is launched
# as a daemon from a known-safe environment. Also store away some of the
# vars we need for a SIGHUP later on.
# Testing for taintedness only works before detainting %ENV
Mail::SpamAssassin::Util::am_running_in_taint_mode();
# First clean PATH and untaint the environment -- need to do this before
# Cwd::cwd(), else it will croak.
Mail::SpamAssassin::Util::clean_path_in_taint_mode();
untaint_var( \%ENV );
# The zeroth argument will be replaced in daemonize().
my $ORIG_ARG0 = untaint_var($0);
# Getopt::Long clears all arguments it processed (untaint both @ARGVs here!)
my @ORIG_ARGV = untaint_var( \@ARGV );
# daemonize() switches to the root later on and we need to come back here
# somehow -- untaint the dir to be on the safe side.
my $ORIG_CWD = untaint_var( Cwd::cwd() );
prepare_for_sighup_restart();
spamd/spamd.raw view on Meta::CPAN
if ( !-e $opt{'server-key'} ) {
die "spamd: server key file $opt{'server-key'} does not exist\n";
}
if ( !-e $opt{'server-cert'} ) {
die "spamd: server certificate file $opt{'server-cert'} does not exist\n";
}
}
# ---------------------------------------------------------------------------
my $sockets_access_lock_tempfile; # a File::Temp object, if locking is needed
my $sockets_access_lock_fh; # per-child file handle on a lock file
my $backchannel = Mail::SpamAssassin::SubProcBackChannel->new();
my $scaling;
if (!$opt{'round-robin'})
{
my $max_children = $childlimit;
# change $childlimit to avoid churn when we startup and create loads
# of spare servers; when we're using scaling, it's not as important
# as it was with the old algorithm.
if ($childlimit > $opt{'max-spare'}) {
$childlimit = $opt{'max-spare'};
}
if ($childlimit < $opt{'min-children'}) {
$childlimit = $opt{'min-children'};
}
$scaling = Mail::SpamAssassin::SpamdForkScaling->new({
backchannel => $backchannel,
min_children => $opt{'min-children'},
max_children => $max_children,
min_idle => $opt{'min-spare'},
max_idle => $opt{'max-spare'},
cur_children_ref => \$childlimit
});
}
# ---------------------------------------------------------------------------
sub compose_listen_info_string {
my @listeninfo;
for my $socket_info (@listen_sockets) {
next if !$socket_info;
my $socket = $socket_info->{socket};
next if !$socket;
my $socket_specs = $socket_info->{specs};
if ($socket->isa('IO::Socket::UNIX')) {
push(@listeninfo, "UNIX domain socket " . $socket_info->{path});
} elsif ( $socket->isa('IO::Socket::INET') ||
$socket->isa('IO::Socket::INET6') ||
$socket->isa('IO::Socket::IP') ) {
push(@listeninfo, sprintf("%s [%s]:%s", ref $socket,
$socket_info->{ip_addr}, $socket_info->{port}));
} elsif ($socket->isa('IO::Socket::SSL')) {
push(@listeninfo, sprintf("SSL [%r]:%s", $socket_info->{ip_addr},
$socket_info->{port}));
}
}
# just for reporting at startup
return join(', ', @listeninfo);
}
sub server_sock_setup {
my($sub, @args) = @_;
# retry 3 times to bind to the listening socket; 3 seconds delay,
# max, but should allow a little time for any existing shutting-down
# server to complete shutdown
my $lastretry = 10;
for my $retry (1 .. $lastretry) {
if ($retry > 1) { sleep 1; }
eval { &$sub(@args) } and last; # success => break
if ($retry == $lastretry) {
die $@; # this is fatal
} else {
warn "server socket setup failed, retry $retry: $@";
# but retry
}
}
}
# ---------------------------------------------------------------------------
# Create the sockets
sub server_sock_setup_unix {
my($socket_specs, $path) = @_;
# see if the socket is in use: if we connect to the current socket, it
# means that spamd is already running, so we have to bail on our own.
# Yes, there is a window here: best we can do for now. There is almost
# certainly a better way, but we don't know it. Yet.
if (-e $path) {
unless (-S $path) {
die "spamd: file $path exists but is no socket, exiting\n";
}
if ( IO::Socket::UNIX->new( Peer => $path, Type => &SOCK_STREAM ) ) {
# socket bind successful: must already be running
# make sure not to enter this socket into @listen_sockets,
# otherwise exit handlers would unlink it!
die "spamd: already running on $path, exiting\n";
}
else {
dbg("spamd: removing stale socket file $path");
unlink $path;
}
}
if (not -d (File::Spec->splitpath($path))[1]) {
die "spamd: directory for $path does not exist, exiting\n";
spamd/spamd.raw view on Meta::CPAN
if (!chmod $mode, $path) { # make sure everybody can talk to it
die "spamd: could not chmod $path to $mode: $!";
}
push(@listen_sockets, { specs => $socket_specs,
path => $path,
socket => $server_unix,
fd => $server_unix->fileno }) if $server_unix;
1;
}
sub server_sock_setup_inet {
my($socket_specs, $addr, $port, $ssl) = @_;
$have_inet4 || $have_inet6
or warn "spamd: neither the PF_INET (IPv4) nor the PF_INET6 (IPv6) ".
"protocol families seem to be available, pushing our luck anyway\n";
my $ai_family = &AF_UNSPEC; # defaults to any address family (i.e. both)
if ($have_inet6 && (!$have_inet4 || $opt{'force_ipv6'})) {
$ai_family = &AF_INET6;
} elsif ($have_inet4 && (!$have_inet6 || $opt{'force_ipv4'})) {
$ai_family = &AF_INET;
}
my($error, @addresses);
if (!defined $addr || lc $addr eq 'localhost') { # loopback interface
push(@addresses, '::1')
if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET6;
push(@addresses, '127.0.0.1')
if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET;
} elsif ($addr eq '*' || $addr eq '') { # any address
push(@addresses, '::')
if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET6;
push(@addresses, '0.0.0.0')
if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET;
} else {
($error, @addresses) = ip_or_name_to_ip_addresses($addr, $ai_family);
}
die "spamd: invalid address for a listen socket: \"$socket_specs\": $error\n"
if $error;
die "spamd: no valid address for a listen socket: \"$socket_specs\"\n"
if !@addresses;
dbg("spamd: attempting to listen on IP addresses: %s, port %d",
join(', ',@addresses), $port);
my(@diag_succ, @diag_fail);
for my $adr (@addresses) {
my %sockopt = (
LocalAddr => $adr,
LocalPort => $port,
Type => &SOCK_STREAM,
Proto => 'tcp',
ReuseAddr => 1,
Listen => &SOMAXCONN,
);
$sockopt{V6Only} = 1 if $io_socket_module_name eq 'IO::Socket::IP'
&& IO::Socket::IP->VERSION >= 0.09;
if ($ssl) {
if (!$have_ssl_module) {
eval { require IO::Socket::SSL; }
or die "spamd: SSL encryption requested, ".
"but IO::Socket::SSL is unavailable ($@)\n";
$have_ssl_module = 1;
}
%sockopt = (%sockopt, (
SSL_server => 1,
SSL_key_file => $opt{'server-key'},
SSL_cert_file => $opt{'server-cert'},
SSL_on_peer_shutdown => sub { return 0 },
));
my $ssl_mode;
if ($opt{'ssl-verify'}) {
$ssl_mode = Net::SSLeay::VERIFY_PEER()
| Net::SSLeay::VERIFY_FAIL_IF_NO_PEER_CERT();
if ($opt{'ssl-ca-file'}) {
$sockopt{SSL_ca_file} = $opt{'ssl-ca-file'};
}
if ($opt{'ssl-ca-path'}) {
$sockopt{SSL_ca_path} = $opt{'ssl-ca-path'};
}
$sockopt{SSL_check_crl} = 0;
$sockopt{SSL_verifycn_scheme} = 'none';
$sockopt{SSL_verifycn_publicsuffix} = '';
} else {
$ssl_mode = Net::SSLeay::VERIFY_NONE()
| Net::SSLeay::VERIFY_FAIL_IF_NO_PEER_CERT();
}
$sockopt{SSL_verify_mode} = $ssl_mode;
}
dbg("spamd: creating %s socket: %s",
$ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
join(', ', map("$_: ".(defined $sockopt{$_} ? $sockopt{$_} : "(undef)"),
sort keys %sockopt)));
my $server_inet = $ssl ? IO::Socket::SSL->new(%sockopt)
: $io_socket_module_name->new(%sockopt);
my $diag;
if (!$server_inet) {
$diag = sprintf("could not create %s socket on [%s]:%s: %s",
$ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
$adr, $port, $ssl && $IO::Socket::SSL::SSL_ERROR ?
"$!,$IO::Socket::SSL::SSL_ERROR" : $!);
push(@diag_fail, $diag);
} else {
$diag = sprintf("created %s socket on [%s]:%s",
$ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
$adr, $port);
push(@diag_succ, $diag);
push(@listen_sockets, { specs => $socket_specs,
ip_addr => $adr, port => $port,
socket => $server_inet,
fd => $server_inet->fileno });
}
dbg("spamd: %s", $diag);
}
if (!@diag_fail) {
# no failures, nothing to report
} elsif (@diag_succ) { # some failures and some success
# just warn of all attempts, successful and failed
warn "spamd: $_\n" for @diag_succ;
warn "spamd: $_\n" for @diag_fail;
} else { # all failed, no success
warn "spamd: $_\n" for @diag_fail[0 .. $#diag_fail-1];
die "spamd: $_\n" for $diag_fail[-1];
}
1;
}
# ---------------------------------------------------------------------------
# for select() purposes: make a map of the server socket FDs
map_server_sockets();
if (!$scaling && @listen_sockets > 1) {
require File::Temp;
# Have multiple sockets and autonomous child processes (--round-robin),
# prepare an anonymous lock file to protect access to select+accept.
# using the same choice of a tmp dir as in Util::secure_tmpfile()
my $tmpdir = untaint_file_path($ENV{'TMPDIR'} || File::Spec->tmpdir);
# the file will be automatically removed by DESTROY on program exit
$sockets_access_lock_tempfile =
File::Temp->new(DIR => $tmpdir, SUFFIX => '.lck', EXLOCK => 0);
dbg("spamd: created a lock file %s to protect select+accept",
$sockets_access_lock_tempfile->filename);
}
if ( defined $opt{'pidfile'} ) {
$opt{'pidfile'} = untaint_file_path( $opt{'pidfile'} );
}
my $spamtest = Mail::SpamAssassin->new(
{
dont_copy_prefs => $dontcopy,
rules_filename => ( $opt{'configpath'} || 0 ),
site_rules_filename => ( $opt{'siteconfigpath'} || 0 ),
pre_config_text => join("\n", @{$opt{'pre'}})."\n",
post_config_text => join("\n", @{$opt{'cf'}})."\n",
force_ipv4 => ( $opt{'force_ipv4'} || 0 ),
local_tests_only => ( $opt{'local'} || 0 ),
debug => ( $opt{'debug'} || 0 ),
spamd/spamd.raw view on Meta::CPAN
eval {
if (!@listen_sockets) {
# nothing?
die "no sockets?";
} elsif (@listen_sockets == 1) {
$selected_socket_info = $listen_sockets[0];
} else {
# 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)
if ($sockets_access_lock_fh) {
dbg("spamd: acquiring a lock over select+accept");
# with multiple sockets a lock across select+accept is needed, Bug 6996
flock($sockets_access_lock_fh, LOCK_EX)
or die "Can't acquire lock access to sockets: $!";
$locked = 1;
}
my $sel_mask_str = unpack('b*', $server_select_mask);
dbg("spamd: select() on fd bit field %s, %s, %s",
$sel_mask_str, defined $timeout ? "timeout $timeout" : "no timeout",
$locked ? "locked" : "not locked");
my $fdvec = $server_select_mask;
my $nfound = select($fdvec, undef, undef, $timeout);
if (!defined $nfound || $nfound < 0) {
die "select failed on fd bit field $sel_mask_str: $!";
} elsif (!$nfound) {
die "no fd ready, fd bit field $sel_mask_str";
}
my(@ready_fd) = # list of file descriptors ready for read
grep(defined $_->{fd} && vec($fdvec, $_->{fd}, 1), @listen_sockets);
if (!@ready_fd) {
die "no file descriptors matching a bit field " . unpack('b*',$fdvec);
} elsif (@ready_fd == 1) { # easy, just one is ready
$selected_socket_info = $ready_fd[0];
} else { # give equal opportunity to each ready socket
my $j = int rand(@ready_fd);
$selected_socket_info = $ready_fd[$j];
dbg("spamd: requests ready on multiple sockets, picking #%d out of %d",
$j+1, scalar @ready_fd);
}
} # end multiple sockets case
if ($selected_socket_info) {
my $socket = $selected_socket_info->{socket};
$socket or die "no socket???, impossible";
dbg("spamd: accept() on fd %d", $selected_socket_info->{fd});
$client = $socket->accept;
if (!defined $client) {
if (defined $socket) {
die sprintf("%s accept failed: %s\n", ref $socket,
$socket->isa('IO::Socket::SSL') ?
$socket->errstr : $@);
} else {
die "accept failed: no socket available: $!\n";
}
}
}
1; # end eval with success
} or do {
my $err = $@ ne '' ? $@ : "errno=$!"; chomp $err;
info("spamd: accept_a_conn: $err");
};
if ($locked) {
dbg("spamd: releasing a lock over select+accept");
flock($sockets_access_lock_fh, LOCK_UN)
or die "Can't release sockets-access lock: $!";
}
return ($client, $selected_socket_info);
}
sub accept_a_conn {
my ($timeout) = @_;
my $socket_info;
# $client is a global variable
($client, $socket_info) = accept_from_any_server_socket($timeout);
if ($scaling) {
$scaling->update_child_status_busy();
}
# Bah!
if ( !$client || !defined $client->connected() ) {
# this can happen when interrupted by SIGCHLD on Solaris,
# perl 5.8.0, and some other platforms with -m.
if ( $! == &Errno::EINTR ) {
return 0;
}
elsif ( $@ =~ /ssl3_get_record:wrong version number/ ||
$@ =~ /peer did not return a certificate/ ) {
# Handshake error, not speaking SSL? No need to respawn
return 0;
}
else {
return -1;
}
}
$client->autoflush(1);
# keep track of start time
$spamtest->timer_reset;
my $start = time;
my ($remote_hostname, $remote_hostaddr, $local_port);
if ($client->isa('IO::Socket::UNIX')) {
$remote_hostname = 'localhost';
$remote_hostaddr = '127.0.0.1';
$remote_port = $socket_info->{path};
info("spamd: got connection over %s", $socket_info->{path});
}
else {
($remote_port, $remote_hostaddr, $remote_hostname, $local_port) =
peer_info_from_socket($client);
$remote_hostaddr or die 'failed to obtain port and ip from socket';
my $ssl_info = '';
if ($client->isa('IO::Socket::SSL')) {
$ssl_info = ', ';
my $ssl_version = $client->get_sslversion();
if (defined $ssl_version) {
$ssl_info .= $ssl_version.'/';
} else {
$ssl_version = $client->get_sslversion_int();
if ($ssl_version == 0x0304) { $ssl_info .= 'TLSv1.3/'; }
elsif ($ssl_version == 0x0303) { $ssl_info .= 'TLSv1.2/'; }
elsif ($ssl_version == 0x0302) { $ssl_info .= 'TLSv1.1/'; }
elsif ($ssl_version == 0x0301) { $ssl_info .= 'TLSv1.0/'; }
elsif ($ssl_version == 0x0300) { $ssl_info .= 'SSLv3/'; }
elsif ($ssl_version == 0x0002) { $ssl_info .= 'SSLv2/'; }
}
$ssl_info .= $client->get_cipher();
}
my $msg = sprintf("connection from %s [%s]:%s to port %d, fd %d%s",
$remote_hostname, $remote_hostaddr, $remote_port,
$local_port, $socket_info->{fd}, $ssl_info);
if (ip_is_allowed($remote_hostaddr)) {
info("spamd: $msg");
}
else {
warn("spamd: unauthorized $msg");
$client->close;
return 0;
}
}
local ($_);
eval {
Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
die "tcp timeout";
});
alarm $timeout_tcp if ($timeout_tcp);
# send the request to the child process
$_ = $client->getline;
};
alarm 0;
if ($@) {
if ($@ =~ /tcp timeout/) {
service_timeout("($timeout_tcp second socket timeout reading input from client)");
} else {
warn "spamd: $@";
}
$client->close;
return 0;
}
if ( !defined $_ ) {
protocol_error("(closed before headers)");
$client->close;
return 0;
}
s/\r?\n//;
# It might be a CHECK message, meaning that we should just check
# if it's spam or not, then return the appropriate response.
spamd/spamd.raw view on Meta::CPAN
=item B<--min-children>=I<number>
The minimum number of children that will be kept running. The minimum value is
C<1>, the default value is C<1>. If you have lots of free RAM, you may want to
increase this.
=item B<--min-spare>=I<number>
The lower limit for the number of spare children allowed to run. A
spare, or idle, child is one that is not handling a scan request. If
there are too few spare children available, a new server will be started
every second or so. The default value is C<1>.
=item B<--max-spare>=I<number>
The upper limit for the number of spare children allowed to run. If there
are too many spare children, one will be killed every second or so until
the number of idle children is in the desired range. The default value
is C<2>.
=item B<--max-conn-per-child>=I<number>
This option specifies the maximum number of connections each child
should process before dying and letting the master spamd process spawn
a new child. The minimum value is C<1>, the default value is C<200>.
=item B<--round-robin>
By default, C<spamd> will attempt to keep a small number of "hot" child
processes as busy as possible, and keep any others as idle as possible, using
something similar to the Apache httpd server scaling algorithm. This is
accomplished by the master process coordinating the activities of the children.
This switch will disable this scaling algorithm, and the behaviour seen in
the 3.0.x versions will be used instead, where all processes receive an
equal load and no scaling takes place.
=item B<--timeout-tcp>=I<number>
This option specifies the number of seconds to wait for headers from a
client (spamc) before closing the connection. The minimum value is C<1>,
the default value is C<30>, and a value of C<0> will disable socket
timeouts completely.
=item B<--timeout-child>=I<number>
This option specifies the number of seconds to wait for a spamd child to
process or check a message. The minimum value is C<1>, the default
value is C<300>, and a value of C<0> will disable child timeouts completely.
=item B<-H> I<directory>, B<--helper-home-dir>=I<directory>
Specify that external programs such as Razor, DCC, and Pyzor should have
a HOME environment variable set to a specific directory. The default
is to use the HOME environment variable setting from the shell running
spamd. By specifying no argument, spamd will use the spamc caller's
home directory instead.
=item B<--ssl>
Accept only SSL connections on the associated port.
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-verify>
Implies B<--ssl>. Request a client certificate and verify the certificate.
Requires B<--ssl-ca-file> or B<--ssl-ca-path>.
=item B<--ssl-ca-file>=I<cafile>
Implies B<--ssl-verify>. Use the specified Certificate Authority
certificate to verify the client certificate. The client certificate must
be signed by this certificate.
=item B<--ssl-ca-path>=I<capath>
Implies B<--ssl-verify>. Use the Certificate Authority certificate files in
the specified set of directories to verify the client certificate. The
client certificate must be signed by one of these Certificate Authorities.
See the man page for B<IO::Socket::SSL> for additional details.
=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.
=item B<--server-cert> I<certfile>
Specify the SSL certificate file to use for SSL connections.
=item B<--socketpath> I<pathname>
Listen on a UNIX domain socket at path I<pathname>, in addition to
sockets specified with a C<--listen> option. This option is provided
for compatibility with older versions of spamd. Starting with version
3.4.0 the C<--listen> option can also take a UNIX domain socket as its
value (an absolute path name). Unlike C<--socketpath>, the C<--listen>
option may be specified multiple times if spamd needs to listen on
multiple UNIX or INET or INET6 sockets.
Warning: the Perl support on BSD platforms for UNIX domain sockets seems to
have a bug regarding paths of over 100 bytes or so (SpamAssassin bug 4380).
If 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> without B<--listen> will inhibit
SSL connections and unencrypted TCP connections. To add other sockets,
specify them with B<--listen>, e.g. '--listen=:' or '--listen=*:'
=item B<--socketowner> I<name>
Set UNIX domain socket to be owned by the user named I<name>. Note
that this requires that spamd be started as C<root>, and if C<-u>
is used, that user should have write permissions to unlink the file
later, for when the C<spamd> server is killed.
=item B<--socketgroup> I<name>
Set UNIX domain socket to be owned by the group named I<name>. See
C<--socketowner> for notes on ownership and permissions.
=item B<--socketmode> I<mode>
Set UNIX domain socket to use the octal mode I<mode>. Note that if C<-u> is
used, that user should have write permissions to unlink the file later, for
when the C<spamd> server is killed.
=item B<--timing>
Enable timing measurements and output the information for logging. This
is the same information as provided by the TIMING tag.
=back
( run in 0.782 second using v1.01-cache-2.11-cpan-39bf76dae61 )