File Coverage

blib/lib/TipJar/MTA.pm
Criterion Covered Total %
statement 125 756 16.5
branch 42 386 10.8
condition 11 67 16.4
subroutine 19 40 47.5
pod 0 22 0.0
total 197 1271 15.5


line stmt bran cond sub pod time code
1             package TipJar::MTA;
2              
3 2     2   61282 use strict;
  2         6  
  2         90  
4 2     2   12 use warnings;
  2         6  
  2         74  
5 2     2   12 use Carp;
  2         12  
  2         244  
6             sub mylog(@);
7 2     2   1954 use POSIX qw/strftime/;
  2         18918  
  2         20  
8             my $ONCE = 0;
9             BEGIN {
10 2 50   2   14876 if ( $ENV{TJMTADEBUG} ) {
11 0         0 eval 'sub DEBUG(){1}';
12             }
13             else {
14 2         202 eval 'sub DEBUG(){0}';
15             }
16             }
17 2         650 use vars qw/
18             $VERSION $MyDomain $interval $basedir
19             $ReturnAddress $Recipient $InitialRecipCount @Recipients
20             $AgeBeforeDeferralReport
21             $LogToStdout
22             $OnlyOnce
23             $LastChild
24             $TimeStampFrequency
25             $timeout
26             $Domain $line
27             $ConnectionProblem $dateheader
28             $dnsmxpath $ConRetryDelay $ReuseQuota $ReuseQuotaInitial
29             @NoBounceRegexList
30             $MaxActiveKids
31             $FourErrCacheLifetime
32             $BindAddress
33             %SMTProutes
34             $PostDataTrouble
35 2     2   28 /;
  2         6  
36              
37             $ConRetryDelay = 17 * 60;
38             $FourErrCacheLifetime = 7 * 60;
39              
40             # $dnsmxpath = 'dnsmx';
41             $ReuseQuotaInitial = 20;
42              
43             my $res; # used by Net::DNS
44              
45 2     2   2014 use dateheader;
  2         992  
  2         14  
46             sub concachetest($);
47             sub cachepurge();
48              
49             $TimeStampFrequency = 200; # just under an hour at 17 seconds each
50              
51             $MaxActiveKids = 5; # just how much spam are we sending?
52              
53             sub CRLF() {
54             "\015\012";
55             }
56              
57 2     2   220 use Fcntl ':flock'; # import LOCK_* constants
  2         4  
  2         586  
58             $interval = 17;
59             $AgeBeforeDeferralReport = 4 * 3600; # four hours
60              
61             $VERSION = '0.34';
62              
63             sub VERSION {
64 0 0   0 0 0 $_[1] or return $VERSION;
65 0 0       0 $_[1] <= 0.14 and croak 'TipJar::MTA now uses Net::DNS instead of dnsmx';
66              
67 0 0       0 $_[1] > $VERSION
68             and croak
69             "you are requesting TipJar::MTA version $_[1] but this is only $VERSION";
70              
71 0         0 $VERSION;
72             }
73              
74 2     2   2024 use Sys::Hostname;
  2         7260  
  2         13574  
