File Coverage

blib/lib/Mail/Transport/SMTP.pm
Criterion Covered Total %
statement 18 93 19.3
branch 0 46 0.0
condition 0 14 0.0
subroutine 6 11 54.5
pod 3 5 60.0
total 27 169 15.9


line stmt bran cond sub pod time code
1             # This code is part of Perl distribution Mail-Transport version 4.01.
2             # The POD got stripped from this file by OODoc version 3.05.
3             # For contributors see file ChangeLog.
4              
5             # This software is copyright (c) 2001-2025 by Mark Overmeer.
6              
7             # This is free software; you can redistribute it and/or modify it under
8             # the same terms as the Perl 5 programming language system itself.
9             # SPDX-License-Identifier: Artistic-1.0-Perl OR GPL-1.0-or-later
10              
11              
12             package Mail::Transport::SMTP;{
13             our $VERSION = '4.01';
14             }
15              
16 1     1   2331 use parent 'Mail::Transport::Send';
  1         2  
  1         4  
17              
18 1     1   80 use strict;
  1         1  
  1         19  
19 1     1   2 use warnings;
  1         1  
  1         53  
20              
21 1     1   35 use Log::Report 'mail-transport', import => [ qw/__x error notice trace warning/ ];
  1         2  
  1         10  
22              
23 1     1   1038 use Net::SMTP ();
  1         117618  
  1         32  
24              
25 1     1   7 use constant CMD_OK => 2;
  1         1  
  1         1135  
26              
27             #--------------------
28              
29             sub init($)
30 0     0 0   { my ($self, $args) = @_;
31 0   0       $args->{via} ||= 'smtp';
32 0   0       $args->{port} ||= '25';
33              
34 0           my $hosts = $args->{hostname};
35 0 0         unless($hosts)
36 0           { require Net::Config;
37 0           $hosts = $Net::Config::NetConfig{smtp_hosts};
38 0 0         undef $hosts unless @$hosts;
39 0           $args->{hostname} = $hosts;
40             }
41              
42 0 0         $self->SUPER::init($args) or return;
43              
44             my $helo = $args->{helo}
45             || eval { require Net::Config; $Net::Config::NetConfig{inet_domain} }
46 0   0       || eval { require Net::Domain; Net::Domain::hostfqdn() };
47              
48 0   0       $self->{MTS_net_smtp_opts} = +{ Hello => $helo, Debug => ($args->{smtp_debug} || 0) };
49 0           $self->{MTS_esmtp_options} = $args->{esmtp_options};
50 0           $self->{MTS_from} = $args->{from};
51 0           $self;
52             }
53              
54              
55             sub trySend($@)
56 0     0 1   { my ($self, $message, %args) = @_;
57 0 0         my %send_options = ( %{$self->{MTS_esmtp_options} || {}}, %{$args{esmtp_options} || {}} );
  0 0          
  0            
58              
59             # From whom is this message.
60 0   0       my $from = $args{from} || $self->{MTS_from} || $message->sender || '<>';
61 0 0 0       $from = $from->address if ref $from && $from->isa('Mail::Address');
62              
63             # Which are the destinations.
64             ! defined $args{To}
65 0 0         or warning __x"use option `to' to overrule the destination: `To' refers to a field.";
66              
67 0           my @to = map $_->address, $self->destinations($message, $args{to});
68 0 0         @to or notice(__x"no addresses found to send the message to, no connection made."), return 1;
69              
70             #### Prepare the message.
71              
72 0           my $out = '';
73 0           open my $fh, '>:raw', \$out;
74 0           $self->putContent($message, $fh, undisclosed => 0);
75 0 0         $out =~ m![\r\n]\z! or $out .= "\r\n";
76 0           close $fh;
77              
78             #### Send
79              
80 0           my $server;
81 0 0         if(wantarray)
82             { # In LIST context
83 0 0         $server = $self->contactAnyServer
84             or return (0, 500, "Connection Failed", "CONNECT", 0);
85              
86 0 0         $server->mail($from, %send_options)
87             or return (0, $server->code, $server->message, 'FROM', $server->quit);
88              
89 0           foreach (@to)
90 0 0         { next if $server->to($_);
91             #??? must we be able to disable this? f.i:
92             #??? next if $args{ignore_erroneous_destinations}
93 0           return (0, $server->code, $server->message, "To $_", $server->quit);
94             }
95              
96 0           my $bodydata = $message->body->file;
97              
98 0 0         $server->datafast(\$out) #!! destroys $out
99             or return (0, $server->code, $server->message, 'DATA', $server->quit);
100              
101 0           my $accept = ($server->message)[-1];
102 0           chomp $accept;
103              
104 0           my $rc = $server->quit;
105 0           return ($rc, $server->code, $server->message, 'QUIT', $rc, $accept);
106             }
107              
108             # in SCALAR context
109 0 0         $server = $self->contactAnyServer
110             or return 0;
111              
112 0 0         $server->mail($from, %send_options)
113             or ($server->quit, return 0);
114              
115 0           foreach (@to)
116 0 0         { next if $server->to($_);
117 0           $server->quit;
118 0           return 0;
119             }
120              
121 0 0         $server->datafast(\$out) #!! destroys $out
122             or ($server->quit, return 0);
123              
124 0           $server->quit;
125             }
126              
127             # Improvement on Net::CMD::datasend(), mainly bulk adding dots and avoiding copying
128             # About 79% performance gain on huge messages.
129             # Be warned: this method destructs the content of $data!
130             sub Net::SMTP::datafast($)
131 0     0 0   { my ($self, $data) = @_;
132 0 0         $self->_DATA or return 0;
133              
134 0           $$data =~ tr/\r\n/\015\012/ if "\r" ne "\015"; # mac
135 0           $$data =~ s/(? crlf as sep. Needed?
136 0           $$data =~ s/^\./../; # data starts with a dot, escape it
137 0           $$data =~ s/\012\./\012../g; # other lines which start with a dot
138              
139 0           $self->_syswrite_with_timeout($$data . ".\015\012");
140 0           $self->response == CMD_OK;
141             }
142              
143             #--------------------
144              
145             sub contactAnyServer()
146 0     0 1   { my $self = shift;
147              
148 0           my ($enterval, $count, $timeout) = $self->retry;
149 0           my ($host, $port, $username, $password) = $self->remoteHost;
150 0 0         my @hosts = ref $host ? @$host : $host;
151 0           my $opts = $self->{MTS_net_smtp_opts};
152              
153 0           foreach my $host (@hosts)
154 0 0         { my $server = $self->tryConnectTo($host, Port => $port, %$opts, Timeout => $timeout)
155             or next;
156              
157 0           trace "opened SMTP connection to $host.";
158              
159 0 0         if(defined $username)
160 0 0         { unless($server->auth($username, $password))
161 0           { error __x"authentication for {host} failed.", host => $host;
162 0           return undef;
163             }
164 0           trace "$host: Authentication succeeded.";
165             }
166              
167 0           return $server;
168             }
169              
170 0           undef;
171             }
172              
173              
174             sub tryConnectTo($@)
175 0     0 1   { my ($self, $host) = (shift, shift);
176 0           Net::SMTP->new($host, @_);
177             }
178              
179             1;