File Coverage

lib/Haineko/SMTPD/Relay/MX.pm
Criterion Covered Total %
statement 84 130 64.6
branch 10 52 19.2
condition 10 15 66.6
subroutine 13 13 100.0
pod 2 2 100.0
total 119 212 56.1


line stmt bran cond sub pod time code
1             package Haineko::SMTPD::Relay::MX;
2 1     1   15716 use parent 'Haineko::SMTPD::Relay';
  1         404  
  1         6  
3 1     1   40 use strict;
  1         2  
  1         25  
4 1     1   4 use warnings;
  1         2  
  1         21  
5 1     1   1080 use Net::SMTP;
  1         63859  
  1         58  
6 1     1   484 use Haineko::DNS;
  1         4  
  1         39  
7 1     1   528 use Haineko::SMTPD::Response;
  1         3  
  1         35  
8 1     1   375 use Haineko::SMTPD::Greeting;
  1         3  
  1         27  
9 1     1   895 use Email::MIME;
  1         72362  
  1         42  
10 1     1   1212 use Time::Piece;
  1         12769  
  1         10  
11 1     1   99 use Encode;
  1         2  
  1         1565  
12              
13             sub new {
14 3     3 1 10871 my $class = shift;
15 3         21 my $argvs = { @_ };
16              
17 3         13 $argvs->{'host'} = '';
18 3         8 $argvs->{'port'} = 25;
19 3   66     38 $argvs->{'time'} ||= Time::Piece->new;
20 3   100     540 $argvs->{'sleep'} ||= 1;
21 3   100     17 $argvs->{'timeout'} ||= 30;
22 3         8 $argvs->{'startls'} = 0;
23 3         14 return bless $argvs, __PACKAGE__;
24             }
25              
26             sub sendmail {
27 2     2 1 9648 my $self = shift;
28              
29 2         8 my $headerlist = [];
30 2   50     26 my $emencoding = uc( $self->{'attr'}->{'charset'} || 'UTF-8' );
31 2         18 my $methodargv = {
32 2         5 'body' => Encode::encode( $emencoding, ${ $self->{'body'} } ),
33             'attributes' => $self->{'attr'},
34             };
35 2 50       293 utf8::decode $methodargv->{'body'} unless utf8::is_utf8 $methodargv->{'body'} ;
36              
37 2         4 for my $e ( @{ $self->{'head'}->{'Received'} } ) {
  2         10  
38             # Convert email headers
39 0         0 push @$headerlist, 'Received' => $e;
40             }
41 2         8 push @$headerlist, 'To' => $self->{'rcpt'};
42              
43 2         5 for my $e ( keys %{ $self->{'head'} } ) {
  2         10  
44             # Make email headers except ``Received'' and ``MIME-Version''
45 8 100       20 next if $e eq 'Received';
46 6 50       14 next if $e eq 'MIME-Version';
47              
48 6 50       19 if( ref $self->{'head'}->{ $e } eq 'ARRAY' ) {
49              
50 0         0 for my $f ( @{ $self->{'head'}->{ $e } } ) {
  0         0  
51 0         0 push @$headerlist, $e => $f;
52             }
53             }
54             else {
55 6         22 push @$headerlist, $e => $self->{'head'}->{ $e };
56             }
57             }
58 2         9 $methodargv->{'header'} = $headerlist;
59              
60 2         24 my $mimeobject = Email::MIME->create( %$methodargv );
61 2         6399 my $mailstring = $mimeobject->as_string;
62 2         149 my $maillength = length $mailstring;
63              
64 2   50     37 my $smtpparams = {
      50        
65             'Port' => $self->{'port'},
66             'Hello' => $self->{'ehlo'},
67             'Debug' => $self->{'debug'} || 0,
68             'Timeout' => $self->{'timeout'} || 30,
69             };
70              
71 2         5 my $netsmtpobj = undef;
72 2         4 my $authensasl = undef;
73 2         4 my $nekogreets = undef;
74 2         5 my $smtpstatus = 0;
75 2         5 my $thecommand = q();
76 2         4 my $pipelining = q();
77 2   50     16 my $retryuntil = $self->{'retry'} || 0;
78              
79             my $sendmailto = sub {
80              
81 2     2   7 $thecommand = 'ehlo';
82 2 50       33 return 0 unless $netsmtpobj = Net::SMTP->new( $self->{'host'}, %$smtpparams );
83 0         0 $nekogreets = Haineko::SMTPD::Greeting->new( $netsmtpobj->message );
84              
85 0 0       0 if( $nekogreets->pipelining ) {
86             # 250-PIPELINING
87 0         0 $thecommand = 'data';
88 0         0 $pipelining = sprintf( "MAIL FROM: <%s>", $self->{'mail'} );
89 0 0       0 $pipelining .= sprintf( ' RET=FULL' ) if $nekogreets->dsn;
90 0 0       0 $pipelining .= sprintf( " SIZE=%d", $maillength ) if $nekogreets->size;
91 0         0 $pipelining .= sprintf( "\r\n" );
92 0         0 $pipelining .= sprintf( "RCPT TO: <%s>", $self->{'rcpt'} );
93 0 0       0 $pipelining .= sprintf( ' NOTIFY=FAILURE,DELAY' ) if $nekogreets->dsn;
94 0         0 $pipelining .= sprintf( "\r\n" );
95 0         0 $pipelining .= sprintf( "DATA\r\n" );
96 0         0 $pipelining .= sprintf( "%s", $mailstring );
97 0 0       0 return 0 unless $netsmtpobj->datasend( $pipelining );
98 0 0       0 return 0 unless $netsmtpobj->dataend();
99              
100             } else {
101             # External SMTP Server does not support PIPELINING
102 0         0 my $cmdargvs = [];
103 0         0 my $cmdparam = {};
104              
105 0         0 $thecommand = 'mail';
106 0         0 $cmdargvs = [ $self->{'mail'} ];
107 0 0       0 $cmdparam->{'Return'} = 'FULL' if $nekogreets->dsn;
108 0 0       0 $cmdparam->{'Size'} = $maillength if $nekogreets->size;
109 0 0       0 push @$cmdargvs, %$cmdparam if keys %$cmdparam;
110 0 0       0 return 0 unless $netsmtpobj->mail( @$cmdargvs );
111              
112 0         0 $thecommand = 'rcpt';
113 0         0 $cmdargvs = [ $self->{'rcpt'} ];
114 0         0 $cmdparam = {};
115 0 0       0 $cmdparam->{'Notify'} = [ 'FAILURE', 'DELAY' ] if $nekogreets->dsn;
116 0 0       0 push @$cmdargvs, %$cmdparam if keys %$cmdparam;
117 0 0       0 return 0 unless $netsmtpobj->to( @$cmdargvs );
118              
119 0         0 $thecommand = 'data';
120 0 0       0 return 0 unless $netsmtpobj->data();
121 0 0       0 return 0 unless $netsmtpobj->datasend( $mailstring );
122 0 0       0 return 0 unless $netsmtpobj->dataend();
123             }
124              
125 0         0 $thecommand = 'QUIT';
126 0         0 $netsmtpobj->quit;
127 0         0 $smtpstatus = 1;
128 0         0 return 1;
129 2         31 };
130              
131             # Resolve MXRR, ARR
132 2         71 my $domainpart = [ split( '@', $self->{'rcpt'} ) ]->[1];
133 2         25 my $hainekodns = Haineko::DNS->new( $domainpart );
134 2         9 my $exchangers = $hainekodns->mxrr;
135              
136 2 50       12 push @$exchangers, @{ $hainekodns->arr } unless scalar @$exchangers;
  2         10  
137 2         7 $retryuntil = scalar @$exchangers;
138              
139 2         3 while(1) {
140 2         10 $self->{'host'} = shift @$exchangers;
141 2 50       15 last if $sendmailto->();
142 2 50       69 last if $retryuntil == 0;
143              
144 0 0       0 $netsmtpobj->quit if defined $netsmtpobj;
145 0         0 $retryuntil--;
146 0         0 sleep $self->{'sleep'};
147             }
148              
149 2 50       8 if( defined $netsmtpobj ) {
150             # Check the response from SMTP server
151 0         0 $smtpparams = {
152             'code' => $netsmtpobj->code,
153             'host' => $self->{'host'},
154             'port' => $self->{'port'},
155             'rcpt' => $self->{'rcpt'},
156             'mailer' => 'MX',
157             'message' => [ $netsmtpobj->message ],
158             'command' => $thecommand,
159             };
160 0         0 $self->response( Haineko::SMTPD::Response->p( %$smtpparams ) );
161 0         0 $netsmtpobj->quit;
162              
163             } else {
164 2         23 $self->response( Haineko::SMTPD::Response->r( 'conn', 'cannot-connect' ) );
165 2         18 map { $self->response->{ $_ } = $self->{ $_ } } ( qw|host port rcpt| );
  6         48  
166             }
167 2         147 return $smtpstatus;
168             }
169              
170             1;
171             __END__