line
stmt
bran
cond
sub
pod
time
code
1
package Postfix::ContentFilter;
2
3
3
3
189302
use Modern::Perl;
3
43405
3
26
4
3
3
620
use Carp;
3
10
3
394
5
3
3
2216
use Try::Tiny 0.11;
3
1712
3
212
6
3
3
3541
use IPC::Open3 1.03;
3
10431
3
206
7
3
3
27
use Scalar::Util qw(blessed);
3
6
3
6344
8
9
=head1 NAME
10
11
Postfix::ContentFilter - a perl content_filter for postfix
12
13
=head1 VERSION
14
15
Version 1.11
16
17
=cut
18
19
our $VERSION = '1.11';
20
21
=head1 SYNOPSIS
22
23
use Postfix::ContentFilter;
24
25
$exitcode = Postfix::ContentFilter->process(sub{
26
$entity = shift; # isa MIME::Entity
27
28
# do something with $entity
29
30
return $entity;
31
});
32
33
# Or specifying the parser
34
my $cf = Postfix::ContentFilter->new({ parser => 'Mail::Message' });
35
36
$exitcode = $cf->process(sub{
37
$entity = shift; # isa Mail::Message
38
39
# do something with $entity
40
41
return $entity;
42
});
43
44
exit $exitcode;
45
46
=head1 DESCRIPTION
47
48
Postfix::ContentFilter can be used for C scripts, as described here: L.
49
50
=cut
51
52
our $parser;
53
our $sendmail = [qw[ /usr/sbin/sendmail -G -i ]];
54
our $output;
55
our $error;
56
57
=head1 FUNCTIONS
58
59
=head2 new($args)
60
C creates a new Postfix::Contentfilter. It takes an optional argument of a hash with the key 'parser', which specifies the parser to use as per C. This can be either C or C.
61
62
Alternatively C can be called directly.
63
64
=cut
65
66
sub new($%)
67
0
0
1
{ my ($class, $options) = @_;
68
0
my $self = bless {}, $class;
69
0
0
0
if ($options && $options->{parser})
70
{
71
0
parser($self, $options->{parser});
72
}
73
74
0
$self;
75
}
76
77
=head2 parser($string)
78
79
C specifies the parser to use, which can be either C or C. It defaults to C, if available, or C whichever could be found first. When called without any arguments, it returns the current parser.
80
81
=cut
82
83
sub _load_any {
84
0
0
foreach my $module (@_) {
85
0
my $path = $module;
86
0
$path =~ s/::/\//g;
87
0
$path .= '.pm';
88
0
0
return $module if exists $INC{$path};
89
0
0
eval "require $module; 1" and return $module;
90
}
91
0
croak("Couldn't find any of these implementations: @_");
92
}
93
94
sub parser {
95
0
0
1
my ($self, $ptype) = @_;
96
0
my $parsers = {
97
# Key is parser, value is returned entity
98
'MIME::Parser' => 'MIME::Entity',
99
'Mail::Message' => 'Mail::Message',
100
};
101
102
0
0
0
return $self->{parser} if defined $self->{parser} and not defined $ptype;
103
104
0
0
$ptype = _load_any($ptype || qw(MIME::Parser Mail::Message));
105
106
0
0
if (my $ent = $parsers->{$ptype}) {
107
0
$self->{parser} = $ptype;
108
0
$self->{entity} = $ent;
109
} else {
110
0
croak "Unknown parser $ptype";
111
}
112
113
0
return $self->{parser};
114
}
115
116
sub _parse {
117
0
0
my ($self, $handle) = @_;
118
}
119
120
=head2 process($coderef [, $inputhandle])
121
122
C reads the mail from C (or C<$inputhandle>, if given), parses it, calls the coderef and finally runs C with our own command-line arguments (C<@ARGV>).
123
124
This function returns the exitcode of C.
125
126
=cut
127
128
sub process($&;*) {
129
0
0
1
my ($class, $coderef, $handle) = @_;
130
131
0
0
my $self = blessed $class
132
? $class
133
: bless {}, $class
134
; # For backwards compatibility, to enable calling directly
135
136
0
0
confess "please call as ".__PACKAGE__."->process(sub{ ... })" unless ref $coderef eq 'CODE';
137
138
0
0
$handle = \*STDIN unless ref $handle eq 'GLOB';
139
140
0
my $entity;
141
0
my $parser = $self->parser;
142
143
0
0
given (ref $parser || $parser) {
144
0
when ('Mail::Message') {
145
0
0
$entity = $parser->read($handle) or confess "failed to parse with Mail::Message";
146
}
147
0
when ('MIME::Parser') {
148
0
$parser = $parser->new;
149
0
0
$entity = $parser->parse($handle) or confess "failed to parse wth MIME::Parser";
150
}
151
0
default {
152
0
confess "Unkown parser $parser";
153
}
154
}
155
156
try {
157
0
0
$entity = $coderef->($entity);
158
} catch {
159
0
0
0
given (ref $parser || $parser) {
160
0
when ('Mail::Message') {
161
0
$entity->DESTROY;
162
}
163
0
when ('MIME::Parser') {
164
0
$parser->filer->purge;
165
}
166
}
167
0
confess $_;
168
0
};
169
170
0
0
0
confess "subref should return instance of $self->{entity}"
171
unless blessed($entity) and $entity->isa($self->{entity});
172
173
0
my $ret = -1;
174
175
0
0
0
$SIG{CHLD} = sub { wait; $ret = $? if $? >= 0 };
0
0
176
177
0
0
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'} if ${^TAINT};
178
179
0
my ($in, $out, $err);
180
0
0
my $pid = open3 ($in, $out, $err, @$sendmail, @ARGV) or confess "open3: $!";
181
182
0
0
$entity->print($in) or confess "print: $!";
183
184
0
close $in;
185
186
0
0
$output = join '' => <$out> if defined $out;
187
0
0
$error = join '' => <$err> if defined $err;
188
189
0
close $out;
190
191
0
waitpid($pid, 0);
192
0
0
$ret = $? if $? >= 0;
193
194
0
0
given (ref $parser || $parser) {
195
0
when ('Mail::Message') {
196
0
$entity->DESTROY;
197
}
198
0
when ('MIME::Parser') {
199
0
$parser->filer->purge;
200
}
201
}
202
203
0
0
return $ret == 0 ? 1 : 0;
204
}
205
206
=head1 VARIABLES
207
208
=over 4
209
210
=item * C<$sendmail>
211
212
C<$sendmail> defaults to C.
213
214
$Postfix::ContentFilter::sendmail = [ '/usr/local/sbin/sendmail', '-G', '-i' ];
215
216
Please note C<$sendmail> must be an arrayref. Don't forget to use the proper arguments for C, or just replace the first element in array.
217
218
Additional arguments can be added with:
219
220
push @$Postfix::ContentFilter::sendmail => '-t';
221
222
=item * C<$output>
223
224
Any output from C command is populated in C<$output>.
225
226
=item * C<$parser>
227
228
The L object is available via C<$parser>. To tell where to put the things, use:
229
230
$Postfix::ContentFilter::parser->output_under('/tmp');
231
232
=back
233
234
=head1 CAVEATS
235
236
If taint mode is on, %ENV will be stripped:
237
238
delete @ENV{'IFS', 'CDPATH', 'ENV', 'BASH_ENV', 'PATH'}
239
240
So set C<$Postfix::ContentFilter::sendmail> to an absolute path, if you are using taint mode. See L for more details about unsafe variables and tainted input.
241
242
=head1 SEE ALSO
243
244
=over 4
245
246
=item * L
247
248
=item * L
249
250
=item * L
251
252
=back
253
254
=head1 AUTHOR
255
256
David Zurborg, C<< >>
257
258
=head1 BUGS
259
260
Please report any bugs or feature requests trough L. I will be notified, and then you'll
261
automatically be notified of progress on your bug as I make changes.
262
263
=head1 SUPPORT
264
265
You can find documentation for this module with the perldoc command.
266
267
perldoc Postfix::ContentFilter
268
269
You can also look for information at:
270
271
=over 4
272
273
=item * Redmine: Homepage of this module
274
275
L
276
277
=item * RT: CPAN's request tracker
278
279
L
280
281
=item * AnnoCPAN: Annotated CPAN documentation
282
283
L
284
285
=item * CPAN Ratings
286
287
L
288
289
=item * Search CPAN
290
291
L
292
293
=back
294
295
=head1 COPYRIGHT & LICENSE
296
297
Copyright 2014 David Zurborg, all rights reserved.
298
299
This program is free software; you can redistribute it and/or modify it under the terms of the ISC license.
300
301
=cut
302
303
1; # End of Postfix::ContentFilter