Lines 12-18
Link Here
|
12 |
|
12 |
|
13 |
use lib '../lib'; # added by jm for use inside the distro |
13 |
use lib '../lib'; # added by jm for use inside the distro |
14 |
use strict; |
14 |
use strict; |
15 |
use Socket; |
15 |
use IO::Socket; |
|
|
16 |
use IO::Handle; |
16 |
use Carp; |
17 |
use Carp; |
17 |
use Config; |
18 |
use Config; |
18 |
use Mail::SpamAssassin; |
19 |
use Mail::SpamAssassin; |
Lines 48-53
Link Here
|
48 |
my @OLD_ARGV = @ARGV; # Getopt::Long tends to clear @ARGV |
50 |
my @OLD_ARGV = @ARGV; # Getopt::Long tends to clear @ARGV |
49 |
Getopt::Long::Configure ("bundling"); |
51 |
Getopt::Long::Configure ("bundling"); |
50 |
GetOptions( |
52 |
GetOptions( |
|
|
53 |
'server-key=s' => \$opt{'server-key'}, |
54 |
'server-cert=s' => \$opt{'server-cert'}, |
55 |
'ssl' => \$opt{'ssl'}, |
51 |
'auto-whitelist|whitelist|a' => \$opt{'auto-whitelist'}, |
56 |
'auto-whitelist|whitelist|a' => \$opt{'auto-whitelist'}, |
52 |
'create-prefs!', => \$opt{'create-prefs'}, 'c' => \$opt{'create-prefs'}, |
57 |
'create-prefs!', => \$opt{'create-prefs'}, 'c' => \$opt{'create-prefs'}, |
53 |
'daemonize!' => \$opt{'daemonize'}, 'd' => \$opt{'daemonize'}, |
58 |
'daemonize!' => \$opt{'daemonize'}, 'd' => \$opt{'daemonize'}, |
Lines 79-84
Link Here
|
79 |
|
84 |
|
80 |
$opt{'help'} and pod2usage(-exitval => $resphash{'EX_USAGE'}, -verbose => 0, -message => 'For more details, use "man spamd"'); |
85 |
$opt{'help'} and pod2usage(-exitval => $resphash{'EX_USAGE'}, -verbose => 0, -message => 'For more details, use "man spamd"'); |
81 |
|
86 |
|
|
|
87 |
# Check for server certs |
88 |
$opt{'server-key'} ||= "$LOCAL_RULES_DIR/certs/server-key.pem"; |
89 |
$opt{'server-cert'} ||= "$LOCAL_RULES_DIR/certs/server-cert.pem"; |
90 |
if ($opt{'ssl'}) |
91 |
{ |
92 |
require IO::Socket::SSL; |
93 |
|
94 |
if (!-e $opt{'server-key'}) |
95 |
{ |
96 |
die "The server key file $opt{'server-key'} does not exist"; |
97 |
} |
98 |
if (!-e $opt{'server-cert'}) |
99 |
{ |
100 |
die "The server certificate file $opt{'server-cert'} does not exist"; |
101 |
} |
102 |
} |
103 |
|
82 |
# These can be changed on command line with -A flag |
104 |
# These can be changed on command line with -A flag |
83 |
if(@{$opt{'allowed-ip'}}) |
105 |
if(@{$opt{'allowed-ip'}}) |
84 |
{ |
106 |
{ |
Lines 138-153
Link Here
|
138 |
setlogsock($socktype) unless $log_facility eq 'stderr'; |
161 |
setlogsock($socktype) unless $log_facility eq 'stderr'; |
139 |
|
162 |
|
140 |
my $port = $opt{'port'} || 783; |
163 |
my $port = $opt{'port'} || 783; |
141 |
my $addr = gethostbyname($opt{'listen-ip'} || '127.0.0.1'); |
164 |
my $addr = (gethostbyname($opt{'listen-ip'} || '127.0.0.1'))[0]; |
142 |
my $proto = getprotobyname('tcp'); |
165 |
my $proto = getprotobyname('tcp'); |
143 |
|
166 |
|
144 |
($port) = $port =~ /^(\d+)$/ or die "invalid port"; |
167 |
($port) = $port =~ /^(\d+)$/ or die "invalid port"; |
145 |
|
168 |
|
146 |
# Be a well-behaved daemon |
169 |
# Be a well-behaved daemon |
147 |
socket(Server, PF_INET, SOCK_STREAM, $proto) || die "socket: $!"; |
170 |
my $server; |
148 |
setsockopt(Server,SOL_SOCKET,SO_REUSEADDR,pack("l",1)) || die "setsockopt: $!"; |
171 |
if ($opt{'ssl'}) |
149 |
bind(Server, sockaddr_in($port, $addr)) || die "bind: $!"; |
172 |
{ |
150 |
listen(Server,SOMAXCONN) || die "listen: $!"; |
173 |
$server = new IO::Socket::SSL(LocalAddr => $addr, |
|
|
174 |
LocalPort => $port, |
175 |
Proto => $proto, |
176 |
Type => SOCK_STREAM, |
177 |
ReuseAddr => 1, |
178 |
Listen => 5, |
179 |
SSL_verify_mode => 0x00, |
180 |
SSL_key_file => $opt{'server-key'}, |
181 |
SSL_cert_file => $opt{'server-cert'}) |
182 |
|| die "new IO::Socket::SSL: $! $@"; |
183 |
} |
184 |
else |
185 |
{ |
186 |
$server = new IO::Socket::INET(LocalAddr => $addr, |
187 |
LocalPort => $port, |
188 |
Proto => $proto, |
189 |
Type => SOCK_STREAM, |
190 |
ReuseAddr => 1, |
191 |
Listen => 10) |
192 |
|| die "new IO::Socket::INET: $! $@"; |
193 |
} |
151 |
|
194 |
|
152 |
$opt{'daemonize'} and daemonize(); |
195 |
$opt{'daemonize'} and daemonize(); |
153 |
|
196 |
|
Lines 205-210
Link Here
|
205 |
|
248 |
|
206 |
my $current_user; |
249 |
my $current_user; |
207 |
my $paddr; |
250 |
my $paddr; |
|
|
251 |
my $client; |
208 |
|
252 |
|
209 |
sub REAPER { # Adapted from the perlipc manpage |
253 |
sub REAPER { # Adapted from the perlipc manpage |
210 |
cleanupchildren; |
254 |
cleanupchildren; |
Lines 227-238
Link Here
|
227 |
} |
271 |
} |
228 |
logmsg "server started on port $port (running version ".Mail::SpamAssassin::Version().")"; |
272 |
logmsg "server started on port $port (running version ".Mail::SpamAssassin::Version().")"; |
229 |
|
273 |
|
230 |
for ( ; ($paddr = accept(Client,Server)) ; close Client) |
274 |
while (1) |
231 |
{ |
275 |
{ |
232 |
|
276 |
$client = $server->accept |
|
|
277 |
|| next; |
233 |
my $start = time; |
278 |
my $start = time; |
234 |
|
279 |
|
235 |
my($port,$iaddr) = sockaddr_in($paddr); |
280 |
my($port,$iaddr) = sockaddr_in($client->peername); |
236 |
my $name = gethostbyaddr($iaddr,AF_INET); |
281 |
my $name = gethostbyaddr($iaddr,AF_INET); |
237 |
|
282 |
|
238 |
if (ip_is_allowed(inet_ntoa($iaddr))) { |
283 |
if (ip_is_allowed(inet_ntoa($iaddr))) { |
Lines 241-257
Link Here
|
241 |
} else { |
286 |
} else { |
242 |
logmsg "unauthorized connection from $name [", |
287 |
logmsg "unauthorized connection from $name [", |
243 |
inet_ntoa($iaddr),"] at port $port"; |
288 |
inet_ntoa($iaddr),"] at port $port"; |
|
|
289 |
$client->close; |
244 |
next; |
290 |
next; |
245 |
} |
291 |
} |
246 |
|
292 |
|
247 |
spawn sub { |
293 |
spawn sub { |
248 |
$|=1; # always immediately flush output |
294 |
$|=1; # always immediately flush output |
249 |
|
295 |
|
|
|
296 |
my ($client) = @_; |
297 |
|
250 |
# First request line off stream |
298 |
# First request line off stream |
251 |
local $_ = <STDIN>; |
299 |
local $_ = <$client>; |
252 |
|
300 |
|
253 |
if (!defined $_) { |
301 |
if (!defined $_) { |
254 |
protocol_error ("(closed before headers)"); |
302 |
protocol_error ("(closed before headers)", $client); |
255 |
return 1; |
303 |
return 1; |
256 |
} |
304 |
} |
257 |
|
305 |
|
Lines 285-294
Link Here
|
285 |
{ |
333 |
{ |
286 |
while(1) |
334 |
while(1) |
287 |
{ |
335 |
{ |
288 |
$_ = <STDIN>; |
336 |
$_ = <$client>; |
289 |
if(!defined $_) |
337 |
if(!defined $_) |
290 |
{ |
338 |
{ |
291 |
protocol_error ("(EOF during headers)"); |
339 |
protocol_error ("(EOF during headers)", $client); |
292 |
return 1; |
340 |
return 1; |
293 |
} |
341 |
} |
294 |
|
342 |
|
Lines 348-354
Link Here
|
348 |
my $actual_length; |
396 |
my $actual_length; |
349 |
my $in_header = 1; |
397 |
my $in_header = 1; |
350 |
my $msgid; |
398 |
my $msgid; |
351 |
for (<STDIN>) { |
399 |
while (<$client>) { |
352 |
if ($in_header) { |
400 |
if ($in_header) { |
353 |
if (/^$/) { |
401 |
if (/^$/) { |
354 |
$in_header = 0; |
402 |
$in_header = 0; |
Lines 363-368
Link Here
|
363 |
} |
411 |
} |
364 |
push(@msglines, $_); |
412 |
push(@msglines, $_); |
365 |
$actual_length += length; |
413 |
$actual_length += length; |
|
|
414 |
last if $actual_length == $expected_length; |
366 |
} |
415 |
} |
367 |
|
416 |
|
368 |
logmsg "checking message $msgid for $current_user:$>" . |
417 |
logmsg "checking message $msgid for $current_user:$>" . |
Lines 403-411
Link Here
|
403 |
{ |
452 |
{ |
404 |
$response_header .= "Spam: False ; $msg_score / $msg_threshold"; |
453 |
$response_header .= "Spam: False ; $msg_score / $msg_threshold"; |
405 |
} |
454 |
} |
406 |
print $response_header, "\r\n\r\n"; |
455 |
print $client $response_header, "\r\n\r\n"; |
407 |
print $status->get_names_of_tests_hit,"\r\n" if ($method eq "SYMBOLS"); |
456 |
print $client $status->get_names_of_tests_hit,"\r\n" if ($method eq "SYMBOLS"); |
408 |
print $status->get_report,"\r\n" if ($method eq "REPORT" or $method eq "REPORT_IFSPAM" and $status->is_spam); |
457 |
print $client $status->get_report,"\r\n" if ($method eq "REPORT" or $method eq "REPORT_IFSPAM" and $status->is_spam); |
409 |
$current_user ||= '(unknown)'; |
458 |
$current_user ||= '(unknown)'; |
410 |
logmsg "$was_it_spam ($msg_score/$msg_threshold) for $current_user:$> in ". |
459 |
logmsg "$was_it_spam ($msg_score/$msg_threshold) for $current_user:$> in ". |
411 |
sprintf("%3d", time - $start) ." seconds, $actual_length bytes.\n"; |
460 |
sprintf("%3d", time - $start) ." seconds, $actual_length bytes.\n"; |
Lines 429-438
Link Here
|
429 |
{ |
478 |
{ |
430 |
while(1) |
479 |
while(1) |
431 |
{ |
480 |
{ |
432 |
$_ = <STDIN>; |
481 |
$_ = <$client>; |
433 |
if(!defined $_) |
482 |
if(!defined $_) |
434 |
{ |
483 |
{ |
435 |
protocol_error ("(EOF during headers)"); |
484 |
protocol_error ("(EOF during headers)", $client); |
436 |
return 1; |
485 |
return 1; |
437 |
} |
486 |
} |
438 |
|
487 |
|
Lines 492-498
Link Here
|
492 |
my $actual_length; |
541 |
my $actual_length; |
493 |
my $in_header = 1; |
542 |
my $in_header = 1; |
494 |
my $msgid; |
543 |
my $msgid; |
495 |
for (<STDIN>) { |
544 |
while (<$client>) { |
496 |
if ($in_header) { |
545 |
if ($in_header) { |
497 |
if (/^$/) { |
546 |
if (/^$/) { |
498 |
$in_header = 0; |
547 |
$in_header = 0; |
Lines 507-512
Link Here
|
507 |
} |
556 |
} |
508 |
push(@msglines, $_); |
557 |
push(@msglines, $_); |
509 |
$actual_length += length; |
558 |
$actual_length += length; |
|
|
559 |
last if $actual_length == $expected_length; |
510 |
} |
560 |
} |
511 |
|
561 |
|
512 |
logmsg "processing message $msgid for $current_user:$>" . |
562 |
logmsg "processing message $msgid for $current_user:$>" . |
Lines 519-525
Link Here
|
519 |
# Check length if we're supposed to |
569 |
# Check length if we're supposed to |
520 |
if($expected_length) |
570 |
if($expected_length) |
521 |
{ |
571 |
{ |
522 |
if($actual_length != $expected_length) { protocol_error ("(Content-length mismatch: $expected_length vs. $actual_length)"); return 1; } |
572 |
if($actual_length != $expected_length) { |
|
|
573 |
protocol_error ("(Content-length mismatch: $expected_length vs. $actual_length)", $client); return 1; } |
523 |
} |
574 |
} |
524 |
|
575 |
|
525 |
# Now use copy-on-writed (hopefully) SA object |
576 |
# Now use copy-on-writed (hopefully) SA object |
Lines 531-543
Link Here
|
531 |
my $msg_resp_length = length($msg_resp); |
582 |
my $msg_resp_length = length($msg_resp); |
532 |
if($version >= 1.2) # Spamc protocol 1.2 means it accepts content-length |
583 |
if($version >= 1.2) # Spamc protocol 1.2 means it accepts content-length |
533 |
{ |
584 |
{ |
534 |
print "SPAMD/1.1 $resphash{$resp} $resp\r\n", |
585 |
print $client "SPAMD/1.1 $resphash{$resp} $resp\r\n", |
535 |
"Content-length: $msg_resp_length\r\n\r\n", |
586 |
"Content-length: $msg_resp_length\r\n\r\n", |
536 |
$msg_resp; |
587 |
$msg_resp; |
537 |
} |
588 |
} |
538 |
else # Earlier than 1.2 didn't accept content-length |
589 |
else # Earlier than 1.2 didn't accept content-length |
539 |
{ |
590 |
{ |
540 |
print "SPAMD/1.0 $resphash{$resp} $resp\r\n", |
591 |
print $client "SPAMD/1.0 $resphash{$resp} $resp\r\n", |
541 |
$msg_resp; |
592 |
$msg_resp; |
542 |
} |
593 |
} |
543 |
my $was_it_spam; |
594 |
my $was_it_spam; |
Lines 555-561
Link Here
|
555 |
|
606 |
|
556 |
else |
607 |
else |
557 |
{ |
608 |
{ |
558 |
protocol_error ($_); |
609 |
protocol_error ($_, $client); |
559 |
} |
610 |
} |
560 |
}; |
611 |
}; |
561 |
|
612 |
|
Lines 566-578
Link Here
|
566 |
# essentially does this! |
617 |
# essentially does this! |
567 |
|
618 |
|
568 |
cleanupchildren; |
619 |
cleanupchildren; |
|
|
620 |
$client->close; |
569 |
} |
621 |
} |
570 |
|
622 |
|
571 |
sub protocol_error { |
623 |
sub protocol_error { |
572 |
local $_ = shift; |
624 |
local $_ = shift; |
|
|
625 |
my $client = shift; |
573 |
|
626 |
|
574 |
my $resp = "EX_PROTOCOL"; |
627 |
my $resp = "EX_PROTOCOL"; |
575 |
print "SPAMD/1.0 $resphash{$resp} Bad header line: $_\r\n"; |
628 |
print $client "SPAMD/1.0 $resphash{$resp} Bad header line: $_\r\n"; |
576 |
logmsg "bad protocol: header error: $_"; |
629 |
logmsg "bad protocol: header error: $_"; |
577 |
} |
630 |
} |
578 |
|
631 |
|
Lines 605-614
Link Here
|
605 |
} |
658 |
} |
606 |
# else I'm the child -- go spawn |
659 |
# else I'm the child -- go spawn |
607 |
|
660 |
|
608 |
close Server; |
661 |
$server->close; |
609 |
open(STDIN, "<&Client") || die "can't dup client to stdin"; |
662 |
#STDIN->fdopen($client, "r") || die "can't dup client to stdin"; |
610 |
open(STDOUT, ">&Client") || die "can't dup client to stdout"; |
663 |
#STDOUT->fdopen($client, "w")|| die "can't dup client to stdout"; |
611 |
exit &$coderef(); |
664 |
exit &$coderef($client); |
612 |
} |
665 |
} |
613 |
|
666 |
|
614 |
sub handle_user |
667 |
sub handle_user |
Lines 773-779
Link Here
|
773 |
{ |
826 |
{ |
774 |
my ($sig) = @_; |
827 |
my ($sig) = @_; |
775 |
logmsg "server killed by SIG$sig, shutting down"; |
828 |
logmsg "server killed by SIG$sig, shutting down"; |
776 |
close Server; |
829 |
$server->close; |
777 |
defined($opt{'pidfile'}) and unlink($opt{'pidfile'}); |
830 |
defined($opt{'pidfile'}) and unlink($opt{'pidfile'}); |
778 |
exit 0; |
831 |
exit 0; |
779 |
} |
832 |
} |
Lines 855-860
Link Here
|
855 |
-V, --virtual-config=dir Enable Virtual configs (needs -x) |
908 |
-V, --virtual-config=dir Enable Virtual configs (needs -x) |
856 |
-r pidfile, --pidfile Write the process id to pidfile |
909 |
-r pidfile, --pidfile Write the process id to pidfile |
857 |
-s facility, --syslog=facility Specify the syslog facility (default: mail) |
910 |
-s facility, --syslog=facility Specify the syslog facility (default: mail) |
|
|
911 |
--server-key keyfile Specify an SSL keyfile |
912 |
--server-cert certfile Specify an SSL certificate |
913 |
--ssl Run an SSL server |
858 |
-u username, --username=username Run as username |
914 |
-u username, --username=username Run as username |
859 |
-v, --vpopmail Enable vpopmail config |
915 |
-v, --vpopmail Enable vpopmail config |
860 |
-x, --nouser-config Disable user config files |
916 |
-x, --nouser-config Disable user config files |