75              
76             my $RealHostName = $MyDomain = ( hostname() || 'sys.hostname.returned.false' );
77              
78             my $time;
79             sub newmessage($);
80              
81             sub OneWeek() { 7 * 24 * 3600; }
82             sub SixHours() { 6 * 3600; }
83              
84             sub Scramble($) {
85 0     0 0 0 my @a = @{ shift(@_) };
  0         0  
86 0         0 my ( $i, $ii );
87 0         0 my $max = @a;
88 0         0 for ( $i = 0 ; $i < $max ; $i++ ) {
89 0         0 $ii = rand $max;
90 0         0 @a[ $i, $ii ] = @a[ $ii . $i ];
91             }
92 0         0 @a;
93             }
94              
95             sub import {
96 2     2   20 shift; #package name
97 2 50       14 if ( grep { m/^nodns$/i } @_ ) {
  0         0  
98             *dnsmx = sub($) {
99 0     0   0 my $host = lc(shift);
100 0 0       0 if ( exists $SMTProutes{$host} ) {
101 0 0       0 ref( $SMTProutes{$host} )
102             and return Scramble( $SMTProutes{$host} );
103 0         0 return $SMTProutes{$host};
104             }
105 0 0       0 if ( exists $SMTProutes{SMARTHOST} ) {
106 0 0       0 ref( $SMTProutes{SMARTHOST} )
107             and return Scramble( $SMTProutes{$host} );
108 0         0 return $SMTProutes{SMARTHOST};
109             }
110 0         0 mylog "nodns: %SMTProutes has no entry for <$host> or SMARTHOST";
111 0         0 return $host;
112              
113 0         0 };
114             }
115             else {
116 2 50   2   2012 eval 'use Net::DNS; 1' or die "failed to load Net::DNS: $@";
  2         670870  
  2         224  
  2         178  
117            
118 2         38 $res = Net::DNS::Resolver->new;
119 2         1876 *dnsmx = \&_dnsmx;
120             }
121 2         8 $basedir = shift;
122 2   50     20 $basedir ||= './MTAdir';
123 2         136 DEBUG and warn "basedir will be $basedir";
124              
125             }
126              
127             $LogToStdout = 1;
128              
129             {
130             my $LogTime = 0;
131              
132             sub DLsave($);
133             sub DLpurge();
134              
135             sub mylog(@) {
136              
137 5 100   5 0 35 if ( time - $LogTime > 30 ) {
138 2         4 $LogTime = time;
139 2         86 mylog scalar localtime;
140             }
141              
142 5 100       22 defined $Recipient or $Recipient = 'no recipient';
143              
144 5 50 0     335 open LOG, ">>$basedir/log/current" or print( @_, "\n" ) and return;
145 5 50       66 flock LOG, LOCK_EX or die "flock: $!";
146 5 50       19 if ($LogToStdout) {
147 5         833 seek STDOUT, 2, 0;
148 5         166 print "$$ $Recipient ", @_;
149 5         26 print "\n";
150             }
151             else {
152 0         0 seek LOG, 2, 0;
153 0         0 print LOG "$$ $Recipient ", @_;
154 0         0 print LOG "\n";
155             }
156 5         36 flock LOG, LOCK_UN; # flushes before unlocking
157             }
158              
159             };
160              
161             my $ActiveKids = 0;
162             $SIG{CHLD} = sub { $ActiveKids--; wait };
163              
164             sub recursive_immed($) { # "$qdir/$this";
165              
166             # immediatize all files under here, then delete the dir.
167 0     0 0 0 my $this = shift;
168 0         0 my @dirs = ($this);
169 0         0 my $e;
170             my @rmdirs;
171 0         0 while (@dirs) {
172 0         0 $this = shift @dirs;
173 0         0 DEBUG and warn "immanentizing $this";
174 0         0 opendir RI_DIR, $this;
175 0         0 for $e ( readdir RI_DIR ) {
176 0 0       0 $e =~ /^\.\.?$/ and next;
177 0         0 my $abs = "$this/$e";
178 0 0       0 if ( -d $abs ) {
    0          
179 0         0 push @dirs, $abs;
180             }
181             elsif ( -f _ ) {
182 0         0 mylog "immanentizing $abs";
183 0         0 my $ext = 'Q';
184 0         0 my $newname;
185 0         0 while ( -e "$basedir/immediate/$e$ext" ) {
186 0         0 $ext++;
187             }
188 0 0       0 rename $abs, "$basedir/immediate/$e$ext"
189             or mylog "rename failed (with extension $ext): $!";
190             }
191             else {
192 0         0 mylog "UNLINKING NONFILE NONDIR $abs";
193             # abs hasn't been opened, don't need to close it
194 0 0       0 unlink $abs or mylog "UNLINK FAILED: $!";
195             }
196             }
197 0         0 unshift @rmdirs, $this;
198             }
199 0 0       0 for $e (@rmdirs) { rmdir $e or mylog "Can't rmdir $e: $!" }
  0         0  
200             }
201              
202             {
203             # static variables
204             my $string = 'a';
205             my $outboundfname = 'a';
206              
207             sub run() {
208              
209 2     2   10 INIT { $string = 'a' }
210 2     2 0 324 undef $Recipient;
211              
212 2 50 33     108 -d $basedir
213             or mkdir $basedir, 0770
214             or die "could not mkdir $basedir: $!";
215              
216 2 50       44 -w $basedir or croak "base dir <$basedir> must be writable!";
217              
218             # log dir contains logs (duh)
219 2 50 33     48 -d "$basedir/log"
220             or mkdir "$basedir/log", 0770
221             or die "could not mkdir $basedir/log: $!";
222              
223             # queue dir contains deferred messageobjects
224 2 50 33     46 -d "$basedir/queue"
225             or mkdir "$basedir/queue", 0770
226             or die "could not mkdir $basedir/queue: $!";
227              
228             # domain dir contains lists of queued messages, per domain.
229 2 50 33     44 -d "$basedir/domain"
230             or mkdir "$basedir/domain", 0770
231             or die "could not mkdir $basedir/domain: $!";
232              
233             # 4error dir contains lists of 4NN-error remote addresses, per domain.
234 2 50 33     48 -d "$basedir/4error"
235             or mkdir "$basedir/4error", 0770
236             or die "could not mkdir $basedir/4error: $!";
237              
238             # 5error dir contains lists of 5NN-error remote addresses, per domain.
239 2 50 33     42 -d "$basedir/5error"
240             or mkdir "$basedir/5error", 0770
241             or die "could not mkdir $basedir/5error: $!";
242              
243             # conerror dir contains domains we are having trouble connecting to.
244 2 50 33     46 -d "$basedir/conerror"
245             or mkdir "$basedir/conerror", 0770
246             or die "could not mkdir $basedir/conerror: $!";
247              
248             # temp dir contains message objects under construction
249 2 50 33     46 -d "$basedir/temp"
250             or mkdir "$basedir/temp", 0770
251             or die "could not mkdir $basedir/temp: $!";
252              
253 2 50       14 $ONCE or do { # only one MTA at a time, so we can run this
254              
255             # from cron
256 2         2382 open PID, ">>$basedir/temp/MTApid"; # "touch" sort of
257 2 50       114 open PID, "+<$basedir/temp/MTApid"
258             or die "could not open pid file '$basedir/temp/MTApid'";
259 2         22 flock PID, LOCK_EX;
260 2         68 chomp( my $oldpid = );
261              
262 2 50 33     58 if ( $oldpid and kill 0, $oldpid ) {
263 0         0 print "$$ MTA process number $oldpid is still running\n";
264 0         0 mylog "MTA process number $oldpid is still running";
265 0         0 exit;
266             }
267              
268 2         18 seek PID, 0, 0;
269 2         4 DEBUG and warn "main proc is $$";
270 2         10 print PID "$$\n";
271 2         86 flock PID, LOCK_UN;
272 2         24 close PID;
273             };
274              
275             # immediate dir contains reprioritized deferred objects
276 2 50 33     54 -d "$basedir/immediate"
277             or mkdir "$basedir/immediate", 0770
278             or die "could not mkdir $basedir/immediate: $!";
279              
280             # endless top level loop
281 2         16 mylog "starting fork-and-wait loop: will launch every $interval seconds.";
282 2         4 my $count;
283 2         4 for ( ; ; ) {
284 2 50       12 ++$count % $TimeStampFrequency
285             or mylog( time, ": ", scalar(localtime), " ", $count );
286              
287 2 50       14 rand(1000) < 1 and cachepurge; # how long is 17000 seconds?
288              
289 2 50       14 if ( $ActiveKids > $MaxActiveKids ) {
290 0         0 mylog "$ActiveKids child procs (more than $MaxActiveKids)";
291 0         0 sleep( 1 + int( $interval / 3 ) );
292 0         0 next;
293             }
294              
295             # new child drops out of the waiting loop
296 2 50       10 $ONCE and last;
297 2 100       11112 $LastChild = fork or last;
298 1         29 $ActiveKids++;
299 1 50       75 if ($OnlyOnce) {
300 1         71 mylog "OnlyOnce flag set to [$OnlyOnce]";
301 1         47 return $OnlyOnce;
302             }
303 0         0 my $slept = 0;
304 0         0 while ( $slept < $interval ) {
305 0         0 $slept += sleep( 1 + $interval - $slept );
306             }
307             }
308 1         50 my $file;
309 1         28 $time = time;
310 1         58 DEBUG and warn "queuerunner launched at " . localtime $time;
311              
312             # process new files if any
313 1         165 opendir BASEDIR, $basedir;
314 1         432 my @entries = readdir BASEDIR;
315 1         19 my $outfile;
316 1         100 for $file (@entries) {
317 10 50       326 -f "$basedir/$file" or next;
318 0 0       0 -s "$basedir/$file" or next;
319 0         0 mylog "processing new message file $file";
320             rename "$basedir/$file",
321             $outfile = "$basedir/temp/$$-" . $outboundfname++ . time
322 0 0       0 or do {
323 0         0 mylog "could not rename $file: $!";
324 0         0 next;
325             };
326 0         0 DEBUG and warn "renamed new message $basedir/$file to $outfile";
327              
328             # expand and write into temp, then try to
329             # deliver each file as it is expanded
330 0 0       0 unless ( open MESSAGE0, "<$outfile" ) {
331 0         0 mylog "CRITICAL: Could not open $outfile for reading";
332 0         0 next;
333             }
334             eval
335 0         0 " END{ close MESSAGE0; DEBUG and warn q{ unlinking $outfile }; unlink q{$outfile} or mylog q{CRITICAL: could not unlink $outfile}} ";
336              
337 0         0 my @MessData = ();
338 0         0 mylog scalar(@MessData), "lines of message data";
339              
340 0         0 chomp( my $FirstLine = shift @MessData );
341 0         0 mylog "from [[$FirstLine]]";
342              
343             # never mind $FirstLine =~ s/\s*<*([^<>\s]*).*$/$1/s;
344              
345 0         0 my $Recip;
346             my %DOMAIN_MATRIX;
347 0         0 my $bestmx;
348 0         0 for ( ; ; ) {
349 0         0 chomp( $Recip = shift @MessData );
350 0         0 DEBUG and warn "recip $Recip";
351 0 0       0 unless (@MessData) {
352 0         0 mylog "no body in message file $outfile";
353 0         0 die "no body in message file $outfile";
354             }
355              
356             # never mind $Recip =~ s/\s*<*([^<>\s]+\@[\w\-\.]+).*$/$1/s or last;
357 0 0       0 ($Domain) = $Recip =~ /\@([\w\-\.]+)/ or last;
358 0         0 ($bestmx) = dnsmx($Domain);
359 0         0 mylog "for $Recip (via $bestmx)";
360 0         0 push @{ $DOMAIN_MATRIX{$bestmx} }, $Recip;
  0         0  
361             }
362              
363 0         0 foreach $bestmx ( keys %DOMAIN_MATRIX ) {
364 0         0 DEBUG and warn "mx $bestmx gets @{$DOMAIN_MATRIX{$bestmx}}";
365 0         0 $string++;
366 0 0       0 open TEMP, ">$basedir/temp/$time.$$.$string" or die "FAILURE: $!";
367 0         0 DEBUG and warn "in $basedir/temp/$time.$$.$string";
368 0         0 print TEMP "$FirstLine\n@{$DOMAIN_MATRIX{$bestmx}}\n", @MessData,
  0         0  
369             "\n";
370 0         0 close TEMP;
371 0 0       0 rename
372             "$basedir/temp/$time.$$.$string",
373             "$basedir/immediate/$time.$$.$string.$bestmx"
374             or die "rename: $!";
375             }
376              
377             }
378              
379             # process all messages in immediate directory
380 1 50       98 opendir BASEDIR, "$basedir/immediate"
381             or die "could not open immediate dir: $!";
382 1         23 @entries = readdir BASEDIR;
383 1         3 for $file (@entries) {
384 2 50       56 my $M = newmessage "$basedir/immediate/$file" or next;
385 0         0 DEBUG and warn "created message object $M for immediate message file $file";
386 0         0 $M->attempt(); # will skip or requeue or delete
387 0         0 undef $Recipient;
388             }
389              
390             # reprioritize deferred messages
391 1         5 my $qdir = "$basedir/queue";
392              
393 1         27 my @reprime;
394 1         272 for my $NEXTPIECE ( split / /, strftime "%Y %m %d %H %M %S", localtime ) {
395 1         2 DEBUG and warn "looking at queue dir $qdir";
396 1         39 opendir QDIR, $qdir;
397 1         2 my $this;
398 1         20 while ( defined( $this = readdir QDIR ) ) {
399 2 50       126 if ( -f "$qdir/$this" ) {
400 0         0 mylog "immanentizing $qdir/$this";
401 0         0 rename "$qdir/$this", "$basedir/immediate/${this}Q";
402 0         0 next;
403             }
404 2 50       1284 unless ( -d "$qdir/$this" ) {
405 0         0 mylog "UNLINKING NONFILE NONDIR $qdir/$this";
406             # hasn't been opened no need to close it
407 0 0       0 unlink "$qdir/$this" or mylog "UNLINK FAILED: $!";
408 0         0 next;
409             }
410              
411 2 50       191 $this =~ /^\.\.?$/ and next;
412              
413 0 0       0 if ( $this < $NEXTPIECE ) {
414 0         0 recursive_immed "$qdir/$this";
415             }
416             }
417 1         140 $qdir .= "/$NEXTPIECE";
418 1 50       79 -d $qdir or last;
419             }
420              
421 1 50       2003 $ONCE or exit;
422             } # end sub run
423              
424 0     0 0 0 sub once { $ONCE = 1; run}
  0         0  
425              
426              
427             }; # end sub run and enclosing scope
428              
429             # only one active message per process.
430             # (MESSAGE, $ReturnAddress, $Recipient) are all global.
431              
432             sub newmessage($) {
433              
434             #my $pack = shift;
435 2     2 0 16 my $messageID = shift;
436 2 50       325 -f $messageID or return undef;
437 0 0       0 -s $messageID or do {
438              
439             # eliminate freeze on zero-length message files
440             # hasn't been opened no need to close it
441 0         0 unlink $messageID;
442 0         0 return undef;
443             };
444 0 0       0 open MESSAGE, "<$messageID" or return undef;
445 0 0       0 flock MESSAGE, LOCK_EX | LOCK_NB or return undef;
446 0         0 chomp( $ReturnAddress = );
447 0         0 chomp( $Recipient = );
448 0         0 @Recipients = split / +/, $Recipient;
449 0         0 $InitialRecipCount = @Recipients;
450 0         0 undef $PostDataTrouble;
451 0         0 bless \$messageID;
452             }
453              
454             my $purgecount;
455             sub purgedir($);
456              
457             sub purgedir($) {
458 0     0 0 0 my $now = time();
459 0         0 my $dir = shift;
460 0         0 my $nonempty;
461             my @dirs;
462 0         0 opendir SUBDIR, $dir;
463 0         0 foreach ( readdir SUBDIR ) {
464 0 0       0 /^\.{1,2}$/ and next;
465 0         0 $nonempty = 1;
466 0 0       0 -d "$dir/$_" and push @dirs, $_;
467 0 0       0 -f "$dir/$_" or next;
468 0         0 my @statresult = stat(_);
469 0         0 my $mtime = $statresult[9];
470 0 0       0 if ( ( $now - $mtime ) > ( 4 * 60 * 60 ) ) {
471 0 0       0 unlink "$dir/$_" or mylog "problem unlinking $dir/$_: $!";
472 0         0 $purgecount++;
473             }
474             }
475 0         0 foreach my $sdir (@dirs) {
476 0         0 purgedir("$dir/$sdir");
477             }
478 0 0       0 rmdir $dir unless ($nonempty); # patience is a virtue
479             }
480              
481             sub cachepurge() {
482 0     0 0 0 $purgecount = 0;
483 0         0 opendir DIR, "$basedir/4error/";
484 0         0 my @fours = map { "$basedir/4error/$_" } readdir DIR;
  0         0  
485              
486 0         0 opendir DIR, "$basedir/5error/";
487 0         0 my @fives = map { "$basedir/5error/$_" } readdir DIR;
  0         0  
488              
489 0         0 foreach ( @fours, @fives ) {
490 0 0       0 /error\/\.\.?$/ and next;
491 0         0 purgedir($_);
492             }
493 0         0 mylog "purged 4XX,5XX cache and eliminated $purgecount entries";
494              
495 0         0 opendir DIR, "$basedir/conerror/";
496 0         0 foreach ( readdir DIR ) { concachetest $_; }
  0         0  
497              
498             }
499              
500             sub concache($) {
501 0     0 0 0 mylog "caching connection failure to $_[0]";
502 0         0 open TOUCH, ">>$basedir/conerror/$_[0]";
503 0         0 print TOUCH '.';
504 0         0 close TOUCH;
505             }
506              
507             sub concachetest($) {
508 0 0   0 0 0 -f "$basedir/conerror/$_[0]" or return undef;
509 0         0 my @SR = stat(_);
510 0 0       0 ( time() - $SR[9] ) < $ConRetryDelay and return 1;
511              
512 0         0 mylog "ready to try connecting to $_[0] again";
513 0 0       0 unlink "$basedir/conerror/$_[0]" or mylog "trouble unlinking $basedir/conerror/$_[0]: $!";
514              
515 0         0 undef;
516             }
517              
518             sub cache4($) {
519 0     0 0 0 mylog "caching ", $_[0], $line;
520 0 0       0 my ( $user, $host ) = split '@', $_[0], 2 or return undef;
521 0         0 $host =~ y/A-Z/a-z/;
522 0         0 $host =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
523 0         0 $user =~ y/A-Z/a-z/;
524 0         0 $user =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
525 0 0 0     0 -d "$basedir/4error/$host"
526             or mkdir "$basedir/4error/$host", 0770
527             or die "could not mkdir $basedir/4error/$host: $!";
528 0         0 open CACHE, ">$basedir/4error/$host/$user.TMP$$";
529 0         0 print CACHE time(), "\n$line cached " . localtime() . "\n";
530 0         0 close CACHE;
531 0         0 rename "$basedir/4error/$host/$user.TMP$$", "$basedir/4error/$host/$user";
532              
533             }
534              
535             sub cache4test($) {
536 0 0   0 0 0 my ( $user, $host ) = split '@', $_[0], 2 or return undef;
537 0         0 $host =~ y/A-Z/a-z/;
538 0         0 $host =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
539 0         0 $user =~ y/A-Z/a-z/;
540 0         0 $user =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
541 0 0       0 -d "$basedir/4error/$host" or return undef;
542 0 0       0 -f "$basedir/4error/$host/$user" or return undef;
543 0         0 open CACHE, "<$basedir/4error/$host/$user";
544 0         0 my $ctime;
545 0         0 ( $ctime, $line ) = ;
546 0         0 close CACHE;
547              
548 0 0       0 if ( ( time() - $ctime ) > $FourErrCacheLifetime ) {
549              
550             # 4-file is more than seven minutes old
551 0 0       0 unlink "$basedir/4error/$host/$user" or mylog "trouble unlinking $basedir/4error/$host/$user: $!";
552 0         0 return undef;
553             }
554 0         0 mylog "4cached ", $line;
555 0         0 return $ctime;
556             }
557              
558             sub cache5($) {
559 0     0 0 0 mylog "caching ", $_[0], $line;
560 0 0       0 my ( $user, $host ) = split '@', $_[0], 2 or return undef;
561 0         0 $host =~ y/A-Z/a-z/;
562 0         0 $host =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
563 0         0 $user =~ y/A-Z/a-z/;
564 0         0 $user =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
565 0 0 0     0 -d "$basedir/5error/$host"
566             or mkdir "$basedir/5error/$host", 0770
567             or die "could not mkdir $basedir/5error/$host: $!";
568 0 0       0 open CACHE, ">$basedir/5error/$host/$user.TMP$$"
569             or mylog "CACHEfile: $basedir/5error/$host/$user.TMP$$ $!";
570 0         0 print CACHE time(), "\n$line cached " . localtime() . "\n";
571 0         0 close CACHE;
572 0         0 rename "$basedir/5error/$host/$user.TMP$$", "$basedir/5error/$host/$user";
573             }
574              
575             sub cache5test($) {
576 0 0   0 0 0 my ( $user, $host ) = split '@', $_[0], 2 or return undef;
577 0         0 $host =~ y/A-Z/a-z/;
578 0         0 $host =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
579 0         0 $user =~ y/A-Z/a-z/;
580 0         0 $user =~ s/([^\w\.\-])/'X'.ord($1).'Y'/ge;
  0         0  
581 0 0       0 -d "$basedir/5error/$host" or return undef;
582 0 0       0 -f "$basedir/5error/$host/$user" or return undef;
583 0         0 open CACHE, "<$basedir/5error/$host/$user";
584 0         0 flock CACHE, LOCK_SH;
585 0         0 my $ctime;
586 0         0 ( $ctime, $line ) = ;
587 0         0 close CACHE;
588              
589 0 0       0 if ( ( time() - $ctime ) > ( 4 * 60 * 60 ) ) {
590              
591             # 5-file is more than 4 hours old
592 0 0       0 unlink "$basedir/5error/$host/$user" or mylog "trouble unlinking $basedir/5error/$host/$user: $!";
593 0         0 return undef;
594             }
595 0         0 mylog "5cached ", $line;
596 0         0 return $ctime;
597             }
598              
599 2     2   27088 use Socket;
  2         10162  
  2         16070  
