1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214
|
#!perl
=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)>.
When called with more than one parameter, this plugin is probably not usable
with qpsmtpd-async.
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 File::Path qw(mkpath);
use Sys::Hostname qw(hostname);
use Time::HiRes qw(gettimeofday);
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->{_perms};
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);
}
|