2012-04-29 10:35:59 +02:00
|
|
|
#!perl -w
|
2008-06-28 10:14:14 +02:00
|
|
|
|
2004-05-24 13:36:04 +02:00
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
queue/maildir
|
|
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
|
2008-06-28 10:14:14 +02:00
|
|
|
This plugin delivers mails to a maildir spool.
|
2004-05-24 13:36:04 +02:00
|
|
|
|
|
|
|
=head1 CONFIG
|
|
|
|
|
|
|
|
It takes one required parameter, the location of the maildir.
|
|
|
|
|
2008-06-28 10:14:14 +02:00
|
|
|
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 %%
|
2013-04-21 06:50:39 +02:00
|
|
|
#
|
2008-06-28 10:14:14 +02:00
|
|
|
# Replaced by a single percent sign (%)
|
2013-04-21 06:50:39 +02:00
|
|
|
#
|
2008-06-28 10:14:14 +02:00
|
|
|
# =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.
|
|
|
|
|
2004-05-24 13:36:04 +02:00
|
|
|
=cut
|
|
|
|
|
2014-09-17 20:38:40 +02:00
|
|
|
use strict;
|
|
|
|
|
2004-05-24 13:36:04 +02:00
|
|
|
use File::Path qw(mkpath);
|
|
|
|
use Sys::Hostname qw(hostname);
|
|
|
|
use Time::HiRes qw(gettimeofday);
|
|
|
|
|
2014-09-17 20:38:40 +02:00
|
|
|
use Qpsmtpd::Constants;
|
|
|
|
|
2004-05-24 13:36:04 +02:00
|
|
|
sub register {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $qp, @args) = @_;
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
if (@args > 0) {
|
|
|
|
($self->{_maildir}) = ($args[0] =~ m!([-/\w\.]+)!);
|
|
|
|
}
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
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;
|
|
|
|
}
|
2008-06-28 10:14:14 +02:00
|
|
|
}
|
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
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});
|
2008-06-28 10:14:14 +02:00
|
|
|
}
|
|
|
|
|
2014-09-17 20:38:40 +02:00
|
|
|
$self->{_perms} ||= 0700;
|
2008-06-28 10:14:14 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
unless ($self->{_maildir}) {
|
|
|
|
$self->log(LOGWARN, "WARNING: maildir directory not specified");
|
|
|
|
return 0;
|
|
|
|
}
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
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;
|
|
|
|
}
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
my $hostname = (hostname =~ m/([\w\._\-]+)/)[0];
|
|
|
|
$self->{_hostname} = $hostname;
|
2004-05-24 13:36:04 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
my $maildir_counter = 0;
|
|
|
|
|
2005-07-07 06:17:39 +02:00
|
|
|
sub hook_queue {
|
2013-04-21 06:50:39 +02:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
}
|
2008-06-28 10:14:14 +02:00
|
|
|
umask $old_umask;
|
2013-04-21 06:50:39 +02:00
|
|
|
return (OK, @msg); # last @msg is the same like any other before...
|
2008-06-28 10:14:14 +02:00
|
|
|
}
|
2013-04-21 06:50:39 +02:00
|
|
|
|
|
|
|
$transaction->header->add('Delivered-To', $_->address, 0)
|
|
|
|
for $transaction->recipients;
|
|
|
|
($rc, @msg) = $self->write_file($transaction, $self->{_maildir});
|
2008-06-28 10:14:14 +02:00
|
|
|
umask $old_umask;
|
2013-04-21 06:50:39 +02:00
|
|
|
return ($rc, @msg);
|
2008-06-28 10:14:14 +02:00
|
|
|
}
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2008-06-28 10:14:14 +02:00
|
|
|
sub write_file {
|
2013-04-21 06:50:39 +02:00
|
|
|
my ($self, $transaction, $maildir, $addr) = @_;
|
|
|
|
my ($time, $microseconds) = gettimeofday;
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
$time = ($time =~ m/(\d+)/)[0];
|
|
|
|
$microseconds =~ s/\D//g;
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
my $unique = "P$$" . "M$microseconds" . "Q" . $maildir_counter++;
|
|
|
|
my $file = join ".", $time, $unique, $self->{_hostname};
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
open(MF, ">$maildir/tmp/$file")
|
|
|
|
or $self->log(LOGWARN, "could not open $maildir/tmp/$file: $!"),
|
|
|
|
return (DECLINED, "queue error (open)");
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
print MF "Return-Path: ", $transaction->sender->format, "\n";
|
2008-08-05 11:06:07 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
print MF "Delivered-To: ", $addr->address, "\n"
|
|
|
|
if $addr; # else it had been added before...
|
2008-06-28 10:14:14 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
$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)");
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
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)");
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
unlink "$maildir/tmp/$file";
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
my $msg_id = $transaction->header->get('Message-Id') || '';
|
|
|
|
$msg_id =~ s/[\r\n].*//s;
|
2004-05-24 13:36:04 +02:00
|
|
|
|
2013-04-21 06:50:39 +02:00
|
|
|
return (OK, "Queued! $msg_id");
|
2004-05-24 13:36:04 +02:00
|
|
|
}
|
2008-06-28 10:14:14 +02:00
|
|
|
|
|
|
|
sub deliver_user {
|
2013-04-21 06:50:39 +02:00
|
|
|
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);
|
2008-06-28 10:14:14 +02:00
|
|
|
}
|
|
|
|
|