600              
601             # { no warnings; sub dnsmx($){
602             # # look up MXes for domain
603             # my @mxresults = sort {$a <=> $b} `$dnsmxpath $_[0]`;
604             # # djbdns program dnsmx provides lines of form /\d+ $domain\n
605             # return map {/\d+ (\S+)/; $1} @mxresults;
606             # };};
607              
608             # use Net::DNS; now in Import
609             # now in import = Net::DNS::Resolver->new;
610             sub _dnsmx($) {
611              
612 2     2   416 my $name = shift;
613            
614 2         6 my $host = $name;
615 2 50       12 if ( exists $SMTProutes{$host} ) {
616 0 0       0 ref( $SMTProutes{$host} )
617             and return Scramble( $SMTProutes{$host} );
618 0         0 return ($SMTProutes{$host});
619             }
620 2 50       8 if ( exists $SMTProutes{SMARTHOST} ) {
621 0 0       0 ref( $SMTProutes{SMARTHOST} )
622             and return Scramble( $SMTProutes{$host} );
623 0         0 return ($SMTProutes{SMARTHOST});
624             };
625            
626 2         14 my @mx = map { $_->exchange } mx( $res, $name );
  6         47128  
627 2 50       86 @mx or return ($name);
628              
629 2         14 return @mx;
630             }
631              
632             # my $calls;
633             # sub SOCKready(){
634             # my $rin='';
635             # vec($rin,fileno('SOCK'),1) = 1;
636             # my ($n, $tl) = select(my $r=$rin,undef,undef,0.25);
637             # print "$calls\n";
638             # $calls++ > 200 and exit;
639             # return $n;
640             # };
641              
642             my $CRLF = CRLF;
643              
644             sub eofSOCK() {
645 2     2   30 no warnings;
  2         6  
  2         23940  
646 0     0 0   my $hersockaddr = getpeername(SOCK);
647 0 0         if ( defined $hersockaddr ) {
648 0           return undef;
649             }
650             else {
651 0           mylog "SOCK not connected";
652 0           return 1;
653             }
654             }
655              
656             sub getresponse($) {
657              
658             # mylog "sending: [$_[0]]";
659              
660 0 0   0 0   if (eofSOCK) {
661 0           mylog "problem with SOCK";
662 0           return undef;
663             }
664              
665 0           $timeout = 0;
666 0           alarm 130;
667 0 0         unless ( print SOCK "$_[0]$CRLF" ) {
668 0           mylog "print SOCK: $!";
669 0           return undef;
670             }
671              
672 0           DEBUG and mylog "sent $_[0]";
673              
674 0           my ( $dash, $response ) = ( '-', '' );
675 0           while ( $dash eq '-' ) {
676 0           my $letter;
677             my @letters;
678 0           my $i = 0;
679 0           my $more = 1;
680 0           my $BOL = 1; # "beginning of line"
681 0           do {
682 0 0         if ($timeout) {
683 0           mylog "timeout in getresponse";
684 0           return undef;
685             }
686 0 0         if (eofSOCK) {
687 0           mylog "eofSOCK";
688 0           return undef;
689             }
690 0           sysread( SOCK, $letter, 1 );
691 0 0 0       if ( $letter eq "\r" or $letter eq "\n" ) {
692 0           $more = $BOL;
693             }
694             else {
695 0           $BOL = 0;
696 0 0         if ( length($letter) ) {
697 0           $letters[ $i++ ] = $letter;
698              
699             # mylog @letters;
700             }
701             else {
702 0           sleep 1;
703             }
704             }
705             } while ($more);
706              
707 0           my $iline = join( '', @letters );
708              
709 0           DEBUG and mylog "received: [$iline]";
710 0           $response .= $iline;
711 0           ($dash) = $iline =~ /^\d+([\-\ ])/;
712             }
713 0           $response;
714             }
715              
716             my $onioning = 0;
717              
718             sub deferralmessage {
719              
720             # usage: $message->deferralmessage("reason we are deferring")
721              
722 0 0   0 0   $ReturnAddress =~ /\@/ or return; #suppress doublebounces
723 0           my $filename = join '.', time, 'DeferralReport', rand(10000000);
724 0           open BOUNCE, ">$basedir/temp/$filename";
725 0           print BOUNCE <
726             <>
727             $ReturnAddress
728             $dateheader
729             Message-Id: <$filename\@$MyDomain>
730             From: MAILER-DAEMON
731             To: $ReturnAddress
732             Subject: delivery deferral to <$Recipient>
733             Content-type: text/plain
734              
735             $_[0]
736              
737             The first eighty lines of the message follow below:
738             -------------------------------------------------------------
739             EOF
740              
741 0           seek( MESSAGE, 0, 0 );
742 0           for ( 1 .. 80 ) {
743 0 0         defined( my $lin = ) or last;
744 0           print BOUNCE $lin;
745             }
746 0           close BOUNCE;
747 0           rename "$basedir/temp/$filename", "$basedir/immediate/$filename";
748             }
749              
750             # end sub deferralmessage
751              
752             sub attempt {
753 0 0   0 0   $onioning or $ReuseQuota = $ReuseQuotaInitial;
754 0           $line = '';
755 0           $ConnectionProblem = 0;
756              
757             # deliver and delete, or requeue; also send bounces if appropriate
758 0           my $message = shift;
759 0           mylog "Attempting [$ReturnAddress] -> [$Recipient]";
760              
761             # Message Data is supposed to start on third line
762              
763             ########################################
764             # reuse sock or define global $Domain
765             ########################################
766 0 0 0       if ( defined($Domain) and $Domain and $Recipient =~ /\@$Domain$/i ) {
      0        
767 0 0         eofSOCK or goto HaveSOCK;
768             }
769              
770 0 0         unless ( ($Domain) = $Recipient =~ /\@([^\s>]+)/ ) {
771 0           mylog "no domain in recipient [$Recipient], discarding message";
772 0           close MESSAGE;
773 0 0         unlink $$message or mylog "trouble unlinking $$message: $!";
774              
775 0           return;
776             }
777 0           $Domain =~ y/A-Z/a-z/;
778             ########################################
779             # $Domain is now defined
780             ########################################
781              
782 0 0         if ( concachetest $Domain) {
783 0           mylog "$Domain connection failure cached";
784 0           goto ReQueue_unconnected;
785             }
786              
787 0           my @dnsmxes;
788 0           @dnsmxes = dnsmx($Domain);
789 0           my $dnsmx_count = @dnsmxes;
790 0           mylog "[[$Domain]] MX handled by @dnsmxes";
791 0 0         unless (@dnsmxes) {
792 0           mylog "requeueing due to empty dnsmx result";
793 0           goto ReQueue_unconnected;
794             }
795 0           my $Peerout;
796              
797 0 0         cache4test $Recipient
798             and goto ReQueue;
799 0 0         cache5test $Recipient
800             and goto Bounce;
801              
802             TryAgain:
803              
804 0           while ( $Peerout = shift @dnsmxes ) {
805              
806             # mylog "attempting $Peerout";
807              
808             # connect to $Peerout, smtp
809 0           my @GHBNres;
810 0 0         unless ( @GHBNres = gethostbyname($Peerout) ) {
811 0 0 0       if ( $dnsmx_count == 1
812             and $Peerout eq $Domain )
813             {
814 0           mylog $line= "Apparently there is no valid MX for $Domain";
815 0           $ConnectionProblem = 0;
816 0           goto Bounce;
817             }
818 0           next;
819             }
820 0 0         my $iaddr = $GHBNres[4] or next;
821 0           my $paddr = sockaddr_in( 25, $iaddr );
822 0 0         socket( SOCK, PF_INET, SOCK_STREAM, getprotobyname('tcp') )
823             or die "$$ socket: $!";
824              
825 0 0         if (defined $BindAddress){
826 0 0         bind(SOCK, sockaddr_in(0, inet_aton($BindAddress)))
827             or die "could not bind to $BindAddress: $!";
828             };
829              
830 0 0         connect( SOCK, $paddr ) || next;
831 0           mylog "connected to $Peerout";
832 0           my $oldfh = select(SOCK);
833 0           $| = 1;
834 0           select($oldfh);
835 0           goto SMTPsession;
836              
837             }
838              
839 0           concache $Domain;
840 0           mylog "Unable to establish SMTP connection to $Domain MX";
841 0           $ConnectionProblem = 1;
842 0           goto ReQueue_unconnected;
843              
844             # talk SMTP
845             SMTPsession:
846             $SIG{ALRM} = sub {
847 0     0     mylog 'TIMEOUT -- caught alarm signal in attempt()';
848 0           $message->requeue("timed out during SMTP interaction");
849 0           close MESSAGE;
850 0 0         unlink $$message or mylog "trouble unlinking $$message: $!";
851 0 0         $onioning and unlink "$basedir/domain/$Domain.$$";
852 0 0         $ONCE or exit;
853 0           };
854              
855             # expect 220
856 0           alarm 60;
857 0           my $Greetingcounter = 0;
858 0           ExpectGreeting:
859              
860             my @GreetArr = ();
861 0           do {
862 0 0         eval { defined( $line = ) or die "no line from socket. [$!]"; };
  0            
863 0 0 0       if ( $@ or ++$Greetingcounter > 20 ) {
864 0           mylog @GreetArr, "Error: $@";
865 0           close SOCK;
866 0           goto TryAgain;
867             }
868 0           chomp $line;
869 0           mylog $line;
870 0           push @GreetArr, $line;
871             } while ( substr( $line, 0, 4 ) ne '220 ' )
872             ; # this condition will enforce greeting compliance
873              
874 0           $line = join ' / ', @GreetArr;
875 0           $line =~ s/[\r\n]//g;
876 0 0         @GreetArr > 1 and mylog "extended greeting: $line";
877              
878             # print SOCK "HELO $MyDomain",CRLF;
879             # expect 250
880             # $line = getresponse "HELO $MyDomain" or goto TryAgain;
881 0 0         $line = getresponse "EHLO $MyDomain" or goto TryAgain;
882 0 0         unless ( $line =~ /^250[ \-]/ ) {
883 0           mylog "peer not happy with EHLO: [$line]";
884 0 0         $line = getresponse "HELO $MyDomain" or goto TryAgain;
885 0 0         unless ( $line =~ /^250[ \-]/ ) {
886 0           mylog "peer not happy with HELO: [$line]";
887 0           close SOCK;
888 0           goto TryAgain;
889             }
890             }
891 0           mylog $line;
892              
893 0 0         HaveSOCK:
894             $line = getresponse "RSET" or goto TryAgain;
895              
896             # expect 250
897             # $line = getresponse;
898             # mylog "RSET and got [$line]";
899 0 0         unless ( $line =~ /^250[ \-]/ ) {
900 0           mylog
901             "peer not happy with RSET: [$line] will not reuse this connection";
902 0           $ReuseQuota = 0;
903              
904             # close SOCK;
905             # goto TryAgain;
906             }
907              
908             # remove angle brackets if any
909 0           $ReturnAddress =~ s/^.*
910 0           $ReturnAddress =~ s/>.*$//;
911              
912 0 0         $line = getresponse "MAIL FROM:<$ReturnAddress>" or goto TryAgain;
913 0           mylog "$line";
914 0 0         unless ( $line =~ /^[2]/ ) {
915 0           mylog "peer not happy with return address: [$line]";
916 0 0         if ( $line =~ /^[4]/ ) {
917 0           mylog "requeueing";
918 0           goto ReQueue;
919             }
920 0 0         if ( $line =~ /^[5]/ ) {
921 0           goto Bounce;
922             }
923 0           mylog "and response was neither 2,4 or 5 coded.";
924 0           goto TryAgain;
925             }
926              
927             # print SOCK "RCPT TO: <$Recipient>\r\n";
928             # expect 250
929 0           my @recips4;
930             my @recips5;
931              
932 0 0         @Recipients > 1 and do {
933 0           for ( my $i = 0 ; $i < @Recipients ; $i++ ) {
934 0           my $r = int rand @Recipients;
935 0           @Recipients[ $i, $r ] = @Recipients[ $r, $i ];
936             }
937             };
938              
939 0           my @GoodR;
940             my @emsgmap;
941 0           foreach (@Recipients) {
942              
943             # remove angle brackets if any
944 0           s/^.*
945 0           s/>.*$//;
946              
947 0 0         $line = getresponse "RCPT TO:<$_>" or goto TryAgain;
948 0 0         if ( $line =~ /^2/ ) {
949 0           push @GoodR, $_;
950             }
951             else {
952 0           mylog "peer not happy with recipient $_: [$line]";
953 0 0         if ( $line =~ /^4/ ) {
    0          
954 0 0         if ( @Recipients > 1 ) {
955 0           push @recips4, $_;
956              
957             # no emsgmap4 is needed because 4-deferred recipients
958             # get split apart, eventually there will be only one
959             # in the message and then 4-bounces will happen
960             }
961             else {
962 0           cache4 $Recipient;
963 0           mylog "requeueing";
964 0           goto ReQueue;
965             }
966             }
967             elsif ( $line =~ /^5/ ) {
968 0 0         if ( @Recipients > 1 ) {
969 0 0         if (/\@$Domain$/) {
970 0           push @recips5, $_;
971 0           push @emsgmap, " $_: $line";
972             }
973             else {
974 0           push @recips4, $_;
975             }
976             }
977             else {
978 0           cache5 $Recipient;
979 0           goto Bounce;
980             }
981             }
982             else {
983 0           mylog
984             "noncompliant SMTP peer [$Peerout] gave funny response $line";
985 0           goto TryAgain;
986             }
987             }
988             }
989              
990             ReQueueFours:
991 0 0         if ( @recips4 + @recips5 ) {
992 0           DEBUG and warn "4: @recips4";
993 0           DEBUG and warn "5: @recips5";
994 0 0         if ( @recips5 == @Recipients ) {
995 0           goto Bounce;
996             }
997              
998             # if ((@recips5 + @recips4) == @Recipients){
999             # goto ReQueue;
1000             # };
1001              
1002 2     2   14 my $counter = $ReQ::counter++; INIT { $ReQ::counter='a' };
  0            
1003 0           open BODY, ">$basedir/temp/BODY.$$.$counter";
1004 0           eval "END{ close BODY; unlink '$basedir/temp/BODY.$$.$counter' }";
1005 0           while () {
1006 0           print BODY $_;
1007             }
1008 0           close BODY;
1009 0           open MESSAGE, "<$basedir/temp/BODY.$$.$counter";
1010              
1011 0 0         if (@recips4) {
1012 0           DEBUG and warn "requeing for @recips4";
1013 0           open ONE, ">$basedir/temp/RETRY.$$.$counter.ONE";
1014 0           print ONE "$ReturnAddress\n";
1015 0 0         if (@recips4 > 1 ){
1016 0           open TWO, ">$basedir/temp/RETRY.$$.$counter.TWO";
1017 0           print TWO "$ReturnAddress\n";
1018 0           while (@recips4) {
1019 0           print ONE ( ( shift @recips4 ) . "\n" );
1020 0 0         @recips4 and print TWO ( ( shift @recips4 ) . "\n" );
1021             }
1022 0           print ONE "\nX-TipJar-Mta-Requeue-A-$dateheader";
1023 0           print TWO "\nX-TipJar-Mta-Requeue-B-$dateheader";
1024 0           while () {
1025 0           print ONE $_;
1026 0           print TWO $_;
1027              
1028             }
1029 0           close TWO;
1030 0           close ONE;
1031 0           rename "$basedir/temp/RETRY.$$.$counter.ONE",
1032             "$basedir/RETRY4a" . rand(98765);
1033 0           rename "$basedir/temp/RETRY.$$.$counter.TWO",
1034             "$basedir/RETRY4b" . rand(98765);
1035              
1036             }else{
1037 0           print ONE ( ( shift @recips4 ) . "\n\n" );
1038 0           print ONE "X-TipJar-Mta-Singleton-Requeue-$dateheader";
1039 0           print ONE ();
1040 0           close ONE;
1041 0           rename "$basedir/temp/RETRY.$$.$counter.ONE",
1042             "$basedir/".rand(99999)."RETRY4singleton" . rand(98765);
1043              
1044             };
1045 0           open MESSAGE, "<$basedir/temp/BODY.$$.$counter";
1046             RECIP5:
1047 0           while (@recips5) {
1048 0 0         $ReturnAddress =~ /\@/ or last; #suppress doublebounces
1049              
1050             # grep {$ReturnAddress =~ m/$_/} @NoBounceRegexList and goto GoodDelivery
1051 0           for (@NoBounceRegexList) {
1052 0 0         if ( $ReturnAddress =~ m/$_/ ) {
1053 0           mylog "suppressing bounce to <$ReturnAddress>";
1054 0           next RECIP5;
1055             }
1056             }
1057 0           mylog "bouncing to <$ReturnAddress>";
1058 0           my $filename = join '.', time(), 'HardFail', rand(10000000);
1059 0           open BOUNCE, ">$basedir/temp/$filename";
1060 0           local $" = "\n";
1061 0           print BOUNCE <
1062             <>
1063             $ReturnAddress
1064             $dateheader
1065             Message-Id: <$filename\@$MyDomain>
1066             From: MAILER-DAEMON
1067             To: $ReturnAddress
1068             Subject: multiple SMTP rejections for <@recips5>
1069             Content-type: text/plain
1070              
1071             While connected to SMTP peer $Peerout,
1072             the $MyDomain e-mail system received the error messages
1073              
1074             @emsgmap
1075              
1076             which indicate permanent errors.
1077             The first hundred and fifty lines of the message follow below:
1078             -------------------------------------------------------------
1079             EOF
1080              
1081 0           for ( 1 .. 150 ) {
1082 0 0         defined( my $lin = ) or last;
1083 0           print BOUNCE $lin;
1084             }
1085 0           close BOUNCE;
1086 0           mylog "renaming temp file to immediate $filename";
1087 0           rename "$basedir/temp/$filename",
1088             "$basedir/immediate/$filename";
1089             }
1090 0           @recips5 = ();
1091             }
1092 0           open MESSAGE, "<$basedir/temp/BODY.$$.$counter";
1093             }
1094              
1095 0 0         $PostDataTrouble and goto GoodDelivery;
1096              
1097             # DATA_TRANSACTION:
1098 0           $Recipient = "@GoodR";
1099              
1100             # print SOCK "DATA\r\n";
1101             # expect 354
1102 0 0         $line = getresponse 'DATA' or goto TryAgain;
1103 0 0         unless ( $line =~ /^354 / ) {
1104 0           mylog "peer not happy with DATA: [$line]";
1105 0 0         if ( @GoodR == 1 ) {
1106 0 0         if ( $line =~ /^4/ ) {
1107 0           goto ReQueue;
1108             }
1109 0 0         if ( $line =~ /^5/ ) {
1110 0           goto Bounce;
1111             }
1112 0           mylog "reporting noncompliant SMTP peer [$Peerout]";
1113 0           goto TryAgain;
1114             }
1115 0           @recips4 = @GoodR;
1116 0           $PostDataTrouble = 1;
1117 0           goto ReQueueFours;
1118              
1119             }
1120 0           my $linecount;
1121             my $bytecount;
1122 0 0         print SOCK "X-Tipjar-Mta-Transmitted-By: $MyDomain\r\n" or die $!;
1123 0           while () {
1124 0           $linecount++;
1125 0           $bytecount += length;
1126 0           chomp;
1127 0           eval {
1128 0           alarm 60;
1129 0 0         if ( $_ eq '.' ) {
1130 0 0         print SOCK "..\r\n" or die $!;
1131             }
1132             else {
1133 0 0         print SOCK $_, "\r\n" or die $!;
1134             }
1135             };
1136 0 0         if ($@) {
1137 0           mylog $@;
1138 0           goto TryAgain;
1139             }
1140             }
1141 0           close MESSAGE;
1142             # print SOCK ".\r\n";
1143             # expect 250
1144 0           mylog "$linecount lines ($bytecount chars) of message data, sending dot"
1145             ; # TryAgain will pop the MX list when there are more than 1 MX
1146 0 0         $line = getresponse '.' or goto TryAgain;
1147 0 0         unless ( $line =~ /^2/ ) {
1148 0           mylog "peer not happy with message body: [$line]";
1149 0 0         if ( $line =~ /^4/ ) {
1150 0           @recips4 = @GoodR;
1151 0           $PostDataTrouble = 1;
1152 0 0         @recips4 > 1 and goto ReQueueFours;
1153 0           mylog "requeueing";
1154 0           goto ReQueue;
1155             }
1156 0 0         if ( $line =~ /^5/ ) {
1157 0           goto Bounce;
1158             }
1159 0           mylog "reporting noncompliant SMTP peer [$Peerout]";
1160 0           goto TryAgain;
1161             }
1162              
1163 0           goto GoodDelivery;
1164              
1165 0           ReQueue:
1166             $message->requeue($line);
1167 0           goto GoodDelivery;
1168              
1169 0           ReQueue_unconnected:
1170             $message->requeue($line);
1171 0           return undef;
1172              
1173 0 0         Bounce:
1174              
1175             $ReturnAddress =~ /\@/ or goto GoodDelivery; #suppress doublebounces
1176              
1177             # grep {$ReturnAddress =~ m/$_/} @NoBounceRegexList and goto GoodDelivery
1178 0           for (@NoBounceRegexList) {
1179 0 0         if ( $ReturnAddress =~ m/$_/ ) {
1180 0           mylog "suppressing bounce to <$ReturnAddress>";
1181              
1182 0           goto GoodDelivery;
1183             }
1184             }
1185 0           mylog "bouncing to <$ReturnAddress>";
1186 0           my $filename = join '.', time(), 'HardFail', rand(10000000);
1187 0           open BOUNCE, ">$basedir/temp/$filename";
1188 0 0         defined($line) or $line = 'unknown reason';
1189 0 0         defined($Recipient) or $Recipient = 'unknown recipient';
1190 0 0         defined($ReturnAddress) or $ReturnAddress = '<>';
1191 0 0         defined($Peerout) or $Peerout = 'unknown peer';
1192              
1193 0           print BOUNCE <
1194             <>
1195             $ReturnAddress
1196             $dateheader
1197             Message-Id: <$filename\@$MyDomain>
1198             From: MAILER-DAEMON
1199             To: $ReturnAddress
1200             Subject: delivery failure to <$Recipient>
1201             Content-type: text/plain
1202              
1203             While connected to SMTP peer $Peerout,
1204             the $MyDomain e-mail system received the error message
1205              
1206             $line
1207              
1208             which indicates a permanent error.
1209             The first hundred and fifty lines of the message follow below:
1210             -------------------------------------------------------------
1211             EOF
1212              
1213 0           seek( MESSAGE, 0, 0 );
1214 0           for ( 1 .. 150 ) {
1215 0 0         defined( my $lin = ) or last;
1216 0           print BOUNCE $lin;
1217             }
1218 0           close BOUNCE;
1219 0           rename "$basedir/temp/$filename", "$basedir/immediate/$filename";
1220              
1221 0           GoodDelivery:
1222             undef $Recipient;
1223 0           close MESSAGE; # windows can't unlink an open file
1224 0 0         unlink $$message or die "FAILED TO UNLINK $$message: $!"; # "true"
1225              
1226 0           alarm 0;
1227 0 0         if ($onioning) {
1228 0           mylog "already onioning";
1229 0           return;
1230             }
1231 0 0         if ( -f "$basedir/domain/$Domain" ) {
1232 0           mylog "onioning $Domain";
1233 0           open DOMAINLOCK, ">>$basedir/domain/.lock";
1234 0           flock DOMAINLOCK, LOCK_EX;
1235 0           rename "$basedir/domain/$Domain", "$basedir/domain/$Domain.$$";
1236 0           flock DOMAINLOCK, LOCK_UN;
1237 0           close DOMAINLOCK;
1238              
1239             # sleep 4; # let any writers finish writing
1240 0           local *DOMAINLIST;
1241 0           $onioning++;
1242 0           open DOMAINLIST, "<$basedir/domain/$Domain.$$";
1243 0           while () {
1244 0           chomp;
1245 0 0         -f $_ or next;
1246 0 0 0       if ( --$ReuseQuota < 0 or eofSOCK ) { # no more socket reuse.
1247 0           open MOREDOMAIN, ">>$basedir/domain/$Domain";
1248 0           flock MOREDOMAIN, LOCK_EX;
1249 0           seek MOREDOMAIN, 2, 0;
1250 0           while () {
1251 0           chomp;
1252 0 0         -f $_ or next;
1253 0           print MOREDOMAIN "$_\n";
1254             }
1255 0           flock MOREDOMAIN, LOCK_UN;
1256 0           close MOREDOMAIN;
1257 0           last;
1258             }
1259 0           mylog "reusing sock with $_";
1260 0           my $M = newmessage $_; # sets some globals
1261 0 0         $M or next;
1262 0           $M->attempt();
1263 0           undef $Recipient;
1264             };
1265 0           close DOMAINLIST;
1266 0 0         unlink "$basedir/domain/$Domain.$$" or mylog "trouble unlinking DOMAINLIST domain/$Domain.$$: $!";
1267 0           $onioning--;
1268             }
1269             else {
1270 0           mylog "no onion file for $Domain";
1271             }
1272              
1273 0 0         eofSOCK or mylog getresponse 'QUIT';
1274 0           close SOCK;
1275              
1276 0           return;
1277              
1278             }
1279              
1280             sub requeue {
1281 0     0 0   my $message = shift;
1282 0 0         -f $$message or do {
1283 0           mylog "message $$message is missing, probably already reQd.";
1284 0           return;
1285             };
1286 0           my @stat = stat(_);
1287 0           my $reason = shift;
1288 0           my ( $fdir, $fname ) = $$message =~ m#^(.+)/([^/]+)$#;
1289 0           my $age = $time - $stat[9];
1290 0           mylog "reQing $$message which is $age seconds old";
1291 0           DEBUG and warn "reQing $$message which is $age seconds old";
1292              
1293 0 0         if ( $age > OneWeek ) {
1294 0           mylog "bouncing message $age seconds old";
1295 0 0         $ReturnAddress =~ /\@/ or goto unlinkme; #suppress doublebounces
1296 0           my $filename = join '.', time, $$, 'FinalFail', rand(10000000);
1297 0           open BOUNCE, ">$basedir/temp/$filename";
1298 0           print BOUNCE <
1299             <>
1300             $ReturnAddress
1301             $dateheader
1302             Message-Id: <$filename\@$MyDomain>
1303             From: MAILER-DAEMON
1304             To: $ReturnAddress
1305             Subject: delivery failure to <$Recipient>
1306             Content-type: text/plain
1307              
1308             A message has been enqueued for delivery for over a week,
1309             the $MyDomain e-mail system is deleting it.
1310              
1311             Final temporary deferral reason:
1312             $reason
1313              
1314             The first hundred and fifty lines of the message follow below:
1315             -------------------------------------------------------------
1316             EOF
1317              
1318 0           seek( MESSAGE, 0, 0 );
1319 0           for ( 1 .. 150 ) {
1320 0 0         defined( my $lin = ) or last;
1321 0           print BOUNCE $lin;
1322             }
1323 0           close BOUNCE;
1324 0           rename "$basedir/temp/$filename", "$basedir/immediate/$filename";
1325              
1326 0           unlinkme:
1327             close MESSAGE;
1328 0 0         unlink $$message or mylog "trouble unlinking $$message: $!";
1329              
1330             # clean up per-domain queue
1331 0           DLpurge;
1332 0           return;
1333             }
1334              
1335 0 0 0       if (
      0        
1336             $age > $AgeBeforeDeferralReport
1337             and $reason
1338             and $ReturnAddress =~ /\@/ # suppress doublebounces
1339             )
1340             {
1341 0           my $filename = join '.', time, $$, 'ReQueue', rand(10000000);
1342 0           open BOUNCE, ">$basedir/temp/$filename";
1343 0           print BOUNCE <
1344             <>
1345             $ReturnAddress
1346             $dateheader
1347             Message-Id: <$filename\@$MyDomain>
1348             From: MAILER-DAEMON
1349             To: $ReturnAddress
1350             Subject: delivery deferral to <$Recipient>
1351             Content-type: text/plain
1352              
1353             The $MyDomain e-mail system is not able to deliver
1354             a message to $Recipient right now.
1355             Attempts will continue until the message is over a week old.
1356              
1357             Temporary deferral reason:
1358             $reason
1359              
1360             The first hundred and fifty lines of the message follow below:
1361             -------------------------------------------------------------
1362             EOF
1363              
1364 0           seek( MESSAGE, 0, 0 );
1365 0           for ( 1 .. 150 ) {
1366 0 0         defined( my $lin = ) or last;
1367 0           print BOUNCE $lin;
1368             }
1369 0           close BOUNCE;
1370 0           rename "$basedir/temp/$filename", "$basedir/immediate/$filename";
1371              
1372             }
1373             ; # if old enough to report as deferred
1374              
1375 0           my $futuretime = int( time + 100 + ( $age * ( 3 + rand(2) ) / 4 ) );
1376              
1377             # print "futuretime will be $futuretime\n";
1378 0           my @DirPieces = split / /, strftime "%Y %m %d %H %M %S",
1379             localtime $futuretime;
1380              
1381             # print "dir,subdir is $dir,$subdir\n";
1382 0           my $dir = "$basedir/queue";
1383 0           while (@DirPieces) {
1384 0           $dir .= ( '/' . shift @DirPieces );
1385 0 0 0       -d $dir
1386             or mkdir $dir, 0777
1387             or croak "$$ Permissions problems: mkdir $dir: [$!]\n";
1388             }
1389              
1390 0           rename $$message, "$dir/$fname";
1391 0           mylog "message queued to $dir/$fname";
1392              
1393 0 0         $ConnectionProblem and DLsave("$dir/$fname");
1394             }
1395              
1396             sub DLpurge() {
1397              
1398             # -f "$basedir/domain/$Domain" or return;
1399             # rename fails when source file ain't there
1400 0 0   0 0   rename "$basedir/domain/$Domain", "$basedir/domain/$Domain$$" or return;
1401 0           my $fn;
1402 0           open DOMAINLIST, "<$basedir/domain/$Domain$$";
1403              
1404 0           while ( $fn = ) {
1405 0           chomp $fn;
1406 0           my ($namepart) = ( $fn =~ m{([^/]+)$} );
1407 0           rename $fn, "$basedir/immediate/DRUSH$$.$namepart";
1408             }
1409              
1410 0           close DOMAINLIST;
1411 0 0         unlink "$basedir/domain/$Domain$$" or mylog "trouble unlinking domainlist $Domain$$: $!";
1412             }
1413              
1414             sub DLsave($) {
1415 0 0   0 0   open DOMAINLISTLOCK, ">>$basedir/domain/.lock"
1416             or return mylog "could not open [$basedir/domain/.lock] for append";
1417 0           alarm 0; # we're going to block for the lock
1418 0           flock DOMAINLISTLOCK, LOCK_EX;
1419 0 0         open DOMAINLIST, ">>$basedir/domain/$Domain"
1420             or return mylog "could not open [$basedir/domain/$Domain] for append";
1421 0           print DOMAINLIST "$_[0]\n";
1422 0           close DOMAINLIST;
1423 0           flock DOMAINLISTLOCK, LOCK_UN;
1424 0           close DOMAINLISTLOCK;
1425              
1426             }
1427              
1428             1;
1429             __END__