File Coverage

blib/lib/Mail/Spool.pm
Criterion Covered Total %
statement 166 315 52.7
branch 38 132 28.7
condition 17 68 25.0
subroutine 27 37 72.9
pod 16 18 88.8
total 264 570 46.3


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Mail::Spool - adpO - Extensible Perl Mail Spooler
4             #
5             # $Id: Spool.pm,v 1.7 2001/12/07 23:55:26 rhandom Exp $
6             #
7             # Copyright (C) 2001, Paul T Seamons
8             # paul@seamons.com
9             # http://seamons.com/
10             #
11             # This package may be distributed under the terms of either the
12             # GNU General Public License
13             # or the
14             # Perl Artistic License
15             #
16             # All rights reserved.
17             #
18             # Please read the perldoc Mail::Spool
19             #
20             ################################################################
21              
22             package Mail::Spool;
23              
24 1     1   1229 use strict;
  1         2  
  1         44  
25 1         110 use vars qw(@EXPORT_OK
26             @ISA
27             $AUTOLOAD
28             $REV
29             $VERSION
30             $DEQUEUE_DIR
31             $DEQUEUE_PERIODS
32             $DEQUEUE_PRIORITY
33             $DEQUEUE_TIMEOUT
34             $MAX_DEQUEUE_PROCESSES
35             $MAX_CONNECTION_TIME
36 1     1   4 $USAGE_LOG);
  1         1  
37 1     1   5 use Exporter ();
  1         13  
  1         19  
38 1     1   1694 use File::NFSLock 1.10 ();
  1         4481  
  1         24  
39 1     1   1702 use Net::DNS ();
  1         153411  
  1         25  
40 1     1   988 use Net::SMTP ();
  1         23719  
  1         31  
41 1     1   1087 use IO::File ();
  1         3029  
  1         23  
42 1     1   962 use Mail::Internet ();
  1         17509  
  1         28  
43 1     1   17 use Mail::Address ();
  1         1  
  1         21  
44 1     1   5 use Digest::MD5 qw(md5_hex);
  1         2  
  1         83  
45              
46 1     1   695 use Mail::Spool::Handle ();
  1         3  
  1         15  
47 1     1   671 use Mail::Spool::Node ();
  1         2  
  1         4337  
