1
--- amavisd.ori Tue Jun 27 13:22:56 2006
2
+++ amavisd Tue Jun 27 13:23:43 2006
6
-#( Amavis::In::Courier )
11
fetch_modules('REQUIRED BASIC MODULES', 1, qw(
12
Exporter POSIX Fcntl Socket Errno Carp Time::HiRes
13
- IO::Handle IO::File IO::Socket IO::Socket::UNIX IO::Socket::INET
14
+ IO::Handle IO::File IO::Select IO::Socket IO::Socket::UNIX IO::Socket::INET
15
IO::Wrap IO::Stringy Digest::MD5 Unix::Syslog File::Basename
16
Mail::Field Mail::Address Mail::Header Mail::Internet Compress::Zlib
18
use POSIX qw(locale_h);
22
# body digest for caching, either SHA1 or MD5
23
@@ -6868,4 +6869,31 @@
26
+### This hook takes place immediately after the "->run()" method is called.
27
+### This hook allows for setting up the object before any built in configuration
28
+### takes place. This allows for custom configurability.
31
+ if ($courierfilter_shutdown) {
32
+ # Duplicate the courierfilter pipe to another fd since STDIN is closed if we
34
+ $self->{courierfilter_pipe} = IO::File->new('<&STDIN')
35
+ or die "Can't duplicate courierfilter shutdown pipe: $!";
36
+ $self->{courierfilter_select} = IO::Select->new($self->{courierfilter_pipe});
41
+### This hook occurs just after the bind process and just before any
42
+### chrooting, change of user, or change of group occurs. At this point
43
+### the process will still be running as the user who started the server.
46
+ if (c('protocol') eq 'COURIER') {
47
+ # Allow courier to write to the socket
48
+ chmod(0660, $unix_socketname);
53
### This hook occurs in the parent (master) process after chroot,
54
### change of user, and change of group has occured. It allows
55
@@ -6908,4 +6936,15 @@
57
Amavis::SpamControl::init_pre_fork() if $extra_code_antispam;
58
+ if ($courierfilter_shutdown) {
59
+ # Tell courierfilter we have finished initialisation by closing fd 3
60
+ # But make sure it's a pipe (and not the courierfilter shutdown pipe)
61
+ # first: if we have been started using filterctl (i.e. not when
62
+ # courierfilter itself starts) then there is no initial pipe on fd 3 so
63
+ # it could be assigned to another file
64
+ open(my $fh3, '<&3');
65
+ if (-p $fh3 && $self->{courierfilter_pipe}->fileno() != 3) {
72
if ($sock->NS_proto eq 'UNIX') { # traditional amavis helper program
73
if ($suggested_protocol eq 'COURIER') {
74
- die "unavailable support for protocol: $suggested_protocol";
75
+ # courierfilter client
76
+ $courier_in_obj = Amavis::In::Courier->new if !$courier_in_obj;
77
+ $courier_in_obj->process_courier_request($sock, $conn, \&check_mail);
78
} elsif ($suggested_protocol eq 'AM.PDP') {
79
$amcl_in_obj = Amavis::In::AMCL->new if !$amcl_in_obj;
80
@@ -7285,4 +7326,14 @@
83
+### Net::Server::PreForkSimple hook
84
+### Is run by the master process every 10 seconds if $courierfilter_shutdown is set
87
+ if ($self->{courierfilter_select}->can_read(0)) {
88
+ do_log(0, "Instructed by courierfilter to shutdown");
89
+ $self->server_close();
93
### Child is about to be terminated
94
### user customizable Net::Server hook
96
log_level => $DEBUG ? 4 : 2,
97
log_file => undef, # will be overridden to call do_log()
98
+ # 9 to ensure it runs EVERY 10 seconds
99
+ # (Net::Server::PreForkSimple only checks every 10 seconds)
100
+ check_for_dequeue => $courierfilter_shutdown ? 9 : undef,
101
+ max_dequeue => $courierfilter_shutdown ? 1 : undef,
104
@@ -12151,5 +12206,413 @@
105
no warnings 'uninitialized';
107
-BEGIN { die "Code not available for module Amavis::In::Courier" }
110
+ use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
111
+ $VERSION = '2.044';
112
+ @ISA = qw(Exporter);
118
+ import Amavis::Conf qw(:platform :confvars ca c);
119
+ import Amavis::Util qw(do_log am_id untaint debug_oneshot snmp_counters_init
120
+ switch_to_my_time switch_to_client_time
121
+ read_text xtext_encode xtext_decode);
122
+ import Amavis::Lookup qw(lookup);
123
+ import Amavis::Lookup::IP qw(lookup_ip_acl);
124
+ import Amavis::rfc2821_2822_Tools qw(quote_rfc2821_local qquote_rfc2821_local);
125
+ import Amavis::Timing qw(section_time);
126
+ import Amavis::TempDir;
127
+ import Amavis::In::Message;
130
+# Amavis::In::Courier->new()
131
+# Creates a new Amavis::In::Courier object
134
+ my $tempdir = Amavis::TempDir->new;
135
+ bless { tempdir => $tempdir }, $class;
138
+# courier_in_obj->process_courier_request(socket, conn, check_mail)
139
+# Processes a request from Courier to check a single message
140
+# socket: the socket to communicate with courierfilter
141
+# conn: Amavis::In::Connection object
142
+# check_mail: reference to the MTA-independent function called to check the
144
+sub process_courier_request($$$) {
145
+ my($self, $socket, $conn, $check_mail) = @_;
147
+ # Save the policy bank so that it can be restored at the end
148
+ my %baseline_policy_bank = %current_policy_bank;
149
+ my $policy_bank_changed = 0;
152
+ $self->init_request();
153
+ $self->read_courierfilter_socket($socket);
154
+ $self->open_mail_text();
155
+ $policy_bank_changed = $self->change_policy_bank();
156
+ $self->call_check_mail($conn, $check_mail);
157
+ $self->process_result();
160
+ # An exception occurred
162
+ my $msg = "Error in processing: $@";
163
+ do_log(-2, "TROUBLE in process_courier_request: 451 4.5.0 %s", $msg);
164
+ # Close the mail text file
165
+ $self->{msginfo}->mail_text->close() if ($self->{msginfo}->mail_text);
166
+ $self->{msginfo}->mail_text(undef);
167
+ # Send a temporary failure to Courier
168
+ $self->{smtp_resp} = "451 4.5.0 $msg";
171
+ # Send the SMTP reponse back to Courier (done outside the eval to ensure that
172
+ # it always happens exactly once, whether or not there is an exception)
173
+ do_log(3, "Mail checking ended: %s", $self->{smtp_resp});
174
+ send($socket, "$self->{smtp_resp}\n", 0);
177
+ section_time('send response');
178
+ do_log(2, Amavis::Timing::report());
180
+ # Restore the policy bank
181
+ %current_policy_bank = %baseline_policy_bank if ($policy_bank_changed);
184
+ $self->{per_recip_data} = undef;
185
+ $self->{control_files} = undef;
188
+# courier_in_obj->init_request( )
189
+# Begins processing for a single request: initialises global variables and
190
+# creates msginfo object
191
+sub init_request() {
195
+ am_id("$$-$Amavis::child_invocation_count");
196
+ Amavis::Timing::init();
197
+ snmp_counters_init();
199
+ # Create msginfo object
200
+ $self->{msginfo} = Amavis::In::Message->new;
201
+ $self->{msginfo}->rx_time(time);
204
+# courier_in_obj->read_courierfilter_socket(socket)
205
+# Reads the courierfilter socket, which specifies the path to the mail text and
206
+# the control files, storing the path to the mail text in the msginfo object
207
+# Also reads the control files and stores their data in msginfo
208
+# socket: The courierfilter socket
209
+sub read_courierfilter_socket($) {
210
+ my($self, $socket) = @_;
215
+ # Read the path to the mail text
216
+ switch_to_client_time("start receiving message text path");
217
+ my $text_path = $socket->getline;
218
+ switch_to_my_time("received message text path");
219
+ $text_path || die "Can't read message text path: $!";
221
+ $text_path = untaint($text_path) if ($text_path =~ m{^[A-Za-z0-9/._=+-]+\z});
222
+ $self->{msginfo}->mail_text_fn($text_path);
224
+ # Read control files
225
+ $self->{control_files} = [];
227
+ switch_to_client_time("start receving control file paths");
228
+ for ($! = 0; defined($path = $socket->getline); $! = 0) {
230
+ # courierfilter indicates end of control files by sending a blank line
233
+ switch_to_my_time("received control file path");
234
+ $path = untaint($path) if ($path =~ m{^[A-Za-z0-9/._=+-]+\z});
235
+ push(@{ $self->{control_files} }, $path);
236
+ $self->read_control_file($path);
237
+ switch_to_client_time("receiving control file paths");
239
+ switch_to_my_time("finished receiving control file paths");
241
+ # Check we did actually get a control file
242
+ @{ $self->{control_files} } || die "No control files specified";
243
+ # Record the recipients in msginfo
244
+ $self->{msginfo}->per_recip_data($self->{per_recip_data});
247
+ section_time('read control');
250
+# courier_in_obj->read_control_file(path)
251
+# Reads a single Courier control file, adding its recipients to
252
+# $self->{per_recip_data} and storing other information in msginfo.
253
+# $self->{per_recip_data} is an array of Amavis::In::Message::PerRecip objects.
254
+# (Note that this method will overwrite any previous settings for sender, etc,
255
+# but if there are multiple control files they should contain the same
257
+# path: the path to the control file
258
+sub read_control_file($) {
259
+ my($self, $path) = @_;
261
+ do_log(3, "Reading Courier control file %s", $path);
264
+ my $control_data = read_text($path);
265
+ my ($rcpt_idx, $recip) = (0, undef);
266
+ foreach (split(/\n/, $control_data)) {
267
+ # Parse a line of the control file
269
+ if (/^s ( .*? (?: \[ (?: \\. | [^\]\\] )* \]
270
+ | [^@"<>\[\]\\\s] )*
272
+ $self->{msginfo}->sender($1);
276
+ if (/^r ( .*? (?: \[ (?: \\. | [^\]\\] )* \]
277
+ | [^@"<>\[\]\\\s] )*
279
+ $recip = Amavis::In::Message::PerRecip->new;
280
+ $recip->recip_addr($1);
281
+ $recip->courier_control_file($path);
282
+ $recip->courier_recip_index($rcpt_idx);
283
+ $recip->recip_destiny(D_PASS); # Default destiny
284
+ push(@{ $self->{per_recip_data} }, $recip);
288
+ # Original Recipient (RFC 3461)
289
+ if (/^R ( [!-~]+ ) \z/xs) { $recip->dsn_orcpt($1) }
290
+ # RFC 3461 NOTIFY value
291
+ if (/^N ( [FSDN]+ ) \z/xs) {
292
+ my %notify_values = ( F => 'FAILURE', S => 'SUCCESS', D => 'DELAY', N => 'NEVER' );
293
+ $recip->dsn_notify([ map { $notify_values{$_} } split(m//, $1) ]);
296
+ # DSN RET parameter (RFC 3461)
297
+ if (/^t F \z/xs) { $self->{msginfo}->dsn_ret('FULL') }
298
+ if (/^t H \z/xs) { $self->{msginfo}->dsn_ret('HDRS') }
300
+ if (/^e ( [!-~]+ ) \z/xs) { $self->{msginfo}->dsn_envid($1) }
302
+ # Authenticated submitter (RFC 2554)
303
+ if (/^i ( [!-~]+ ) \z/xs) { $self->{msginfo}->auth_submitter(xtext_decode($1)) }
305
+ # Received-From-MTA
306
+ if (/^f .*? ;\s* ( [A-Za-z0-9\.-]+ | \[ [0-9A-Fa-f\.:]+ \] ) \s*
307
+ \( ( [A-Za-z0-9\.-]* ) \s* \[ ( [0-9A-Fa-f\.:]+ ) \] \)
309
+ $self->{msginfo}->client_helo($1);
310
+ $self->{msginfo}->client_name($2);
311
+ $self->{msginfo}->client_addr($3);
315
+ if (/^M ( [0-9A-Fa-f]+ \. [0-9A-Fa-f]+ \. [0-9A-Fa-f]+ )
317
+ $self->{msginfo}->queue_id($1);
322
+# courier_in_obj->open_mail_text( )
323
+# Opens the mail text file, whose path has been read into msginfo->mail_text_fn
324
+# The file handle is stored in msginfo->mail_text
325
+sub open_mail_text() {
329
+ my $fh = IO::File->new($self->{msginfo}->mail_text_fn, 'r');
330
+ $fh || die "Can't open ", $self->{msginfo}->mail_text_fn, ": $!";
332
+ # Disable UTF-8 decoding of input data
333
+ if ($unicode_aware) {
334
+ binmode($fh, ':bytes') || die "Can't cancel :utf8 mode: $!";
337
+ # Store file handle
338
+ $self->{msginfo}->mail_text($fh);
341
+ section_time('open text');
344
+# courier_in_obj->change_policy_bank( )
345
+# Loads a new policy bank if necessary
346
+# Also enables debug_oneshot if necessary, and sets msginfo->client_addr_mynets
347
+# Returns 1 if the policy bank is changed, 0 otherwise
348
+sub change_policy_bank() {
350
+ my $cl_ip = $self->{msginfo}->client_addr;
351
+ my $sender = $self->{msginfo}->sender;
352
+ my $policy_changed = 0;
354
+ # Enable debug_oneshot if set for this sender
355
+ debug_oneshot(1) if lookup(0, $sender, @{ ca('debug_sender_maps') });
357
+ # Load MYNETS policy bank if client IP is local
358
+ my $cl_ip_mynets = ($cl_ip eq '' ? undef
359
+ : lookup_ip_acl($cl_ip, @{ ca('mynetworks_maps') }));
360
+ $self->{msginfo}->client_addr_mynets($cl_ip_mynets);
361
+ if ($cl_ip_mynets && defined($policy_bank{'MYNETS'})) {
362
+ Amavis::load_policy_bank('MYNETS');
363
+ $policy_changed = 1;
366
+ # Load MYUSERS policy bank if sender is local
367
+ if ($sender ne '' && defined($policy_bank{'MYUSERS'})
368
+ && lookup(0, $sender, @{ ca('local_domains_maps') }))
370
+ Amavis::load_policy_bank('MYUSERS');
371
+ $policy_changed = 1;
377
+# courier_in_obj->call_check_mail(conn, check_mail)
378
+# Calls the check_mail function to check a message - the properties of msginfo
379
+# must already be set
380
+# Also handles the tempdir and closes the mail_text file afterwards
381
+# Saves the STMP response returned by check_mail in $self->{smtp_resp}
382
+# conn: Amavis::In::Connection object
383
+# check_mail: reference to the function to call
384
+sub call_check_mail($$) {
385
+ my($self, $conn, $check_mail) = @_;
387
+ # Initialise variables
388
+ Amavis::check_mail_begin_task();
390
+ # Prepare temporary directory
391
+ $self->{tempdir}->prepare();
392
+ $self->{msginfo}->mail_tempdir($self->{tempdir}->path);
394
+ # Courier is responsible for relaying the message, and so for success DSNs
395
+ $self->{msginfo}->dsn_passed_on(c('forward_method') eq '' ? 1 : 0);
398
+ do_log(1, 'Courier %s %s: <%s> -> %s%s',
399
+ $self->{msginfo}->queue_id, $self->{tempdir}->path,
400
+ $self->{msginfo}->sender,
401
+ join(',', qquote_rfc2821_local(@{ $self->{msginfo}->recips })),
403
+ !$self->{msginfo}->auth_submitter ||
404
+ $self->{msginfo}->auth_submitter eq '<>' ? ():
405
+ ' AUTH='.$self->{msginfo}->auth_submitter,
406
+ !$self->{msginfo}->dsn_ret ? () :
407
+ ' RET='.$self->{msginfo}->dsn_ret,
408
+ !$self->{msginfo}->dsn_envid ? () :
409
+ ' ENVID='.xtext_decode($self->{msginfo}->dsn_envid),
412
+ # The temporary directory is about to become non-empty
413
+ $self->{tempdir}->empty(0);
415
+ my ($smtp_resp, $exit_code, $preserve_evidence)
416
+ = $check_mail->($conn, $self->{msginfo}, 0);
417
+ # Preserve evidence if necessary
418
+ $preserve_evidence && $self->{tempdir}->preserve(1);
420
+ # Clean the temporary directory
421
+ $self->{tempdir}->clean();
423
+ # Close the mail text file
424
+ $self->{msginfo}->mail_text->close() || die "Can't close temp file: $!";
425
+ $self->{msginfo}->mail_text(undef);
427
+ # Save the SMTP response
428
+ $self->{smtp_resp} = $smtp_resp;
431
+# courier_in_obj->process_result( )
432
+# Processes the result of mail scanning - recipient addition/deletion (for the
433
+# time being we do not support this and only put a warning in the log)
434
+# Before calling this, the SMTP response must be stored in $self->{smtp_resp}
435
+# and may be altered
436
+# This does not send the SMTP response back to Courier
437
+sub process_result() {
440
+ if ($self->{smtp_resp} =~ /^25/) {
441
+ foreach my $r (@{ $self->{msginfo}->per_recip_data }) {
442
+ my ($addr, $newaddr) = ($r->recip_addr, $r->recip_final_addr);
444
+ if ($r->recip_done) {
445
+ $self->delete_recipient($r);
447
+ } elsif ($newaddr ne $addr) {
448
+ $r->recip_smtp_response("251 2.1.5 Amavisd replaced recip with <$newaddr>");
449
+ $self->delete_recipient($r);
451
+ my $orcpt = $r->dsn_orcpt || 'rfc822;'.xtext_encode(quote_rfc2821_local($_));
452
+ $self->add_recipient($newaddr, $orcpt, $r->dsn_notify);
458
+ section_time('process result');
461
+# delete_recipient(recip)
462
+# Deletes a recipient by marking them as successfully delivered in the control
463
+# file. If the same recipient appears more than once in the control files,
464
+# every instance will be marked as done.
465
+# recip: Amavis::In::Message::PerRecip object
466
+sub delete_recipient($) {
467
+ my($self, $recip) = @_;
469
+ do_log(1, "Amavis::In::Courier: Deleting recipient <%s>: %s",
470
+ $recip->recip_addr, $recip->recip_smtp_response);
472
+ my $filename = $recip->courier_control_file;
473
+ my $control_file = IO::File->new($filename, 'a');
474
+ # Not sure why we do the seek when the file is already opened for append,
475
+ # but courier-pythonfilter does it so it's probably a good idea
476
+ seek($control_file, 0, 2);
477
+ # Courier may still append to the control file after calling the filter,
478
+ # so write a long blank line first to ensure that its additional records
479
+ # only overwrite the blank line
480
+ $control_file->print(" " x 254, "\n");
482
+ # Tell Courier the message is delivered
483
+ $control_file->printf("I%d R %s\n", $recip->courier_recip_index,
484
+ $recip->recip_smtp_response);
485
+ $control_file->printf("S%d %d\n", $recip->courier_recip_index, time);
487
+ $control_file->close or die "Error closing control file $filename: $!";
490
+# add_recipient(recip)
491
+# Adds a recipient to the last control file for the message.
492
+# address: recipient address to add
493
+# orig_recip: RFC 3461 original recipient (if any)
494
+# notify: reference to array containing values of the DSN NOTIFY value
495
+sub add_recipient($$$) {
496
+ my ($self, $address, $orig_recip, $notify) = @_;
498
+ do_log(1, "Amavis::In::Courier: Adding recipient <%s>", $address);
500
+ # Convert $notify array into character string
501
+ my $notify_str = join('', map { substr($_, 0, 1) } @$notify);
503
+ # Open the last control file
504
+ my $filename = $self->{control_files}->[-1];
505
+ my $control_file = IO::File->new($filename, 'a');
506
+ # Take care with the control file: see comments in delete_recipient
507
+ seek($control_file, 0, 2);
508
+ $control_file->print(" " x 254, "\n");
510
+ # Add recipient to control file
511
+ $control_file->print("r$address\n");
512
+ $control_file->print("R$orig_recip\n");
513
+ $control_file->print("N$notify_str\n");
515
+ $control_file->close or die "Error closing control file $filename: $!";
519
--- amavisd.conf-sample.ori Tue Jun 27 13:23:34 2006
520
+++ amavisd.conf-sample Tue Jun 27 13:25:58 2006
522
#$notify_method = $forward_method;
524
+# COURIER using courierfilter
525
+#$forward_method = undef; # no explicit forwarding, Courier does it itself
526
+#$notify_method = 'pipe:flags=q argv=perl -e $pid=fork();if($pid==-1){exit(75)}elsif($pid==0){exec(@ARGV)}else{exit(0)} /usr/sbin/sendmail -f ${sender} -- ${recipient}';
527
+# Only set $courierfilter_shutdown to 1 if you are using courierfilter to
528
+# control the startup and shutdown of amavis
529
+#$courierfilter_shutdown = 1; # (default 0)
531
# prefer to collect mail for forwarding as BSMTP files?
532
#$forward_method = "bsmtp:$MYHOME/out-%i-%n.bsmtp";
536
-# AMAVIS-CLIENT PROTOCOL INPUT SETTINGS (e.g. with amavisd-release, or
537
+# AMAVIS-CLIENT AND COURIER PROTOCOL INPUT SETTINGS (e.g. amavisd-release, or
538
# sendmail milter through helper clients like amavis-milter.c and amavis.c)
539
# option(s) -p overrides $inet_socket_port and $unix_socketname
541
#$unix_socketname = undef; # disable listening on a unix socket
542
# (default is undef, i.e. disabled)
543
+#$unix_socketname = "/var/lib/courier/allfilters/amavisd"; # Courier socket
544
# (usual setting is $MYHOME/amavisd.sock)
546
@@ -2303,4 +2311,7 @@
547
#$interface_policy{'SOCK'} = 'AM.PDP-SOCK';
549
+# Needed for Courier: speak courier protocol on the socket
550
+#$interface_policy{'SOCK'} = 'AM-SOCK';
551
+#$policy_bank{'AM-SOCK'} = {protocol => 'COURIER'};
553
# Want to execute additional configuration files from some directory?