File Coverage

blib/lib/Net/SMTPS.pm
Criterion Covered Total %
statement 15 104 14.4
branch 0 46 0.0
condition 0 38 0.0
subroutine 5 8 62.5
pod 2 3 66.6
total 22 199 11.0


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   32294 use vars qw ( $VERSION @ISA );
  1         2  
  1         60  
10              
11             $VERSION = "0.04";
12              
13 1     1   5 use base qw ( Net::SMTP );
  1         2  
  1         658  
14 1     1   28844 use Net::Cmd; # import CMD_OK, CMD_MORE, ...
  1         6  
  1         61  
15 1     1   5 use Net::Config;
  1         1  
  1         116  
16              
17             eval {
18             require IO::Socket::INET6
19             and unshift @ISA, 'IO::Socket::INET6';
20             } or do {
21             require IO::Socket::INET
22             and unshift @ISA, 'IO::Socket::INET';
23             };
24              
25 1     1   4 use strict;
  1         2  
  1         836  
26              
27             # Override to support SSL/TLS.
28             sub new {
29 0     0 1   my $self = shift;
30 0   0       my $type = ref($self) || $self;
31 0           my ($host, %arg);
32 0 0         if (@_ % 2) {
33 0           $host = shift;
34 0           %arg = @_;
35             }
36             else {
37 0           %arg = @_;
38 0           $host = delete $arg{Host};
39             }
40 0           my $ssl = delete $arg{doSSL};
41              
42 0 0         my $hosts = defined $host ? $host : $NetConfig{smtp_hosts};
43 0           my $obj;
44              
45             # eliminate IO::Socket::SSL from @ISA for multiple call of new.
46 0           @ISA = grep { !/IO::Socket::SSL/ } @ISA;
  0            
47              
48 0           my %_args = map { +"$_" => $arg{$_} } grep {! /^SSL/} keys %arg;
  0            
  0            
49              
50 0           my $h;
51 0   0       $_args{PeerPort} = $_args{Port} || 'smtp(25)';
52 0           $_args{Proto} = 'tcp';
53 0 0         $_args{Timeout} = defined $_args{Timeout} ? $_args{Timeout} : 120;
54              
55 0 0         foreach $h (@{ref($hosts) ? $hosts : [$hosts]}) {
  0            
56 0           $_args{PeerAddr} = ($host = $h);
57              
58             #if ($_args{Debug}) {
59             # foreach my $i (keys %_args) {
60             # print STDERR "$type>>> arg $i: $_args{$i}\n";
61             # }
62             #}
63              
64 0 0         $obj = $type->SUPER::new(
65             %_args
66             )
67             and last;
68             }
69              
70             return undef
71 0 0         unless defined $obj;
72              
73 0           $obj->autoflush(1);
74              
75 0 0         $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
76              
77             # common in SSL
78 0           my %ssl_args;
79 0 0         if ($ssl) {
80             eval {
81 0           require IO::Socket::SSL;
82 0 0         } or do {
83 0           $obj->set_status(500, ["Need working IO::Socket::SSL"]);
84 0           $obj->close;
85 0           return undef;
86             };
87 0           %ssl_args = map { +"$_" => $arg{$_} } grep {/^SSL/} keys %arg;
  0            
  0            
88 0 0         $IO::Socket::SSL::DEBUG = (exists $arg{Debug} ? $arg{Debug} : undef);
89             }
90              
91             # OverSSL
92 0 0 0       if (defined($ssl) && $ssl =~ /ssl/i) {
93             $obj->ssl_start(\%ssl_args)
94 0 0         or do {
95 0           $obj->set_status(500, ["Cannot start SSL"]);
96 0           $obj->close;
97 0           return undef;
98             };
99             }
100              
101 0 0         unless ($obj->response() == CMD_OK) {
102 0           $obj->close();
103 0           return undef;
104             }
105              
106 0           ${*$obj}{'net_smtp_exact_addr'} = $arg{ExactAddresses};
  0            
107 0           ${*$obj}{'net_smtp_host'} = $host;
  0            
108              
109 0           (${*$obj}{'net_smtp_banner'}) = $obj->message;
  0            
110 0           (${*$obj}{'net_smtp_domain'}) = $obj->message =~ /\A\s*(\S+)/;
  0            
111              
112 0 0 0       unless ($obj->hello($arg{Hello} || "")) {
113 0           $obj->close();
114 0           return undef;
115             }
116              
117             # STARTTLS
118 0 0 0       if (defined($ssl) && $ssl =~ /starttls/i && $obj->supports('STARTTLS') ) {
      0        
119             (($obj->command('STARTTLS')->response() == CMD_OK)
120             and $obj->ssl_start(\%ssl_args)
121             and $obj->hello($arg{Hello} || ""))
122 0 0 0       or do {
      0        
      0        
123 0           $obj->set_status(500, ["Cannot start SSL session"]);
124 0           $obj->close();
125 0           return undef;
126             };
127             }
128              
129 0           $obj;
130             }
131              
132             sub ssl_start {
133 0     0 0   my ($self, $args) = @_;
134 0           my $type = ref($self);
135              
136 0 0 0       (unshift @ISA, 'IO::Socket::SSL'
      0        
      0        
137             and IO::Socket::SSL->start_SSL($self, %$args)
138             and $self->isa('IO::Socket::SSL')
139             and bless $self, $type # re-bless 'cause IO::Socket::SSL blesses himself.
140             ) or return undef;
141             }
142              
143             # Override to specify a certain auth mechanism.
144             sub auth {
145 0     0 1   my ($self, $username, $password, $mech) = @_;
146              
147 0 0         eval {
148 0           require MIME::Base64;
149 0           require Authen::SASL;
150             } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0;
151              
152 0           my $mechanisms = $self->supports('AUTH', 500, ["Command unknown: 'AUTH'"]);
153 0 0         if ($mech) {
154 0           $mechanisms = $mech;
155             }
156 0 0         return unless $mechanisms;
157              
158 0           my $sasl;
159              
160 0 0 0       if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) {
161 0           $sasl = $username;
162 0           $sasl->mechanism($mechanisms);
163             }
164             else {
165 0 0         die "auth(username, password)" if not length $username;
166 0           $sasl = Authen::SASL->new(
167             mechanism => $mechanisms,
168             callback => {
169             user => $username,
170             pass => $password,
171             authname => $username,
172             }
173             );
174             }
175              
176             # We should probably allow the user to pass the host, but I don't
177             # currently know and SASL mechanisms that are used by smtp that need it
178 0           my $client = $sasl->client_new('smtp', ${*$self}{'net_smtp_host'}, 0);
  0            
179 0           my $str = $client->client_start;
180              
181             # We dont support sasl mechanisms that encrypt the socket traffic.
182             # todo that we would really need to change the ISA hierarchy
183             # so we dont inherit from IO::Socket, but instead hold it in an attribute
184              
185 0           my @cmd = ("AUTH", $client->mechanism);
186 0           my $code;
187              
188 0 0 0       push @cmd, MIME::Base64::encode_base64($str, '')
189             if defined $str and length $str;
190              
191 0           while (($code = $self->command(@cmd)->response()) == CMD_MORE) {
192 0           @cmd = (
193             MIME::Base64::encode_base64(
194             $client->client_step(MIME::Base64::decode_base64(($self->message)[0])), ''
195             )
196             );
197             }
198              
199 0           $code == CMD_OK;
200             }
201              
202             1;
203              
204             __END__