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