1#!/usr/bin/perl -T -w
2# <@LICENSE>
3# Licensed to the Apache Software Foundation (ASF) under one or more
4# contributor license agreements.  See the NOTICE file distributed with
5# this work for additional information regarding copyright ownership.
6# The ASF licenses this file to you under the Apache License, Version 2.0
7# (the "License"); you may not use this file except in compliance with
8# the License.  You may obtain a copy of the License at:
9#
10#     http://www.apache.org/licenses/LICENSE-2.0
11#
12# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
17# </@LICENSE>
18
19#IMPORTANT: The order of -T -w above is important for spamd_hup.t on Solaris 10 - changed per bug 6883
20
21use strict;
22use warnings;
23use re 'taint';
24
25my $PREFIX          = '@@PREFIX@@';             # substituted at 'make' time
26my $DEF_RULES_DIR   = '@@DEF_RULES_DIR@@';      # substituted at 'make' time
27my $LOCAL_RULES_DIR = '@@LOCAL_RULES_DIR@@';    # substituted at 'make' time
28my $LOCAL_STATE_DIR = '@@LOCAL_STATE_DIR@@';    # substituted at 'make' time
29use lib '@@INSTALLSITELIB@@';                   # substituted at 'make' time
30
31# added by jm for use inside the distro
32# This is disabled during the "make install" process.
33BEGIN {
34  if ( -e '../blib/lib/Mail/SpamAssassin.pm' ) {    # REMOVEFORINST
35    unshift ( @INC, '../blib/lib' );                # REMOVEFORINST
36  } else {                                          # REMOVEFORINST
37    unshift ( @INC, '../lib' );                     # REMOVEFORINST
38  }                                                 # REMOVEFORINST
39}
40
41our ($have_getaddrinfo_in_core, $have_getaddrinfo_legacy, $io_socket_module_name,
42     $have_inet4, $have_inet6, $ai_addrconfig_flag);
43
44# don't force requirement on IO::Socket::IP or IO::Socket::INET6
45BEGIN {
46  $have_getaddrinfo_in_core = eval {
47    # The Socket module (1.94) bundled with Perl 5.14.* provides
48    # new affordances for IPv6, including implementations of the
49    # Socket::getaddrinfo() and Socket::getnameinfo() functions,
50    # along with related constants and a handful of new functions.
51    # Perl 5.16.0 upgrades the core Socket module to version 2.001.
52  # Socket->VERSION(1.94);  # provides getaddrinfo() and getnameinfo()
53  # Socket->VERSION(1.95);  # provides AI_ADDRCONFIG
54    Socket->VERSION(1.96);  # provides NIx_NOSERV, and Exporter tag :addrinfo
55  # Socket->VERSION(1.97);  # IO::Socket::IP depends on Socket 1.97
56    Socket->import(qw(/^(?:AI|NI|NIx|EAI)_/));
57
58    # AUTOLOADing 'constants' here enables inlining - see Exporter man page
59    &AI_ADDRCONFIG; &AI_PASSIVE;
60    &NI_NUMERICHOST, &NI_NUMERICSERV; &NIx_NOSERV; 1;
61  };
62
63  $have_getaddrinfo_legacy = !$have_getaddrinfo_in_core && eval {
64    require Socket6;
65  # Socket6->VERSION(0.13);  # provides NI_NAMEREQD
66    Socket6->VERSION(0.18);  # provides AI_NUMERICSERV
67    Socket6->import(qw(/^(?:AI|NI|NIx|EAI)_/));
68    &AI_ADDRCONFIG; &AI_PASSIVE;  # enable inlining
69    &NI_NUMERICHOST; &NI_NUMERICSERV; &NI_NAMEREQD; 1;
70  };
71
72  require Socket;
73  Socket->import(qw(:DEFAULT IPPROTO_TCP));
74
75  &SOCK_STREAM; &IPPROTO_TCP; &SOMAXCONN; # enable inlining
76
77  &AF_UNSPEC; &AF_INET; &AF_INET6;  # enable inlining
78
79  $ai_addrconfig_flag = 0;
80
81  if ($have_getaddrinfo_in_core) {
82    # using a modern Socket module
83
84    eval {  # does the operating system recognize an AI_ADDRCONFIG flag?
85      if (&AI_ADDRCONFIG && &EAI_BADFLAGS) {
86        my($err, @res) = Socket::getaddrinfo("localhost", 0,
87                           { family => &AF_UNSPEC, flags => &AI_ADDRCONFIG });
88        $ai_addrconfig_flag = &AI_ADDRCONFIG if !$err || $err != &EAI_BADFLAGS;
89      }
90    };
91
92    *ip_or_name_to_ip_addresses = sub {
93      my($addr, $ai_family) = @_;
94      # Socket::getaddrinfo returns a list of hashrefs
95      my($error, @res) =
96        Socket::getaddrinfo($addr, 0,
97          { family => $ai_family, flags => $ai_addrconfig_flag | &AI_PASSIVE,
98            socktype => &SOCK_STREAM, protocol => &IPPROTO_TCP });
99      my(@ip_addrs);
100      if (!$error) {
101        for my $a (@res) {
102          my($err, $ip_addr) =
103            Socket::getnameinfo($a->{addr},
104                                &NI_NUMERICHOST | &NI_NUMERICSERV, &NIx_NOSERV);
105          if (!$err) { push(@ip_addrs, $ip_addr) }
106          elsif (!$error) { $error = $err }
107        }
108      }
109      return ($error, @ip_addrs);
110    };
111
112    *peer_info_from_socket = sub {
113      my $sock = shift;
114      my $peer_addr = $sock->peerhost;  # textual representation of an IP addr
115      $peer_addr or return;
116      my $peer_hostname;
117      if ($sock->UNIVERSAL::can('peerhostname')) {
118        $peer_hostname = $sock->peerhostname;  # provided by IO::Socket::IP
119      } else {
120        my($err, $host) = Socket::getnameinfo($sock->peername,
121                                              &NI_NAMEREQD, &NIx_NOSERV);
122        $peer_hostname = $host  if !$err;
123      }
124      return ($sock->peerport, $peer_addr, $peer_hostname||$peer_addr,
125              $sock->sockport);
126    };
127
128  } elsif ($have_getaddrinfo_legacy) {
129    # using a legacy Socket6 module; somewhat different API on getaddrinfo()
130    # and getnameinfo() compared to these functions in a module Socket
131
132    eval {  # does the operating system recognize an AI_ADDRCONFIG flag?
133      if (&AI_ADDRCONFIG && &EAI_BADFLAGS) {
134        my @res = Socket6::getaddrinfo("localhost", "", 0, &SOCK_STREAM,
135                                       &IPPROTO_TCP, &AI_ADDRCONFIG);
136        my $err = @res >= 5 ? 0 : $res[0];
137        $ai_addrconfig_flag = &AI_ADDRCONFIG if !$err || $err != &EAI_BADFLAGS;
138      }
139    };
140
141    *ip_or_name_to_ip_addresses = sub {
142      my($addr, $ai_family) = @_;
143      # Socket6::getaddrinfo returns a list of quintuples
144      my @res = Socket6::getaddrinfo($addr, '',
145                                     $ai_family, &SOCK_STREAM, &IPPROTO_TCP,
146                                     $ai_addrconfig_flag | &AI_PASSIVE);
147      my($error, @ip_addrs);
148      if (@res < 5) {
149        $error = $res[0];
150      } else {
151        my($family, $socktype, $proto, $saddr, $canonname);
152        while (@res >= 5) {
153          ($family, $socktype, $proto, $saddr, $canonname, @res) = @res;
154          my(@resinfo) =
155            Socket6::getnameinfo($saddr, &NI_NUMERICHOST | &NI_NUMERICSERV);
156          if (@resinfo >= 2) { push(@ip_addrs, $resinfo[0]) }
157          elsif (!$error) { $error = $resinfo[0] }
158        }
159      }
160      return ($error, @ip_addrs);
161    };
162
163    *peer_info_from_socket = sub {
164      my $sock = shift;
165      my $peer_addr = $sock->peerhost;
166      $peer_addr or return;
167      my @resinfo = (Socket6::getnameinfo($sock->peername, &NI_NAMEREQD))[0];
168      my $peer_hostname = @resinfo > 1 ? $resinfo[0] : undef;
169      return ($sock->peerport, $peer_addr, $peer_hostname||$peer_addr,
170              $sock->sockport);
171    };
172
173  } else {  # IPv4 only, no getaddrinfo() available
174
175    *ip_or_name_to_ip_addresses = sub {
176      my($addr, $ai_family) = @_;
177      $ai_family == &AF_UNSPEC || $ai_family == &AF_INET
178        or die "Protocol family $ai_family not supported on this platform";
179      my($error, @ip_addrs, @binaddr);
180      $! = 0; my @res = gethostbyname($addr);
181      if (!@res) {
182        $error = "no results from gethostbyname $!";
183      } else {
184        my($name,$aliases,$addrtype,$length);
185        ($name,$aliases,$addrtype,$length,@binaddr) = @res;
186      }
187      if (!@binaddr) {
188        $error = "no such host";
189      } else {
190        for (@binaddr) {
191          my $ip_addr = Socket::inet_ntoa($_);
192          push(@ip_addrs, $ip_addr)  if $ip_addr;
193        }
194      }
195      return ($error, @ip_addrs);
196    };
197
198    *peer_info_from_socket = sub {
199      my $sock = shift;
200      my ($peer_port, $in_addr) = Socket::sockaddr_in($sock->peername)
201        or return;
202      my $peer_addr = Socket::inet_ntoa($in_addr) or return;
203      my $peer_hostname = gethostbyaddr($in_addr, &AF_INET);
204      return ($peer_port, $peer_addr, $peer_hostname||$peer_addr,
205              $sock->sockport);
206    };
207
208  }
209
210  if (eval { require IO::Socket::IP }) {  # handles IPv6 and IPv4
211    IO::Socket::IP->VERSION(0.09);  # implements IPV6_V6ONLY
212    $io_socket_module_name = 'IO::Socket::IP';
213
214  } elsif (eval { require IO::Socket::INET6 }) {  # handles IPv6 and IPv4
215    $io_socket_module_name = 'IO::Socket::INET6';
216
217  } elsif (eval { require IO::Socket::INET }) {  # IPv4 only
218    $io_socket_module_name = 'IO::Socket::INET';
219  }
220
221  $have_inet4 =  # can we create a PF_INET socket?
222    defined $io_socket_module_name && eval {
223      my $sock =
224        $io_socket_module_name->new(LocalAddr => '0.0.0.0', Proto => 'tcp');
225      $sock->close or die "error closing socket: $!"  if $sock;
226      $sock ? 1 : undef;
227    };
228
229  $have_inet6 =  # can we create a PF_INET6 socket?
230    defined $io_socket_module_name &&
231    $io_socket_module_name ne 'IO::Socket::INET' &&
232    eval {
233      my $sock =
234        $io_socket_module_name->new(LocalAddr => '::', Proto => 'tcp');
235      $sock->close or die "error closing socket: $!"  if $sock;
236      $sock ? 1 : undef;
237    };
238
239}
240
241use IO::Handle;
242use IO::Pipe;
243use IO::File ();
244
245use Mail::SpamAssassin;
246use Mail::SpamAssassin::NetSet;
247use Mail::SpamAssassin::SubProcBackChannel;
248use Mail::SpamAssassin::SpamdForkScaling qw(:pfstates);
249use Mail::SpamAssassin::Logger qw(:DEFAULT log_message);
250use Mail::SpamAssassin::Util qw(untaint_var untaint_file_path secure_tmpdir
251                                exit_status_str am_running_on_windows
252                                get_user_groups);
253use Mail::SpamAssassin::Timeout;
254
255use Getopt::Long;
256use POSIX qw(:sys_wait_h);
257use POSIX qw(locale_h setsid sigprocmask _exit);
258use Errno;
259use Fcntl qw(:flock);
260
261use Cwd ();
262use File::Spec 0.8;
263use File::Path;
264use Carp ();
265use Time::HiRes qw(time);
266
267use constant RUNNING_ON_MACOS => ($^O =~ /^darwin/oi);
268
269# Check to make sure the script version and the module version matches.
270# If not, die here!  Also, deal with unchanged VERSION macro.
271if ($Mail::SpamAssassin::VERSION ne '@@VERSION@@' && '@@VERSION@@' ne "\@\@VERSION\@\@") {
272  die 'spamd: spamd script is v@@VERSION@@, but using modules v'.$Mail::SpamAssassin::VERSION."\n";
273}
274
275# Bug 3062: SpamAssassin should be "locale safe"
276POSIX::setlocale(LC_TIME,'C');
277
278my %resphash = (
279  EX_OK          => 0,     # no problems
280  EX_USAGE       => 64,    # command line usage error
281  EX_DATAERR     => 65,    # data format error
282  EX_NOINPUT     => 66,    # cannot open input
283  EX_NOUSER      => 67,    # addressee unknown
284  EX_NOHOST      => 68,    # host name unknown
285  EX_UNAVAILABLE => 69,    # service unavailable
286  EX_SOFTWARE    => 70,    # internal software error
287  EX_OSERR       => 71,    # system error (e.g., can't fork)
288  EX_OSFILE      => 72,    # critical OS file missing
289  EX_CANTCREAT   => 73,    # can't create (user) output file
290  EX_IOERR       => 74,    # input/output error
291  EX_TEMPFAIL    => 75,    # temp failure; user is invited to retry
292  EX_PROTOCOL    => 76,    # remote error in protocol
293  EX_NOPERM      => 77,    # permission denied
294  EX_CONFIG      => 78,    # configuration error
295  EX_TIMEOUT     => 79,    # read timeout
296);
297
298sub print_version {
299  printf("SpamAssassin Server version %s\n", Mail::SpamAssassin::Version());
300  printf("  running on Perl %s\n",
301         join(".", map( 0+($_||0), ($] =~ /(\d)\.(\d{3})(\d{3})?/) )));
302  eval { require IO::Socket::SSL; };
303  printf("  with SSL support (%s %s)\n", "IO::Socket::SSL", $IO::Socket::SSL::VERSION) unless ($@);
304  eval { require Compress::Zlib; };
305  printf("  with zlib support (%s %s)\n", "Compress::Zlib", $Compress::Zlib::VERSION) unless ($@);
306}
307
308sub print_usage_and_exit {
309  my ( $message, $respnam ) = (@_);
310  $respnam ||= 'EX_USAGE';
311
312  if ($respnam eq 'EX_OK' ) {
313    print_version();
314    print("\n");
315  }
316
317  require Pod::Usage;
318  Pod::Usage->import;
319  pod2usage(
320    -verbose => 0,
321    -message => $message,
322    -exitval => $resphash{$respnam},
323  );
324}
325
326# defaults
327my %opt = (
328  'user-config'   => 1,
329  'ident-timeout' => 5.0,
330  # scaling settings; some of these aren't actually settable via cmdline
331  'server-scale-period' => 2,   # how often to scale the # of kids, secs
332  'min-children'  => 1,         # min kids to have running
333  'min-spare'     => 1,         # min kids that must be spare
334  'max-spare'     => 2,         # max kids that should be spare
335  'pre'           => [],        # extra .pre lines
336  'cf'            => [],        # extra config lines
337);
338
339
340# bug 1725, 2192:
341# Untaint all command-line options and ENV vars, since spamd is launched
342# as a daemon from a known-safe environment. Also store away some of the
343# vars we need for a SIGHUP later on.
344
345# Testing for taintedness only works before detainting %ENV
346Mail::SpamAssassin::Util::am_running_in_taint_mode();
347
348# First clean PATH and untaint the environment -- need to do this before
349# Cwd::cwd(), else it will croak.
350Mail::SpamAssassin::Util::clean_path_in_taint_mode();
351untaint_var( \%ENV );
352
353# The zeroth argument will be replaced in daemonize().
354my $ORIG_ARG0 = untaint_var($0);
355
356# Getopt::Long clears all arguments it processed (untaint both @ARGVs here!)
357my @ORIG_ARGV = untaint_var( \@ARGV );
358
359# daemonize() switches to the root later on and we need to come back here
360# somehow -- untaint the dir to be on the safe side.
361my $ORIG_CWD = untaint_var( Cwd::cwd() );
362
363prepare_for_sighup_restart();
364
365# Parse the command line
366Getopt::Long::Configure("bundling");
367GetOptions(
368  'allow-tell'               => \$opt{'tell'},
369  'allowed-ips|A=s'          => \@{ $opt{'allowed-ip'} },
370  'auth-ident'               => \$opt{'auth-ident'},
371  'configpath|C=s'           => \$opt{'configpath'},
372  'c'                        => \$opt{'create-prefs'},
373  'create-prefs!'            => \$opt{'create-prefs'},
374  'daemonize!'               => \$opt{'daemonize'},
375  'debug|D:s'                => \$opt{'debug'},
376  'd'                        => \$opt{'daemonize'},
377  'groupname|g=s'            => \$opt{'groupname'},
378  'helper-home-dir|H:s'      => \$opt{'home_dir_for_helpers'},
379  'help|h'                   => \$opt{'help'},
380  'ident-timeout=f'          => \$opt{'ident-timeout'},
381  '4|ipv4only|ipv4-only|ipv4'=> sub { $opt{'force_ipv4'} = 1;
382                                      $opt{'force_ipv6'} = 0; },
383  '6'                        => sub { $opt{'force_ipv6'} = 1;
384                                      $opt{'force_ipv4'} = 0; },
385  'ldap-config!'             => \$opt{'ldap-config'},
386  'listen|listen-ip|ip-address|i:s' => \@{ $opt{'listen-sockets'} },
387  'local!'                   => \$opt{'local'},
388  'L'                        => \$opt{'local'},
389  'l'                        => \$opt{'tell'},
390  'round-robin!'             => \$opt{'round-robin'},
391  'min-children=i'           => \$opt{'min-children'},
392  'max-children|m=i'         => \$opt{'max-children'},
393  'min-spare=i'              => \$opt{'min-spare'},
394  'max-spare=i'              => \$opt{'max-spare'},
395  'max-conn-per-child=i'     => \$opt{'max-conn-per-child'},
396  'nouser-config|x'          => sub { $opt{'user-config'} = 0 },
397  'paranoid!'                => \$opt{'paranoid'},
398  'P'                        => \$opt{'paranoid'},
399  'pidfile|r=s'              => \$opt{'pidfile'},
400  'port|p=s'                 => \$opt{'port'},
401  'Q'                        => \$opt{'setuid-with-sql'},
402  'q'                        => \$opt{'sql-config'},
403  'server-cert=s'            => \$opt{'server-cert'},
404  'server-key=s'             => \$opt{'server-key'},
405  'setuid-with-ldap'         => \$opt{'setuid-with-ldap'},
406  'setuid-with-sql'          => \$opt{'setuid-with-sql'},
407  'siteconfigpath=s'         => \$opt{'siteconfigpath'},
408  'pre=s'                    => \@{$opt{'pre'}},
409  'cf=s'                     => \@{$opt{'cf'}},
410  'socketgroup=s'            => \$opt{'socketgroup'},
411  'socketmode=s'             => \$opt{'socketmode'},
412  'socketowner=s'            => \$opt{'socketowner'},
413  'socketpath=s'             => \$opt{'socketpath'},
414  'sql-config!'              => \$opt{'sql-config'},
415  'ssl'                      => \$opt{'ssl'},
416  'ssl-port=s'               => \$opt{'ssl-port'},
417  'syslog-socket=s'          => \$opt{'syslog-socket'},
418  'syslog|s=s'               => \$opt{'syslog'},
419  'log-timestamp-fmt:s'      => \$opt{'log-timestamp-fmt'},
420  'timeout-tcp|T=i'          => \$opt{'timeout-tcp'},
421  'timeout-child|t=i'        => \$opt{'timeout-child'},
422  'timing'                   => \$opt{'timing'},
423  'user-config'              => \$opt{'user-config'},
424  'username|u=s'             => \$opt{'username'},
425  'version|V'                => \$opt{'version'},
426  'virtual-config-dir=s'     => \$opt{'virtual-config-dir'},
427  'v'                        => \$opt{'vpopmail'},
428  'vpopmail!'                => \$opt{'vpopmail'},
429
430  #
431  # NOTE: These are old options.  We should ignore (but warn about)
432  # the ones that are now defaults.  Everything else gets a die (see note2)
433  # so the user doesn't get us doing something they didn't expect.
434  #
435  # NOTE2: 'die' doesn't actually stop the process, GetOptions() catches
436  # it, then passes the error on, so we'll end up doing a Usage statement.
437  # You can avoid that by doing an explicit exit in the sub.
438  #
439
440  # last in 2.3
441  'F:i'                   => sub { warn "spamd: the -F option has been removed from spamd, please remove from your commandline and re-run\n"; exit 2; },
442  'add-from!'             => sub { warn "spamd: the --add-from option has been removed from spamd, please remove from your commandline and re-run\n"; exit 2; },
443
444  # last in 2.4
445  'stop-at-threshold|S' => sub { warn "spamd: the -S option has been deprecated and is no longer supported, ignoring\n" },
446
447) or print_usage_and_exit();
448
449if ($opt{'help'}) {
450  print_usage_and_exit(qq{For more details, use "man spamd".\n}, 'EX_OK');
451}
452if ($opt{'version'}) {
453  print_version();
454  exit($resphash{'EX_OK'});
455}
456
457my $log_timestamp_fmt = $opt{'log-timestamp-fmt'};
458if (defined $log_timestamp_fmt && lc($log_timestamp_fmt) eq 'default') {
459  undef $log_timestamp_fmt;  # undefined implies per-logger's default
460}
461if (defined $log_timestamp_fmt) {
462  # a nondefault timestamp format was specified, need to reopen stderr logger
463  Mail::SpamAssassin::Logger::remove('stderr');
464  Mail::SpamAssassin::Logger::add(method => 'stderr',
465				  timestamp_fmt => $log_timestamp_fmt,
466				  escape => 1);
467}
468
469# Enable debugging, if any areas were specified.  We do this already here,
470# accessing some non-public API so we can use the convenient dbg() routine.
471# Don't do this at home (aka any 3rd party tools), kids!
472if (defined $opt{'debug'}) {
473  $opt{'debug'} ||= 'all';
474}
475# always turn on at least info-level debugging for spamd
476$opt{'debug'} ||= 'info';
477# turn on debugging facilities as soon as possible
478Mail::SpamAssassin::Logger::add_facilities($opt{'debug'});
479
480# bug 2228: make the values of (almost) all parameters which accept file paths
481# absolute, so they are still valid after daemonize()
482foreach my $opt (
483  qw(
484  configpath
485  siteconfigpath
486  socketpath
487  pidfile
488  home_dir_for_helpers
489  )
490  )
491{
492  # rel2abs taints the new value!
493  $opt{$opt} =
494    untaint_file_path(File::Spec->rel2abs( $opt{$opt} )) if $opt{$opt};
495}
496
497# These can be changed on command line with -A flag
498my $allowed_nets = Mail::SpamAssassin::NetSet->new();
499if ( @{ $opt{'allowed-ip'} } ) {
500  set_allowed_ip( grep length, map { split /,/ } @{ $opt{'allowed-ip'} } );
501} else {
502  set_allowed_ip('127.0.0.1', '::1');
503}
504
505# ident-based spamc user authentication
506if ( $opt{'auth-ident'} ) {
507  eval { require Net::Ident }
508  or die "spamd: ident-based authentication requested, ".
509         "but Net::Ident is unavailable: $@\n";
510
511  $opt{'ident-timeout'} = undef if $opt{'ident-timeout'} <= 0.0;
512  Net::Ident->import(qw(ident_lookup));
513}
514
515### Begin initialization of logging ########################
516
517# The syslog facility can be changed on the command line with the
518# --syslog flag. Special cases are:
519# * A log facility of 'stderr' will log to STDERR
520# * " "   "        "  'null' disables all logging
521# * " "   "        "  'file' logs to the file "spamd.log"
522# * Any facility containing non-word characters is interpreted as the name
523#   of a specific logfile
524my $log_facility = $opt{'syslog'} || 'mail';
525
526# The --syslog-socket option specifies one of the possible socket types or
527# logging mechanisms as accepted by the Sys::Syslog::setlogsock() subroutine.
528# Depending on a version of Sys::Syslog and on the underlying operating system,
529# one of the following values (or their subset) can be used: native, eventlog,
530# tcp, udp, inet, unix, stream, pipe, console. The value 'eventlog' is
531# specific to Win32 events logger and requires a perl module Win32::EventLog.
532#
533# In addition to values acceptable by Sys::Syslog::setlogsock(),
534# a --syslog-socket=none is mapped to --syslog=stderr and $log_socket='file'.
535#
536# A value 'file' in variable $log_socket implies logging to any file handler
537# (either a specific log file or STDERR), A value 'none' in $log_socket
538# represents no logging, equivalent to --syslog=null.
539#
540# (old text: The socket to log over can be changed on the command line with
541#  the --syslog-socket flag. Logging to any file handler (either a specific log
542#  file or STDERR) is internally represented by a socket 'file', no logging
543#  at all is 'none'. The latter is different from --syslog-socket=none which
544#  gets mapped to --syslog=stderr and such --syslog-socket=file. An internal
545#  socket of 'none' means as much as --syslog=null. Sounds complicated? It is.
546#  But it works.
547# )
548
549my $log_socket = $opt{'syslog-socket'};
550
551if (!defined $log_socket || $log_socket eq '') {
552  $log_socket = am_running_on_windows() ? 'none' : 'unix';
553} else {
554  $log_socket = lc $log_socket;
555}
556
557# This is the default log file; it can be changed on the command line
558# via a --syslog flag containing non-word characters.
559my $log_file = "spamd.log";
560
561# A specific log file was given (--syslog=/path/to/file).
562if ($log_facility =~ /[^a-z0-9]/) {
563  $log_file = $log_facility;
564  $log_socket = 'file';
565}
566# The generic log file was requested (--syslog=file).
567elsif (lc($log_facility) eq 'file') {
568  $log_socket = 'file';
569}
570# The casing is kept only if the facility specified a file.
571else {
572  $log_facility = lc($log_facility);
573}
574
575# Either above or at the command line the socket was set
576# to 'file' (--syslog-socket=file).
577if ($log_socket eq 'file') {
578  $log_facility = 'file';
579}
580# The socket 'none' (--syslog-socket=none) historically
581# represents logging to STDERR.
582elsif ($log_socket eq 'none') {
583  $log_facility = 'stderr';
584}
585
586# Either above or at the command line the facility was set
587# to 'stderr' (--syslog=stderr).
588if ($log_facility eq 'stderr') {
589  $log_socket = 'file';
590}
591
592# The --log-timestamp-fmt option can provide a POSIX strftime(3) format for
593# timestamps included in each logged message. Each logger (stderr, file,
594# syslog) has its own default value for a timestamp format, which applies when
595# --log-timestamp-fmt option is not given, or with --log-timestamp-fmt=default
596# Timestamps can be turned off by specifying an empty string with this
597# option, e.g. --log-timestamp-fmt='' or just --log-timestamp-fmt=
598# Typical use: --log-timestamp-fmt='%a %b %e %H:%M:%S %Y' .
599
600# Logging via syslog is requested.
601if ($log_socket ne 'file' && $log_facility ne 'null') {
602  if (!Mail::SpamAssassin::Logger::add(method => 'syslog',
603				       socket => $log_socket,
604				       facility => $log_facility,
605				       ident => 'spamd',
606				       timestamp_fmt => $log_timestamp_fmt,
607				       escape => 1))
608  {
609    # syslog method failed
610    $log_facility = 'stderr';
611  }
612}
613# Otherwise, the user wants to log to some file.
614elsif ($log_facility eq 'file') {
615  if (!Mail::SpamAssassin::Logger::add(method => 'file',
616				       filename => $log_file,
617				       timestamp_fmt => $log_timestamp_fmt,
618				       escape => 1))
619  {
620    # file method failed
621    $log_facility = 'stderr';
622  }
623}
624
625### End initialization of logging ##########################
626
627# REIMPLEMENT: if $log_socket is none, fall back to log_facility 'stderr'.
628# If log_fac is stderr and defined $opt{'debug'}, set log_fac to 'null' to
629# avoid duplicating log messages.
630# TVD: isn't this already done up above?
631
632# support setuid() to user unless:
633# run with -u
634# we're not root
635# doing --vpopmail or --virtual-config-dir
636# using --sql-config or --ldap-config
637# (unless we're also using --setuid-with-sql or --setuid-with-ldap)
638my $setuid_to_user = (
639        $opt{'username'} ||
640        $> != 0 ||
641        $opt{'vpopmail'} ||
642        $opt{'virtual-config-dir'} ||
643        ($opt{'sql-config'} && !$opt{'setuid-with-sql'}) ||
644        ($opt{'ldap-config'} && !$opt{'setuid-with-ldap'})
645      ) ? 0 : 1;
646
647dbg("spamd: will perform setuids? $setuid_to_user");
648
649if ( $opt{'vpopmail'} ) {
650  if ( !$opt{'username'} ) {
651    die "spamd: cannot use --vpopmail without -u\n";
652  }
653}
654
655if ( $opt{'virtual-config-dir'} ) {
656  if ( !$opt{'username'} ) {
657    die "spamd: cannot use --virtual-config-dir without -u\n";
658  }
659}
660
661if ($opt{'sql-config'} && !$opt{'setuid-with-sql'}) {
662  if ( !$opt{'username'} ) {
663    die "spamd: cannot use --sql-config without -u\n";
664  }
665}
666
667if ($opt{'ldap-config'} && !$opt{'setuid-with-ldap'}) {
668  if ( !$opt{'username'} ) {
669    die "spamd: cannot use --ldap-config without -u\n";
670  }
671}
672
673# always copy the config, later code may disable
674my $copy_config_p = 1;
675
676my $current_user;
677
678my $client;               # used for the client connection ...
679my $childlimit;           # max number of kids allowed
680my $timeout_tcp;          # socket timeout (connect->headers), 0=no timeout
681my $timeout_child;        # processing timeout (headers->finish), 0=no timeout
682my $clients_per_child;    # number of clients each child should process
683my %children;             # current children
684my @children_exited;
685
686if ( defined $opt{'max-children'} ) {
687  $childlimit = $opt{'max-children'};
688
689  # Make sure that the values are at least 1
690  $childlimit = undef if ( $childlimit < 1 );
691}
692
693if ( defined $opt{'max-conn-per-child'} ) {
694  $clients_per_child = $opt{'max-conn-per-child'};
695
696  # Make sure that the values are at least 1
697  $clients_per_child = undef if ( $clients_per_child < 1 );
698}
699
700# Set some "sane" limits for defaults
701$childlimit        ||= 5;
702$clients_per_child ||= 200;
703
704if (defined $opt{'timeout-tcp'} && $opt{'timeout-tcp'} >= 0) {
705  $timeout_tcp = $opt{'timeout-tcp'};
706  $timeout_tcp = undef if ($timeout_tcp == 0);
707}
708else {
709  $timeout_tcp = 30;
710}
711
712if (defined $opt{'timeout-child'} && $opt{'timeout-child'} >= 0) {
713  $timeout_child = $opt{'timeout-child'};
714  $timeout_child = undef if ($timeout_child == 0);
715}
716else {
717  $timeout_child = 300;
718}
719
720# ensure scaling parameters are logical
721if ($opt{'min-children'} < 1) {
722  $opt{'min-children'} = 1;
723}
724if ($opt{'min-spare'} < 0) {
725  $opt{'min-spare'} = 0;
726}
727if ($opt{'min-spare'} > $childlimit) {
728  $opt{'min-spare'} = $childlimit-1;
729}
730if ($opt{'max-spare'} < $opt{'min-spare'}) {
731  # emulate Apache behaviour:
732  # http://httpd.apache.org/docs-2.0/mod/prefork.html#maxspareservers
733  $opt{'max-spare'} = $opt{'min-spare'}+1;
734}
735
736my $dontcopy = 1;
737if ( $opt{'create-prefs'} ) { $dontcopy = 0; }
738
739my $orighome;
740if ( defined $ENV{'HOME'} ) {
741  if ( defined $opt{'username'} )
742  {    # spamd is going to run as another user, so reset $HOME
743    if ( my $nh = ( getpwnam( $opt{'username'} ) )[7] ) {
744      $ENV{'HOME'} = $nh;
745    }
746    else {
747      die "spamd: unable to determine home directory for user '"
748        . $opt{'username'} . "'\n";
749    }
750  }
751
752  $orighome = $ENV{'HOME'};    # keep a copy for use by Razor, Pyzor etc.
753  delete $ENV{'HOME'};         # we do not want to use this when running spamd
754}
755
756# Do whitelist later in tmp dir. Side effect: this will be done as -u user.
757
758$opt{'server-key'}  ||= "$LOCAL_RULES_DIR/certs/server-key.pem";
759$opt{'server-cert'} ||= "$LOCAL_RULES_DIR/certs/server-cert.pem";
760
761# ---------------------------------------------------------------------------
762# Server (listening) socket setup for the various supported types
763
764dbg("spamd: socket module of choice: %s %s, Socket %s".
765    ", %s PF_INET, %s PF_INET6, %s, AI_ADDRCONFIG %s",
766    $io_socket_module_name,
767    $io_socket_module_name->VERSION,
768    Socket->VERSION,
769    $have_inet4 ? 'have' : 'no',
770    $have_inet6 ? 'have' : 'no',
771    $have_getaddrinfo_in_core ? 'using Socket::getaddrinfo'
772    : $have_getaddrinfo_legacy ? 'using legacy Socket6::getaddrinfo'
773    : 'no getaddrinfo, using gethostbyname, IPv4-only',
774    $ai_addrconfig_flag ? "is supported" : "not supported",
775);
776
777my $have_ssl_module;
778my @listen_sockets;  # list of hashrefs, contains info on all listen sockets
779my $server_select_mask;
780
781my @listen_socket_specs = @{$opt{'listen-sockets'}};
782
783{ # merge legacy option --socketpath into @listen_socket_specs
784  my $socketpath = $opt{'socketpath'};
785  if (defined $socketpath && $socketpath ne '') {
786    $socketpath =~ m{^/}
787      or die "socketpath option should specify an absolute path: $socketpath";
788    push(@listen_socket_specs, $socketpath);
789  }
790}
791
792# supply a default socket (loopback IP address) if none specified
793push(@listen_socket_specs, 'localhost')  if !@listen_socket_specs;
794
795for (@listen_socket_specs) {
796  my $socket_specs = $_;
797
798  $socket_specs = '*'  if $socket_specs eq '';  # empty implies all interfaces
799
800  local($1,$2,$3,$4,$5,$6);
801  if ($socket_specs =~
802           m{^ (?: (ssl) : )?
803               ( / .* ) \z }xsi) {  # unix socket - absolute path
804    my($proto,$path) = ($1, $2);
805  # $proto = 'ssl'  if defined $opt{'ssl'} || defined $opt{'ssl-port'};
806    $proto = !defined($proto) ? '' : lc($proto);
807    # abstracted out the setup-retry code
808    dbg("spamd: unix socket: %s", $path);
809    server_sock_setup(\&server_sock_setup_unix, $socket_specs, $path);
810
811  } elsif ($socket_specs =~
812           m{^ (?: (ssl) : )?
813               (?: \[ ( [^\]]* ) \]
814                 | ( [a-z0-9._-]* )
815                 | ( [a-f0-9]* : [a-f0-9]* : [a-f0-9:]*
816                     (?: % [a-z0-9._~-]* )? \z )
817                 | ( \* )
818               )?
819               (?: : ( [a-z0-9-]* ) )? \z }xsi) {
820    my($proto,$addr,$port) = ($1, $2||$3||$4||$5, $6);
821    $addr = 'localhost'  if !defined $addr;
822    $proto = 'ssl'  if defined $opt{'ssl'} || defined $opt{'ssl-port'};
823    $proto = !defined($proto) ? '' : lc($proto);
824    $port = $opt{'ssl-port'}  if !defined $port && $proto eq 'ssl';
825    $port = $opt{'port'}      if !defined $port || $port eq '';
826    $port = '783'             if !defined $port || $port eq '';
827    if ($port ne '' && $port !~ /^(\d+)\z/) {
828      $port = ( getservbyname($port,'tcp') )[2];
829      $port or die "spamd: invalid port: $port, socket: $socket_specs\n";
830    }
831    # abstracted out the setup-retry code
832    dbg('spamd: %s socket specification: "%s", IP address: %s, port: %s',
833        $proto, $socket_specs, $addr, $port);
834    server_sock_setup(\&server_sock_setup_inet,
835                      $socket_specs, $addr, $port, $proto eq 'ssl' ? 1 : 0);
836  } else {
837    die "Invalid socket specification syntax: $socket_specs\n";
838  }
839}
840
841@listen_sockets  or die "No listen sockets specified, aborting\n";
842
843# ---------------------------------------------------------------------------
844
845# Check for server certs
846if ( $have_ssl_module ) {
847  if ( !-e $opt{'server-key'} ) {
848    die "spamd: server key file $opt{'server-key'} does not exist\n";
849  }
850  if ( !-e $opt{'server-cert'} ) {
851    die "spamd: server certificate file $opt{'server-cert'} does not exist\n";
852  }
853}
854
855# ---------------------------------------------------------------------------
856
857my $sockets_access_lock_tempfile;  # a File::Temp object, if locking is needed
858my $sockets_access_lock_fh;  # per-child file handle on a lock file
859
860my $backchannel = Mail::SpamAssassin::SubProcBackChannel->new();
861my $scaling;
862
863if (!$opt{'round-robin'})
864{
865  my $max_children = $childlimit;
866
867  # change $childlimit to avoid churn when we startup and create loads
868  # of spare servers; when we're using scaling, it's not as important
869  # as it was with the old algorithm.
870  if ($childlimit > $opt{'max-spare'}) {
871    $childlimit = $opt{'max-spare'};
872  }
873  if ($childlimit < $opt{'min-children'}) {
874    $childlimit = $opt{'min-children'};
875  }
876
877  $scaling = Mail::SpamAssassin::SpamdForkScaling->new({
878        backchannel => $backchannel,
879        min_children => $opt{'min-children'},
880        max_children => $max_children,
881        min_idle => $opt{'min-spare'},
882        max_idle => $opt{'max-spare'},
883        cur_children_ref => \$childlimit
884      });
885}
886
887# ---------------------------------------------------------------------------
888
889sub compose_listen_info_string {
890  my @listeninfo;
891
892  for my $socket_info (@listen_sockets) {
893    next if !$socket_info;
894    my $socket = $socket_info->{socket};
895    next if !$socket;
896    my $socket_specs = $socket_info->{specs};
897
898    if ($socket->isa('IO::Socket::UNIX')) {
899      push(@listeninfo, "UNIX domain socket " . $socket_info->{path});
900
901    } elsif ( $socket->isa('IO::Socket::INET')  ||
902              $socket->isa('IO::Socket::INET6') ||
903              $socket->isa('IO::Socket::IP') ) {
904      push(@listeninfo, sprintf("%s [%s]:%s", ref $socket,
905                      $socket_info->{ip_addr}, $socket_info->{port}));
906
907    } elsif ($socket->isa('IO::Socket::SSL')) {
908      push(@listeninfo, sprintf("SSL [%r]:%s", $socket_info->{ip_addr},
909                      $socket_info->{port}));
910    }
911  }
912
913  # just for reporting at startup
914  return join(', ', @listeninfo);
915}
916
917sub server_sock_setup {
918  my($sub, @args) = @_;
919
920  # retry 3 times to bind to the listening socket; 3 seconds delay,
921  # max, but should allow a little time for any existing shutting-down
922  # server to complete shutdown
923  my $lastretry = 10;
924  for my $retry (1 .. $lastretry) {
925    if ($retry > 1) { sleep 1; }
926
927    eval { &$sub(@args) } and last;  # success => break
928
929    if ($retry == $lastretry) {
930      die $@;               # this is fatal
931    } else {
932      warn "server socket setup failed, retry $retry: $@";
933      # but retry
934    }
935  }
936}
937
938# ---------------------------------------------------------------------------
939
940# Create the sockets
941sub server_sock_setup_unix {
942  my($socket_specs, $path) = @_;
943
944  # see if the socket is in use: if we connect to the current socket, it
945  # means that spamd is already running, so we have to bail on our own.
946  # Yes, there is a window here: best we can do for now. There is almost
947  # certainly a better way, but we don't know it. Yet.
948
949  if (-e $path) {
950    unless (-S $path) {
951      die "spamd: file $path exists but is no socket, exiting\n";
952    }
953
954    if ( IO::Socket::UNIX->new( Peer => $path, Type => &SOCK_STREAM ) ) {
955      # socket bind successful: must already be running
956
957      # make sure not to enter this socket into @listen_sockets,
958      # otherwise exit handlers would unlink it!
959      die "spamd: already running on $path, exiting\n";
960    }
961    else {
962      dbg("spamd: removing stale socket file $path");
963      unlink $path;
964    }
965  }
966  if (not -d (File::Spec->splitpath($path))[1]) {
967    die "spamd: directory for $path does not exist, exiting\n";
968  }
969
970  my %socket = (
971    Local  => $path,
972    Type   => &SOCK_STREAM,
973    Listen => &SOMAXCONN,
974  );
975  dbg("spamd: creating UNIX socket:\n" . join("\n", map { " $_: " . (defined $socket{$_} ? $socket{$_} : "(undef)") } sort keys %socket));
976  my $server_unix = IO::Socket::UNIX->new(%socket);
977
978  # sanity check!  cf. bug 3490
979  if (not $server_unix or not -S $path) {
980    unless ($server_unix) {
981      dbg "spamd: socket path might have been truncated due to system limits\n";
982      die "spamd: could not create UNIX socket on $path: $!\n";
983    }
984    my $hostpath = $server_unix->hostpath();
985    if ($hostpath ne $path) {
986      warn "spamd: socket path was truncated at position " . length($hostpath) . "\n";
987      warn "spamd: leaving stale socket at $hostpath\n" if -S $hostpath;
988      die "spamd: path length for UNIX socket on $path exceeds system limit, exiting\n";
989    }
990    else {
991      die "spamd: could not find newly-created UNIX socket on $path: $!\n";
992    }
993  }
994
995  my $mode = $opt{socketmode};
996  if ($mode) {
997    $mode = oct $mode;
998  } else {
999    $mode = 0666;        # default
1000  }
1001
1002  my $owner = $opt{socketowner};
1003  my $group = $opt{socketgroup};
1004  if ($owner || $group) {
1005    my $uid = -1;
1006    my $gid = -1;
1007    if ($owner) {
1008      my ($login,$pass,$puid,$pgid) = getpwnam($owner)
1009                           or die "spamd: $owner not in passwd database\n";
1010      $uid = $puid;
1011    }
1012    if ($group) {
1013      my ($name,$pass,$ggid,$members) = getgrnam($group)
1014                           or die "spamd: $group not in group database\n";
1015      $gid = $ggid;
1016    }
1017    if (!chown $uid, $gid, $path) {
1018      die "spamd: could not chown $path to $uid/$gid: $!";
1019    }
1020  }
1021
1022  if (!chmod $mode, $path) {    # make sure everybody can talk to it
1023    die "spamd: could not chmod $path to $mode: $!";
1024  }
1025
1026  push(@listen_sockets, { specs => $socket_specs,
1027                          path => $path,
1028                          socket => $server_unix,
1029                          fd => $server_unix->fileno })  if $server_unix;
1030  1;
1031}
1032
1033sub server_sock_setup_inet {
1034  my($socket_specs, $addr, $port, $ssl) = @_;
1035
1036  $have_inet4 || $have_inet6
1037    or warn "spamd: neither the PF_INET (IPv4) nor the PF_INET6 (IPv6) ".
1038            "protocol families seem to be available, pushing our luck anyway\n";
1039
1040  my $ai_family = &AF_UNSPEC;  # defaults to any address family (i.e. both)
1041  if      ($have_inet6 && (!$have_inet4 || $opt{'force_ipv6'})) {
1042    $ai_family = &AF_INET6;
1043  } elsif ($have_inet4 && (!$have_inet6 || $opt{'force_ipv4'})) {
1044    $ai_family = &AF_INET;
1045  }
1046  my($error, @addresses);
1047  if (!defined $addr || lc $addr eq 'localhost') {  # loopback interface
1048    push(@addresses, '::1')
1049      if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET6;
1050    push(@addresses, '127.0.0.1')
1051      if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET;
1052  } elsif ($addr eq '*' || $addr eq '') {  # any address
1053    push(@addresses, '::')
1054      if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET6;
1055    push(@addresses, '0.0.0.0')
1056      if $ai_family == &AF_UNSPEC || $ai_family == &AF_INET;
1057  } else {
1058    ($error, @addresses) = ip_or_name_to_ip_addresses($addr, $ai_family);
1059  }
1060  die "spamd: invalid address for a listen socket: \"$socket_specs\": $error\n"
1061    if $error;
1062  die "spamd: no valid address for a listen socket: \"$socket_specs\"\n"
1063    if !@addresses;
1064
1065  dbg("spamd: attempting to listen on IP addresses: %s, port %d",
1066      join(', ',@addresses), $port);
1067  my(@diag_succ, @diag_fail);
1068  for my $adr (@addresses) {
1069    my %sockopt = (
1070      LocalAddr => $adr,
1071      LocalPort => $port,
1072      Type      => &SOCK_STREAM,
1073      Proto     => 'tcp',
1074      ReuseAddr => 1,
1075      Listen    => &SOMAXCONN,
1076    );
1077    $sockopt{V6Only} = 1  if $io_socket_module_name eq 'IO::Socket::IP'
1078                             && IO::Socket::IP->VERSION >= 0.09;
1079    %sockopt = (%sockopt, (
1080      SSL_verify_mode => 0x00,
1081      SSL_key_file    => $opt{'server-key'},
1082      SSL_cert_file   => $opt{'server-cert'},
1083    ))  if $ssl;
1084    dbg("spamd: creating %s socket: %s",
1085        $ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
1086        join(', ', map("$_: ".(defined $sockopt{$_} ? $sockopt{$_} : "(undef)"),
1087                       sort keys %sockopt)));
1088    if ($ssl && !$have_ssl_module) {
1089      eval { require IO::Socket::SSL }
1090        or die "spamd: SSL encryption requested, ".
1091               "but IO::Socket::SSL is unavailable ($@)\n";
1092      $have_ssl_module = 1;
1093    }
1094    my $server_inet = $ssl ? IO::Socket::SSL->new(%sockopt)
1095                           : $io_socket_module_name->new(%sockopt);
1096    my $diag;
1097    if (!$server_inet) {
1098      $diag = sprintf("could not create %s socket on [%s]:%s: %s",
1099                      $ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
1100                      $adr, $port, $ssl && $IO::Socket::SSL::SSL_ERROR ?
1101                      "$!,$IO::Socket::SSL::SSL_ERROR" : $!);
1102      push(@diag_fail, $diag);
1103    } else {
1104      $diag = sprintf("created %s socket on [%s]:%s",
1105                      $ssl ? 'IO::Socket::SSL' : $io_socket_module_name,
1106                      $adr, $port);
1107      push(@diag_succ, $diag);
1108      push(@listen_sockets, { specs => $socket_specs,
1109                              ip_addr => $adr, port => $port,
1110                              socket => $server_inet,
1111                              fd => $server_inet->fileno });
1112    }
1113    dbg("spamd: %s", $diag);
1114  }
1115  if (!@diag_fail) {
1116    # no failures, nothing to report
1117  } elsif (@diag_succ) {  # some failures and some success
1118    # just warn of all attempts, successful and failed
1119    warn "spamd: $_\n"  for @diag_succ;
1120    warn "spamd: $_\n"  for @diag_fail;
1121  } else {  # all failed, no success
1122    warn "spamd: $_\n"  for @diag_fail[0 .. $#diag_fail-1];
1123    die  "spamd: $_\n"  for $diag_fail[-1];
1124  }
1125
1126  1;
1127}
1128
1129# ---------------------------------------------------------------------------
1130
1131# for select() purposes: make a map of the server socket FDs
1132map_server_sockets();
1133
1134if (!$scaling && @listen_sockets > 1) {
1135  require File::Temp;
1136
1137  # Have multiple sockets and autonomous child processes (--round-robin),
1138  # prepare an anonymous lock file to protect access to select+accept.
1139
1140  # using the same choice of a tmp dir as in Util::secure_tmpfile()
1141  my $tmpdir = untaint_file_path($ENV{'TMPDIR'} || File::Spec->tmpdir);
1142
1143  # the file will be automatically removed by DESTROY on program exit
1144  $sockets_access_lock_tempfile =
1145    File::Temp->new(DIR => $tmpdir, SUFFIX => '.lck', EXLOCK => 0);
1146
1147  dbg("spamd: created a lock file %s to protect select+accept",
1148      $sockets_access_lock_tempfile->filename);
1149}
1150
1151if ( defined $opt{'pidfile'} ) {
1152  $opt{'pidfile'} = untaint_file_path( $opt{'pidfile'} );
1153}
1154
1155
1156my $spamtest = Mail::SpamAssassin->new(
1157  {
1158    dont_copy_prefs      => $dontcopy,
1159    rules_filename       => ( $opt{'configpath'} || 0 ),
1160    site_rules_filename  => ( $opt{'siteconfigpath'} || 0 ),
1161    pre_config_text      => join("\n", @{$opt{'pre'}})."\n",
1162    post_config_text     => join("\n", @{$opt{'cf'}})."\n",
1163    force_ipv4           => ( $opt{'force_ipv4'} || 0 ),
1164    local_tests_only     => ( $opt{'local'} || 0 ),
1165    debug                => ( $opt{'debug'} || 0 ),
1166    paranoid             => ( $opt{'paranoid'} || 0 ),
1167    require_rules        => 1,
1168    skip_prng_reseeding  => 1,  # let us do the reseeding by ourselves
1169    home_dir_for_helpers => (
1170      defined $opt{'home_dir_for_helpers'}
1171      ? $opt{'home_dir_for_helpers'}
1172      : $orighome
1173    ),
1174    PREFIX          => $PREFIX,
1175    DEF_RULES_DIR   => $DEF_RULES_DIR,
1176    LOCAL_RULES_DIR => $LOCAL_RULES_DIR,
1177    LOCAL_STATE_DIR => $LOCAL_STATE_DIR
1178  }
1179);
1180
1181#Enable Timing?
1182if ($opt{'timing'}) {
1183  $spamtest->timer_enable();
1184}
1185
1186# if $clients_per_child == 1, there's no point in copying configs around
1187unless ($clients_per_child > 1) {
1188  # unset $copy_config_p so we don't bother trying to copy things back
1189  # after closing the connection
1190  $copy_config_p = 0;
1191}
1192
1193# See Bug 6837: establishing a __DIE__ handler should be done after
1194# most modules have been loaded, as the $^S cannot distinguish
1195# true failures from eval attempt failures within a 'require'-d module.
1196# If the problem persists in some late-lodaded modules, we may need
1197# to tighten the condition to something like 'if defined $^S && !$^S'.
1198#
1199# redirect __WARN__ and __DIE__
1200# do not trap warnings here based on eval scope; evals are very
1201# common throughout.  die()s can be trapped though.
1202$SIG{__WARN__} = sub {
1203  log_message("warn", $_[0]);
1204};
1205$SIG{__DIE__} = sub {
1206  # see http://use.perl.org/comments.pl?mode=flat&sid=33872 for $^S
1207  log_message("error", $_[0]) unless $^S;
1208};
1209
1210## DAEMONIZE! ##
1211
1212my $originalparent = $$;
1213$opt{'daemonize'} and daemonize();
1214
1215# bug 3443: setup signal handlers before the kids since we may have to
1216# kill them...  make sure this happens before setting up the pidfile to
1217# avoid a race condition.
1218my $got_sighup;
1219setup_parent_sig_handlers();
1220
1221# should be done post-daemonize such that any files created by this
1222# process are written with the right ownership and everything.
1223preload_modules_with_tmp_homedir();
1224
1225# this must be after preload_modules_with_tmp_homedir(), for bug 5606
1226$spamtest->init_learner({
1227  opportunistic_expire_check_only => 1,
1228});
1229
1230# bayes DBs may still be tied() at this point, so untie them and such.
1231$spamtest->finish_learner();
1232
1233# If we're going to be switching users in check(), let's backup the
1234# fresh configuration now for later restoring ...  MUST be placed after
1235# the M::SA creation.
1236my %conf_backup;
1237my %msa_backup;
1238
1239if ($copy_config_p) {
1240  foreach( 'username', 'user_dir', 'userstate_dir', 'learn_to_journal' ) {
1241    $msa_backup{$_} = $spamtest->{$_} if (exists $spamtest->{$_});
1242  }
1243
1244  $spamtest->copy_config(undef, \%conf_backup) ||
1245    die "spamd: error returned from copy_config\n";
1246}
1247
1248# bonus: SIGUSR2 to dump a stack trace.  this is never reset
1249my $current_msgid = "(none)";
1250$SIG{USR2} = \&backtrace_handler  if !am_running_on_windows();
1251
1252# log server started, but processes watching the log to wait for connect
1253# should wait until they see the pid, after signal handlers are in place
1254# FIXME: two calls are one too much
1255info("spamd: server started on %s (running version %s)",
1256     compose_listen_info_string(), Mail::SpamAssassin::Version());
1257
1258my $remote_port;
1259
1260# Make the pidfile ...
1261if (defined $opt{'pidfile'}) {
1262  if (open PIDF, ">$opt{'pidfile'}") {
1263    print PIDF "$$\n";
1264    close PIDF;
1265  }
1266  else {
1267    warn "spamd: cannot write to PID file: $!\n";
1268  }
1269}
1270
1271# The "prefork_init" plugin callback is called in the parent process shortly
1272# before forking off child processes. It allows plugins which were activated
1273# by the master spamd process to prepare for a fork, e.g. by closing or
1274# dropping some resources which won't be of any use by a child process.
1275#
1276$spamtest->call_plugins("prefork_init");  # since SA 3.4.0
1277
1278# now allow waiting processes to connect, if they're watching the log.
1279# The test suite does this!
1280info("spamd: server pid: $$");
1281kill("USR1",$originalparent) if ($opt{'daemonize'});
1282
1283# Fork off our children.
1284for ( 1 .. $childlimit ) {
1285  spawn();
1286}
1287
1288if ($scaling) {
1289  $scaling->set_server_fh(map($_->{socket},@listen_sockets));
1290}
1291
1292while (1) {
1293  if (!$scaling) {
1294    # wait for a signal (ie: child's death)
1295    # bug 4190: use a time-limited sleep, and call child_handler() even
1296    # if haven't received a SIGCHLD, due to inherent race condition
1297    sleep 10;
1298  } else {
1299    $scaling->main_server_poll($opt{'server-scale-period'});
1300  }
1301  # bug 6377: on win32 the parent never receives SIGCHLD
1302# child_handler()  if !$scaling || am_running_on_windows();
1303  child_handler();  # it doesn't hurt to call child_handler unconditionally
1304
1305  child_cleaner();
1306
1307  do_sighup_restart()  if defined $got_sighup;
1308
1309  for (my $i = keys %children; $i < $childlimit; $i++) {
1310    spawn();
1311  }
1312}
1313
1314# Kicks off a kid ...
1315sub spawn {
1316  my $pid;
1317
1318  $backchannel->setup_backchannel_parent_pre_fork();
1319
1320  # block signal for fork
1321  my $sigset;
1322  if (!am_running_on_windows()) {
1323    $sigset = POSIX::SigSet->new( POSIX::SIGINT(), POSIX::SIGCHLD() );
1324    sigprocmask( POSIX::SIG_BLOCK(), $sigset )
1325      or die "spamd: cannot block SIGINT/SIGCHLD for fork: $!\n";
1326  }
1327
1328  $pid = fork();
1329  die "spamd: fork: $!" unless defined $pid;
1330
1331  if ($pid) {
1332    ## PARENT
1333
1334    $children{$pid} = 1;
1335    info("spamd: server successfully spawned child process, pid $pid");
1336    $backchannel->setup_backchannel_parent_post_fork($pid);
1337    if ($scaling) {
1338      $scaling->add_child($pid);
1339    }
1340    if (!am_running_on_windows()) {
1341      sigprocmask( POSIX::SIG_UNBLOCK(), $sigset )
1342        or die "spamd: cannot unblock SIGINT/SIGCHLD for fork: $!\n";
1343    }
1344    #Changing to return the process id to improve communications for bug 6304
1345    return $pid;
1346  }
1347  else {
1348    ## CHILD
1349
1350    # Reset signal handling to default settings, and unblock.
1351    # These lines must be as soon as possible after the fork (bug 4304)
1352    setup_child_sig_handlers();
1353    if (!am_running_on_windows()) {
1354      sigprocmask( POSIX::SIG_UNBLOCK(), $sigset )
1355        or die "spamd: cannot unblock SIGINT/SIGCHLD for fork: $!\n";
1356    }
1357
1358    srand;  # reseed pseudorandom number generator soon for each child process
1359    if ($sockets_access_lock_tempfile) {
1360      # A lock will be required across select+accept in a child processes,
1361      # Bug 6996. Need to have a per-child filehandle on the same lock file
1362      # for flock to work, let's dup(2) the parent's file handle.
1363      my $fname = $sockets_access_lock_tempfile->filename;
1364      $sockets_access_lock_fh = IO::File->new($fname, "+>");
1365      $sockets_access_lock_fh or die "Can't open a lock file $fname: $!";
1366    }
1367
1368    # support non-root use
1369    if ( $opt{'username'} ) {
1370      my ( $uuid, $ugid ) = ( getpwnam( $opt{'username'} ) )[ 2, 3 ];
1371      if ( !defined $uuid || $uuid == 0 ) {
1372        die "spamd: cannot run as nonexistent user or root with -u option\n";
1373      }
1374
1375      if ( $opt{'groupname'} ) {
1376        $ugid = getgrnam( $opt{'groupname'} ) || $ugid;
1377      }
1378
1379      # bug 5518: assignments to $) and $( don't always work on all platforms
1380      # bug 3900: assignments to $> and $< problems with BSD perl bug
1381      # use the POSIX functions to hide the platform specific workarounds
1382      dbg("spamd: Privilege de-escalation from user $< and groups $(\n");
1383      $! = 0; POSIX::setgid($ugid);  # set effective and real gid
1384      if ($!) { warn("spamd: POSIX::setgid $ugid failed: $!\n"); }
1385      $! = 0; $( = $ugid;
1386      if ($!) { warn("spamd: failed to set gid $ugid: $!\n"); }
1387      # set effective and real gid/grouplist another way because we lack initgroups in Perl
1388      my $togids = "$ugid ".get_user_groups($uuid);
1389      $! = 0; $) = $togids;
1390      if ($!) {
1391        # could be perl 5.30 bug #134169, let's be safe
1392        if (grep { $_ eq '0' } split(/ /, ${)})) {
1393          die("spamd: failed to set effective gid $togids: $!\n");
1394        } else {
1395          warn("spamd: failed to set effective gid $togids: $!\n");
1396        }
1397      }
1398      $! = 0; POSIX::setuid($uuid);  # set effective and real UID
1399      if ($!) { warn("spamd: POSIX::setuid $uuid failed: $!\n"); }
1400      $! = 0; $< = $uuid; $> = $uuid;   # bug 5574
1401      if ($!) { warn("spamd: setuid $uuid failed: $!\n"); }
1402      dbg("spamd: now running as: ruid=$< euid=$> rgid=$( egid=$)");
1403
1404      # keep the sanity check to catch problems like bug 3900 just in case
1405      if ( $> != $uuid and $> != ( $uuid - 2**32 ) ) {
1406        sleep(1); # prevent spamd fork flooding
1407        die "spamd: setuid to uid $uuid failed (ruid=$<, euid=$>), not started as root?\n";
1408      }
1409    }
1410
1411    # set process name where supported
1412    # this will help make it clear via process listing which is child/parent
1413    $0 = 'spamd child';
1414
1415    # Let's call spamd_child_init only after root privs are dropped
1416    # Mail::SpamAssassin::main() will also run this to set global_state_dir
1417    $spamtest->call_plugins("spamd_child_init");
1418
1419    $backchannel->setup_backchannel_child_post_fork();
1420    if ($scaling) {     # only do this once, for efficiency; $$ is a syscall
1421      $scaling->set_my_pid($$);
1422    }
1423
1424    # handle $clients_per_child connections, then die in "old" age...
1425    my $orders;
1426    for ( my $i = 0 ; $i < $clients_per_child ; $i++ ) {
1427      if ($scaling) {
1428        $scaling->update_child_status_idle();
1429        $orders = $scaling->wait_for_orders(); # and sleep...
1430
1431        if ($orders != PFORDER_ACCEPT) {
1432          info("spamd: unknown order: $orders");
1433        }
1434      }
1435
1436      # use a large eval scope to catch die()s and ensure they
1437      # don't kill the server.
1438      my $evalret = eval { accept_a_conn($scaling ? 0.5 : undef); };
1439
1440      if (!defined $evalret) {
1441        warn("spamd: error: $@ $!, continuing");
1442        if ($client) { $client->close(); }  # avoid fd leaks
1443      }
1444      elsif ($evalret == -1) {
1445        # serious error; used for accept() failure
1446        die("spamd: respawning server");
1447      }
1448
1449      $spamtest->call_plugins("spamd_child_post_connection_close");
1450
1451      # if we changed UID during processing, change back!
1452      if ($setuid_to_user && ($> != $<) && ($> != ($< - 2**32))) {
1453        $) = "$( $(";    # change eGID
1454        $> = $<;         # change eUID
1455
1456        # check again; ensure the change happened
1457        if ($> != $< && ($> != ( $< - 2**32))) {
1458          # make it fatal to avoid security breaches
1459          die("spamd: return setuid failed");
1460        }
1461      }
1462
1463      if ($copy_config_p) {
1464        # use a timeout!  There are bugs in Storable on certain platforms
1465        # that can cause spamd to hang -- see bug 3828 comment 154.
1466        # we don't use Storable any more, but leave this in -- just
1467        # in case.
1468	# bug 4699: this is the alarm that often ends up with an empty $@
1469
1470	my $timer = Mail::SpamAssassin::Timeout->new({ secs => 20 });
1471	my $err = $timer->run(sub {
1472
1473          while(my($k,$v) = each %msa_backup) {
1474            $spamtest->{$k} = $v;
1475          }
1476
1477          # if we changed user, we would have also loaded up new configs
1478          # (potentially), so let's restore back the saved version we
1479          # had before.
1480          $spamtest->copy_config(\%conf_backup, undef) ||
1481            die "spamd: error returned from copy_config\n";
1482        });
1483
1484	if ($timer->timed_out()) {
1485	  warn("spamd: copy_config timeout, respawning child process after ".
1486		($i+1)." messages");
1487	  exit;		# so that the master spamd can respawn
1488	}
1489      }
1490      undef $current_user;
1491
1492      #LOG TIMING
1493      if ($opt{'timing'}) {
1494        info("timing: " . $spamtest->timer_report());
1495      } else {
1496        dbg("timing: " . $spamtest->timer_report()) if would_log('dbg', 'timing');
1497      }
1498    }
1499
1500    # If the child lives to get here, it will die ...  Muhaha.
1501    exit;
1502  }
1503}
1504
1505sub accept_from_any_server_socket {
1506  my($timeout) = @_;
1507  my($client, $selected_socket_info, $socket, $locked);
1508
1509  eval {
1510    if (!@listen_sockets) {
1511      # nothing?
1512      die "no sockets?";
1513
1514    } elsif (@listen_sockets == 1) {
1515      $selected_socket_info = $listen_sockets[0];
1516
1517    } else {
1518      # determine which of our server FDs is ready using select().
1519      # We only need to do this if we have more than one server
1520      # socket supported, since otherwise there can only be one socket
1521      # with a client waiting.
1522      # (TODO: we could extend the prefork protocol to pass this data)
1523
1524      if ($sockets_access_lock_fh) {
1525        dbg("spamd: acquiring a lock over select+accept");
1526        # with multiple sockets a lock across select+accept is needed, Bug 6996
1527        flock($sockets_access_lock_fh, LOCK_EX)
1528          or die "Can't acquire lock access to sockets: $!";
1529        $locked = 1;
1530      }
1531
1532      my $sel_mask_str = unpack('b*', $server_select_mask);
1533      dbg("spamd: select() on fd bit field %s, %s, %s",
1534          $sel_mask_str, defined $timeout ? "timeout $timeout" : "no timeout",
1535          $locked ? "locked" : "not locked");
1536
1537      my $fdvec = $server_select_mask;
1538      my $nfound = select($fdvec, undef, undef, $timeout);
1539
1540      if (!defined $nfound || $nfound < 0) {
1541        die "select failed on fd bit field $sel_mask_str: $!";
1542      } elsif (!$nfound) {
1543        die "no fd ready, fd bit field $sel_mask_str";
1544      }
1545
1546      my(@ready_fd) =  # list of file descriptors ready for read
1547        grep(defined $_->{fd} && vec($fdvec, $_->{fd}, 1), @listen_sockets);
1548      if (!@ready_fd) {
1549        die "no file descriptors matching a bit field " . unpack('b*',$fdvec);
1550      } elsif (@ready_fd == 1) {  # easy, just one is ready
1551        $selected_socket_info = $ready_fd[0];
1552      } else {  # give equal opportunity to each ready socket
1553        my $j = int rand(@ready_fd);
1554        $selected_socket_info = $ready_fd[$j];
1555        dbg("spamd: requests ready on multiple sockets, picking #%d out of %d",
1556            $j+1, scalar @ready_fd);
1557      }
1558
1559    } # end multiple sockets case
1560
1561    if ($selected_socket_info) {
1562      my $socket = $selected_socket_info->{socket};
1563      $socket or die "no socket???, impossible";
1564      dbg("spamd: accept() on fd %d", $selected_socket_info->{fd});
1565      $client = $socket->accept;
1566    }
1567    1;  # end eval with success
1568
1569  } or do {
1570    my $err = $@ ne '' ? $@ : "errno=$!";  chomp $err;
1571    if ($locked) {
1572      dbg("spamd: releasing a lock over select+accept");
1573      flock($sockets_access_lock_fh, LOCK_UN)
1574        or die "Can't release sockets-access lock: $!";
1575      $locked = 0;
1576    }
1577    die "accept_a_conn: $err";
1578  };
1579
1580  if ($locked) {
1581    dbg("spamd: releasing a lock over select+accept");
1582    flock($sockets_access_lock_fh, LOCK_UN)
1583      or die "Can't release sockets-access lock: $!";
1584  }
1585  if(!defined $client) {
1586    if(defined($socket)) {
1587      die sprintf("accept_a_conn: %s accept failed: %s",
1588                         ref $socket,
1589                         !$socket->isa('IO::Socket::SSL') ? $!
1590                           : $socket->errstr.", $!");
1591    } else {
1592      die sprintf("accept_a_conn: no socket available");
1593    }
1594  }
1595  return ($client, $selected_socket_info);
1596}
1597
1598sub accept_a_conn {
1599  my ($timeout) = @_;
1600
1601  my $socket_info;
1602  # $client is a global variable
1603  ($client, $socket_info) = accept_from_any_server_socket($timeout);
1604
1605  if ($scaling) {
1606    $scaling->update_child_status_busy();
1607  }
1608
1609  # Bah!
1610  if ( !$client  || !defined $client->connected() ) {
1611
1612    # this can happen when interrupted by SIGCHLD on Solaris,
1613    # perl 5.8.0, and some other platforms with -m.
1614    if ( $! == &Errno::EINTR ) {
1615      return 0;
1616    }
1617    else {
1618      warn("spamd: accept failed: $!");
1619      return -1;
1620    }
1621  }
1622
1623  $client->autoflush(1);
1624
1625  # keep track of start time
1626  $spamtest->timer_reset;
1627  my $start = time;
1628
1629  my ($remote_hostname, $remote_hostaddr, $local_port);
1630
1631  if ($client->isa('IO::Socket::UNIX')) {
1632    $remote_hostname = 'localhost';
1633    $remote_hostaddr = '127.0.0.1';
1634    $remote_port = $socket_info->{path};
1635    info("spamd: got connection over %s", $socket_info->{path});
1636  }
1637  else {
1638    ($remote_port, $remote_hostaddr, $remote_hostname, $local_port) =
1639      peer_info_from_socket($client);
1640    $remote_hostaddr or die 'failed to obtain port and ip from socket';
1641
1642    my $msg = sprintf("connection from %s [%s]:%s to port %d, fd %d",
1643                      $remote_hostname, $remote_hostaddr, $remote_port,
1644                      $local_port, $socket_info->{fd});
1645    if (ip_is_allowed($remote_hostaddr)) {
1646      info("spamd: $msg");
1647    }
1648    else {
1649      warn("spamd: unauthorized $msg");
1650      $client->close;
1651      return 0;
1652    }
1653  }
1654
1655  local ($_);
1656  eval {
1657    Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
1658                          die "tcp timeout";
1659                        });
1660    alarm $timeout_tcp if ($timeout_tcp);
1661    # send the request to the child process
1662    $_ = $client->getline;
1663  };
1664  alarm 0;
1665
1666  if ($@) {
1667    if ($@ =~ /tcp timeout/) {
1668      service_timeout("($timeout_tcp second socket timeout reading input from client)");
1669    } else {
1670      warn "spamd: $@";
1671    }
1672    $client->close;
1673    return 0;
1674  }
1675
1676  if ( !defined $_ ) {
1677    protocol_error("(closed before headers)");
1678    $client->close;
1679    return 0;
1680  }
1681
1682  s/\r?\n//;
1683
1684  # It might be a CHECK message, meaning that we should just check
1685  # if it's spam or not, then return the appropriate response.
1686  # If we get the PROCESS command, the client is going to send a
1687  # message that we need to filter.
1688
1689  if (/(PROCESS|CHECK|SYMBOLS|REPORT|HEADERS|REPORT_IFSPAM) SPAMC\/(.*)/) {
1690    my $method = $1;
1691    my $version = $2;
1692    eval {
1693      Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
1694                          die "child processing timeout";
1695                        });
1696      alarm $timeout_child if ($timeout_child);
1697      check($method, $version, $start, $remote_hostname, $remote_hostaddr);
1698    };
1699    alarm 0;
1700
1701    if ($@) {
1702      if ($@ =~ /child processing timeout/) {
1703        service_timeout("($timeout_child second timeout while trying to $method)");
1704      } else {
1705	warn "spamd: $@";
1706      }
1707      $client->close();
1708      return 0;
1709    }
1710  }
1711
1712  elsif (/(TELL) SPAMC\/(.*)/) {
1713    my $method = $1;
1714    my $version = $2;
1715    eval {
1716      Mail::SpamAssassin::Util::trap_sigalrm_fully(sub {
1717						     die "child processing timeout";
1718						   });
1719      alarm $timeout_child if ($timeout_child);
1720      dotell($method, $version, $start, $remote_hostname, $remote_hostaddr);
1721    };
1722    alarm 0;
1723
1724    if ($@) {
1725      if ($@ =~ /child processing timeout/) {
1726        service_timeout("($timeout_child second timeout while trying to $method)");
1727      } else {
1728	warn "spamd: $@";
1729      }
1730      $client->close();
1731      return 0;
1732    }
1733  }
1734
1735  # Looks like a client is just seeing if we're alive or changed its mind
1736
1737  elsif (/(SKIP|PING) SPAMC\/(.*)/) {
1738    my $method = $1;
1739    my $version = $2;
1740
1741    if ($method eq 'SKIP') {
1742      # It may be a SKIP message, meaning that the client (spamc)
1743      # thinks it is too big to check.  So we don't do any real work
1744      # in that case.
1745      info("spamd: skipped large message in %3.1f seconds", time - $start);
1746    }
1747    doskip_or_ping($method, $version,
1748                   $start, $remote_hostname, $remote_hostaddr);
1749  }
1750
1751  # If it was none of the above, then we don't know what it was.
1752
1753  else {
1754    protocol_error($_);
1755  }
1756
1757  # Close out our connection to the client ...
1758  $client->close();
1759  return 1;
1760}
1761
1762sub handle_setuid_to_user {
1763  if ($spamtest->{paranoid}) {
1764    die("spamd: in paranoid mode, still running as root: closing connection");
1765  }
1766  if (!am_running_on_windows()) {
1767    warn("spamd: still running as root: user not specified with -u, "
1768         . "not found, or set to root, falling back to nobody\n");
1769
1770    my ($name, $pwd, $uid, $gid, $quota, $comment, $gcos, $dir, $etc) =
1771        getpwnam('nobody');
1772
1773    $) = (get_user_groups($uid));       # eGID
1774    $> = $uid;                          # eUID
1775    if (!defined($uid) || ($> != $uid and $> != ($uid - 2**32))) {
1776      die("spamd: setuid to nobody failed");
1777  }
1778
1779  $spamtest->signal_user_changed(
1780    {
1781      username => $name,
1782      user_dir => $dir
1783    }
1784  );
1785  }
1786}
1787
1788sub parse_body {
1789  my ($client, $expected_length, $compress_zlib, $start_time) = @_;
1790
1791  my @msglines;
1792  my $actual_length;
1793
1794  if ($compress_zlib && !defined($expected_length)) {
1795    service_unavailable_error("Compress requires Content-length header");
1796    return;
1797  }
1798
1799  if ($compress_zlib) {
1800    $actual_length = zlib_inflate_read($client, $expected_length, \@msglines);
1801    if ($actual_length < 0) { return; }
1802    $expected_length = $actual_length;
1803  }
1804  else {
1805    @msglines = ();
1806    $actual_length = 0;
1807    while (defined($_ = $client->getline())) {
1808      $actual_length += length($_);
1809      push(@msglines, $_);
1810      last if (defined $expected_length && $actual_length >= $expected_length);
1811    }
1812  }
1813
1814  # Now parse *only* the message headers; the MIME tree won't be generated
1815  # yet, it will be done on demand later on.
1816  my $mail = $spamtest->parse(\@msglines, 0,
1817                       !$timeout_child || !$start_time ? ()
1818                       : { master_deadline => $start_time + $timeout_child } );
1819
1820  return ($mail, $actual_length);
1821}
1822
1823sub zlib_inflate_read {
1824  my ($client, $expected_length, $msglinesref) = @_;
1825  my $out;
1826  my $actual_length;
1827
1828  eval {
1829    require Compress::Zlib;
1830    my ($zlib, $status) = Compress::Zlib::inflateInit();
1831    if (!$zlib) { die "inflateInit failed: $status"; }
1832
1833    my $red = 0;
1834    my $buf;
1835
1836    # TODO: inflate in smaller buffers instead of at EOF
1837    while (1) {
1838      my $numbytes = $client->read($buf, (1024 * 64) + $red, $red);
1839      if (!defined $numbytes) {
1840        die "read of zlib data failed: $!";
1841        return -1;
1842      }
1843      last if $numbytes == 0;
1844      $red += $numbytes;
1845    }
1846
1847    if ($red > $expected_length) {
1848      warn "hmm, zlib read $red > expected_length $expected_length";
1849      substr ($buf, $expected_length) = '';
1850    }
1851
1852    ($out, $status) = $zlib->inflate($buf);
1853    if ($status != Compress::Zlib::Z_STREAM_END()) {
1854      die "failed to find end of zlib stream";
1855    }
1856  };
1857
1858  if ($@) {
1859    service_unavailable_error("zlib: $@");
1860    return -1;
1861  }
1862
1863  $actual_length = length($out);
1864
1865  # TODO: split during inflate, too
1866  # note that this preserves line endings
1867  @{$msglinesref} = map { my $s=$_; $s=~s/$/\n/gs; $s } split(/\n/, $out);
1868  return $actual_length;
1869}
1870
1871sub parse_msgids {
1872  my ($mail) = @_;
1873
1874  # Extract the Message-Id(s) for logging purposes.
1875  my $msgid  = $mail->get_pristine_header("Message-Id");
1876  my $rmsgid = $mail->get_pristine_header("Resent-Message-Id");
1877  foreach my $id ((\$msgid, \$rmsgid)) {
1878    if ( $$id ) {
1879      # no re "strict";  # since perl 5.21.8: Ranges of ASCII printables...
1880      while ( $$id =~ s/\([^\(\)]*\)// )
1881         { }                           # remove comments and
1882      $$id =~ s/^\s+|\s+$//g;          # leading and trailing spaces
1883      $$id =~ s/\s+/ /g;               # collapse whitespaces
1884      $$id =~ s/^.*?<(.*?)>.*$/$1/;    # keep only the id itself
1885      $$id =~ s/[^\x21-\x7e]/?/g;      # replace all weird chars
1886      $$id =~ s/[<>]/?/g;              # plus all dangling angle brackets
1887      $$id =~ s/^(.+)$/<$1>/;          # re-bracket the id (if not empty)
1888    }
1889  }
1890  return ($msgid, $rmsgid);
1891}
1892
1893sub check {
1894  my ( $method, $version, $start_time, $remote_hostname, $remote_hostaddr ) = @_;
1895  local ($_);
1896  my $expected_length;
1897  my $compress_zlib;
1898
1899  # used to ensure we don't accidentally fork (bug 4370)
1900  my $starting_self_pid = $$;
1901
1902  # Protocol version 1.0 and greater may have "User:" and
1903  # "Content-length:" headers.  But they're not required.
1904
1905  if ( $version > 1.0 ) {
1906    my $hdrs = {};
1907
1908    return 0 unless (parse_headers($hdrs, $client));
1909
1910    $expected_length = $hdrs->{expected_length};
1911    $compress_zlib = $hdrs->{compress_zlib};
1912  }
1913
1914  return 0 unless do_user_handling();
1915  if ($> == 0 && !am_running_on_windows()) {
1916	die "spamd: still running as root! dying";
1917  }
1918
1919  my $resp = "EX_OK";
1920
1921  # generate mail object from input
1922  my ($mail, $actual_length) = parse_body($client, $expected_length,
1923                                          $compress_zlib, $start_time);
1924  return 0 unless defined($mail);       # error
1925
1926  if ($compress_zlib) {
1927    $expected_length = $actual_length;  # previously it was the gzipped length
1928  }
1929
1930  # attempt to fetch the message ids
1931  my ($msgid, $rmsgid) = parse_msgids($mail);
1932
1933  $msgid        ||= "(unknown)";
1934  $current_user ||= "(unknown)";
1935  $current_msgid = $msgid;      # for the SIGUSR2 backtrace
1936  info("spamd: " . ($method eq 'PROCESS' ? "processing" : "checking")
1937       . " message $msgid"
1938       . ( $rmsgid ? " aka $rmsgid" : "" )
1939       . " for ${current_user}:$>");
1940
1941  # Check length if we're supposed to.
1942  if (defined $expected_length && $actual_length != $expected_length) {
1943    protocol_error(
1944      "(Content-Length mismatch: Expected $expected_length bytes, got $actual_length bytes)"
1945    );
1946    $mail->finish();
1947    return 0;
1948  }
1949
1950  # Go ahead and check the message
1951  $spamtest->init(1);
1952  my $status = Mail::SpamAssassin::PerMsgStatus->new($spamtest, $mail);
1953  $status->check();
1954
1955  my $msg_score     =  &Mail::SpamAssassin::Util::get_tag_value_for_score($status->get_score, $status->get_required_score, $status->is_spam);
1956  my $msg_threshold = sprintf( "%2.1f", $status->get_required_score );
1957
1958  my $response_spam_status = "";
1959  my $was_it_spam;
1960  if ( $status->is_spam ) {
1961    $response_spam_status = $method eq "REPORT_IFSPAM" ? "Yes" : "True";
1962    $was_it_spam = 'identified spam';
1963  }
1964  else {
1965    $response_spam_status = $method eq "REPORT_IFSPAM" ? "No" : "False";
1966    $was_it_spam = 'clean message';
1967  }
1968
1969  my $spamhdr = "Spam: $response_spam_status ; $msg_score / $msg_threshold";
1970
1971  if ( $method eq 'PROCESS' || $method eq 'HEADERS' ) {
1972
1973    $status->set_tag('REMOTEHOSTNAME', $remote_hostname);
1974    $status->set_tag('REMOTEHOSTADDR', $remote_hostaddr);
1975
1976    # Build the message to send back and measure it
1977    my $msg_resp        = $status->rewrite_mail();
1978
1979    if ($method eq 'HEADERS') {
1980      # just the headers; delete everything after first \015\012\015\012
1981      $msg_resp =~ s/(\015?\012\015?\012).*$/$1/gs;
1982    }
1983
1984    my $msg_resp_length = length($msg_resp);
1985
1986    if ( $version >= 1.3 )    # Spamc protocol 1.3 means multi hdrs are OK
1987    {
1988      syswrite_full_buffer( $client, "SPAMD/1.1 $resphash{$resp} $resp\r\n" .
1989        "Content-length: $msg_resp_length\r\n" . $spamhdr . "\r\n\r\n" .
1990        $msg_resp );
1991    }
1992    elsif (
1993      $version >= 1.2 )    # Spamc protocol 1.2 means it accepts content-length
1994    {
1995      syswrite_full_buffer( $client, "SPAMD/1.1 $resphash{$resp} $resp\r\n" .
1996        "Content-length: $msg_resp_length\r\n\r\n" . $msg_resp );
1997    }
1998    else                   # Earlier than 1.2 didn't accept content-length
1999    {
2000      syswrite_full_buffer( $client, "SPAMD/1.0 $resphash{$resp} $resp\r\n" . $msg_resp );
2001    }
2002  }
2003  else                     # $method eq 'CHECK' et al
2004  {
2005    syswrite_full_buffer( $client, "SPAMD/1.1 $resphash{$resp} $resp\r\n" );
2006
2007    if ( $method eq "CHECK" ) {
2008      syswrite( $client, "$spamhdr\r\n\r\n" );
2009    }
2010    else {
2011      my $msg_resp = '';
2012
2013      if ( $method eq "REPORT"
2014        or ( $method eq "REPORT_IFSPAM" and $status->is_spam ) )
2015      {
2016        $msg_resp = $status->get_report;
2017      }
2018      elsif ( $method eq "REPORT_IFSPAM" ) {
2019
2020        # message is ham, $msg_resp remains empty
2021      }
2022      elsif ( $method eq "SYMBOLS" ) {
2023        $msg_resp = $status->get_names_of_tests_hit;
2024        $msg_resp .= "\r\n" if ( $version < 1.3 );
2025      }
2026      else {
2027        die "spamd: unknown method $method";
2028      }
2029
2030      if ( $version >= 1.3 )    # Spamc protocol > 1.2 means multi hdrs are OK
2031      {
2032        my $msg_resp_length = length($msg_resp);
2033        syswrite_full_buffer( $client,
2034                  "Content-length: $msg_resp_length\r\n" .
2035                  $spamhdr . "\r\n\r\n" . $msg_resp );
2036      }
2037      else {
2038        syswrite_full_buffer( $client, $spamhdr . "\r\n\r\n" . $msg_resp );
2039      }
2040    }
2041  }
2042
2043  my $scantime = sprintf( "%.1f", time - $start_time );
2044
2045  info("spamd: $was_it_spam ($msg_score/$msg_threshold) for $current_user:$> in"
2046       . " $scantime seconds, $actual_length bytes." );
2047
2048  # add a summary "result:" line, based on mass-check format
2049  my @extra;
2050  push(@extra, "scantime=".$scantime, "size=$actual_length",
2051            "user=".$current_user, "uid=".$>,
2052            "required_score=".$msg_threshold,
2053            "rhost=".$remote_hostname, "raddr=".$remote_hostaddr,
2054            "rport=".$remote_port);
2055
2056  {
2057    # no re "strict";  # since perl 5.21.8: Ranges of ASCII printables...
2058    my $safe = $msgid; $safe =~ s/[\x00-\x20\s,]/_/gs; push(@extra, "mid=$safe");
2059  }
2060  if ($rmsgid) {
2061    # no re "strict";  # since perl 5.21.8: Ranges of ASCII printables...
2062    my $safe = $rmsgid; $safe =~ s/[\x00-\x20\s,]/_/gs; push(@extra, "rmid=$safe");
2063  }
2064  if (defined $status->{bayes_score}) {
2065    push(@extra, "bayes=".sprintf("%06f", $status->{bayes_score}));
2066  }
2067  push(@extra, "autolearn=".$status->get_autolearn_status());
2068  push(@extra, $status->get_spamd_result_log_items());
2069
2070  my $yorn = $status->is_spam() ? 'Y' : '.';
2071  my $score = $status->get_score();
2072  my $tests = join(",", sort(grep(length,$status->get_names_of_tests_hit())));
2073
2074  my $log = sprintf("spamd: result: %s %2d - %s %s", $yorn, $score,
2075	       $tests, join(",", @extra));
2076  info($log);
2077
2078  # bug 3808: log scan results to any listening plugins, too
2079  $spamtest->call_plugins("log_scan_result", { result => $log });
2080
2081  # bug 3466: handle the bayes expiry bits after the results were returned to
2082  # the client.  keeps clients from timing out.  if bayes_expiry_due is set,
2083  # then the opportunistic check has already checked.  go ahead and do another
2084  # sync/expire run.
2085  if ($status->{'bayes_expiry_due'}) {
2086    dbg("spamd: bayes expiry was marked as due, running post-check");
2087    $spamtest->rebuild_learner_caches();
2088    $spamtest->finish_learner();
2089  }
2090
2091  $status->finish();    # added by jm to allow GC'ing
2092  $mail->finish();
2093
2094  # ensure we didn't accidentally fork (bug 4370)
2095  if ($starting_self_pid != $$) {
2096    eval { warn("spamd: accidental fork: $$ != $starting_self_pid"); };
2097    POSIX::_exit(1);        # avoid END and dtor processing
2098  }
2099
2100  return 1;
2101}
2102
2103sub dotell {
2104  my ($method, $version, $start_time, $remote_hostname, $remote_hostaddr) = @_;
2105  local ($_);
2106
2107  my $hdrs = {};
2108
2109  return 0 unless (parse_headers($hdrs, $client));
2110
2111  my $expected_length = $hdrs->{expected_length};
2112  my $compress_zlib = $hdrs->{compress_zlib};
2113
2114  return 0 unless do_user_handling();
2115  if ($> == 0 && !am_running_on_windows()) {
2116	die "spamd: still running as root! dying";
2117  }
2118
2119  if (!$opt{tell}) {
2120    service_unavailable_error("TELL commands are not enabled, set the --allow-tell switch.");
2121    return 0;
2122  }
2123
2124  if ($hdrs->{set_local} && $hdrs->{remove_local}) {
2125    protocol_error("Unable to set local and remove local in the same operation.");
2126    return 0;
2127  }
2128
2129  if ($hdrs->{set_remote} && $hdrs->{remove_remote}) {
2130    protocol_error("Unable to set remote and remove remote in the same operation.");
2131    return 0;
2132  }
2133
2134  if ($opt{'sql-config'} && !defined($current_user)) {
2135    unless (handle_user_sql('nobody')) {
2136      service_unavailable_error("Error fetching user preferences via SQL");
2137      return 0;
2138    }
2139  }
2140
2141  if ($opt{'ldap-config'} && !defined($current_user)) {
2142    handle_user_ldap('nobody');
2143  }
2144
2145  my $resp = "EX_OK";
2146
2147  # generate mail object from input
2148  my($mail, $actual_length) =
2149    parse_body($client, $expected_length, $compress_zlib, $start_time);
2150
2151  return 0 unless defined($mail);       # error
2152
2153  if ($compress_zlib) {
2154    $expected_length = $actual_length;  # previously it was the gzipped length
2155  }
2156
2157  if ( $mail->get_header("X-Spam-Checker-Version") ) {
2158    my $new_mail = $spamtest->parse($spamtest->remove_spamassassin_markup($mail), 1);
2159    $mail->finish();
2160    $mail = $new_mail;
2161  }
2162
2163  # attempt to fetch the message ids
2164  my ($msgid, $rmsgid) = parse_msgids($mail);
2165
2166  $msgid        ||= "(unknown)";
2167  $current_user ||= "(unknown)";
2168
2169  # Check length if we're supposed to.
2170  if (defined $expected_length && $actual_length != $expected_length) {
2171    protocol_error("(Content-Length mismatch: Expected $expected_length bytes, got $actual_length bytes)");
2172    $mail->finish();
2173    return 0;
2174  }
2175
2176  my @did_set;
2177  my @did_remove;
2178
2179  if ($hdrs->{set_local}) {
2180    my $status = $spamtest->learn($mail, undef, ($hdrs->{message_class} eq 'spam' ? 1 : 0), 0);
2181
2182    push(@did_set, 'local') if ($status->did_learn());
2183    $status->finish();
2184  }
2185
2186  if ($hdrs->{remove_local}) {
2187    my $status = $spamtest->learn($mail, undef, undef, 1);
2188
2189    push(@did_remove, 'local') if ($status->did_learn());
2190    $status->finish();
2191  }
2192
2193  if ($hdrs->{set_remote}) {
2194    require Mail::SpamAssassin::Reporter;
2195    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
2196
2197    push(@did_set, 'remote') if ($msgrpt->report());
2198  }
2199
2200  if ($hdrs->{remove_remote}) {
2201    require Mail::SpamAssassin::Reporter;
2202    my $msgrpt = Mail::SpamAssassin::Reporter->new($spamtest, $mail);
2203
2204    push(@did_remove, 'remote') if ($msgrpt->revoke());
2205  }
2206
2207  my $hdr = "";
2208  my $info_str;
2209
2210  if (scalar(@did_set)) {
2211    $hdr .= "DidSet: " . join(',', @did_set) . "\r\n";
2212    $info_str .= " Setting " . join(',', @did_set) . " ";
2213  }
2214
2215  if (scalar(@did_remove)) {
2216    $hdr .= "DidRemove: " . join(',', @did_remove) . "\r\n";
2217    $info_str .= " Removing " . join(',', @did_remove) . " ";
2218  }
2219
2220  if (!$info_str) {
2221    $info_str = " Did nothing ";
2222  }
2223
2224  print $client "SPAMD/1.1 $resphash{$resp} $resp\r\n",
2225    $hdr . "\r\n\r\n";
2226
2227  my $scantime = sprintf( "%.1f", time - $start_time );
2228
2229  info("spamd: Tell:${info_str}for $current_user:$> in"
2230       . " $scantime seconds, $actual_length bytes");
2231
2232  $mail->finish();
2233  return 1;
2234}
2235
2236sub doskip_or_ping {
2237  my ($method, $version, $start_time, $remote_hostname, $remote_hostaddr) = @_;
2238
2239  if ( $version >= 1.5 ) {
2240    # Spamc protocol 1.5 means client is expected to send a protocol header
2241    # (usually just a null header), followed by an empty line
2242    # Fixes Bug 6187.
2243
2244    my $hdrs = {};
2245    return 0 unless (parse_headers($hdrs, $client));
2246  }
2247
2248  if ($method eq 'PING') {
2249    print $client "SPAMD/1.5 $resphash{EX_OK} PONG\r\n";
2250  }
2251
2252  return 1;
2253}
2254
2255###########################################################################
2256
2257sub do_user_handling {
2258  if ($setuid_to_user && $> == 0) {
2259    handle_setuid_to_user();
2260  }
2261
2262  if ( $opt{'sql-config'} && !defined($current_user) ) {
2263    unless ( handle_user_sql('nobody') ) {
2264      service_unavailable_error("Error fetching user preferences via SQL");
2265      return 0;
2266    }
2267  }
2268
2269  if ( $opt{'ldap-config'} && !defined($current_user) ) {
2270    handle_user_ldap('nobody');
2271  }
2272
2273  dbg ("spamd: running as uid $>");
2274  return 1;
2275}
2276
2277# generalised header parser.
2278sub parse_headers {
2279  my ($hdrs, $client) = @_;
2280
2281  my $got_user_header;
2282
2283  # max 255 headers
2284  for my $hcount ( 0 .. 255 ) {
2285    my $line = $client->getline;
2286
2287    unless (defined $line) {
2288      protocol_error("(EOF during headers)");
2289      return 0;
2290    }
2291    $line =~ s/\r\n$//;
2292
2293    if (!length $line) {    # end of headers
2294      if (!$got_user_header && $opt{'auth-ident'}) {
2295        service_unavailable_error('User header required');
2296        return 0;
2297      }
2298      return 1;
2299    }
2300
2301    my ($header, $value) = split (/:\s*/, $line, 2);
2302    unless (defined $value) {
2303      protocol_error("(header not in 'Name: value' format)");
2304      return 0;
2305    }
2306
2307    if ($header eq 'Content-length') {
2308      return 0 unless got_clen_header($hdrs, $header, $value);
2309    }
2310    elsif ($header eq 'User') {
2311      return 0 unless got_user_header($hdrs, $header, $value);
2312      $got_user_header++;
2313    }
2314    elsif ($header eq 'Message-class') {
2315      return 0 unless got_message_class_header($hdrs, $header, $value);
2316    }
2317    elsif ($header eq 'Set') {
2318      return 0 unless got_set_header($hdrs, $header, $value);
2319    }
2320    elsif ($header eq 'Remove') {
2321      return 0 unless got_remove_header($hdrs, $header, $value);
2322    }
2323    elsif ($header eq 'Compress') {
2324      return 0 unless &got_compress_header($hdrs, $header, $value);
2325    }
2326  }
2327
2328  # avoid too-many-headers DOS attack
2329  protocol_error("(too many headers)");
2330  return 0;
2331}
2332
2333# We'll run handle user unless we've been told not
2334# to process per-user config files.  Otherwise
2335# we'll check and see if we need to try SQL
2336# lookups.  If $opt{'user-config'} is true, we need to try
2337# their config file and then do the SQL lookup.
2338# If $opt{'user-config'} IS NOT true, we skip the conf file and
2339# only need to do the SQL lookup if $opt{'sql-config'} IS
2340# true.  (I got that wrong the first time.)
2341#
2342sub got_user_header {
2343  my ( $client, $header, $value ) = @_;
2344
2345  { # no re "strict";  # since perl 5.21.8: Ranges of ASCII printables...
2346    local $1;
2347    if ( $value !~ /^([\x20-\xFF]*)$/ ) {
2348      protocol_error("(User header contains control chars)");
2349      return 0;
2350    }
2351    $current_user = $1;
2352  }
2353
2354  if ($opt{'auth-ident'} && !auth_ident($current_user)) {
2355    return 0;
2356  }
2357
2358  if ( !$opt{'user-config'} ) {
2359    if ( $opt{'sql-config'} ) {
2360      unless ( handle_user_sql($current_user) ) {
2361        service_unavailable_error("Error fetching user preferences via SQL");
2362	return 0;
2363      }
2364    }
2365    elsif ( $opt{'ldap-config'} ) {
2366      handle_user_ldap($current_user);
2367    }
2368    elsif ( $opt{'virtual-config-dir'} ) {
2369      handle_virtual_config_dir($current_user);
2370    }
2371    elsif ( $opt{'setuid-with-sql'} ) {
2372      unless ( handle_user_setuid_with_sql($current_user) ) {
2373        service_unavailable_error("Error fetching user preferences via SQL");
2374	return 0;
2375      }
2376      $setuid_to_user = 1;    #to benefit from any paranoia.
2377    }
2378    elsif ( $opt{'setuid-with-ldap'} ) {
2379      handle_user_setuid_with_ldap($current_user);
2380      $setuid_to_user = 1;    # as above
2381    }
2382    else {
2383      handle_user_setuid_basic($current_user);
2384    }
2385  }
2386  else {
2387    handle_user_setuid_basic($current_user);
2388    if ( $opt{'sql-config'} ) {
2389      unless ( handle_user_sql($current_user) ) {
2390        service_unavailable_error("Error fetching user preferences via SQL");
2391	return 0;
2392      }
2393    }
2394  }
2395  return 1;
2396}
2397
2398sub got_clen_header {
2399  my ( $hdrs, $header, $value ) = @_;
2400  if ( $value !~ /^(\d*)$/ ) {
2401    protocol_error("(Content-Length contains non-numeric bytes)");
2402    return 0;
2403  }
2404  $hdrs->{expected_length} = $1;
2405  return 1;
2406}
2407
2408sub got_message_class_header {
2409  my ($hdrs, $header, $value) = @_;
2410
2411  unless (lc($value) ne 'spam' || lc($value) ne 'ham') {
2412    protocol_error("(Message-class header contains invalid class)");
2413    return 0;
2414  }
2415  $hdrs->{message_class} = $value;
2416
2417  return 1;
2418}
2419
2420sub got_set_header {
2421  my ($hdrs, $header, $value) = @_;
2422
2423  $hdrs->{set_local} = 0;
2424  $hdrs->{set_remote} = 0;
2425
2426  if ($value =~ /local/i) {
2427    $hdrs->{set_local} = 1;
2428  }
2429
2430  if ($value =~ /remote/i) {
2431    $hdrs->{set_remote} = 1;
2432  }
2433
2434  return 1;
2435}
2436
2437sub got_remove_header {
2438  my ($hdrs, $header, $value) = @_;
2439
2440  $hdrs->{remove_local} = 0;
2441  $hdrs->{remove_remote} = 0;
2442
2443  if ($value =~ /local/i) {
2444    $hdrs->{remove_local} = 1;
2445  }
2446
2447  if ($value =~ /remote/i) {
2448    $hdrs->{remove_remote} = 1;
2449  }
2450
2451  return 1;
2452}
2453
2454sub got_compress_header {
2455  my ($hdrs, $header, $value) = @_;
2456
2457  if ($value =~ /zlib/i) {
2458    eval { require Compress::Zlib; };
2459    if ($@) {
2460      protocol_error("(compression not supported, Compress::Zlib not installed: $@)");
2461      return 0;
2462    }
2463    $hdrs->{compress_zlib} = 1;
2464    dbg("spamd: compress header received\n");
2465  }
2466  else {
2467    protocol_error("(compression type not supported)");
2468    return 0;
2469  }
2470
2471  return 1;
2472}
2473
2474sub protocol_error {
2475  my ($err) = @_;
2476  my $resp = "EX_PROTOCOL";
2477  syswrite($client, "SPAMD/1.0 $resphash{$resp} Bad header line: $err\r\n");
2478  warn("spamd: bad protocol: header error: $err\n");
2479}
2480
2481sub service_unavailable_error {
2482  my ($err) = @_;
2483  my $resp = "EX_UNAVAILABLE";
2484  syswrite($client,
2485	   "SPAMD/1.0 $resphash{$resp} Service Unavailable: $err\r\n");
2486  warn("spamd: service unavailable: $err\n");
2487}
2488
2489sub service_timeout {
2490  my ($err) = @_;
2491  my $resp = "EX_TIMEOUT";
2492  print $client "SPAMD/1.0 $resphash{$resp} Timeout: $err\r\n";
2493  warn("spamd: timeout: $err\n");
2494}
2495
2496###########################################################################
2497
2498sub auth_ident {
2499  my $username = shift;
2500  my $ident_username = ident_lookup( $client, $opt{'ident-timeout'} );
2501  my $dn = $ident_username || 'NONE';    # display name
2502  dbg("ident: ident_username = $dn, spamc_username = $username\n");
2503  if ( !defined($ident_username) || $username ne $ident_username ) {
2504    info("spamd: ident username ($dn) does not match "
2505	 . "spamc username ($username)" );
2506    return 0;
2507  }
2508  return 1;
2509}
2510
2511sub handle_user_setuid_basic {
2512  my $username = shift;
2513
2514  # If $opt{'username'} in use, then look up userinfo for that uid;
2515  # otherwise use what was passed via $username
2516  #
2517  my $suidto = $username;
2518  if ( $opt{'username'} ) {
2519    $suidto = $opt{'username'};
2520  }
2521  my ($name, $pwd, $uid, $gid, $quota, $comment, $gcos, $suiddir, $etc) =
2522      am_running_on_windows() ? ('nobody') : getpwnam($suidto);
2523
2524  if (!defined $uid) {
2525      my $errmsg =
2526        "spamd: handle_user (getpwnam) unable to find user: '$suidto'";
2527      die "$errmsg\n" if $spamtest->{'paranoid'};
2528      # if we are given a username, but can't look it up, maybe name
2529      # services are down?  let's break out here to allow them to get
2530      # 'defaults' when we are not running paranoid
2531      info($errmsg);
2532      return 0;
2533  }
2534
2535  if ($setuid_to_user) {
2536    $) = (get_user_groups($uid));     # change eGID
2537    $> = $uid;                        # change eUID
2538    if ( !defined($uid) || ( $> != $uid and $> != ( $uid - 2**32 ) ) ) {
2539      # make it fatal to avoid security breaches
2540      die("spamd: fatal error: setuid to $suidto failed");
2541    }
2542    else {
2543      info("spamd: setuid to $suidto succeeded");
2544    }
2545  }
2546
2547  my $userdir;
2548
2549  # if $opt{'user-config'} is in use, read user prefs from the remote
2550  # username's home dir (if it exists): bug 5611
2551  if ( $opt{'user-config'} ) {
2552    my $prefsfrom = $username;  # the one passed, NOT $opt{username}
2553
2554    if ($prefsfrom eq $suidto) {
2555      $userdir = $suiddir;  # reuse the already-looked-up info, tainted
2556    } elsif ( $opt{'vpopmail'} ) {
2557      #
2558      # If vpopmail config enabled then set $userdir to virtual homedir
2559      #
2560      my $username_untainted;
2561      $username_untainted =
2562        untaint_var($username)  if $username =~ /^[-:,.=+A-Za-z0-9_\@~]+\z/;
2563      my $vpopdir = $suiddir; # This should work with common vpopmail setups
2564      $userdir = `$vpopdir/bin/vuserinfo -d \Q$username_untainted\E`;
2565      if ($? == 0) {
2566        chomp($userdir);
2567      } else {
2568        $userdir = handle_user_vpopmail($username_untainted,$vpopdir);
2569      }
2570    } else {
2571      $userdir = (getpwnam($prefsfrom))[7];
2572    }
2573
2574    # we *still* die if this can't be found
2575    if (!defined $userdir) {
2576        my $errmsg =
2577          "spamd: handle_user (userdir) unable to find user: '$prefsfrom'\n";
2578        die $errmsg if $spamtest->{'paranoid'};
2579        # if we are given a username, but can't look it up, maybe name
2580        # services are down?  let's break out here to allow them to get
2581        # 'defaults' when we are not running paranoid
2582        info($errmsg);
2583        return 0;
2584    }
2585  }
2586
2587  # call this anyway, regardless of --user-config, so that
2588  # signal_user_changed() is called
2589  handle_user_set_user_prefs(untaint_var($userdir), $username);
2590}
2591
2592sub handle_user_vpopmail {
2593  #
2594  # If vuserinfo failed $username could be an alias
2595  # As the alias could be an alias itself we'll try to resolve it recursively
2596  # Because we're mistrusting vpopmail we'll set off an alarm
2597  #
2598  my $username = shift;
2599  my $vpopdir = shift;
2600  my $userdir;
2601  my $vpoptimeout = 5;
2602  my $vptimer = Mail::SpamAssassin::Timeout->new({ secs => $vpoptimeout });
2603
2604  $vptimer->run(sub {
2605    my $vpopusername = $username;
2606    my @aliases = split(/\n/, `$vpopdir/bin/valias \Q$vpopusername\E`);
2607    while (@aliases) {
2608      my $vpopusername_tainted = shift(@aliases);
2609      local $1;
2610      if ($vpopusername_tainted =~ /-> &?(.+)$/) {
2611        $vpopusername = untaint_var($1);
2612        if ($vpopusername =~ s{^(/.+)/Maildir/$}{$1}) {
2613          # this is the path to a real mailbox
2614          $userdir = $vpopusername;
2615        } elsif ($vpopusername !~ /^[#| \t]/ &&
2616                 $vpopusername =~ /^[^@ \t]+\@[^@ \t]+\s*$/) {
2617          # this is a forward to another e-mail address
2618          $vpopusername =~ s{^.+ -> (.+)}{$1};
2619          $vpopusername_tainted = `$vpopdir/bin/vuserinfo -d \Q$vpopusername\E`;
2620          if ($? == 0 && $vpopusername_tainted ne '') {
2621            $userdir = untaint_var($vpopusername_tainted);
2622          } else {
2623            unshift(@aliases,
2624                    split(/\n/, `$vpopdir/bin/valias \Q$vpopusername\E`));
2625          }
2626        }
2627        last if defined $userdir;
2628      }
2629    }
2630  });
2631
2632  if ($vptimer->timed_out()) {
2633    dbg("spamd: timed out resolving vpopmail user/alias '%s'", $username);
2634    undef $userdir;
2635  } elsif (!defined($userdir)) {
2636    dbg("spamd: failed to resolve vpopmail user/alias '%s'", $username);
2637  } else {
2638    chomp($userdir);
2639  }
2640  return $userdir;
2641}
2642
2643sub handle_user_set_user_prefs {
2644  my ($dir, $username) = @_;
2645
2646  # don't do this if we weren't passed a directory
2647  if ($dir) {
2648    my $cf_file = $dir . "/.spamassassin/user_prefs";
2649    create_default_cf_if_needed( $cf_file, $username, $dir );
2650    $spamtest->read_scoreonly_config($cf_file);
2651  }
2652
2653  # signal_user_changed will ignore undef user_dirs, so this is ok
2654  $spamtest->signal_user_changed(
2655    {
2656      username => $username,
2657      user_dir => $dir
2658    }
2659  );
2660
2661  return 1;
2662}
2663
2664# Handle user configs without the necessity of having individual users or a
2665# SQL/LDAP database.
2666sub handle_virtual_config_dir {
2667  my ($username) = @_;
2668
2669  my $dir = $opt{'virtual-config-dir'};
2670  my $userdir;
2671  my $prefsfile;
2672
2673  if ( defined $dir ) {
2674    my $safename = $username;
2675    $safename =~ s/[^-A-Za-z0-9\+_\.\,\@\=]/_/gs;
2676    my $localpart = '';
2677    my $domain    = '';
2678    if ( $safename =~ /^(.*)\@(.*)$/ ) { $localpart = $1; $domain = $2; }
2679
2680    # Do userdir lookup exim-style.
2681    # If a config for the full address exists, use that one
2682    # else look for a domain default
2683    if ($dir=~/%x/) {
2684      ($userdir=$dir)=~s/%x/${safename}/g;
2685
2686      $prefsfile=$userdir.'/user_prefs';
2687      if (-f $prefsfile) {
2688        $dir = $userdir;
2689
2690      } else {
2691        $dir =~ s/%x/${domain}/g;
2692
2693        $prefsfile = $dir.'/user_prefs';
2694        $userdir   = $dir;
2695      }
2696
2697    # Use the normal escaping
2698    } else {
2699      $dir =~ s/\%u/${safename}/g;
2700      $dir =~ s/\%l/${localpart}/g;
2701      $dir =~ s/\%d/${domain}/g;
2702      $dir =~ s/\%\%/\%/g;
2703
2704      $userdir   = $dir;
2705      $prefsfile = $dir . '/user_prefs';
2706    }
2707
2708    # Log that the default configuration is being used for a user.
2709    info("spamd: using default config for $username: $prefsfile");
2710  }
2711
2712  if ( -f $prefsfile ) {
2713
2714    # Found a config, load it.
2715    $spamtest->read_scoreonly_config($prefsfile);
2716  }
2717
2718  # assume that $userdir will be a writable directory we can
2719  # use for Bayes dbs etc.
2720  $spamtest->signal_user_changed(
2721    {
2722      username => $username,
2723      userstate_dir => $userdir,
2724      user_dir => $userdir
2725    }
2726  );
2727  return 1;
2728}
2729
2730sub handle_user_sql {
2731  my ($username) = @_;
2732
2733  unless ( $spamtest->load_scoreonly_sql($username) ) {
2734    return 0;
2735  }
2736  $spamtest->signal_user_changed(
2737    {
2738      username => $username,
2739      user_dir => undef
2740    }
2741  );
2742  return 1;
2743}
2744
2745sub handle_user_ldap {
2746  my $username = shift;
2747  dbg("ldap: entering handle_user_ldap($username)");
2748  $spamtest->load_scoreonly_ldap($username);
2749  $spamtest->signal_user_changed(
2750    {
2751      username => $username,
2752      user_dir => undef
2753    }
2754  );
2755  return 1;
2756}
2757
2758sub handle_user_setuid_with_sql {
2759  my $username = shift;
2760
2761  # Bug 6313: interestingly, if $username is not tainted than $pwd, $gcos and
2762  # $etc end up tainted but other fields not;  if $username _is_ tainted,
2763  # getpwnam does not complain, but all returned fields are tainted (which
2764  # makes sense, but is worth remembering)
2765  #
2766  my ($name, $pwd, $uid, $gid, $quota, $comment, $gcos, $dir, $etc) =
2767      getpwnam(untaint_var($username));
2768
2769  if (!$spamtest->{'paranoid'} && !defined($uid)) {
2770    # if we are given a username, but can't look it up, maybe name
2771    # services are down?  let's break out here to allow them to get
2772    # 'defaults' when we are not running paranoid
2773    info("spamd: handle_user (sql) unable to find user: $username");
2774    return 0;
2775  }
2776
2777  if ($setuid_to_user) {
2778    $) = (get_user_groups($uid));     # change eGID
2779    $> = $uid;                        # change eUID
2780    if (!defined($uid) || ($> != $uid and $> != ($uid - 2**32))) {
2781      # make it fatal to avoid security breaches
2782      die("spamd: fatal error: setuid to $username failed");
2783    }
2784    else {
2785      info("spamd: setuid to $username succeeded, reading scores from SQL");
2786    }
2787  }
2788
2789  my $spam_conf_dir = $dir . '/.spamassassin'; # needed for Bayes, etc.
2790
2791  if ( ($opt{'user-config'} || defined $opt{'home_dir_for_helpers'})
2792       && ! -d $spam_conf_dir ) {
2793    if (mkdir $spam_conf_dir, 0700) {
2794      info("spamd: created $spam_conf_dir for $username");
2795    }
2796    else {
2797      info("spamd: failed to create $spam_conf_dir for $username");
2798    }
2799  }
2800
2801  unless ($spamtest->load_scoreonly_sql($username)) {
2802    return 0;
2803  }
2804
2805  $spamtest->signal_user_changed( { username => $username } );
2806  return 1;
2807}
2808
2809sub handle_user_setuid_with_ldap {
2810  my $username = shift;
2811  my ($name, $pwd, $uid, $gid, $quota, $comment, $gcos, $dir, $etc) =
2812      getpwnam($username);
2813
2814  if (!$spamtest->{'paranoid'} && !defined($uid)) {
2815    # if we are given a username, but can't look it up, maybe name
2816    # services are down?  let's break out here to allow them to get
2817    # 'defaults' when we are not running paranoid
2818    info("spamd: handle_user (ldap) unable to find user: $username");
2819    return 0;
2820  }
2821
2822  if ($setuid_to_user) {
2823    $) = (get_user_groups($uid));    # change eGID
2824    $> = $uid;           # change eUID
2825    if (!defined($uid) || ($> != $uid and $> != ($uid - 2**32))) {
2826      # make it fatal to avoid security breaches
2827      die("spamd: fatal error: setuid to $username failed");
2828    }
2829    else {
2830      info("spamd: setuid to $username succeeded, reading scores from LDAP");
2831    }
2832  }
2833
2834  my $spam_conf_dir = $dir . '/.spamassassin'; # needed for Bayes, etc.
2835  if (! -d $spam_conf_dir) {
2836    if (mkdir $spam_conf_dir, 0700) {
2837      info("spamd: created $spam_conf_dir for $username");
2838    }
2839    else {
2840      info("spamd: failed to create $spam_conf_dir for $username");
2841    }
2842  }
2843
2844  $spamtest->load_scoreonly_ldap($username);
2845
2846  $spamtest->signal_user_changed( { username => $username } );
2847  return 1;
2848}
2849
2850sub create_default_cf_if_needed {
2851  my ( $cf_file, $username, $userdir ) = @_;
2852
2853  # Parse user scores, creating default .cf if needed:
2854  if ( !-r $cf_file && !$spamtest->{'dont_copy_prefs'} ) {
2855    info("spamd: creating default_prefs: $cf_file");
2856
2857    # If vpopmail config enabled then pass virtual homedir onto
2858    # create_default_prefs via $userdir
2859    $spamtest->create_default_prefs( $cf_file, $username, $userdir );
2860
2861    if (! -r $cf_file) {
2862      info("spamd: failed to create readable default_prefs: $cf_file");
2863    }
2864  }
2865}
2866
2867# sig handlers: parent process
2868sub setup_parent_sig_handlers {
2869  $SIG{HUP}  = \&restart_handler;
2870  $SIG{CHLD} = \&child_handler;
2871  $SIG{INT}  = \&kill_handler;
2872  $SIG{TERM} = \&kill_handler;
2873  $SIG{PIPE} = 'IGNORE';
2874}
2875
2876# sig handlers: child processes
2877sub setup_child_sig_handlers {
2878  # note: all the signals changed in setup_parent_sig_handlers() must
2879  # be reset to appropriate values here!
2880  my $h = 'DEFAULT';
2881  if (am_running_on_windows()) {
2882    # on win32 the parent never receives SIGCHLD
2883    $h = sub { my($sig) = @_;
2884               info("spamd: child got SIG$sig, exiting");
2885               kill QUIT => 0;
2886               exit 0;
2887             };
2888  }
2889  $SIG{$_} = $h  foreach qw(HUP INT TERM CHLD);
2890  $SIG{PIPE} = 'IGNORE';
2891}
2892
2893sub kill_handler {
2894  my ($sig) = @_;
2895  info("spamd: server killed by SIG$sig, shutting down");
2896
2897  for my $socket_info (@listen_sockets) {
2898    next if !$socket_info;
2899
2900    my $socket = $socket_info->{socket};
2901    $socket->close  if $socket;  # ignoring status
2902
2903    my $path = $socket_info->{path};
2904    if (defined $path) {  # unlink a UNIX domain socket
2905      unlink($path) or warn "spamd: cannot unlink $path: $!\n";
2906    }
2907  }
2908
2909  if (defined($opt{'pidfile'})) {
2910    unlink($opt{'pidfile'})
2911      or warn "spamd: cannot unlink $opt{'pidfile'}: $!\n";
2912  }
2913
2914  $SIG{CHLD} = 'DEFAULT';    # we're going to kill our children
2915  if ($scaling) {
2916    $scaling->set_exiting_flag(); # don't start new ones
2917  }
2918  my $killsig = am_running_on_windows() ? 'KILL' : 'INT';
2919  foreach my $pid (keys %children) {
2920    kill($killsig, $pid)
2921      or info("spamd: cannot send SIG$killsig to child process [$pid]: $!");
2922  }
2923  exit 0;
2924}
2925
2926# takes care of dead children
2927sub child_handler {
2928  my ($sig) = @_;
2929
2930  # do NOT call syslog here unless the child's pid is in our list of known
2931  # children.  This is due to syslog-ng brokenness -- bugs 3625, 4237;
2932  # see also bug 6745.
2933
2934  # clean up any children which have exited
2935  for (;;) {
2936    # waitpid returns a pid of the deceased process, or -1 if there is no
2937    # such child process. On some systems, a value of 0 indicates that there
2938    # are processes still running. Note that Windows uses negative pids for
2939    # child processes - bug 6376, bug 6356.
2940    #
2941    my $pid = waitpid(-1, WNOHANG);
2942    last if !$pid || $pid == -1;
2943    push(@children_exited, [$pid, $?, $sig, time]);
2944  }
2945
2946  $SIG{CHLD} = \&child_handler;    # reset as necessary, should be at end
2947}
2948
2949# takes care of dead children, as noted by a child_handler()
2950# called in a main program flow (not from a signal handler)
2951#
2952sub child_cleaner {
2953  while (@children_exited) {
2954    my $tuple = shift(@children_exited);
2955    next if !$tuple;  # just in case
2956    my($pid, $child_stat, $sig, $timestamp) = @$tuple;
2957
2958    # ignore this child if we didn't realise we'd forked it. bug 4237
2959    next if !defined $children{$pid};
2960
2961    # remove them from our child listing
2962    delete $children{$pid};
2963
2964    if ($scaling) {
2965      $scaling->child_exited($pid);
2966    } else {
2967      my $sock = $backchannel->get_socket_for_child($pid);
2968      if ($sock) { $sock->close(); }
2969    }
2970    info("spamd: handled cleanup of child pid [%s]%s: %s",
2971         $pid, (defined $sig ? " due to SIG$sig" : ""),
2972         exit_status_str($child_stat,0));
2973  }
2974}
2975
2976sub restart_handler {
2977  my ($sig) = @_;
2978  info("spamd: server hit by SIG$sig, restarting");
2979
2980  $SIG{CHLD} = 'DEFAULT';    # we're going to kill our children
2981  if ($scaling) {
2982    $scaling->set_exiting_flag(); # don't start new ones
2983  }
2984
2985  foreach (keys %children) {
2986    kill 'INT' => $_;
2987    my $pid = waitpid($_, 0);
2988    my $child_stat = $pid > 0 ? $? : undef;
2989    if ($scaling) {
2990      $scaling->child_exited($pid);
2991    }
2992    info("spamd: child [%s] killed successfully: %s",
2993         $pid, exit_status_str($child_stat,0));
2994  }
2995  %children = ();
2996
2997  for my $socket_info (@listen_sockets) {
2998    next if !$socket_info;
2999    my $socket = $socket_info->{socket};
3000    next if !$socket;
3001    my $socket_specs = $socket_info->{specs};
3002    $socket->shutdown(2) if !$socket->eof;
3003    $socket->close;
3004    if ($socket->isa('IO::Socket::UNIX') && defined $socket_specs) {
3005      unlink($socket_specs)
3006        or warn "spamd: cannot unlink $socket_specs: $!\n";
3007    }
3008    info("spamd: server socket closed, type %s", ref $socket);
3009  }
3010
3011  $got_sighup = 1;
3012}
3013
3014sub backtrace_handler {
3015  Carp::cluck("spamd: caught SIGUSR2 - dumping backtrace. ".
3016        "most recent message: $current_msgid\n");
3017}
3018
3019my  $serverstarted = 0;
3020
3021sub serverstarted {
3022  $serverstarted = 1;
3023}
3024
3025sub daemonize {
3026  # removed bug 7594 # Pretty command line in ps
3027    #$0 = join (' ', $ORIG_ARG0, @ORIG_ARGV) unless would_log("dbg");
3028
3029  # be a nice daemon and chdir to the root so we don't block any
3030  # unmount attempts
3031  chdir '/' or die "spamd: cannot chdir to /: $!\n";
3032
3033  # Redirect in and out to the bit bucket
3034  open STDIN,  "</dev/null" or die "spamd: cannot read from /dev/null: $!\n";
3035  open STDOUT, ">/dev/null" or die "spamd: cannot write to /dev/null: $!\n";
3036
3037  # Remove the stderr logger
3038  Mail::SpamAssassin::Logger::remove('stderr');
3039
3040  # Here we go...
3041  $SIG{USR1} = \&serverstarted;
3042  defined( my $pid = fork ) or die "spamd: cannot fork: $!\n";
3043  if ($pid) {
3044    my $child_stat;
3045    # Bug 6191, Bug 6258: takes almost two minutes on a slow machine
3046    # for a forked child process to report back, bump limit to 180 seconds
3047    for (my $retry=180, my $waited=0;
3048         $retry > 0 && !$serverstarted && $waited != $pid;
3049         $retry--)
3050    {
3051      warn("waitpid failed: $waited $!")  if $waited;
3052      sleep 1;
3053      $waited = waitpid($pid, WNOHANG);
3054      $child_stat = $?  if $waited > 0;
3055    }
3056    die sprintf("child process [%s] exited or timed out ".
3057                "without signaling production of a PID file: %s",
3058                $pid, exit_status_str($child_stat,0)) unless $serverstarted;
3059    exit;
3060  }
3061  delete  $SIG{USR1};
3062  setsid or die "spamd: cannot start new session: $!\n";
3063
3064  # Now we can redirect the errors, too.
3065  open STDERR, '>&STDOUT' or die "spamd: cannot duplicate stdout: $!\n";
3066
3067  dbg("spamd: successfully daemonized");
3068}
3069
3070sub set_allowed_ip {
3071  foreach (@_) {
3072    my $ip = $_;
3073    local($1,$2);
3074    # strip optional square brackets
3075    $ip =~ s{^ \[ (.*) \] \z}{$1}xs
3076    || $ip =~ s{^ \[ (.*) \] ( / \d+ ) \z}{$1$2}xs;
3077  # dbg("spamd: set_allowed_ip %s", $ip);
3078    $allowed_nets->add_cidr($ip)
3079      or die "spamd: aborting due to add_cidr error\n";
3080  }
3081}
3082
3083sub ip_is_allowed {
3084  $allowed_nets->contains_ip(@_);
3085}
3086
3087sub preload_modules_with_tmp_homedir {
3088
3089  # set $ENV{HOME} in a temp directory while we compile and preload everything.
3090  my $tmphome = secure_tmpdir();
3091
3092  # If TMPDIR isn't set, File::Spec->tmpdir() called by secure_tmpdir() may set it to undefined.
3093  # that then breaks other things ...
3094  # If this is really necessary shouldn't secure_tmpdir() be doing it?
3095  delete $ENV{'TMPDIR'} if ( !defined $ENV{'TMPDIR'} );
3096
3097  my $tmpsadir = File::Spec->catdir( $tmphome, ".spamassassin" );
3098
3099  dbg("spamd: Preloading modules with HOME=$tmphome");
3100
3101  if (!-d $tmphome) {
3102    die "spamd: cannot create temp directory $tmphome: $!";
3103  }
3104
3105  # bug 5379: spamd won't start if the temp preloading dir exists; check if exists and remove it
3106  # This check should be unnecessary now that $tmphome created using File::Temp, but leave it just in case
3107  if (-d $tmpsadir) {
3108    rmdir( $tmpsadir ) or die "spamd: $tmpsadir not empty: $!";
3109  }
3110  mkdir( $tmpsadir, 0700 ) or die "spamd: cannot create $tmpsadir: $!";
3111  $ENV{HOME} = $tmphome;
3112
3113  $spamtest->compile_now(0,1);  # ensure all modules etc. are loaded
3114  $/ = "\n";                    # argh, Razor resets this!  Bad Razor!
3115
3116  # now clean up the stuff we just created, and make us taint-safe
3117  delete $ENV{HOME};
3118
3119  # bug 2015, bug 2223: rmpath() is not taint safe, so we've got to implement
3120  # our own poor man's rmpath. If it fails, we report only the first error.
3121  my $err;
3122  foreach my $d ( ( $tmpsadir, $tmphome ) ) {
3123    opendir( TMPDIR, $d ) or $err ||= "open $d: $!";
3124    unless ($err) {
3125      foreach my $f ( File::Spec->no_upwards( readdir(TMPDIR) ) ) {
3126        $f = untaint_file_path( File::Spec->catfile( $d, $f ) );
3127        unlink($f) or $err ||= "remove $f: $!";
3128      }
3129      closedir(TMPDIR) or $err ||= "close $d: $!";
3130    }
3131    rmdir($d) or $err ||= "remove $d: $!";
3132  }
3133
3134  # If the dir still exists, log a warning.
3135  if ( -d $tmphome ) {
3136    $err ||= "do something: $!";
3137    warn "spamd: failed to remove $tmphome: could not $err\n";
3138  }
3139}
3140
3141# Keep calling syswrite until the entire buffer is written out
3142# Retry if EAGAIN/EWOULDBLOCK or when partial buffer is written
3143# Limit the number of retries to keep the execution time bounded
3144sub syswrite_full_buffer {
3145  my ($sock, $buf, $numretries) = @_;
3146  $numretries ||= 10;       # default 10 retries
3147  my $length = length($buf);
3148  my $written = 0;
3149  my $try = 0;
3150
3151  while (($try < $numretries) && ($length > $written)) {
3152      my $nbytes = syswrite($sock, $buf, $length - $written, $written);
3153      if (!defined $nbytes) {
3154	  unless ((exists &Errno::EAGAIN && $! == &Errno::EAGAIN)
3155		  || (exists &Errno::EWOULDBLOCK && $! == &Errno::EWOULDBLOCK))
3156	  {
3157	      # an error that wasn't non-blocking I/O-related.  that's serious
3158	      return;
3159	  }
3160	  # errcode says to try again
3161      }
3162      else {
3163
3164	  if ($nbytes == 0) {
3165	      return $written;  # return early if no error but nothing was written
3166	  }
3167
3168	  $written += $nbytes;
3169      }
3170      $try++;
3171  }
3172
3173  return $written;      # it's complete, we can return
3174}
3175
3176sub map_server_sockets {
3177
3178  $server_select_mask = '';
3179  for my $socket_info (@listen_sockets) {
3180    next if !$socket_info;
3181    my $fd = $socket_info->{fd};
3182    vec($server_select_mask, $fd, 1) = 1  if defined $fd;
3183  }
3184  dbg("spamd: server listen sockets fd bit field: %s",
3185      unpack('b*', $server_select_mask));
3186
3187  my $back_selector = $server_select_mask;
3188  $backchannel->set_selector(\$back_selector);
3189}
3190
3191# do this in advance, since we want to minimize work when SIGHUP
3192# is received
3193my $perl_from_hashbang_line;
3194sub prepare_for_sighup_restart {
3195  # it'd be great if we could introspect the interpreter to figure this
3196  # out, but bizarrely it seems unavailable.
3197  if (open (IN, "<$ORIG_ARG0")) {
3198    my $l = <IN>;
3199    close IN;
3200    if ($l && $l =~ /^#!\s*(\S+)\s*.*?$/) {
3201      $perl_from_hashbang_line = $1;
3202    }
3203  }
3204}
3205
3206sub do_sighup_restart {
3207  if (defined($opt{'pidfile'})) {
3208    unlink($opt{'pidfile'}) || warn "spamd: cannot unlink $opt{'pidfile'}: $!\n";
3209  }
3210
3211  # leave Client fds active, and do not kill children; they can still
3212  # service clients until they exit.  But restart the listener anyway.
3213  # And close the logfile, so the new instance can reopen it.
3214  Mail::SpamAssassin::Logger::close_log();
3215  chdir($ORIG_CWD)
3216    or die "spamd: restart failed: chdir failed: ${ORIG_CWD}: $!\n";
3217
3218  # ensure we re-run spamd using the right perl interpreter, and
3219  # with the right switches (taint mode and warnings) (bug 5255)
3220  my $perl = untaint_var($^X);
3221  my @execs = ( $perl, "-T", "-w", $ORIG_ARG0, @ORIG_ARGV );
3222
3223  if ($perl eq $perl_from_hashbang_line) {
3224    # we're using the same perl as the script uses on the #! line;
3225    # we can safely just exec the script
3226    @execs = ( $ORIG_ARG0, @ORIG_ARGV );
3227  }
3228
3229  warn "spamd: restarting using '" . join (' ', @execs) . "'\n";
3230  exec @execs;
3231
3232  # should not get past that...
3233  die "spamd: restart failed: exec failed: " . join (' ', @execs) . ": $!\n";
3234}
3235
3236__DATA__
3237
3238=head1 NAME
3239
3240spamd - daemonized version of spamassassin
3241
3242=head1 SYNOPSIS
3243
3244spamd [options]
3245
3246Options:
3247
3248 -l, --allow-tell                  Allow learning/reporting
3249 -c, --create-prefs                Create user preferences files
3250 -C path, --configpath=path        Path for default config files
3251 --siteconfigpath=path             Path for site configs
3252 --cf='config line'                Additional line of configuration
3253 --pre='config line'               Additional line of ".pre" (prepended to configuration)
3254 -d, --daemonize                   Daemonize
3255 -h, --help                        Print usage message
3256 -i [ip_or_name[:port]], --listen=[ip_or_name[:port]] Listen on IP addr and port
3257 -p port, --port=port              Listen on specified port, may be overridden by -i
3258 -4, --ipv4-only, --ipv4           Use IPv4 where applicable, disables IPv6
3259 -6                                Use IPv6 where applicable, disables IPv4
3260 -A host,..., --allowed-ips=..,..  Restrict to IP addresses which can connect
3261 -m num, --max-children=num        Allow maximum num children
3262 --min-children=num                Allow minimum num children
3263 --min-spare=num                   Lower limit for number of spare children
3264 --max-spare=num                   Upper limit for number of spare children
3265 --max-conn-per-child=num	   Maximum connections accepted by child
3266                                   before it is respawned
3267 --round-robin                     Use traditional prefork algorithm
3268 --timeout-tcp=secs                Connection timeout for client headers
3269 --timeout-child=secs              Connection timeout for message checks
3270 -q, --sql-config                  Enable SQL config (needs -x)
3271 -Q, --setuid-with-sql             Enable SQL config (needs -x,
3272                                   enables use of -H)
3273 --ldap-config                     Enable LDAP config (needs -x)
3274 --setuid-with-ldap                Enable LDAP config (needs -x,
3275                                   enables use of -H)
3276 --virtual-config-dir=dir          Enable pattern based Virtual configs
3277                                   (needs -x)
3278 -r pidfile, --pidfile             Write the process id to pidfile
3279 -s facility, --syslog=facility    Specify the syslog facility
3280 --syslog-socket=type              How to connect to syslogd
3281 --log-timestamp-fmt=fmt           strftime(3) format for timestamps, may be
3282                                   empty to disable timestamps, or 'default'
3283 -u username, --username=username  Run as username
3284 -g groupname, --groupname=groupname  Run as groupname
3285 -v, --vpopmail                    Enable vpopmail config
3286 -x, --nouser-config               Disable user config files
3287 --auth-ident                      Use ident to identify spamc user (deprecated)
3288 --ident-timeout=timeout           Timeout for ident connections
3289 -D, --debug[=areas]               Print debugging messages (for areas)
3290 -L, --local                       Use local tests only (no DNS)
3291 -P, --paranoid                    Die upon user errors
3292 -H [dir], --helper-home-dir[=dir] Specify a different HOME directory
3293 --ssl                             Enable SSL on TCP connections
3294 --ssl-port port                   Override --port setting for SSL connections
3295 --server-key keyfile              Specify an SSL keyfile
3296 --server-cert certfile            Specify an SSL certificate
3297 --socketpath=path                 Listen on a given UNIX domain socket
3298 --socketowner=name                Set UNIX domain socket file's owner
3299 --socketgroup=name                Set UNIX domain socket file's group
3300 --socketmode=mode                 Set UNIX domain socket file's mode
3301 --timing                          Enable timing and logging
3302 -V, --version                     Print version and exit
3303
3304The --listen option (or -i) may be specified multiple times, its syntax
3305is: [ ssl: ] [ host-name-or-IP-address ] [ : port ]  or an absolute path
3306(filename) of a Unix socket.  If port is omitted it defaults to --port or
3307to 783.  Option --ssl implies a prefix 'ssl:'.  An IPv6 address should be
3308enclosed in square brackets, e.g. [::1]:783, an IPv4 address may be but
3309need not be enclosed in square brackets.  An asterisk '*' in place of a
3310hostname implies an unspecified address, ('0.0.0.0' or '::'), i.e. it
3311binds to all interfaces. An empty option value implies '*'. A default
3312is '--listen localhost', which binds to a loopback interface only.
3313
3314
3315=head1 DESCRIPTION
3316
3317The purpose of this program is to provide a daemonized version of the
3318spamassassin executable.  The goal is improving throughput performance for
3319automated mail checking.
3320
3321This is intended to be used alongside C<spamc>, a fast, low-overhead C client
3322program.
3323
3324See the README file in the C<spamd> directory of the SpamAssassin distribution
3325for more details.
3326
3327Note: Although C<spamd> will check per-user config files for every message, any
3328changes to the system-wide config files will require either restarting spamd
3329or forcing it to reload itself via B<SIGHUP> for the changes to take effect.
3330
3331Note: If C<spamd> receives a B<SIGHUP>, it internally reloads itself, which
3332means that it will change its pid and might not restart at all if its
3333environment changed  (ie. if it can't change back into its own directory).  If
3334you plan to use B<SIGHUP>, you should always start C<spamd> with the B<-r>
3335switch to know its current pid.
3336
3337=head1 OPTIONS
3338
3339Options of the long form can be shortened as long as they remain
3340unambiguous.  (i.e. B<--dae> can be used instead of B<--daemonize>)
3341Also, boolean options (like B<--user-config>) can be negated by
3342adding I<no> (B<--nouser-config>), however, this is usually unnecessary.
3343
3344=over 4
3345
3346=item B<-l>, B<--allow-tell>
3347
3348Allow learning and forgetting (to a local Bayes database), reporting
3349and revoking (to a remote database) by spamd. The client issues a TELL
3350command to tell what type of message is being processed and whether
3351local (learn/forget) or remote (report/revoke) databases should be
3352updated.
3353
3354Note that spamd always trusts the username passed in (unless
3355B<--auth-ident> is used) so clients could maliciously learn messages
3356for other users. (This is not usually a concern with an SQL Bayes
3357store as users will typically have read-write access directly to the
3358database, and can also use C<sa-learn> with the B<-u> option to
3359achieve the same result.)
3360
3361=item B<-c>, B<--create-prefs>
3362
3363Create user preferences files if they don't exist (default: don't).
3364
3365=item B<-C> I<path>, B<--configpath>=I<path>
3366
3367Use the specified path for locating the distributed configuration files.
3368Ignore the default directories (usually C</usr/share/spamassassin> or similar).
3369
3370=item B<--siteconfigpath>=I<path>
3371
3372Use the specified path for locating site-specific configuration files.  Ignore
3373the default directories (usually C</etc/mail/spamassassin> or similar).
3374
3375=item B<--cf='config line'>
3376
3377Add additional lines of configuration directly from the command-line, parsed
3378after the configuration files are read.   Multiple B<--cf> arguments can be
3379used, and each will be considered a separate line of configuration.
3380
3381=item B<--pre='config line'>
3382
3383Add additional lines of .pre configuration directly from the command-line,
3384parsed before the configuration files are read.  Multiple B<--pre> arguments
3385can be used, and each will be considered a separate line of configuration.
3386
3387=item B<-d>, B<--daemonize>
3388
3389Detach from starting process and run in background (daemonize).
3390
3391=item B<-h>, B<--help>
3392
3393Print a brief help message, then exit without further action.
3394
3395=item B<-V>, B<--version>
3396
3397Print version information, then exit without further action.
3398
3399=item B<-i> [I<ipaddress>[:<port>]], B<--listen>[=I<ipaddress>[:<port>]]
3400
3401Additional alias names for this option are --listen-ip and --ip-address.
3402Tells spamd to listen on the specified IP address, defaults to a loopback
3403interface, i.e. C<--listen localhost>).  If no value is specified after the
3404switch, or if an asterisk '*' stands in place of an <ipaddress>, spamd will
3405listen on all interfaces - this is equivalent to address '0.0.0.0' for IPv4
3406and to '::' for IPv6. You can also use a valid hostname which will make spamd
3407listen on all addresses that a name resolves to. The option may be specified
3408multiple times. See also options -4 and -6 for restricting address family
3409to IPv4 or to IPv6. If a port is specified it overrides for this socket the
3410global --port (and --ssl-port) setting. An IPv6 addresses should be enclosed
3411in square brackets, e.g. [::1]:783. For compatibility square brackets on an
3412IPv6 address may be omitted if a port number specification is also omitted.
3413
3414=item B<-p> I<port>, B<--port>=I<port>
3415
3416Optionally specifies the port number for the server to listen on (default: 783).
3417
3418If the B<--ssl> switch is used, and B<--ssl-port> is not supplied, then this
3419port will be used to accept SSL connections instead of unencrypted connections.
3420If the B<--ssl> switch is used, and B<--ssl-port> is set, then unencrypted
3421connections will be accepted on the B<--port> at the same time as encrypted
3422connections are accepted at B<--ssl-port>.
3423
3424=item B<-q>, B<--sql-config>
3425
3426Turn on SQL lookups even when per-user config files have been disabled
3427with B<-x>. this is useful for spamd hosts which don't have user's
3428home directories but do want to load user preferences from an SQL
3429database.
3430
3431If your spamc client does not support sending the C<User:> header,
3432like C<exiscan>, then the SQL username used will always be B<nobody>.
3433
3434This inhibits the setuid() behavior, so the C<-u> option is
3435required. If you want the setuid() behaviour, use C<-Q> or
3436C<--setuid-with-sql> instead.
3437
3438=item B<--ldap-config>
3439
3440Turn on LDAP lookups. This is completely analog to C<--sql-config>,
3441only it is using an LDAP server.
3442
3443Like C<--sql-config>, this disables the setuid behavior, and requires
3444C<-u>. If you want it, use C<--setuid-with-ldap> instead.
3445
3446=item B<-Q>, B<--setuid-with-sql>
3447
3448Turn on SQL lookups even when per-user config files have been disabled
3449with B<-x> and also setuid to the user.  This is useful for spamd hosts
3450which want to load user preferences from an SQL database but also wish to
3451support the use of B<-H> (Helper home directories.)
3452
3453=item B<--setuid-with-ldap>
3454
3455Turn on LDAP lookups even when per-user config files have been disabled
3456with B<-x> and also setuid to the user.  This is again completely analog
3457to C<--setuid-with-sql>, only it is using an LDAP server.
3458
3459=item B<--virtual-config-dir>=I<pattern>
3460
3461This option specifies where per-user preferences can be found for virtual
3462users, for the B<-x> switch. The I<pattern> is used as a base pattern for the
3463directory name.  Any of the following escapes can be used:
3464
3465=over 4
3466
3467=item %u -- replaced with the full name of the current user, as sent by spamc.
3468
3469=item %l -- replaced with the 'local part' of the current username.  In other
3470words, if the username is an email address, this is the part before the C<@>
3471sign.
3472
3473=item %d -- replaced with the 'domain' of the current username.  In other
3474words, if the username is an email address, this is the part after the C<@>
3475sign.
3476
3477=item %x -- replaced with the full name of the current user, as sent by spamc.
3478If the resulting config directory does not exist, replace with the domain part
3479to use a domain-wide default.
3480
3481=item %% -- replaced with a single percent sign (%).
3482
3483=back
3484
3485So for example, if C</vhome/users/%u/spamassassin> is specified, and spamc
3486sends a virtual username of C<jm@example.com>, the directory
3487C</vhome/users/jm@example.com/spamassassin> will be used.
3488
3489The set of characters allowed in the virtual username for this path are
3490restricted to:
3491
3492	A-Z a-z 0-9 - + _ . , @ =
3493
3494All others will be replaced by underscores (C<_>).
3495
3496This path must be a writable directory.  It will be created if it does not
3497already exist.  If a file called B<user_prefs> exists in this directory (note:
3498B<not> in a C<.spamassassin> subdirectory!), it will be loaded as the user's
3499preferences.  The Bayes databases for that user will be stored in this directory.
3500
3501Note that this B<requires> that B<-x> is used, and cannot be combined with
3502SQL- or LDAP-based configuration.
3503
3504The pattern B<must> expand to an absolute directory when spamd is running
3505daemonized (B<-d>).
3506
3507Currently, use of this without B<-u> is not supported. This inhibits setuid.
3508
3509=item B<-r> I<pidfile>, B<--pidfile>=I<pidfile>
3510
3511Write the process ID of the spamd parent to the file specified by I<pidfile>.
3512The file will be unlinked when the parent exits.  Note that when running
3513with the B<-u> option, the file must be writable by that user.
3514
3515=item B<-v>, B<--vpopmail>
3516
3517Enable vpopmail config.  If specified with B<-u> set to the vpopmail user,
3518this allows spamd to lookup/create user_prefs in the vpopmail user's own
3519maildir.  This option is useful for vpopmail virtual users who do not have an
3520entry in the system /etc/passwd file.
3521
3522Currently, use of this without B<-u> is not supported. This inhibits setuid.
3523
3524=item B<-s> I<facility>, B<--syslog>=I<facility>
3525
3526Specify the syslog facility to use (default: mail).  If C<stderr> is specified,
3527output will be written to stderr. (This is useful if you're running C<spamd>
3528under the C<daemontools> package.) With a I<facility> of C<file>, all output
3529goes to spamd.log. I<facility> is interpreted as a file name to log to if it
3530contains any characters except a-z and 0-9. C<null> disables logging completely
3531(used internally).
3532
3533Examples:
3534
3535	spamd -s mail                 # use syslog, facility mail (default)
3536	spamd -s ./mail               # log to file ./mail
3537	spamd -s stderr 2>/dev/null   # log to stderr, throw messages away
3538	spamd -s null                 # the same as above
3539	spamd -s file                 # log to file ./spamd.log
3540	spamd -s /var/log/spamd.log   # log to file /var/log/spamd.log
3541
3542If logging to a file is enabled and that log file is rotated, the spamd server
3543must be restarted with a SIGHUP. (If the log file is just truncated, this is
3544not needed but still recommended.)
3545
3546Note that logging to a file does not use locking, so you cannot intermix
3547logging from spamd and other processes into the same file.  If you want
3548to mix logging like this, use syslog instead.
3549
3550If you use syslog logging, it is essential to send a SIGHUP to the spamd daemon
3551when you restart the syslogd daemon.  (This is due to a shortcoming in Perl's
3552syslog handling, where the disappearance of the connection to the syslogd is
3553considered a fatal error.)
3554
3555=item B<--syslog-socket>=I<type>
3556
3557Specify how spamd should send messages to syslogd. The I<type> can be any
3558of the socket types or logging mechanisms as accepted by the subroutine
3559Sys::Syslog::setlogsock(). Depending on a version of Sys::Syslog and on the
3560underlying operating system, one of the following values (or their subset) can
3561be used: C<native>, C<eventlog>, C<tcp>, C<udp>, C<inet>, C<unix>, C<stream>,
3562C<pipe>, or C<console>.  The value C<eventlog> is specific to Win32 events
3563logger and requires a perl module Win32::EventLog to be installed.
3564For more information please consult the Sys::Syslog documentation.
3565
3566A historical setting --syslog-socket=none is mapped to --syslog=stderr.
3567
3568A default for Windows platforms is C<none>, otherwise the default is
3569to try C<unix> first, falling back to C<inet> if perl detects errors
3570in its C<unix> support.
3571
3572Some platforms, or versions of perl, are shipped with old or dysfunctional
3573versions of the B<Sys::Syslog> module which do not support some socket types,
3574so you may need to set this option explicitly.  If you get error messages
3575regarding B<__PATH_LOG> or similar spamd, try changing this setting.
3576
3577The socket types C<file> is used internally and should not be specified.
3578Use the C<-s> switch instead.
3579
3580=item B<--log-timestamp-fmt>=I<format>
3581
3582The --log-timestamp-fmt option can provide a POSIX strftime(3) format for
3583timestamps included in each logged message. Each logger (stderr, file,
3584syslog) has its own default value for a timestamp format, which applies when
3585--log-timestamp-fmt option is not given, or with --log-timestamp-fmt=default .
3586Timestamps can be turned off by specifying an empty string with this
3587option, e.g. --log-timestamp-fmt='' or just --log-timestamp-fmt= .
3588Typical use: --log-timestamp-fmt='%a %b %e %H:%M:%S %Y' (provides
3589localized weekday and month names in the ctime(3) style),
3590or '%a, %e %b %Y %H:%M:%S %z (%Z)' for a RFC 2822 format,
3591or maybe '%Y-%m-%d %H:%M:%S%z' for an ISO 8601 (EN 28601) format,
3592or just '%Y%m%dT%H%M%S' .
3593
3594=item B<-u> I<username>, B<--username>=I<username>
3595
3596Run as the named user.  If this option is not set, the default behaviour
3597is to setuid() to the user running C<spamc>, if C<spamd> is running
3598as root.
3599
3600Note: "--username=root" is not a valid option.  If specified, C<spamd> will
3601exit with a fatal error on startup.
3602
3603=item B<-g> I<groupname>, B<--groupname>=I<groupname>
3604
3605Run as the named group if --username is being used. If this option is
3606not set when --username is used then the primary group for the user
3607given to --username is used.
3608
3609=item B<-x>, B<--nouser-config>, B<--user-config>
3610
3611Turn off (on) reading of per-user configuration files (user_prefs) from the
3612user's home directory.  The default behaviour is to read per-user
3613configuration from the user's home directory (B<--user-config>).
3614
3615This option does not disable or otherwise influence the SQL, LDAP or
3616Virtual Config Dir settings.
3617
3618=item B<--auth-ident>
3619
3620Verify the username provided by spamc using ident.  This is only
3621useful if connections are only allowed from trusted hosts (because an
3622identd that lies is trivial to create) and if spamc REALLY SHOULD be
3623running as the user it represents.  Connections are terminated
3624immediately if authentication fails.  In this case, spamc will pass
3625the mail through unchecked.  Failure to connect to an ident server,
3626and response timeouts are considered authentication failures.  This
3627requires that Net::Ident be installed. Deprecated.
3628
3629=item B<--ident-timeout>=I<timeout>
3630
3631Wait at most I<timeout> seconds for a response to ident queries.
3632Ident query that takes longer that I<timeout> seconds will fail, and
3633mail will not be processed.  Setting this to 0.0 or less results in no
3634timeout, which is STRONGLY discouraged.  The default is 5 seconds.
3635
3636=item B<-A> I<host,...>, B<--allowed-ips>=I<host,...>
3637
3638Specify a comma-separated list of authorized hosts or networks which
3639can connect to this spamd instance. Each element of the list is either a
3640single IP addresses, or a range of IP addresses in address/masklength CIDR
3641notation, or ranges of IPv4 addresses by specifying 3 or less octets with
3642a trailing dot.  Hostnames are not supported, only IPv4 or IPv6 addresses.
3643This option can be specified multiple times, or can take a list of addresses
3644separated by commas.  IPv6 addresses may be (but need not be) enclosed
3645in square brackets for consistency with option B<--listen>.  Examples:
3646
3647B<-A 10.11.12.13> -- only allow connections from C<10.11.12.13>.
3648
3649B<-A 10.11.12.13,10.11.12.14> -- only allow connections from C<10.11.12.13> and
3650C<10.11.12.14>.
3651
3652B<-A 10.200.300.0/24> -- allow connections from any machine in the range
3653C<10.200.300.*>.
3654
3655B<-A 10.> -- allow connections from any machine in the range C<10.*.*.*>.
3656
3657B<-A [2001:db8::]/32,192.0.2.0/24,::1,127.0.0.0/8> -- only accept
3658connections from specified test networks and from localhost.
3659
3660In absence of the B<-A> option, connections are only accepted from
3661IP address 127.0.0.1 or ::1, i.e. from localhost on a loopback interface.
3662
3663=item B<-D> [I<area,...>], B<--debug> [I<area,...>]
3664
3665Produce debugging output. If no areas are listed, all debugging information is
3666printed. Diagnostic output can also be enabled for each area individually;
3667I<area> is the area of the code to instrument. For example, to produce
3668diagnostic output on bayes, learn, and dns, use:
3669
3670        spamassassin -D bayes,learn,dns
3671
3672Higher priority informational messages that are suitable for logging in normal
3673circumstances are available with an area of "info".
3674
3675For more information about which areas (also known as channels) are available,
3676please see the documentation at:
3677
3678	C<http://wiki.apache.org/spamassassin/DebugChannels>
3679
3680=item B<-4>, B<--ipv4only>, B<--ipv4-only>, B<--ipv4>
3681
3682Use IPv4 where applicable, do not use IPv6.
3683The option affects a set of listen sockets (see option C<--listen>)
3684and disables IPv6 for DNS tests.
3685
3686=item B<-6>
3687
3688Use IPv6 where applicable, do not use IPv4.
3689The option affects a set of listen sockets (see option C<--listen>)
3690and disables IPv4 for DNS tests. Installing a module IO::Socket::IP
3691is recommended if spamd is expected to receive requests over IPv6.
3692
3693=item B<-L>, B<--local>
3694
3695Perform only local tests on all mail.  In other words, skip DNS and other
3696network tests.  Works the same as the C<-L> flag to C<spamassassin(1)>.
3697
3698=item B<-P>, B<--paranoid>
3699
3700Die on user errors (for the user passed from spamc) instead of falling back to
3701user I<nobody> and using the default configuration.
3702
3703=item B<-m> I<number> , B<--max-children>=I<number>
3704
3705This option specifies the maximum number of children to spawn.
3706Spamd will spawn that number of children, then sleep in the background
3707until a child dies, wherein it will go and spawn a new child.
3708
3709Incoming connections can still occur if all of the children are busy,
3710however those connections will be queued waiting for a free child.
3711The minimum value is C<1>, the default value is C<5>.
3712
3713Please note that there is a OS specific maximum of connections that can be
3714queued (Try C<perl -MSocket -e'print SOMAXCONN'> to find this maximum).
3715
3716Note that if you run too many servers for the amount of free RAM available, you
3717run the danger of hurting performance by causing a high swap load as server
3718processes are swapped in and out continually.
3719
3720=item B<--min-children>=I<number>
3721
3722The minimum number of children that will be kept running.  The minimum value is
3723C<1>, the default value is C<1>.  If you have lots of free RAM, you may want to
3724increase this.
3725
3726=item B<--min-spare>=I<number>
3727
3728The lower limit for the number of spare children allowed to run.  A
3729spare, or idle, child is one that is not handling a scan request.   If
3730there are too few spare children available, a new server will be started
3731every second or so.  The default value is C<1>.
3732
3733=item B<--max-spare>=I<number>
3734
3735The upper limit for the number of spare children allowed to run.  If there
3736are too many spare children, one will be killed every second or so until
3737the number of idle children is in the desired range.  The default value
3738is C<2>.
3739
3740=item B<--max-conn-per-child>=I<number>
3741
3742This option specifies the maximum number of connections each child
3743should process before dying and letting the master spamd process spawn
3744a new child.  The minimum value is C<1>, the default value is C<200>.
3745
3746=item B<--round-robin>
3747
3748By default, C<spamd> will attempt to keep a small number of "hot" child
3749processes as busy as possible, and keep any others as idle as possible, using
3750something similar to the Apache httpd server scaling algorithm.  This is
3751accomplished by the master process coordinating the activities of the children.
3752This switch will disable this scaling algorithm, and the behaviour seen in
3753the 3.0.x versions will be used instead, where all processes receive an
3754equal load and no scaling takes place.
3755
3756=item B<--timeout-tcp>=I<number>
3757
3758This option specifies the number of seconds to wait for headers from a
3759client (spamc) before closing the connection.  The minimum value is C<1>,
3760the default value is C<30>, and a value of C<0> will disable socket
3761timeouts completely.
3762
3763=item B<--timeout-child>=I<number>
3764
3765This option specifies the number of seconds to wait for a spamd child to
3766process or check a message.  The minimum value is C<1>, the default
3767value is C<300>, and a value of C<0> will disable child timeouts completely.
3768
3769=item B<-H> I<directory>, B<--helper-home-dir>=I<directory>
3770
3771Specify that external programs such as Razor, DCC, and Pyzor should have
3772a HOME environment variable set to a specific directory.  The default
3773is to use the HOME environment variable setting from the shell running
3774spamd.  By specifying no argument, spamd will use the spamc caller's
3775home directory instead.
3776
3777=item B<--ssl>
3778
3779Accept only SSL connections on the associated port.
3780The B<IO::Socket::SSL> perl module must be installed.
3781
3782If the B<--ssl> switch is used, and B<--ssl-port> is not supplied, then
3783B<--port> port will be used to accept SSL connections instead of unencrypted
3784connections.  If the B<--ssl> switch is used, and B<--ssl-port> is set, then
3785unencrypted connections will be accepted on the B<--port>, at the same time as
3786encrypted connections are accepted at B<--ssl-port>.
3787
3788=item B<--ssl-port>=I<port>
3789
3790Optionally specifies the port number for the server to listen on for
3791SSL connections (default: whatever --port uses).  See B<--ssl> for
3792more details.
3793
3794=item B<--server-key> I<keyfile>
3795
3796Specify the SSL key file to use for SSL connections.
3797
3798=item B<--server-cert> I<certfile>
3799
3800Specify the SSL certificate file to use for SSL connections.
3801
3802=item B<--socketpath> I<pathname>
3803
3804Listen on a UNIX domain socket at path I<pathname>, in addition to
3805sockets specified with a C<--listen> option. This option is provided
3806for compatibility with older versions of spamd. Starting with version
38073.4.0 the C<--listen> option can also take a UNIX domain socket as its
3808value (an absolute path name). Unlike C<--socketpath>, the C<--listen>
3809option may be specified multiple times if spamd needs to listen on
3810multiple UNIX or INET or INET6 sockets.
3811
3812Warning: the Perl support on BSD platforms for UNIX domain sockets seems to
3813have a bug regarding paths of over 100 bytes or so (SpamAssassin bug 4380).
3814If you see a 'could not find newly-created UNIX socket' error message, and
3815the path appears truncated, this may be the cause.  Try using a shorter path
3816to the socket.
3817
3818By default, use of B<--socketpath> without B<--listen> will inhibit
3819SSL connections and unencrypted TCP connections.  To add other sockets,
3820specify them with B<--listen>, e.g. '--listen=:' or '--listen=*:'
3821
3822=item B<--socketowner> I<name>
3823
3824Set UNIX domain socket to be owned by the user named I<name>.  Note
3825that this requires that spamd be started as C<root>, and if C<-u>
3826is used, that user should have write permissions to unlink the file
3827later, for when the C<spamd> server is killed.
3828
3829=item B<--socketgroup> I<name>
3830
3831Set UNIX domain socket to be owned by the group named I<name>.  See
3832C<--socketowner> for notes on ownership and permissions.
3833
3834=item B<--socketmode> I<mode>
3835
3836Set UNIX domain socket to use the octal mode I<mode>.  Note that if C<-u> is
3837used, that user should have write permissions to unlink the file later, for
3838when the C<spamd> server is killed.
3839
3840
3841=item B<--timing>
3842
3843  Enable timing measurements and output the information for logging.  This
3844  is the same information as provided by the TIMING tag.
3845
3846=back
3847
3848=head1 SEE ALSO
3849
3850spamc(1)
3851spamassassin(1)
3852Mail::SpamAssassin::Conf(3)
3853Mail::SpamAssassin(3)
3854
3855=head1 PREREQUISITES
3856
3857C<Mail::SpamAssassin>
3858
3859=head1 AUTHORS
3860
3861The SpamAssassin(tm) Project (https://spamassassin.apache.org/)
3862
3863=head1 LICENSE
3864
3865SpamAssassin is distributed under the Apache License, Version 2.0, as
3866described in the file C<LICENSE> included with the distribution.
3867
3868=cut
3869