1e82ae1bc7
1. the known users of async don't upgrade 2. async becomes a win when concurrent connections exceed a few hundred simultaneous 3. anyone that needs async should be looking at Haraka instead 4. the perl async dependencies aren't maintained
227 lines
5.9 KiB
Perl
227 lines
5.9 KiB
Perl
#!perl -w
|
|
|
|
=head1 NAME
|
|
|
|
queue/maildir
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This plugin delivers mails to a maildir spool.
|
|
|
|
=head1 CONFIG
|
|
|
|
It takes one required parameter, the location of the maildir.
|
|
|
|
A second optional parameter delivers the mail into a sub directory named by
|
|
the recipient of the mail B<for each recipient>. Some substituions take place.
|
|
Before replacing the parts descibed below, any character of the recipient
|
|
address, which is not one of C<-A-Za-z0-9+_.,@=> is set to a C<_>.
|
|
|
|
If a third parameter is given, it will be used as octal (!) permisson of the
|
|
newly created files and directories, any execute bits will be stripped for
|
|
files: Use C<770> to create group writable directories and files with mode
|
|
C<0660>.
|
|
|
|
=head2 Maildir spool directory substitutions
|
|
|
|
=over 4
|
|
|
|
=item %l
|
|
|
|
Replaced by the local part of the address (i.e. the username)
|
|
|
|
=item %d
|
|
|
|
Replaced by the domain part of the address (i.e. the domain name)
|
|
|
|
=item %u
|
|
|
|
Replaced by the full address.
|
|
|
|
=cut
|
|
|
|
# =item %%
|
|
#
|
|
# Replaced by a single percent sign (%)
|
|
#
|
|
# =cut
|
|
|
|
=back
|
|
|
|
Examples: if the plugin is loaded with the parameters
|
|
|
|
queue/maildir /var/spool/qpdeliver %d/%l
|
|
|
|
and the recipient is C<user@example.com> the mails will be written to
|
|
the C<new> sub directory of C</var/spool/qpdeliver/example.com/user/>.
|
|
|
|
With
|
|
|
|
queue/maildir /var/spool/qpdeliver %u
|
|
|
|
and a recipient of C<user@example.org> the mail goes to
|
|
C</var/spool/qpdeliver/user@example.org>.
|
|
|
|
=head1 NOTES
|
|
|
|
Names of the substitution parameters and the replaced charachters are the same
|
|
L<spamd(8)> supports, for more info see the C<--virtual-config-dir>
|
|
option of L<spamd(8)>.
|
|
|
|
With the the second parameter being C<%d> it will still deliver one message
|
|
for each recipient: With the two recpients C<user@example.org> and
|
|
C<user2@example.org> you get two messages in the C<example.org/> directory.
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
|
|
use File::Path qw(mkpath);
|
|
use Sys::Hostname qw(hostname);
|
|
use Time::HiRes qw(gettimeofday);
|
|
|
|
use Qpsmtpd::Constants;
|
|
|
|
sub register {
|
|
my ($self, $qp, @args) = @_;
|
|
|
|
if (@args > 0) {
|
|
($self->{_maildir}) = ($args[0] =~ m!([-/\w\.]+)!);
|
|
}
|
|
|
|
if (@args > 1) {
|
|
($self->{_subdirs}) = ($args[1] =~ m#^(.*\%[ldu].*)$#);
|
|
unless ($self->{_subdirs}) {
|
|
$self->log(LOGWARN,
|
|
"WARNING: sub directory does not contain a "
|
|
. "substitution parameter"
|
|
);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
if (@args > 2) {
|
|
($self->{_perms}) = ($args[2] =~ /^([0-7]{3})$/);
|
|
unless ($self->{_perms}) { # 000 is unfortunately true ;-)
|
|
$self->log(LOGWARN, "WARNING: mode is not an octal number");
|
|
return 0;
|
|
}
|
|
$self->{_perms} = oct($self->{_perms});
|
|
}
|
|
|
|
$self->{_perms} ||= 0700;
|
|
|
|
unless ($self->{_maildir}) {
|
|
$self->log(LOGWARN, "WARNING: maildir directory not specified");
|
|
return 0;
|
|
}
|
|
|
|
unless ($self->{_subdirs}) {
|
|
|
|
# mkpath is influenced by umask...
|
|
my $old_umask = umask 000;
|
|
map {
|
|
my $d = $self->{_maildir} . "/$_";
|
|
-e $d or mkpath $d, 0, $self->{_perms}
|
|
} qw(cur tmp new);
|
|
umask $old_umask;
|
|
}
|
|
|
|
my $hostname = (hostname =~ m/([\w\._\-]+)/)[0];
|
|
$self->{_hostname} = $hostname;
|
|
|
|
}
|
|
|
|
my $maildir_counter = 0;
|
|
|
|
sub hook_queue {
|
|
my ($self, $transaction) = @_;
|
|
my ($rc, @msg);
|
|
my $old_umask = umask($self->{_perms} ^ 0777);
|
|
|
|
if ($self->{_subdirs}) {
|
|
foreach my $addr ($transaction->recipients) {
|
|
($rc, @msg) = $self->deliver_user($transaction, $addr);
|
|
unless ($rc == OK) {
|
|
umask $old_umask;
|
|
return ($rc, @msg);
|
|
}
|
|
}
|
|
umask $old_umask;
|
|
return (OK, @msg); # last @msg is the same like any other before...
|
|
}
|
|
|
|
$transaction->header->add('Delivered-To', $_->address, 0)
|
|
for $transaction->recipients;
|
|
($rc, @msg) = $self->write_file($transaction, $self->{_maildir});
|
|
umask $old_umask;
|
|
return ($rc, @msg);
|
|
}
|
|
|
|
sub write_file {
|
|
my ($self, $transaction, $maildir, $addr) = @_;
|
|
my ($time, $microseconds) = gettimeofday;
|
|
|
|
$time = ($time =~ m/(\d+)/)[0];
|
|
$microseconds =~ s/\D//g;
|
|
|
|
my $unique = "P$$" . "M$microseconds" . "Q" . $maildir_counter++;
|
|
my $file = join ".", $time, $unique, $self->{_hostname};
|
|
|
|
open(MF, ">$maildir/tmp/$file")
|
|
or $self->log(LOGWARN, "could not open $maildir/tmp/$file: $!"),
|
|
return (DECLINED, "queue error (open)");
|
|
|
|
print MF "Return-Path: ", $transaction->sender->format, "\n";
|
|
|
|
print MF "Delivered-To: ", $addr->address, "\n"
|
|
if $addr; # else it had been added before...
|
|
|
|
$transaction->header->print(\*MF);
|
|
$transaction->body_resetpos;
|
|
while (my $line = $transaction->body_getline) {
|
|
print MF $line;
|
|
}
|
|
close MF
|
|
or $self->log(LOGWARN, "could not close $maildir/tmp/$file: $!")
|
|
and return (DECLINED, "queue error (close)");
|
|
|
|
link "$maildir/tmp/$file",
|
|
"$maildir/new/$file"
|
|
or $self->log(LOGWARN,
|
|
"could not link $maildir/tmp/$file to $maildir/new/$file: $!")
|
|
and return (DECLINED, "queue error (link)");
|
|
|
|
unlink "$maildir/tmp/$file";
|
|
|
|
my $msg_id = $transaction->header->get('Message-Id') || '';
|
|
$msg_id =~ s/[\r\n].*//s;
|
|
|
|
return (OK, "Queued! $msg_id");
|
|
}
|
|
|
|
sub deliver_user {
|
|
my ($self, $transaction, $addr) = @_;
|
|
my $user = $addr->user;
|
|
$user =~ tr/-A-Za-z0-9+_.,@=/_/c;
|
|
my $host = $addr->host;
|
|
$host =~ tr/-A-Za-z0-9+_.,@=/_/c;
|
|
my $rcpt = $user . '@' . $host;
|
|
|
|
my $subdir = $self->{_subdirs};
|
|
$subdir =~ s/\%l/$user/g;
|
|
$subdir =~ s/\%d/$host/g;
|
|
$subdir =~ s/\%u/$rcpt/g;
|
|
|
|
# $subdir =~ s/\%%/%/g;
|
|
|
|
my $maildir = $self->{_maildir} . "/$subdir";
|
|
my $old_umask = umask 000;
|
|
map { my $d = $maildir . "/$_"; -e $d or mkpath $d, 0, $self->{_perms} }
|
|
qw(cur tmp new);
|
|
umask $old_umask;
|
|
|
|
return $self->write_file($transaction, $maildir, $addr);
|
|
}
|
|
|