line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Sendmail::Queue::Qf; |
2
|
4
|
|
|
4
|
|
143080
|
use strict; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
126
|
|
3
|
4
|
|
|
4
|
|
24
|
use warnings; |
|
4
|
|
|
|
|
6
|
|
|
4
|
|
|
|
|
102
|
|
4
|
4
|
|
|
4
|
|
19
|
use Carp; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
281
|
|
5
|
|
|
|
|
|
|
|
6
|
4
|
|
|
4
|
|
25
|
use Scalar::Util qw(blessed); |
|
4
|
|
|
|
|
4
|
|
|
4
|
|
|
|
|
288
|
|
7
|
4
|
|
|
4
|
|
22
|
use File::Spec; |
|
4
|
|
|
|
|
9
|
|
|
4
|
|
|
|
|
114
|
|
8
|
4
|
|
|
4
|
|
3819
|
use IO::File; |
|
4
|
|
|
|
|
15277
|
|
|
4
|
|
|
|
|
635
|
|
9
|
4
|
|
|
4
|
|
22721
|
use Time::Local (); |
|
4
|
|
|
|
|
15271
|
|
|
4
|
|
|
|
|
196
|
|
10
|
4
|
|
|
4
|
|
28
|
use Fcntl qw( :flock ); |
|
4
|
|
|
|
|
7
|
|
|
4
|
|
|
|
|
777
|
|
11
|
4
|
|
|
4
|
|
1030
|
use Errno qw( EEXIST ); |
|
4
|
|
|
|
|
1779
|
|
|
4
|
|
|
|
|
636
|
|
12
|
4
|
|
|
4
|
|
7410
|
use Mail::Header::Generator (); |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
use Storable (); |
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $fcntl_struct = 's H60'; |
16
|
|
|
|
|
|
|
my $fcntl_structlockp = pack($fcntl_struct, Fcntl::F_WRLCK, |
17
|
|
|
|
|
|
|
"000000000000000000000000000000000000000000000000000000000000"); |
18
|
|
|
|
|
|
|
my $fcntl_structunlockp = pack($fcntl_struct, Fcntl::F_UNLCK, |
19
|
|
|
|
|
|
|
"000000000000000000000000000000000000000000000000000000000000"); |
20
|
|
|
|
|
|
|
|
21
|
|
|
|
|
|
|
## no critic 'ProhibitMagicNumbers' |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
# TODO: should we fail if total size of headers > 32768 bytes, or let sendmail die? |
24
|
|
|
|
|
|
|
|
25
|
|
|
|
|
|
|
use Sendmail::Queue::Base; |
26
|
|
|
|
|
|
|
our @ISA = qw( Sendmail::Queue::Base ); |
27
|
|
|
|
|
|
|
__PACKAGE__->make_accessors(qw( |
28
|
|
|
|
|
|
|
queue_id |
29
|
|
|
|
|
|
|
queue_fh |
30
|
|
|
|
|
|
|
queue_directory |
31
|
|
|
|
|
|
|
sender |
32
|
|
|
|
|
|
|
recipients |
33
|
|
|
|
|
|
|
headers |
34
|
|
|
|
|
|
|
timestamp |
35
|
|
|
|
|
|
|
product_name |
36
|
|
|
|
|
|
|
helo |
37
|
|
|
|
|
|
|
relay_address |
38
|
|
|
|
|
|
|
relay_hostname |
39
|
|
|
|
|
|
|
local_hostname |
40
|
|
|
|
|
|
|
protocol |
41
|
|
|
|
|
|
|
received_header |
42
|
|
|
|
|
|
|
priority |
43
|
|
|
|
|
|
|
qf_version |
44
|
|
|
|
|
|
|
data_is_8bit |
45
|
|
|
|
|
|
|
user |
46
|
|
|
|
|
|
|
macros |
47
|
|
|
|
|
|
|
)); |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
=head1 NAME |
50
|
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
Sendmail::Queue::Qf - Represent a Sendmail qfXXXXXXXX (control) file |
52
|
|
|
|
|
|
|
|
53
|
|
|
|
|
|
|
=head1 SYNOPSIS |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
use Sendmail::Queue::Qf; |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
# Create a new qf file object |
58
|
|
|
|
|
|
|
my $qf = Sendmail::Queue::Qf->new({ |
59
|
|
|
|
|
|
|
queue_directory => $dir |
60
|
|
|
|
|
|
|
}); |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# Creates a new qf file, locked. |
63
|
|
|
|
|
|
|
$qf->create_and_lock(); |
64
|
|
|
|
|
|
|
|
65
|
|
|
|
|
|
|
$qf->set_sender('me@example.com'); |
66
|
|
|
|
|
|
|
$qf->add_recipient('you@example.org'); |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
$qf->set_headers( $some_header_data ); |
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
# Add a received header using the information already provided |
71
|
|
|
|
|
|
|
$qf->synthesize_received_header(); |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
$qf->write( ); |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
$qf->sync(); |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
$qf->close(); |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head1 DESCRIPTION |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
Sendmail::Queue::Qf provides a representation of a Sendmail qf file. |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
=head1 METHODS |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=head2 new ( \%args ) |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Create a new Sendmail::Queue::Qf object. |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=cut |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
sub new |
92
|
|
|
|
|
|
|
{ |
93
|
|
|
|
|
|
|
my ($class, $args) = @_; |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
my $self = { |
96
|
|
|
|
|
|
|
headers => '', |
97
|
|
|
|
|
|
|
recipients => [], |
98
|
|
|
|
|
|
|
product_name => 'Sendmail::Queue', |
99
|
|
|
|
|
|
|
local_hostname => 'localhost', |
100
|
|
|
|
|
|
|
timestamp => time, |
101
|
|
|
|
|
|
|
priority => 30000, |
102
|
|
|
|
|
|
|
macros => {}, |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
# This code generates V6-compatible qf files to work |
105
|
|
|
|
|
|
|
# with Sendmail 8.12. |
106
|
|
|
|
|
|
|
qf_version => '6', |
107
|
|
|
|
|
|
|
%{ $args || {} }, }; |
108
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
bless $self, $class; |
110
|
|
|
|
|
|
|
|
111
|
|
|
|
|
|
|
return $self; |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
{ |
115
|
|
|
|
|
|
|
my @base_60_chars = ( 0..9, 'A'..'Z', 'a'..'x' ); |
116
|
|
|
|
|
|
|
sub _generate_queue_id_template |
117
|
|
|
|
|
|
|
{ |
118
|
|
|
|
|
|
|
my ($time) = @_; |
119
|
|
|
|
|
|
|
$time = time unless defined $time; |
120
|
|
|
|
|
|
|
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime( $time ); |
121
|
|
|
|
|
|
|
|
122
|
|
|
|
|
|
|
# First char is year minus 1900, mod 60 |
123
|
|
|
|
|
|
|
# (perl's localtime conveniently gives us the year-1900 already) |
124
|
|
|
|
|
|
|
# 2nd and 3rd are month, day |
125
|
|
|
|
|
|
|
# 4th through 6th are hour, minute, second |
126
|
|
|
|
|
|
|
# 7th and 8th characters are a random sequence number |
127
|
|
|
|
|
|
|
# (to be filled in later) |
128
|
|
|
|
|
|
|
# 9th through 14th are the PID |
129
|
|
|
|
|
|
|
my $tmpl = join('', @base_60_chars[ |
130
|
|
|
|
|
|
|
$year % 60, |
131
|
|
|
|
|
|
|
$mon, |
132
|
|
|
|
|
|
|
$mday, |
133
|
|
|
|
|
|
|
$hour, |
134
|
|
|
|
|
|
|
$min, |
135
|
|
|
|
|
|
|
$sec], |
136
|
|
|
|
|
|
|
'%2.2s', |
137
|
|
|
|
|
|
|
sprintf('%06d', $$) |
138
|
|
|
|
|
|
|
); |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
return $tmpl; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
sub _fill_template |
144
|
|
|
|
|
|
|
{ |
145
|
|
|
|
|
|
|
my ($template, $seq_number) = @_; |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
return sprintf $template, |
148
|
|
|
|
|
|
|
$base_60_chars[ int($seq_number / 60) ] . $base_60_chars[ $seq_number % 60 ]; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
} |
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
=head2 create_and_lock ( [$lock_both] ) |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
Generate a Sendmail 8.12-compatible queue ID, and create a locked qf |
155
|
|
|
|
|
|
|
file with that name. If $lock_both is true, we lock the file using |
156
|
|
|
|
|
|
|
both fcntl and flock-style locking. |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
See Bat Book 3rd edition, section 11.2.1 for information on how the |
159
|
|
|
|
|
|
|
queue file name is generated. |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
Note that we create the qf file directly, rather than creating an |
162
|
|
|
|
|
|
|
intermediate tf file and renaming aftewards. This is all good and well |
163
|
|
|
|
|
|
|
for creating /new/ qf files -- sendmail does it that way as well -- but |
164
|
|
|
|
|
|
|
if we ever want to rewrite one, it's not safe. |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
For future reference, Sendmail queuefile creation in queueup() inside |
167
|
|
|
|
|
|
|
sendmail/queue.c does things in the same way -- newly-created queue files |
168
|
|
|
|
|
|
|
are created directly with the qf prefix, then locked, then written. |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
=cut |
171
|
|
|
|
|
|
|
|
172
|
|
|
|
|
|
|
sub create_and_lock |
173
|
|
|
|
|
|
|
{ |
174
|
|
|
|
|
|
|
my ($self, $lock_both) = @_; |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
if( ! -d $self->get_queue_directory ) { |
177
|
|
|
|
|
|
|
die q{Cannot create queue file without queue directory!}; |
178
|
|
|
|
|
|
|
} |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
# 7th and 8th is random sequence number |
181
|
|
|
|
|
|
|
my $seq = int(rand(3600)); |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
my $tmpl = _generate_queue_id_template( $self->get_timestamp ); |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
my $iterations = 0; |
186
|
|
|
|
|
|
|
while( ++$iterations < 3600 ) { |
187
|
|
|
|
|
|
|
my $qid = _fill_template($tmpl, $seq); |
188
|
|
|
|
|
|
|
my $path = File::Spec->catfile( $self->{queue_directory}, "qf$qid" ); |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
my $old_umask = umask(002); |
191
|
|
|
|
|
|
|
my $fh = IO::File->new( $path, O_RDWR|O_CREAT|O_EXCL ); |
192
|
|
|
|
|
|
|
umask($old_umask); |
193
|
|
|
|
|
|
|
if( $fh ) { |
194
|
|
|
|
|
|
|
if( ! flock $fh, LOCK_EX | LOCK_NB ) { |
195
|
|
|
|
|
|
|
# Opened but couldn't lock. This means we probably had: |
196
|
|
|
|
|
|
|
# A: open (us, create) |
197
|
|
|
|
|
|
|
# B: open (them, for read) |
198
|
|
|
|
|
|
|
# B: lock (them, for read) |
199
|
|
|
|
|
|
|
# A: lock (us, failed) |
200
|
|
|
|
|
|
|
# so, give up on this one and try again |
201
|
|
|
|
|
|
|
close($fh); |
202
|
|
|
|
|
|
|
unlink($path); |
203
|
|
|
|
|
|
|
$seq = ($seq + 1) % 3600; |
204
|
|
|
|
|
|
|
next; |
205
|
|
|
|
|
|
|
} |
206
|
|
|
|
|
|
|
if ($lock_both && !fcntl($fh, Fcntl::F_SETLK, $fcntl_structlockp)) { |
207
|
|
|
|
|
|
|
# See above... couldn't lock with fcntl |
208
|
|
|
|
|
|
|
close($fh); |
209
|
|
|
|
|
|
|
unlink($path); |
210
|
|
|
|
|
|
|
$seq = ($seq + 1) % 3600; |
211
|
|
|
|
|
|
|
next; |
212
|
|
|
|
|
|
|
} |
213
|
|
|
|
|
|
|
$self->set_queue_id( $qid ); |
214
|
|
|
|
|
|
|
$self->set_queue_fh( $fh ); |
215
|
|
|
|
|
|
|
last; |
216
|
|
|
|
|
|
|
} elsif( $! == EEXIST ) { |
217
|
|
|
|
|
|
|
# Try the next one |
218
|
|
|
|
|
|
|
$seq = ($seq + 1) % 3600; |
219
|
|
|
|
|
|
|
} else { |
220
|
|
|
|
|
|
|
die qq{Error creating qf file $path: $!}; |
221
|
|
|
|
|
|
|
} |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
} |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
if ($iterations >= 3600 ) { |
226
|
|
|
|
|
|
|
die q{Could not create queue file; too many iterations}; |
227
|
|
|
|
|
|
|
} |
228
|
|
|
|
|
|
|
|
229
|
|
|
|
|
|
|
return 1; |
230
|
|
|
|
|
|
|
} |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# _tz_diff and _format_rfc2822_date borrowed from Email::Date. Why? |
233
|
|
|
|
|
|
|
# Because they depend on Date::Parse and Time::Piece, and I don't want |
234
|
|
|
|
|
|
|
# to add them as dependencies. |
235
|
|
|
|
|
|
|
# Similar functions exist in MIMEDefang as well |
236
|
|
|
|
|
|
|
sub _tz_diff |
237
|
|
|
|
|
|
|
{ |
238
|
|
|
|
|
|
|
my ($time) = @_; |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
my $diff = Time::Local::timegm(localtime $time) |
241
|
|
|
|
|
|
|
- Time::Local::timegm(gmtime $time); |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
my $direc = $diff < 0 ? '-' : '+'; |
244
|
|
|
|
|
|
|
$diff = abs $diff; |
245
|
|
|
|
|
|
|
my $tz_hr = int( $diff / 3600 ); |
246
|
|
|
|
|
|
|
my $tz_mi = int( $diff / 60 - $tz_hr * 60 ); |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
return ($direc, $tz_hr, $tz_mi); |
249
|
|
|
|
|
|
|
} |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
sub _format_rfc2822_date |
252
|
|
|
|
|
|
|
{ |
253
|
|
|
|
|
|
|
my ($time) = @_; |
254
|
|
|
|
|
|
|
$time = time unless defined $time; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
my ($sec, $min, $hour, $mday, $mon, $year, $wday) = localtime $time; |
257
|
|
|
|
|
|
|
my $day = (qw[Sun Mon Tue Wed Thu Fri Sat])[$wday]; |
258
|
|
|
|
|
|
|
my $month = (qw[Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec])[$mon]; |
259
|
|
|
|
|
|
|
$year += 1900; |
260
|
|
|
|
|
|
|
|
261
|
|
|
|
|
|
|
my ($direc, $tz_hr, $tz_mi) = _tz_diff($time); |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
sprintf '%s, %d %s %d %02d:%02d:%02d %s%02d%02d', |
264
|
|
|
|
|
|
|
$day, $mday, $month, $year, $hour, $min, $sec, $direc, $tz_hr, $tz_mi; |
265
|
|
|
|
|
|
|
} |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head2 synthesize_received_header ( ) |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
Create a properly-formatted Received: header for this message, using |
270
|
|
|
|
|
|
|
any data available from the object. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
The generated header is saved internally as 'received_header'. |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
=cut |
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
sub synthesize_received_header |
277
|
|
|
|
|
|
|
{ |
278
|
|
|
|
|
|
|
my ($self) = @_; |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
my $g = Mail::Header::Generator->new(); |
281
|
|
|
|
|
|
|
|
282
|
|
|
|
|
|
|
my $user = $self->get_user(); |
283
|
|
|
|
|
|
|
if(!$user) { |
284
|
|
|
|
|
|
|
$user = getpwuid($>); |
285
|
|
|
|
|
|
|
} |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
$self->{received_header} = $g->received({ |
288
|
|
|
|
|
|
|
helo => $self->get_helo(), |
289
|
|
|
|
|
|
|
hostname => $self->get_local_hostname(), |
290
|
|
|
|
|
|
|
product_name => $self->get_product_name(), |
291
|
|
|
|
|
|
|
protocol => ($self->get_protocol || ''), |
292
|
|
|
|
|
|
|
queue_id => $self->get_queue_id(), |
293
|
|
|
|
|
|
|
recipients => $self->get_recipients(), |
294
|
|
|
|
|
|
|
relay_address => $self->get_relay_address(), |
295
|
|
|
|
|
|
|
relay_hostname => $self->get_relay_hostname(), |
296
|
|
|
|
|
|
|
sender => $self->get_sender(), |
297
|
|
|
|
|
|
|
timestamp => $self->get_timestamp(), |
298
|
|
|
|
|
|
|
user => $user |
299
|
|
|
|
|
|
|
}); |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
return $self->{received_header}; |
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
=head2 get_queue_filename |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
Return the full path name of this queue file. |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
Will return undef if no queue ID exists, and die if queue directory is |
309
|
|
|
|
|
|
|
unset. |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=cut |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
sub get_queue_filename |
314
|
|
|
|
|
|
|
{ |
315
|
|
|
|
|
|
|
my ($self) = @_; |
316
|
|
|
|
|
|
|
|
317
|
|
|
|
|
|
|
if( ! $self->get_queue_directory ) { |
318
|
|
|
|
|
|
|
die q{queue directory not set}; |
319
|
|
|
|
|
|
|
} |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
if( ! $self->get_queue_id ) { |
322
|
|
|
|
|
|
|
return undef; |
323
|
|
|
|
|
|
|
} |
324
|
|
|
|
|
|
|
|
325
|
|
|
|
|
|
|
return File::Spec->catfile( $self->get_queue_directory(), 'qf' . $self->get_queue_id() ); |
326
|
|
|
|
|
|
|
} |
327
|
|
|
|
|
|
|
|
328
|
|
|
|
|
|
|
=head2 add_recipient ( $recipient [, $recipient, $recipient ] ) |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
Add one or more recipients to this object. |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
=cut |
333
|
|
|
|
|
|
|
|
334
|
|
|
|
|
|
|
sub add_recipient |
335
|
|
|
|
|
|
|
{ |
336
|
|
|
|
|
|
|
my ($self, @recips) = @_; |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
push @{$self->{recipients}}, @recips; |
339
|
|
|
|
|
|
|
} |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=head2 write ( ) |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
Writes a qfXXXXXXX file using the object's data. |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
A path to create this queue file under must be provided, by first |
346
|
|
|
|
|
|
|
calling ->set_queue_directory() |
347
|
|
|
|
|
|
|
|
348
|
|
|
|
|
|
|
=cut |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
sub write |
351
|
|
|
|
|
|
|
{ |
352
|
|
|
|
|
|
|
my ($self) = @_; |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
my $fh = $self->get_queue_fh; |
355
|
|
|
|
|
|
|
|
356
|
|
|
|
|
|
|
if ( ! $fh || ! $fh->opened ) { |
357
|
|
|
|
|
|
|
die q{write() cannot write without an open filehandle}; |
358
|
|
|
|
|
|
|
} |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
foreach my $chunk ( |
361
|
|
|
|
|
|
|
$self->_format_qf_version(), |
362
|
|
|
|
|
|
|
$self->_format_create_time(), |
363
|
|
|
|
|
|
|
$self->_format_last_processed(), |
364
|
|
|
|
|
|
|
$self->_format_times_processed(), |
365
|
|
|
|
|
|
|
$self->_format_priority(), |
366
|
|
|
|
|
|
|
$self->_format_flag_bits(), |
367
|
|
|
|
|
|
|
$self->_format_macros(), |
368
|
|
|
|
|
|
|
$self->_format_sender_address(), |
369
|
|
|
|
|
|
|
$self->_format_recipient_addresses(), |
370
|
|
|
|
|
|
|
$self->_format_headers(), |
371
|
|
|
|
|
|
|
$self->_format_end_of_qf(), |
372
|
|
|
|
|
|
|
) { |
373
|
|
|
|
|
|
|
if( ! $fh->print( $chunk, "\n" ) ) { |
374
|
|
|
|
|
|
|
die q{Couldn't print to } . $self->get_queue_filename . ": $!"; |
375
|
|
|
|
|
|
|
} |
376
|
|
|
|
|
|
|
} |
377
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
return 1; |
379
|
|
|
|
|
|
|
} |
380
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
=head2 sync ( ) |
382
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
Force any data written to the current filehandle to be flushed to disk. |
384
|
|
|
|
|
|
|
Returns 1 on success, undef if no queue file is open, and will die on error. |
385
|
|
|
|
|
|
|
|
386
|
|
|
|
|
|
|
=cut |
387
|
|
|
|
|
|
|
|
388
|
|
|
|
|
|
|
sub sync |
389
|
|
|
|
|
|
|
{ |
390
|
|
|
|
|
|
|
my ($self) = @_; |
391
|
|
|
|
|
|
|
|
392
|
|
|
|
|
|
|
my $fh = $self->get_queue_fh; |
393
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
if( ! $fh->opened ) { |
395
|
|
|
|
|
|
|
return undef; |
396
|
|
|
|
|
|
|
} |
397
|
|
|
|
|
|
|
|
398
|
|
|
|
|
|
|
if( ! $fh->flush ) { |
399
|
|
|
|
|
|
|
croak q{Couldn't flush filehandle!}; |
400
|
|
|
|
|
|
|
} |
401
|
|
|
|
|
|
|
|
402
|
|
|
|
|
|
|
if( ! $fh->sync ) { |
403
|
|
|
|
|
|
|
croak q{Couldn't sync filehandle!}; |
404
|
|
|
|
|
|
|
} |
405
|
|
|
|
|
|
|
|
406
|
|
|
|
|
|
|
return 1; |
407
|
|
|
|
|
|
|
} |
408
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
=head2 close ( ) |
410
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
Returns true on success, false (as undef) if filehandle wasn't open, or if |
412
|
|
|
|
|
|
|
closing the filehandle fails, and dies if the internal filehandle is missing or |
413
|
|
|
|
|
|
|
isn't a filehandle. |
414
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=cut |
416
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
sub close |
418
|
|
|
|
|
|
|
{ |
419
|
|
|
|
|
|
|
my ($self) = @_; |
420
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
my $fh = $self->get_queue_fh; |
422
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
if( ! ($fh && blessed $fh && $fh->isa('IO::Handle')) ) { |
424
|
|
|
|
|
|
|
croak "get_queue_fh() returned something that isn't a filehandle"; |
425
|
|
|
|
|
|
|
} |
426
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
if( ! $fh->opened ) { |
428
|
|
|
|
|
|
|
return undef; |
429
|
|
|
|
|
|
|
} |
430
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
if( ! $fh->close ) { |
432
|
|
|
|
|
|
|
return undef; |
433
|
|
|
|
|
|
|
} |
434
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
return 1; |
436
|
|
|
|
|
|
|
} |
437
|
|
|
|
|
|
|
|
438
|
|
|
|
|
|
|
=head2 clone ( ) |
439
|
|
|
|
|
|
|
|
440
|
|
|
|
|
|
|
Return a clone of this Sendmail::Queue::Qf object, containing everything EXCEPT: |
441
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=over 4 |
443
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
=item * recipients |
445
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=item * queue ID |
447
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
=item * open queue filehandle |
449
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=item * synthesized Received: header |
451
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
=back |
453
|
|
|
|
|
|
|
|
454
|
|
|
|
|
|
|
=cut |
455
|
|
|
|
|
|
|
|
456
|
|
|
|
|
|
|
sub clone |
457
|
|
|
|
|
|
|
{ |
458
|
|
|
|
|
|
|
my ($self) = @_; |
459
|
|
|
|
|
|
|
|
460
|
|
|
|
|
|
|
# Localize queue_fh first, as dclone() chokes on GLOB values, and we |
461
|
|
|
|
|
|
|
# don't want it cloned anyway. |
462
|
|
|
|
|
|
|
local $self->{queue_fh}; |
463
|
|
|
|
|
|
|
|
464
|
|
|
|
|
|
|
my $clone = Storable::dclone( $self ); |
465
|
|
|
|
|
|
|
|
466
|
|
|
|
|
|
|
# Now clobber the values that shouldn't persist across a clone. We |
467
|
|
|
|
|
|
|
# set_recipients to [] as that's what the constructor does, and delete |
468
|
|
|
|
|
|
|
# the rest. |
469
|
|
|
|
|
|
|
$clone->set_recipients([]); |
470
|
|
|
|
|
|
|
delete $clone->{$_} for qw( sender queue_id received_header queue_fh ); |
471
|
|
|
|
|
|
|
|
472
|
|
|
|
|
|
|
return $clone; |
473
|
|
|
|
|
|
|
} |
474
|
|
|
|
|
|
|
|
475
|
|
|
|
|
|
|
=head2 unlink ( ) |
476
|
|
|
|
|
|
|
|
477
|
|
|
|
|
|
|
Unlink the queue file. Returns true (1) on success, false (undef) on |
478
|
|
|
|
|
|
|
failure. |
479
|
|
|
|
|
|
|
|
480
|
|
|
|
|
|
|
Unlinking the queue file will only succeed if: |
481
|
|
|
|
|
|
|
|
482
|
|
|
|
|
|
|
=over 4 |
483
|
|
|
|
|
|
|
|
484
|
|
|
|
|
|
|
=item * |
485
|
|
|
|
|
|
|
|
486
|
|
|
|
|
|
|
we have a queue directory and queue ID configured for this object |
487
|
|
|
|
|
|
|
|
488
|
|
|
|
|
|
|
=item * |
489
|
|
|
|
|
|
|
|
490
|
|
|
|
|
|
|
the queue file is open and locked |
491
|
|
|
|
|
|
|
|
492
|
|
|
|
|
|
|
=back |
493
|
|
|
|
|
|
|
|
494
|
|
|
|
|
|
|
Otherwise, we fail to delete. |
495
|
|
|
|
|
|
|
|
496
|
|
|
|
|
|
|
=cut |
497
|
|
|
|
|
|
|
|
498
|
|
|
|
|
|
|
sub unlink |
499
|
|
|
|
|
|
|
{ |
500
|
|
|
|
|
|
|
my ($self) = @_; |
501
|
|
|
|
|
|
|
|
502
|
|
|
|
|
|
|
if( ! $self->get_queue_filename ) { |
503
|
|
|
|
|
|
|
# No filename, can't unlink |
504
|
|
|
|
|
|
|
return undef; |
505
|
|
|
|
|
|
|
} |
506
|
|
|
|
|
|
|
|
507
|
|
|
|
|
|
|
if( ! $self->get_queue_fh ) { |
508
|
|
|
|
|
|
|
return undef; |
509
|
|
|
|
|
|
|
} |
510
|
|
|
|
|
|
|
|
511
|
|
|
|
|
|
|
# Only delete the queue file if we have it locked. Thus, we |
512
|
|
|
|
|
|
|
# must call unlink() before close(), or we're no longer holding |
513
|
|
|
|
|
|
|
# the lock. |
514
|
|
|
|
|
|
|
if( 1 != unlink($self->get_queue_filename) ) { |
515
|
|
|
|
|
|
|
return undef; |
516
|
|
|
|
|
|
|
} |
517
|
|
|
|
|
|
|
$self->get_queue_fh->close; |
518
|
|
|
|
|
|
|
$self->set_queue_fh(undef); |
519
|
|
|
|
|
|
|
|
520
|
|
|
|
|
|
|
return 1; |
521
|
|
|
|
|
|
|
} |
522
|
|
|
|
|
|
|
|
523
|
|
|
|
|
|
|
|
524
|
|
|
|
|
|
|
# Internal methods |
525
|
|
|
|
|
|
|
|
526
|
|
|
|
|
|
|
sub _clean_email_address |
527
|
|
|
|
|
|
|
{ |
528
|
|
|
|
|
|
|
my ($self, $addr) = @_; |
529
|
|
|
|
|
|
|
|
530
|
|
|
|
|
|
|
# Sanitize $addr a little. We want to remove any leading/trailing |
531
|
|
|
|
|
|
|
# whitespace, and any < > that might be present |
532
|
|
|
|
|
|
|
# FUTURE: do we want to do any other validation or cleaning of address |
533
|
|
|
|
|
|
|
# here? |
534
|
|
|
|
|
|
|
$addr =~ s/^[<\s]+//; |
535
|
|
|
|
|
|
|
$addr =~ s/[>\s]+$//; |
536
|
|
|
|
|
|
|
|
537
|
|
|
|
|
|
|
return $addr; |
538
|
|
|
|
|
|
|
} |
539
|
|
|
|
|
|
|
|
540
|
|
|
|
|
|
|
sub _format_qf_version |
541
|
|
|
|
|
|
|
{ |
542
|
|
|
|
|
|
|
my ($self) = @_; |
543
|
|
|
|
|
|
|
return 'V' . $self->get_qf_version(); |
544
|
|
|
|
|
|
|
} |
545
|
|
|
|
|
|
|
|
546
|
|
|
|
|
|
|
sub _format_create_time |
547
|
|
|
|
|
|
|
{ |
548
|
|
|
|
|
|
|
my ($self) = @_; |
549
|
|
|
|
|
|
|
return 'T' . $self->get_timestamp(); |
550
|
|
|
|
|
|
|
} |
551
|
|
|
|
|
|
|
|
552
|
|
|
|
|
|
|
sub _format_last_processed |
553
|
|
|
|
|
|
|
{ |
554
|
|
|
|
|
|
|
# Never processed, so zero. |
555
|
|
|
|
|
|
|
return 'K0'; |
556
|
|
|
|
|
|
|
} |
557
|
|
|
|
|
|
|
|
558
|
|
|
|
|
|
|
sub _format_times_processed |
559
|
|
|
|
|
|
|
{ |
560
|
|
|
|
|
|
|
return 'N0'; |
561
|
|
|
|
|
|
|
} |
562
|
|
|
|
|
|
|
|
563
|
|
|
|
|
|
|
sub _format_priority |
564
|
|
|
|
|
|
|
{ |
565
|
|
|
|
|
|
|
my ($self) = @_; |
566
|
|
|
|
|
|
|
|
567
|
|
|
|
|
|
|
return 'P' . $self->get_priority(); |
568
|
|
|
|
|
|
|
} |
569
|
|
|
|
|
|
|
|
570
|
|
|
|
|
|
|
sub _format_flag_bits |
571
|
|
|
|
|
|
|
{ |
572
|
|
|
|
|
|
|
my ($self) = @_; |
573
|
|
|
|
|
|
|
|
574
|
|
|
|
|
|
|
my $flags = ''; |
575
|
|
|
|
|
|
|
# Possible flag bits for V6 queue file: |
576
|
|
|
|
|
|
|
# 8 = Body has 8-bit data (EF_HAS8BIT) |
577
|
|
|
|
|
|
|
# - This should be set if the body contains any |
578
|
|
|
|
|
|
|
# octets with the high bit set. This can be detected |
579
|
|
|
|
|
|
|
# by running |
580
|
|
|
|
|
|
|
# $data =~ tr/\200-\377// |
581
|
|
|
|
|
|
|
# (Sendmail does the C equivalent, char|0x80 in a loop) |
582
|
|
|
|
|
|
|
# but... we don't have the data here in the qf object, |
583
|
|
|
|
|
|
|
# so it must be set in Sendmail::Queue by calling set_data_is_8bit(1). |
584
|
|
|
|
|
|
|
$flags .= '8' if $self->get_data_is_8bit(); |
585
|
|
|
|
|
|
|
# b = delete Bcc: header (EF_DELETE_BCC) |
586
|
|
|
|
|
|
|
# - for our purposes, we want to reproduce the |
587
|
|
|
|
|
|
|
# Bcc: header in the queued mail. Future uses |
588
|
|
|
|
|
|
|
# of this module may wish to set this to have |
589
|
|
|
|
|
|
|
# it removed. |
590
|
|
|
|
|
|
|
# d = envelope has DSN RET= (EF_RET_PARAM) |
591
|
|
|
|
|
|
|
# n = don't return body (EF_NO_BODY_RETN) |
592
|
|
|
|
|
|
|
# - these two work together to set the value of |
593
|
|
|
|
|
|
|
# the ${dsn_ret} macro. If we have both d and |
594
|
|
|
|
|
|
|
# n flags, it's equivalent to RET=HDRS, and if |
595
|
|
|
|
|
|
|
# we have d and no n flag, it's RET=FULL. No d |
596
|
|
|
|
|
|
|
# and no n means a standard DSN, and no d with |
597
|
|
|
|
|
|
|
# n means to suppress the body. |
598
|
|
|
|
|
|
|
# - We will avoid setting this one for now, as |
599
|
|
|
|
|
|
|
# whether or not to return headers should be a |
600
|
|
|
|
|
|
|
# site policy decision. |
601
|
|
|
|
|
|
|
# r = response (EF_RESPONSE) |
602
|
|
|
|
|
|
|
# - this is set if this mail is a bounce, |
603
|
|
|
|
|
|
|
# autogenerated return receipt message, or some |
604
|
|
|
|
|
|
|
# other return-to-sender type thing. |
605
|
|
|
|
|
|
|
# - we will avoid setting this, since we're not |
606
|
|
|
|
|
|
|
# generating DSNs with this code yet. |
607
|
|
|
|
|
|
|
# s = split (EF_SPLIT) |
608
|
|
|
|
|
|
|
# - envelope with multiple recipients has been |
609
|
|
|
|
|
|
|
# split into several envelopes |
610
|
|
|
|
|
|
|
# (dmo) At this point, I think that this flag |
611
|
|
|
|
|
|
|
# means that the envelope has /already/ been |
612
|
|
|
|
|
|
|
# split according to number of recipients, or |
613
|
|
|
|
|
|
|
# queue groups, or what have you by Sendmail, |
614
|
|
|
|
|
|
|
# so we probably want to leave it off. |
615
|
|
|
|
|
|
|
# w = warning sent (EF_WARNING) |
616
|
|
|
|
|
|
|
# - message is a warning DSN. We probably don't |
617
|
|
|
|
|
|
|
# want this flag set, but see 'r' flag above. |
618
|
|
|
|
|
|
|
# Some details available in $$11.11.7 of the bat book. Other |
619
|
|
|
|
|
|
|
# details require looking at Sendmail sources. |
620
|
|
|
|
|
|
|
'F' . $flags; |
621
|
|
|
|
|
|
|
} |
622
|
|
|
|
|
|
|
|
623
|
|
|
|
|
|
|
sub __format_single_macro |
624
|
|
|
|
|
|
|
{ |
625
|
|
|
|
|
|
|
my ($name, $value) = @_; |
626
|
|
|
|
|
|
|
|
627
|
|
|
|
|
|
|
$value = '' unless defined $value; # //= would be nice, but we have to support 5.8.x |
628
|
|
|
|
|
|
|
|
629
|
|
|
|
|
|
|
if( length($name) > 1 ) { |
630
|
|
|
|
|
|
|
return "\${$name}$value"; |
631
|
|
|
|
|
|
|
} |
632
|
|
|
|
|
|
|
return "\$$name$value"; |
633
|
|
|
|
|
|
|
} |
634
|
|
|
|
|
|
|
|
635
|
|
|
|
|
|
|
sub _format_macros |
636
|
|
|
|
|
|
|
{ |
637
|
|
|
|
|
|
|
my ($self) = @_; |
638
|
|
|
|
|
|
|
|
639
|
|
|
|
|
|
|
my $macro_text = ''; |
640
|
|
|
|
|
|
|
|
641
|
|
|
|
|
|
|
my %macro_hash = %{ $self->get_macros() || {} }; |
642
|
|
|
|
|
|
|
|
643
|
|
|
|
|
|
|
if( ! exists $macro_hash{r} ) { |
644
|
|
|
|
|
|
|
$macro_hash{r} = $self->get_protocol(); |
645
|
|
|
|
|
|
|
} |
646
|
|
|
|
|
|
|
|
647
|
|
|
|
|
|
|
# ${daemon_flags} macro - shouldn't need any of these, so set a |
648
|
|
|
|
|
|
|
# blank one. |
649
|
|
|
|
|
|
|
$macro_hash{daemon_flags} = ''; |
650
|
|
|
|
|
|
|
|
651
|
|
|
|
|
|
|
return join("\n", |
652
|
|
|
|
|
|
|
map { __format_single_macro($_, $macro_hash{$_}) } |
653
|
|
|
|
|
|
|
sort keys %macro_hash); |
654
|
|
|
|
|
|
|
} |
655
|
|
|
|
|
|
|
|
656
|
|
|
|
|
|
|
sub _format_sender_address |
657
|
|
|
|
|
|
|
{ |
658
|
|
|
|
|
|
|
my ($self) = @_; |
659
|
|
|
|
|
|
|
|
660
|
|
|
|
|
|
|
if( ! defined $self->get_sender() ) { |
661
|
|
|
|
|
|
|
die q{Cannot queue a message with no sender address}; |
662
|
|
|
|
|
|
|
} |
663
|
|
|
|
|
|
|
return 'S<' . $self->_clean_email_address( $self->get_sender() ). '>'; |
664
|
|
|
|
|
|
|
} |
665
|
|
|
|
|
|
|
|
666
|
|
|
|
|
|
|
sub _format_headers |
667
|
|
|
|
|
|
|
{ |
668
|
|
|
|
|
|
|
my ($self) = @_; |
669
|
|
|
|
|
|
|
|
670
|
|
|
|
|
|
|
my @headers; |
671
|
|
|
|
|
|
|
|
672
|
|
|
|
|
|
|
# Ensure we prepend our generated received header, if it |
673
|
|
|
|
|
|
|
# exists. |
674
|
|
|
|
|
|
|
foreach my $line ( split(/\n/, $self->get_received_header || ''), split(/\n/, $self->get_headers) ) { |
675
|
|
|
|
|
|
|
# Sendmail will happily deal with over-length lines in |
676
|
|
|
|
|
|
|
# a queue file when transmitting, by breaking each line |
677
|
|
|
|
|
|
|
# after 998 characters (to allow for \r\n under the |
678
|
|
|
|
|
|
|
# 1000 character RFC limit) and splitting into a new |
679
|
|
|
|
|
|
|
# line. This is ugly and breaks headers, so we do it nicely by |
680
|
|
|
|
|
|
|
# adding a continuation \n\t at the first whitespace before 998 |
681
|
|
|
|
|
|
|
# characters. |
682
|
|
|
|
|
|
|
# FUTURE: Note that this fails miserably if there is _no_ whitespace in the header. |
683
|
|
|
|
|
|
|
if( length($line) > 998 ) { |
684
|
|
|
|
|
|
|
my @tokens = split(/ /, $line); |
685
|
|
|
|
|
|
|
my $new_line = shift @tokens; |
686
|
|
|
|
|
|
|
foreach my $token (@tokens) { |
687
|
|
|
|
|
|
|
if( length($new_line) + length($token) + 1 < 998 ) { |
688
|
|
|
|
|
|
|
$new_line .= " $token"; |
689
|
|
|
|
|
|
|
} else { |
690
|
|
|
|
|
|
|
push @headers, $new_line; |
691
|
|
|
|
|
|
|
$new_line = "\t$token"; |
692
|
|
|
|
|
|
|
} |
693
|
|
|
|
|
|
|
} |
694
|
|
|
|
|
|
|
push @headers, $new_line; |
695
|
|
|
|
|
|
|
} else { |
696
|
|
|
|
|
|
|
push @headers, $line; |
697
|
|
|
|
|
|
|
} |
698
|
|
|
|
|
|
|
} |
699
|
|
|
|
|
|
|
|
700
|
|
|
|
|
|
|
# It doesn't appear that we need to escape any possible |
701
|
|
|
|
|
|
|
# ${whatever} macro expansion in H?? lines, based on |
702
|
|
|
|
|
|
|
# tests using 8.13.8 queue files. |
703
|
|
|
|
|
|
|
# |
704
|
|
|
|
|
|
|
# We do not want any delivery-agent flags between ??. |
705
|
|
|
|
|
|
|
# Even Return-Path, which ordinarily has ?P?, we shall |
706
|
|
|
|
|
|
|
# ignore flags for, as we want to pass on every header |
707
|
|
|
|
|
|
|
# that we originally received. |
708
|
|
|
|
|
|
|
return join("\n", |
709
|
|
|
|
|
|
|
# Handle already-wrapped lines properly, by appending them |
710
|
|
|
|
|
|
|
# as-is (no H?? prepend). Wrapped lines can begin with any |
711
|
|
|
|
|
|
|
# whitespace, but it's most commonly a tab. |
712
|
|
|
|
|
|
|
map { /^\s/ ? $_ : "H??$_" } @headers); |
713
|
|
|
|
|
|
|
} |
714
|
|
|
|
|
|
|
|
715
|
|
|
|
|
|
|
sub _format_end_of_qf |
716
|
|
|
|
|
|
|
{ |
717
|
|
|
|
|
|
|
my ($self) = @_; |
718
|
|
|
|
|
|
|
|
719
|
|
|
|
|
|
|
# Dot signifies end of queue file. |
720
|
|
|
|
|
|
|
return '.'; |
721
|
|
|
|
|
|
|
} |
722
|
|
|
|
|
|
|
|
723
|
|
|
|
|
|
|
sub _format_recipient_addresses |
724
|
|
|
|
|
|
|
{ |
725
|
|
|
|
|
|
|
my ($self) = @_; |
726
|
|
|
|
|
|
|
|
727
|
|
|
|
|
|
|
my $recips = $self->get_recipients(); |
728
|
|
|
|
|
|
|
if( scalar @$recips < 1 ) { |
729
|
|
|
|
|
|
|
die q{Cannot queue a message with no recipient addresses}; |
730
|
|
|
|
|
|
|
} |
731
|
|
|
|
|
|
|
|
732
|
|
|
|
|
|
|
my @out; |
733
|
|
|
|
|
|
|
foreach my $recip ( map { $self->_clean_email_address( $_ ) } @{$recips} ) { |
734
|
|
|
|
|
|
|
|
735
|
|
|
|
|
|
|
push @out, "rRFC822; $recip"; |
736
|
|
|
|
|
|
|
|
737
|
|
|
|
|
|
|
|
738
|
|
|
|
|
|
|
# R line: R: |
739
|
|
|
|
|
|
|
# Possible flags: |
740
|
|
|
|
|
|
|
# P - Primary address. Addresses via SMTP or |
741
|
|
|
|
|
|
|
# commandline are always considered primary, so |
742
|
|
|
|
|
|
|
# we need this flag. |
743
|
|
|
|
|
|
|
# S,F,D - DSN Notify on success, failure or delay. |
744
|
|
|
|
|
|
|
# We may not want this notification for the |
745
|
|
|
|
|
|
|
# client queue, but current injection with |
746
|
|
|
|
|
|
|
# sendmail binary does add FD, so we will do so |
747
|
|
|
|
|
|
|
# here. |
748
|
|
|
|
|
|
|
# N - Flag says whether or not notification was |
749
|
|
|
|
|
|
|
# enabled at SMTP time with the NOTIFY extension. |
750
|
|
|
|
|
|
|
# If not enabled, S, F and D have no effect. |
751
|
|
|
|
|
|
|
# A - address is result of alias expansion. No, |
752
|
|
|
|
|
|
|
# we don't want this |
753
|
|
|
|
|
|
|
push @out, "RPFD:$recip"; |
754
|
|
|
|
|
|
|
} |
755
|
|
|
|
|
|
|
|
756
|
|
|
|
|
|
|
return join("\n", @out); |
757
|
|
|
|
|
|
|
} |
758
|
|
|
|
|
|
|
|
759
|
|
|
|
|
|
|
|
760
|
|
|
|
|
|
|
1; |
761
|
|
|
|
|
|
|
__END__ |