1#!perl -w
2
3=head1 NAME
4
5postfix-queue
6
7=head1 DESCRIPTION
8
9This plugin passes mails on to the postfix cleanup daemon.
10
11=head1 CONFIG
12
13The first optional parameter is the location of the cleanup socket. If it does
14not start with a ``/'', it is treated as a flag for cleanup (see below).
15The 'postfix_queue' plugin can also contain a list of cleanup socket paths
16and/or remote postfix cleanup service hosts specified in the form of
17'address:port'.  If set, the environment variable POSTFIXQUEUE overrides both
18of these settings.
19
20All other parameters are flags for cleanup, no flags are enabled by default.
21See below in ``POSTFIX COMPATIBILITY'' for flags understood by your postfix
22version. Supported by all postfix versions E<gt>= 2.1 are:
23
24=over 4
25
26=item FLAG_FILTER
27
28Set the CLEANUP_FLAG_FILTER for cleanup. This enables the use of
29I<header_filter>, I<body_filter> or I<content_filter> in postfix' main.cf.
30
31=item FLAG_BCC_OK
32
33Setting this flag enables (for example) the I<recipient_bcc_maps> parameter
34
35=item FLAG_MAP_OK
36
37This flag enables the use of other recipient mappings (e.g.
38I<virtual_alias_maps>) in postfix' cleanup.
39
40=item FLAG_MASK_EXTERNAL
41
42This flag mask combines FLAG_FILTER, FLAG_MILTER (only in postfix >= 2.3)
43FLAG_BCC_OK and FLAG_MAP_OK and is used by postfix for external messages.
44This is probably what you want to use.
45
46=back
47
48For more flags see below in ``POSTFIX COMPATIBILITY'', your postfix version
49(grep _FLAG_ src/global/cleanup_user.h) and/or lib/Qpsmtpd/Postfix/Constants.pm
50
51=head1 POSTFIX COMPATIBILITY
52
53The first version of this plugin was written for postfix 1.x.
54
55The next step for Postfix 2.1 (and later) was to add the FLAG_FILTER,
56FLAG_BCC_OK and FLAG_MAP_OK flags for submission to the cleanup deamon.
57
58This version can use all flags found in Postfix 2.x (up to 2.4 currently).
59Unknown flags are ignored by the cleanup daemon (just tested with postfix
602.1), so it should be safe to set flags just understood by later versions
61of postfix/cleanup.
62
63Even if all known flags can be set, some are not that useful when feeding
64the message from qpsmtpd, e.g.
65
66=head2 FLAG_NONE
67
68no effect
69
70=head2 FLAG_DISCARD
71
72DON'T USE, use another plugin which hooks the I<hook_queue()> and returns
73B<OK> just for the messages you want to drop. As long as this plugin does
74not support setting queue flags on the fly from other modules, this flag
75would drop ALL messages. Don't use!
76
77=head2 FLAG_BOUNCE
78
79Qpsmtpd should be configured not to accept bad messages...
80
81=head2 FLAG_HOLD
82
83Not useful in production setup, maybe in testing environment (untested, what
84real effects this has).
85
86=over 4
87
88=item Flags known by postfix 1.1:
89
90   FLAG_NONE          - No special features
91   FLAG_BOUNCE        - Bounce bad messages
92   FLAG_FILTER        - Enable content filter
93
94=item Flags known by postfix 2.1, 2.2
95
96all flags from postfix 1.1, plus the following:
97   FLAG_HOLD          - Place message on hold
98   FLAG_DISCARD       - Discard message silently
99   FLAG_BCC_OK        - Ok to add auto-BCC addresses
100   FLAG_MAP_OK        - Ok to map addresses
101   FLAG_MASK_INTERNAL - alias for FLAG_MAP_OK
102   FLAG_MASK_EXTERNAL - FILTER, BCC_OK and MAP_OK
103
104=item Flags known by postfix 2.3
105
106all flags from postfix 2.1, up to FLAG_MASK_INTERNAL. New or changed:
107   FLAG_MILTER        - Enable Milter applications
108   FLAG_FILTER_ALL    - FILTER and MILTER
109   FLAG_MASK_EXTERNAL - FILTER_ALL, BCC_OK, MAP_OK
110
111=item Flags known by postfix 2.4
112
113currently (postfix-2.4-20061019) the same as 2.3
114
115=back
116
117=head1 MAYBE IN FUTURE
118
119Settings the (additional) queue flags from another plugin. Currently at the
120beginning of I<hook_queue()> all flags are reset to the flags given as plugin
121parameters.
122
123=cut
124
125use Qpsmtpd::Postfix;
126use Qpsmtpd::Postfix::Constants;
127
128sub register {
129    my ($self, $qp, @args) = @_;
130
131    $self->log(LOGDEBUG,
132               "using constants generated from Postfix" . "v$postfix_version");
133    $self->{_queue_flags} = 0;
134    if (@args > 0) {
135        if ($args[0] =~ m#^(/.+)#) {
136
137            # untaint socket path
138            $self->{_queue_socket} = $1;
139            shift @args;
140        }
141
142        foreach (@args) {
143            if ($self->can("CLEANUP_" . $_) and /^(FLAG_[A-Z0-9_]+)$/) {
144                $_ = $1;
145                $self->{_queue_flags} |= (eval "CLEANUP_$_;" || 0);
146
147                #print STDERR "queue flag: $_: ".$self->{_queue_flags}."\n";
148            }
149            else {
150                $self->log(LOGWARN, "Ignoring unkown cleanup flag $_");
151            }
152        }
153    }
154    else {
155        $self->{_queue_socket} = "/var/spool/postfix/public/cleanup";
156    }
157
158    $self->{_queue_socket_env} = $ENV{POSTFIXQUEUE} if $ENV{POSTFIXQUEUE};
159
160}
161
162sub hook_queue {
163    my ($self, $transaction) = @_;
164    $transaction->notes('postfix-queue-flags', $self->{_queue_flags});
165    my @queue;
166    @queue = ($self->{_queue_socket_env}) if $self->{_queue_socket_env};
167    @queue = $self->qp->config('cleanup_sockets') unless @queue;
168    @queue = ($self->{_queue_socket} // ()) unless @queue;
169    $transaction->notes('postfix-queue-sockets', \@queue) if @queue;
170
171# $self->log(LOGDEBUG, "queue-flags=".$transaction->notes('postfix-queue-flags'));
172    my ($status, $qid, $reason) = Qpsmtpd::Postfix->inject_mail($transaction);
173    if ($status) {
174
175        # this split is needed, because if cleanup returns
176        # CLEANUP_STAT_MASK_INCOMPLETE we might return DENY (CLEANUP_STAT_SIZE)
177        # instead of DENYSOFT (CLEANUP_STAT_WRITE, CLEANUP_STAT_BAD,
178        # CLEANUP_STAT_DEFER) ... n.b. this is the behaviour of 667.
179        foreach my $key (keys %cleanup_soft) {
180            my $stat = eval $key    # keys have the same names as the constants
181              or next;
182            if ($status & $stat) {
183                return (DENYSOFT, $reason || $cleanup_soft{$key});
184            }
185        }
186        foreach my $key (keys %cleanup_hard) {
187            my $stat = eval $key    # keys have the same names as the constants
188              or next;
189            if ($status & $stat) {
190                return (DENY, $reason || $cleanup_hard{$key});
191            }
192        }
193
194        # we have no idea why we're here.
195        return (DECLINED,
196                $reason || "Unable to queue message ($status, $reason)");
197    }
198
199    my $msg_id = $transaction->header->get('Message-Id') || '';
200    $msg_id =~ s/[\r\n].*//s;    # don't allow newlines in the Message-Id here
201    return (OK, "Queued! $msg_id (Queue-Id: $qid)");
202}
203
204