File Coverage

blib/lib/Net/SMTPS.pm
Criterion Covered Total %
statement 15 120 12.5
branch 0 54 0.0
condition 0 40 0.0
subroutine 5 11 45.4
pod 4 5 80.0
total 24 230 10.4


line stmt bran cond sub pod time code
1             # ====
2             # SSL/STARTTLS extention for G.Barr's Net::SMTP.
3             # plus, enable arbitrary SMTP auth mechanism selection.
4             # IO::Socket::SSL (also Net::SSLeay openssl),
5             # Authen::SASL, MIME::Base64 should be installed.
6             #
7             package Net::SMTPS;
8              
9 1     1   53517 use vars qw ( $VERSION @ISA );
  1         2  
  1         56  
10              
11             $VERSION = '0.08';
12              
13 1     1   6 use strict;
  1         1  
  1         19  
14 1     1   4 use base qw ( Net::SMTP );
  1         4  
  1         331  
15 1     1   67975 use Net::Cmd; # import CMD_OK, CMD_MORE, ...
  1         2  
  1         49  
16 1     1   5 use Net::Config;
  1         1  
  1         995  
17              
18             eval {
19             require IO::Socket::IP
20             and unshift @ISA, 'IO::Socket::IP';
21             } or eval {
22             require IO::Socket::INET6
23             and unshift @ISA, 'IO::Socket::INET6';
24             } or do {
25             require IO::Socket::INET
26             and unshift @ISA, 'IO::Socket::INET';
27             };
28              
29             # Override to support SSL/TLS.
30             sub new {
31 0     0 1   my $self = shift;
32 0   0       my $type = ref($self) || $self;
33 0           my ($host, %arg);
34 0 0         if (@_ % 2) {
35 0           $host = shift;
36 0           %arg = @_;
37             }
38             else {
39 0           %arg = @_;
40 0           $host = delete $arg{Host};
41             }
42 0           my $ssl = delete $arg{doSSL};
43 0 0         if ($ssl =~ /ssl/i) {
44 0           $arg{SSL} = 1;
45             }
46 0 0 0       if (defined($arg{SSL}) && $arg{SSL} > 0) {
47 0           $ssl = 'ssl';
48 0   0       $arg{Port} ||= 465;
49             }
50              
51 0 0         my $hosts = defined $host ? $host : $NetConfig{smtp_hosts};
52 0           my $obj;
53              
54             # eliminate IO::Socket::SSL from @ISA for multiple call of new.
55 0           @ISA = grep { !/IO::Socket::SSL/ } @ISA;
  0            
56              
57 0           my %_args = map { +"$_" => $arg{$_} } grep {! /^SSL/} keys %arg;
  0            
  0            
58              
59 0           my $h;
60 0   0       $_args{PeerPort} = $_args{Port} || 'smtp(25)';
61 0           $_args{Proto} = 'tcp';
62 0 0         $_args{Timeout} = defined $_args{Timeout} ? $_args{Timeout} : 120;
63              
64 0 0         foreach $h (@{ref($hosts) ? $hosts : [$hosts]}) {
  0            
65 0           $_args{PeerAddr} = ($host = $h);
66              
67             #if ($_args{Debug}) {
68             # foreach my $i (keys %_args) {
69             # print STDERR "$type>>> arg $i: $_args{$i}\n";
70             # }
71             #}
72              
73 0 0         $obj = $type->SUPER::new(
74             %_args
75             )
76             and last;
77             }
78              
79             return undef
80 0 0         unless defined $obj;
81              
82 0           $obj->autoflush(1);
83              
84 0 0         $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
85              
86             # common in SSL
87 0           my %ssl_args;
88 0 0 0       if ($ssl || defined($arg{SSL}) ) {
89             eval {
90 0           require IO::Socket::SSL;
91 0 0         } or do {
92 0           $obj->set_status(500, ["Need working IO::Socket::SSL"]);
93 0           $obj->close;
94 0           return undef;
95             };
96 0           %ssl_args = map { +"$_" => $arg{$_} } grep {/^SSL/} keys %arg;
  0            
  0            
97 0 0         $IO::Socket::SSL::DEBUG = (exists $arg{Debug} ? $arg{Debug} : undef);
98             }
99              
100             # OverSSL
101 0 0 0       if (defined($ssl) && $ssl =~ /ssl/i) {
102             $obj->ssl_start(\%ssl_args)
103 0 0         or do {
104 0           $obj->set_status(500, ["Cannot start SSL"]);
105 0           $obj->close;
106 0           return undef;
107             };
108             }
109              
110 0 0         unless ($obj->response() == CMD_OK) {
111 0           $obj->close();
112 0           return undef;
113             }
114              
115 0           ${*$obj}{'net_smtp_arg'} = \%arg;
  0            
116            
117 0           ${*$obj}{'net_smtp_exact_addr'} = $arg{ExactAddresses};
  0            
118 0           ${*$obj}{'net_smtp_host'} = $host;
  0            
119              
120 0           (${*$obj}{'net_smtp_banner'}) = $obj->message;
  0            
121 0           (${*$obj}{'net_smtp_domain'}) = $obj->message =~ /\A\s*(\S+)/;
  0            
122              
123 0 0 0       unless ($obj->hello($arg{Hello} || "")) {
124 0           $obj->close();
125 0           return undef;
126             }
127              
128             # STARTTLS
129 0 0 0       if (defined($ssl) && $ssl =~ /starttls/i && defined($obj->supports('STARTTLS')) ) {
      0        
130             #123006 $obj->supports('STARTTLS') returns '' issue.
131 0 0         unless ($obj->starttls()) {
132 0           return undef;
133             }
134 0   0       $obj->hello($arg{Hello} || "");
135             }
136              
137 0           $obj;
138             }
139              
140             sub ssl_start {
141 0     0 0   my ($self, $args) = @_;
142 0           my $type = ref($self);
143              
144 0 0 0       (unshift @ISA, 'IO::Socket::SSL'
      0        
      0        
145             and IO::Socket::SSL->start_SSL($self, %$args)
146             and $self->isa('IO::Socket::SSL')
147             and bless $self, $type # re-bless 'cause IO::Socket::SSL blesses himself.
148             ) or return undef;
149             }
150              
151             sub starttls {
152 0     0 1   my $self = shift;
153 0           my %arg = %{ ${*$self}{'net_smtp_arg'} };
  0            
  0            
154 0           my %ssl_args = map { +"$_" => $arg{$_} } grep {/^SSL/} keys %arg;
  0            
  0            
155             (
156             $self->_STARTTLS()
157             and $self->ssl_start(\%ssl_args, @_)
158 0 0 0       ) or do {
159 0           $self->set_status(500, ["Cannot start SSL session"]);
160 0           $self->close();
161 0           return undef;
162             };
163             }
164              
165              
166             # Override to specify a certain auth mechanism.
167             sub auth {
168 0     0 1   my ($self, $username, $password, $mech) = @_;
169              
170 0 0         if ($mech) {
171 0 0         $self->debug_print(1, "AUTH-my favorite: ". $mech . "\n") if $self->debug;
172              
173 0           my @cl_mech = split /\s+/, $mech;
174 0           my @matched = ();
175 0 0         if (exists ${*$self}{'net_smtp_esmtp'}->{'AUTH'}) {
  0            
176 0           my $sv = ${*$self}{'net_smtp_esmtp'}->{'AUTH'};
  0            
177 0 0         $self->debug_print(1, "AUTH-server offerred: ". $sv . "\n") if $self->debug;
178              
179 0           foreach my $i (@cl_mech) {
180 0 0 0       if (index($sv, $i) >= 0 && grep(/$i/i, @matched) == () ) {
181 0           push @matched, uc($i);
182             }
183             }
184             }
185 0 0         if (@matched) {
186             ## override AUTH mech as specified.
187             ## if multiple mechs are specified, priority is still up to Authen::SASL module.
188 0           ${*$self}{'net_smtp_esmtp'}->{'AUTH'} = join " ", @matched;
  0            
189 0 0         $self->debug_print(1, "AUTH-negotiated: ". ${*$self}{'net_smtp_esmtp'}->{'AUTH'} . "\n") if $self->debug;
  0            
190             }
191             }
192 0           $self->SUPER::auth($username, $password);
193             }
194              
195              
196             # Fix #121006 no timeout issue.
197             sub getline {
198 0     0 1   my $self = shift;
199 0           $self->Net::Cmd::getline(@_);
200             }
201              
202 0     0     sub _STARTTLS { shift->command("STARTTLS")->response() == CMD_OK }
203              
204             1;
205              
206             __END__