File: maildir

package info (click to toggle)
qpsmtpd 0.84-9
  • links: PTS
  • area: main
  • in suites: wheezy
  • size: 1,376 kB
  • sloc: perl: 8,012; sh: 382; makefile: 61
file content (214 lines) | stat: -rw-r--r-- 5,725 bytes parent folder | download | duplicates (4)
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);
}