48              
49             @ISA = qw(Exporter);
50             @EXPORT_OK = qw(dequeue send_mail daemon);
51              
52             $REV = (q$Revision: 1.7 $ =~ /([\d\.]+)/) ? $1 : ""; # what revision is this
53             $VERSION = '0.50';
54              
55             ###----------------------------------------------------------------###
56              
57             ### directory that will hold mail spool (hate to hard code)
58             $DEQUEUE_DIR = "/var/spool/mail";
59              
60             ### seconds to be in a queue before trying
61             ### see the list_spool_handles sub for further discussion
62             $DEQUEUE_PERIODS = [0, .5*3600, 4*3600, 8*3600, 16*3600, 24*3600, 48*3600];
63              
64             ### directory priority (lower is higher priority)
65             ### see the list_spool_handles sub for further discussion
66             $DEQUEUE_PRIORITY = [1, 3, 9, 25, 50, 100, 200];
67              
68             ### seconds to wait before checking the queues
69             ### see the list_spool_handles sub for further discussion
70             $DEQUEUE_TIMEOUT = 20;
71              
72             ### max number of processes to start at one time
73             $MAX_DEQUEUE_PROCESSES = 20;
74              
75             ###----------------------------------------------------------------###
76              
77             ### directory to store usage information
78             $USAGE_LOG = "$DEQUEUE_DIR/usage";
79              
80             ### maximum amount of time to try and send a message
81             $MAX_CONNECTION_TIME = 6 * 60 * 60;
82              
83             ### RFC line ending
84             my $crlf = "\015\012";
85              
86             ###----------------------------------------------------------------###
87              
88             sub new {
89 1     1 1 73 my $type = shift;
90 1   50     9 my $class = ref($type) || $type || __PACKAGE__;
91 1 50 33     7 my $self = @_ && ref($_[0]) ? shift() : {@_};
92 1         4 return bless $self, $class;
93             }
94              
95             ###----------------------------------------------------------------###
96             ### daemon which can act as the mail dequeuer
97             ### invoke via "perl -e 'use Mail::Spool; Mail::Spool->new->daemon;'"
98             sub daemon {
99 0     0 1 0 my $self = shift;
100              
101 0         0 $self->create_dequeue_dirs();
102            
103 0         0 my $package = "Net::Server::Fork";
104 0         0 require $package;
105 0         0 unshift @ISA, $package;
106              
107 0         0 $self->run(
108             log_file => 'Sys::Syslog', # send any debugging to the syslog
109             setsid => 1, # make sure this truly daemonizes
110            
111             max_servers => 1, # don't fire up any servers
112            
113             check_for_dequeue => $self->dequeue_timeout, # wait before looking at the queue
114             max_dequeue => $self->max_dequeue_processes, # max number to start
115            
116             @_, # any other arguments
117             );
118 0         0 exit;
119             }
120 0     0 0 0 sub pre_bind {}
121 0     0 0 0 sub bind {}
122              
123             ###----------------------------------------------------------------###
124             sub create_dequeue_dirs {
125 1   33 1 1 12 my $self = shift || Mail::Spool->new();
126            
127             ### make sure the dequeue dir is there
128 1 50       19 if( ! -d $self->dequeue_dir ){
129 0         0 mkdir $self->dequeue_dir, 0755;
130 0 0       0 die "Couldn't create dequeue_dir ($!)" if ! -d $self->dequeue_dir;
131             }
132            
133             ### create the queue directories
134 1         11 my $periods = $self->dequeue_periods;
135 1         5 for ( 0 .. $#$periods ){
136 7         19 my $dir = $self->dequeue_dir . "/$_";
137 7 50       117 if( ! -d $dir ){
138 7         415 mkdir $dir, 0755;
139 7 50       98 die "Couldn't create dequeue_dir ($dir) ($!)" if ! -d $dir;
140             }
141             }
142              
143 1         4 return 1;
144             }
145              
146             ### list all of the spools that we may look in
147             sub list_spool_handles {
148 2     2 1 35 my $self = shift;
149              
150             # RFC notes
151             # failed delivery should wait 30 minutes (done)
152             # give up time should be 4-5 days (done)
153             # two attempts during first hour of queue (done)
154              
155             ### This system will work on a network with several mail spool dequeuers running on
156             ### multiple boxes. The system uses probability to make reading of the directory happen
157             ### rather than file locking (individual files are still locked).
158             ###
159             ### A dequeue_priority of 1 will happen 100% of the time (1/x).
160             ### A dequeue_priority of 3 will happen 33.3% of the time.
161             ### A dequeue_priority of 9 will happen 11.1% of the time.
162             ### A dequeue_priority of 25 will happen 4% of the time.
163             ### A dequeue_priority of 50 will happen 2% of the time.
164             ### A dequeue_priority of 100 will happen 1% of the time.
165             ### A dequeue_priority of 200 will happen .5% of the time.
166             ###
167             ### With a dequeue_timeout of 20 and one server, the following will happen on average:
168             ### A dequeue_priority of 1 will check every 20 seconds.
169             ### A dequeue_priority of 3 will check every 60 seconds.
170             ### A dequeue_priority of 9 will check every 3 minutes.
171             ### A dequeue_priority of 25 will check every 8.3 minutes.
172             ### A dequeue_priority of 50 will check every 16.6 minutes.
173             ### A dequeue_priority of 100 will check every 33.3 minutes.
174             ### A dequeue_priority of 200 will check every 66.6 minutes.
175             ### (if the timeout is decreased to 10, priority 1 would check every 10 seconds)
176             ### (if the number of servers doubles, priority 1 would check every 10 seconds)
177             ### (if servers double and timeout is 10, priority 1 would check every 5 seconds)
178             ###
179             ### Following the default dequeue_periods and dequeue_priorities,
180             ### Spool 0 will check every 20 seconds for messages to be sent out immediately.
181             ### Spool 1 will check every 60 seconds for messages that have been there for 30 min.
182             ### Spool 2 will check every 3 minutes for messages that have been there for 4 hours.
183             ### Spool 3 will check every 8 minutes for messages that have been there for 8 hours.
184             ### Spool 4 will check every 16 minutes for messages that have been there for 16 hours.
185             ### Spool 5 will check every 33 minutes for messages that have been there for 24 hours.
186             ### Spool 6 will check every 66 minutes for messages that have been there for 48 hours.
187             ###
188             ### For messages that fail the first, or subsequent times,
189             ### the total retry time is 30m+4h+8h+16h+24h+48h = 100.5h or 4.2 days.
190              
191 2         2 my @spools = ();
192              
193 2         8 my $periods = $self->dequeue_periods;
194 2         5 my $last = $#$periods;
195              
196 2         5 foreach my $i ( 0 .. $last ){
197              
198             ### essentially do this 1/x percent of the time
199 8         66 my $int = int(rand() * $self->dequeue_priority->[$i]);
200 8 100       18 next if $int;
201            
202 2 50       10 my $fallback_spool_dir = ($i == $last) ? undef : $self->dequeue_dir.'/'.($i+1);
203              
204             ### load a spool handle object
205 2         6 my $msh = $self->mail_spool_handle(spool_dir => $self->dequeue_dir.'/'.$i,
206             fallback_dir => $fallback_spool_dir,
207             wait => $periods->[$i],
208             spool => $self,
209             );
210              
211             ### allow for getting only the first spool handle
212 2 100       6 if( ! wantarray ){
213 1         3 return $msh;
214             }
215              
216             ### add to the list
217 1         3 push @spools, $msh;
218             }
219              
220 1         4 return @spools;
221             }
222              
223             sub mail_spool_handle {
224 2     2 1 3 my $self = shift;
225 2         16 return Mail::Spool::Handle->new(@_);
226             }
227              
228             sub mail_spool_node {
229 2     2 1 4 my $self = shift;
230 2         14 return Mail::Spool::Node->new(@_);
231             }
232              
233             ###----------------------------------------------------------------###
234              
235             sub dequeue {
236             ### allow for invocation as function or method
237             ### even though all are object oriented
238 0   0 0 1 0 my $self = shift || __PACKAGE__->new(@_);
239              
240             ### iterate on all of the mail spool handles
241 0         0 foreach my $msh ( $self->list_spool_handles ){
242              
243             ### open up that spool (if necessary)
244 0         0 $msh->open_spool;
245              
246 0         0 while( defined(my $node = $msh->next_node) ){
247              
248             ### get exclusive lock
249 0         0 my $lock = $node->lock_node;
250 0 0       0 next unless $lock;
251            
252             ### get a IO::Handle style filehandle
253 0         0 my $fh = $node->filehandle;
254 0 0       0 if( ! $fh ){
255             # what would be good here?
256 0         0 next;
257             }
258              
259             ### try to send it
260 0   0     0 my $ok = eval{ $self->send_mail(to => $node->to,
261             from => $node->from,
262             filehandle => $fh,
263             delivery => 'Interactive',
264             timeout => $self->max_connection_time,
265             id => $node->id,
266             ) } || '';
267 0   0     0 my $error = $@ || '';
268            
269             ### the message was sent off OK
270 0 0 0     0 if( $ok && ! $error ){
271 0         0 $node->delete_node;
272 0         0 next;
273             }
274            
275             ### PAST THIS POINT - THE MESSAGE IS NOT OK, save for later (maybe)
276            
277             ### maximum number of retries reached.
278             ### (is this the node's job of the mailspoolhandle's)
279 0 0       0 if( ! defined $node->fallback_filename ){
280 0         0 $error = "Undeliverable: maximum number of retries reached contacting \"".$node->to."\"";
281             }
282            
283 0 0       0 warn "D$$: Got \"$ok\" back: $error\n" if $@ !~ /and thus/;
284            
285             ### If the message was couldn't be sent, but is not
286             ### undeliverable, fallback and try again later.
287             ### there was permanent error or we have tried enough
288 0 0       0 if( $error !~ /^Undeliverable/i ){
289            
290 0         0 $node->fallback; # again, is this the node's job or msh's
291 0         0 next;
292              
293             }
294              
295             ### If this was not already an error response, then
296             ### we should have an address and can't forward it
297 0 0 0     0 if( $node->from || length($node->from) ){
298            
299 0         0 my $ok = eval{ $self->send_mail(to => $node->from, # send back to the to
  0         0  
300             from => '<>', # don't allow a return msg
301             message => $error, # the message is the error
302             delivery => 'Interactive',
303             timeout => 5 * 60, # queue after 5 min
304             id => $node->id,
305             ) };
306             ### maybe check status, maybe we don't care
307             ### maybe we should append original message
308             }
309            
310             ### get rid of the file now
311 0         0 $node->delete_node;
312              
313             }
314             }
315             }
316              
317             ###----------------------------------------------------------------###
318              
319             sub parse_for_address {
320 2     2 1 193 my $self = shift;
321 2         4 my $line = shift;
322 2         3 my @objs = eval{ Mail::Address->parse($line) };
  2         213  
323 2 50       1008 if( $@ ){
324             # do something
325 0         0 return ();
326             }
327 2         5 return @objs;
328             }
329              
330             sub new_message_id {
331 1     1 1 2 my $self = shift;
332 1         2 my $m = shift;
333 1         29 return uc(substr(md5_hex( time() . $$ . $m ), 2, 16));
334             }
335              
336             ###----------------------------------------------------------------###
337              
338             ### this sub can be used to replace Mail::SENDMAIL
339             # to - will be used in the "rcpt to" header (will be parsed out of message if not given)
340             # from - will be used in the "mail from" header (will be parsed out of message if not given)
341             # message - Mail::Internet obj, MIME::Entity obj, array ref, scalar ref, or scalar
342             # filehandle - if message is empty, should be a readable IO::Handle style object containing the message
343             # filename - if message and filehandle are empty, should be path to file containing the message
344             # delivery - type of delivery, can be one of the following:
345             # - Deferred (or Standard) - put it in a spool for latter
346             # - Interative - block until sent (or timed out), die on failure
347             # - Background - block until sent (or timed out), put in spool on failure
348             # timeout - on Interactive or Background, seconds to try and connect to a host
349             # id - message id to be used in the queue filename
350              
351             sub send_mail {
352              
353             ### allow for call as a function or a method
354 1     1 1 47 my $self;
355 1 50 33     30 if( @_ && $_[0] && ref($_[0]) && $_[0]->isa(__PACKAGE__) ){
      33        
      33        
356 1         3 $self = shift;
357             }else{
358 0         0 $self = __PACKAGE__->new();
359             }
360              
361             ### read the argument list
362 1 50 33     8 my $args = (@_ && ref($_[0])) ? shift() : { @_ };
363              
364              
365             ### objectify what is passed
366 1         9 my $m = $self->parse_message($args);
367 1 50       4 die "Couldn't parse message [$@]" unless $m;
368 1         1 $args->{message} = $m;
369              
370              
371             ### make sure we have a "to" line
372 1 0       4 my $to = $args->{to} ? ref($args->{to}) eq 'ARRAY' ? $args->{to} : [$args->{to}] : [];
    50          
373 1 50 33     2104 if( ! ref($to) || ! @$to ){
374 1         4 my %to = ();
375 1         206 foreach my $line ($m->head->get('To'),
376             $m->head->get('Cc'),
377             $m->head->get('Bcc'),
378             ){
379 1         2873 foreach my $obj ( $self->parse_for_address($line) ){
380 1         5 my $addr = $obj->address();
381 1         20 $to{$addr} = 1;
382             }
383             }
384 1         5 $to = [keys %to];
385             }
386 1 50       5 die "You didn't supply a \"to\" field and the message didn't have one" unless @$to;
387              
388              
389             ### make sure we have a "from" line (an empty from is fine, just not returnable)
390 1         3 my $from = $args->{from};
391 1 50       3 if( ! defined $from ){
392 1   50     4 my @from = $m->head->get('From') || (undef);
393 1         38 my @objs = $self->parse_for_address($from[0]);
394 1 50       7 $from = @objs ? $objs[0]->address() : undef;
395             }
396 1 50       9 die "You didn't supply a \"from\" field and the message didn't have one"
397             unless defined $from;
398 1         3 $args->{from} = $from;
399              
400              
401             ### don't show bcc's
402 1         8 $m->head->delete('Bcc');
403              
404              
405             ### read the type of delivery
406 1   50     24 $args->{delivery} ||= 'Deferred'; # can be Standard, Deferred, Background, or Interactive
407 1 50       6 $args->{delivery} = 'Deferred'
408             if $args->{delivery} !~ /^(Deferred|Background|Interactive)$/;
409              
410              
411             ### DELIVERY DEFERRED: queue the message
412 1 50       3 if( $args->{delivery} eq 'Deferred' ){
413            
414             ### what is the message id ?
415 1   50     6 my $id = $args->{id} || undef;
416 1 50       3 if( ! $id ){
417 1   33     3 my @received = $m->head->get('Received') || ();
418 1         21 foreach my $tag ( @received ){
419 0 0       0 if( $tag =~ /\s+id\s+\(([^\)]+)\)/ ){
420 0         0 $id = $1;
421 0         0 last;
422             }
423             }
424             }
425 1 50       4 if( ! $id ){
426 1         7 $id = $self->new_message_id($m);
427             }
428              
429             ### get a few more arguments
430 1         3 $args->{id} = $id;
431 1         8 $args->{msh} = $self->list_spool_handles;
432              
433             ### write it to the queue
434             ### iterate on all addresses
435 1         2 foreach my $TO ( @$to ){
436 1         2 my $old = $args->{to};
437 1         2 $args->{to} = $TO;
438              
439             ### send it off
440 1         6 $self->_send_mail_deferred($args);
441              
442 1         132 $args->{to} = $old;
443             }
444              
445             ### DELIVERY NONDEFERED: try to send it now
446             }else{
447            
448             ### deliver it to the remote boxes
449 0         0 foreach my $TO ( @$to ){
450 0         0 my $old = $args->{to};
451 0         0 $args->{to} = $TO;
452              
453             ### send it off
454 0         0 $self->_send_mail_now($args);
455              
456 0         0 $args->{to} = $old;
457             }
458             }
459              
460 1         22 return 1;
461             }
462              
463             ### make sure that what ever they passed us is turned
464             ### into an object that supporst 'head' and 'print'
465             sub parse_message {
466 1     1 1 3 my $self = shift;
467 1 50 33     13 my $args = (@_ && ref($_[0])) ? shift() : { @_ };
468              
469 1   50     5 my $m = $args->{message} || undef;
470 1 50       4 my $ref = $m ? ref($m) : '';
471              
472             ### need to create a suitable object
473 1 50 33     7 if( ! $ref || $ref eq 'SCALAR' ){
    0          
    0          
    0          
474              
475             ### no message -- read one
476 1 50       3 if( ! $m ){
477 0   0     0 my $fh = $args->{filehandle} || undef;
478              
479             ### no filehandle -- create one
480 0 0       0 if( ! $fh ){
481 0 0       0 die "No clue what to do (I need a message or a filename)!" unless $args->{filename};
482 0 0       0 die "File \"$args->{filename}\" doesn't exist and thus cannot be sent" unless -e $args->{filename};
483 0         0 $fh = IO::File->new( $args->{filename}, 'r' );
484 0 0       0 die "Can't open \"$args->{filename}\" [$!]" unless $fh;
485             }
486              
487             ### create an object from the filehandle
488 0         0 $m = eval{ Mail::Internet->new( $fh ) };
  0         0  
489              
490             ### turn passed scalar message into an object
491             }else{
492 1 50       3 my $txt = $ref ? $m : \$m;
493 1         3 $m = eval{ Mail::Internet->new([ ($$txt =~ m/^(.*\n?)/mg) ]) };
  1         18  
494             }
495              
496             ### turn array refs into the object
497             }elsif( $ref eq 'ARRAY' ){
498 0         0 $m = eval{ Mail::Internet->new( $m ) };
  0         0  
499              
500             ### make sure anything else can at least do the right methods
501             }elsif( ! $m->can('head') ){
502 0         0 die "Passed object must have a 'head' method";
503             }elsif( ! $m->can('print') ){
504 0         0 die "Passed object must have a 'print' method";
505             }
506             ### actually they need a ->head, ->print, ->body, ->header,
507             ### ->head->get, ->head->add, ->head->delete
508             ### we will just check the basic ones for them.
509              
510              
511 1         744 return $m;
512             }
513              
514             ###----------------------------------------------------------------###
515              
516             sub _send_mail_deferred {
517 1     1   1 my $self = shift;
518 1 50 33     15 my $args = (@_ && ref($_[0])) ? shift() : {@_};
519 1         2 my $TO = $args->{to};
520 1         3 my $m = $args->{message};
521 1         2 my $from = $args->{from};
522              
523             ### encode values for the filename
524 1         3 foreach ( $TO, $from ){
525 2         5 s/([^\ -~])/sprintf("%%%02X",ord($1))/eg;
  0         0  
526 2         6 s/([\%\/\-])/sprintf("%%%02X",ord($1))/eg;
  0         0  
527             }
528              
529             ### create a new node
530 1         2 my $node = eval{ $self->mail_spool_node(msh => $args->{msh},
  1         50  
531             name => join("-",time(),$args->{id},$TO,$from),
532             ) };
533 1 50       4 die "Couldn't create new node [$@]" unless defined $node;
534            
535             ### lock it
536 1         4 my $lock = $node->lock_node;
537 1 50       5 die "Couldn't get lock on node [".$node->lock_error."]" unless defined $lock;
538            
539             ### write it out
540 1   50     5 my $fh = $node->filehandle('w') || die "Couldn't open node [$!]";
541 1         6 $m->print($fh);
542 1         91 $fh->close();
543            
544             ### record the size
545 1         54 my $bytes = $node->size;
546 1         10 $self->log_usage($bytes,'Spool');
547             }
548              
549              
550             sub _send_mail_now {
551 0     0   0 my $self = shift;
552 0 0 0     0 my $args = (@_ && ref($_[0])) ? shift() : {@_};
553 0         0 my $TO = $args->{to};
554 0         0 my $m = $args->{message};
555 0         0 my $from = $args->{from};
556              
557 0         0 my @to = $self->parse_for_address( $TO );
558 0 0 0     0 die "Not a valid \"to\"" unless @to && ref($to[0]);
559 0         0 my $host = $to[0]->host();
560 0         0 my $sock;
561             my $mx_host;
562              
563             ### protect the lookup with a timeout
564 0     0   0 local $SIG{ALRM} = sub{ die "Timed out\n" };
  0         0  
565 0         0 eval{
566 0 0       0 my $old_alarm = $args->{timeout} ? alarm($args->{timeout}) : undef;
567            
568             ### get the dns for this host
569 0         0 my @mx = $self->lookup_mx($host);
570 0 0       0 die "MX lookup error" unless @mx;
571            
572             ### attempt to connect to one of the mail servers
573 0         0 foreach my $_mx_host ( @mx ){
574 0         0 $mx_host = $self->lookup_host($_mx_host);
575            
576 0         0 warn "S$$: Trying $mx_host\n";
577 0         0 $sock = $self->open_smtp_connection($mx_host);
578 0 0       0 last if defined $sock;
579            
580             }
581              
582 0 0       0 alarm($old_alarm ? $old_alarm : 0);
583             };
584              
585             ### see if we have sock. if not, die unless delivery is to be backgrounded
586 0 0       0 if( ! defined $sock ){
587 0 0       0 if( $args->{delivery} eq 'Background' ){
588 0         0 $args->{delivery} = 'Deferred';
589 0         0 eval{ $self->send_mail( %$args ) };
  0         0  
590 0 0       0 if( $@ ){
591 0         0 die $@;
592             }else{
593 0         0 return 1;
594             }
595             }else{
596 0         0 die "Couldn't open a connection to mx of $host [$!]";
597             }
598             }
599            
600             ### retrieve the greeting
601 0         0 my $status;
602 0         0 my $_msg = $sock->message();
603 0         0 $_msg =~ s/\n(\w)/\n $1/g; # indent for the log
604 0         0 warn "S$$: Connected to host ($mx_host): ".$sock->code()." ".$_msg;
605            
606             ### send the mail from
607 0         0 $sock->mail($from);
608 0         0 $self->check_sock_status($sock,$mx_host,$from,$TO);
609 0         0 warn "S$$: Mail from is done ($from) ".$sock->code()." ".$sock->message();
610            
611             ### send the rcpt to
612 0         0 $sock->to($TO);
613 0         0 $self->check_sock_status($sock,$mx_host,$from,$TO);
614 0         0 warn "S$$: Rcpt to is done ($TO) ".$sock->code()." ".$sock->message();
615            
616             ### request to send data
617             ### we are not using the data method of Net::SMTP because we don't
618             ### want to duplicate this message in memory
619 0         0 $sock->command("DATA");
620 0         0 $sock->response();
621 0         0 $self->check_sock_status($sock,$mx_host,$from,$TO);
622 0         0 warn "S$$: Data request is sent ".$sock->code()." ".$sock->message();
623            
624             ### make sure the headers are folded
625 0         0 $m->head->fold();
626            
627             ### Possible duplication of memory. I hope people
628             ### who write objects are smart with their memory
629             ### and just give us a reference to the lines
630             ### (Mail::Internet is not smart, sadly)
631 0         0 my $body = $m->body();
632            
633             ### send the message header, double newline, and body
634             ### do so on our own because Net::SMTP (Net::Cmd) duplicates memory
635 0         0 my $bytes = 0;
636 0         0 foreach ( @{ $m->header() }, $crlf, @$body ){
  0         0  
637 0         0 s/(^|[^\015])\012/$1$crlf/g; # a cr before lf if none
638 0         0 s/^\./../g; # byte stuff as per RFC
639 0         0 print $sock $_;
640 0         0 $bytes += length($_);
641             }
642              
643             ### if the last line doesn't have a newline, add one
644 0 0       0 if( $body->[$#$body] !~ /$crlf/ ){
645 0         0 print $sock $crlf;
646 0         0 $bytes += length($crlf);
647             }
648              
649 0         0 $self->log_usage($bytes,'Sent');
650            
651             ### do the termination byte
652 0         0 $sock->command('.');
653 0         0 $sock->response();
654 0         0 $self->check_sock_status($sock,$mx_host,$from,$TO);
655 0         0 warn "S$$: Data end sent ".$sock->code()." ".$sock->message();
656            
657             ### all done
658 0 0       0 $sock->quit() || die "Couldn't send the quit [$!]";
659 0         0 $self->check_sock_status($sock,$mx_host,$from,$TO);
660            
661             }
662              
663             ###----------------------------------------------------------------###
664              
665             ### see if the previous command was successful
666             sub check_sock_status {
667 0     0 1 0 my $self = shift;
668 0         0 my $sock = shift;
669 0 0       0 if( !$sock->status() ){
    0          
    0          
670 0         0 die "Couldn't get status, try again later\n";
671             }elsif( $sock->status() == 5 ){
672 0         0 die "Undeliverable: <$_[0]> <$_[1]> <$_[2]>"
673             .$sock->code()." ".$sock->message()."\n";
674             }elsif( $sock->status() == 4 ){
675 0         0 die "Temporary trouble, try again later [".$sock->code()." ".$sock->message()."]\n";
676             }
677             }
678              
679             ### look up the mx records
680             ### we could possibly cache them
681             sub lookup_mx {
682 0     0 1 0 my $self = shift;
683 0         0 my $host = shift;
684              
685 0         0 my @mx = Net::DNS::mx($host);
686              
687 0         0 @mx = sort {$a->preference() <=> $b->preference()} @mx;
  0         0  
688            
689 0         0 @mx = map {$_->exchange()} @mx;
  0         0  
690              
691 0         0 return @mx;
692             }
693              
694             ### we could translate the host into
695             ### an ip right here and cache it
696             sub lookup_host {
697 0     0 1 0 my $self = shift;
698 0         0 my $host = shift;
699 0         0 return $host;
700             }
701              
702             ### return an open socket ready for printing to
703             ### possible caching of connection with RSET
704             ### in between could be done here
705             sub open_smtp_connection {
706 0     0 1 0 my $self = shift;
707 0         0 my $host = shift;
708 0   0     0 my $timeout = shift || 0;
709 0         0 my $sock = Net::SMTP->new($host,
710             Port => 25,
711             Timeout => $timeout,
712             );
713 0         0 return $sock;
714             }
715              
716             ###----------------------------------------------------------------###
717              
718             ### dump routine to log a number and purpose
719             ### usually like "23434 spooled" (number of bytes spooled)
720             sub log_usage {
721 1     1 1 2 my $self = shift;
722 1         2 my $bytes = shift;
723 1         3 my $purpose = shift;
724 1 50       11 return unless -d $self->usage_log;
725 0 0       0 if( ! open(_FH,">>".$self->usage_log."/raw") ){
726 0         0 warn "Couldn't open \"".$self->usage_log."/raw\" ($!)";
727 0         0 return;
728             }
729              
730 0         0 print _FH time()." $bytes $purpose\n";
731 0         0 close _FH;
732             }
733              
734             ###----------------------------------------------------------------###
735              
736             sub AUTOLOAD {
737 5     5   187 my $self = shift;
738              
739 5 50       38 my ($method) = ($AUTOLOAD =~ /([^:]+)$/) ? $1 : '';
740              
741             ### install some some routines if asked
742 5 50       27 if( $method =~ /^(dequeue_dir|
743             dequeue_periods|
744             dequeue_priority|
745             dequeue_timeout|
746             max_dequeue_processes|
747             usage_log|
748             max_connection_time
749             )$/x ){
750 1     1   10 no strict 'refs';
  1         1  
  1         106  
751 5         39 * { __PACKAGE__ ."::". $method } = sub {
752 27     27   34 my $self = shift;
753 27 100       63 $self->{$method} = $ { __PACKAGE__."::".uc($method) }
  5         21  
754             if ! defined $self->{$method};
755 27         33 my $val = $self->{$method};
756 27 100       55 $self->{$method} = shift if @_;
757 27         164 return $val;
758 5         23 };
759 1     1   4 use strict 'refs';
  1         2  
  1         66  
760            
761             ### now that it is installed, call it again
762 5         26 return $self->$method( @_ );
763             }
764              
765             }
766              
767             1;
768              
769             __END__