File Coverage

blib/lib/Net/POP3S.pm
Criterion Covered Total %
statement 15 124 12.1
branch 0 56 0.0
condition 0 38 0.0
subroutine 5 8 62.5
pod 2 3 66.6
total 22 229 9.6


line stmt bran cond sub pod time code
1             # ====
2             # SSL/STARTTLS extention for Graham Barr's Net::POP3.
3             # plus, enable arbitrary POP auth mechanism selection.
4             # IO::Socket::SSL (also Net::SSLeay openssl),
5             # Authen::SASL, MIME::Base64 should be installed.
6             #
7             package Net::POP3S;
8              
9 1     1   42077 use vars qw ( $VERSION @ISA );
  1         2  
  1         80  
10              
11             $VERSION = "0.05";
12              
13 1     1   6 use base qw ( Net::POP3 );
  1         2  
  1         833  
14 1     1   30601 use Net::Cmd; # import CMD_OK, CMD_MORE, ...
  1         10  
  1         108  
15 1     1   5 use Net::Config;
  1         1  
  1         177  
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   9 use strict;
  1         2  
  1         1481  
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{pop3_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} || 'pop3(110)';
52 0           $_args{Proto} = 'tcp';
53 0 0         $_args{Timeout} = defined $_args{Timeout} ? $_args{Timeout} : 120;
54 0 0         if (exists $_args{ResvPort}) {
55 0           $_args{LocalPort} = delete $_args{ResvPort};
56             }
57              
58 0 0         foreach $h (@{ref($hosts) ? $hosts : [$hosts]}) {
  0            
59 0           $_args{PeerAddr} = ($host = $h);
60              
61 0 0         $obj = $type->SUPER::new(
62             %_args
63             )
64             and last;
65             }
66              
67             return undef
68 0 0         unless defined $obj;
69              
70 0           ${*$obj}{'net_pop3_host'} = $host;
  0            
71              
72 0           $obj->autoflush(1);
73 0 0         $obj->debug(exists $arg{Debug} ? $arg{Debug} : undef);
74              
75             # common in SSL
76 0           my %ssl_args;
77 0 0         if ($ssl) {
78             eval {
79 0           require IO::Socket::SSL;
80 0 0         } or do {
81 0           $obj->set_status(500, ["Need working IO::Socket::SSL"]);
82 0           $obj->close;
83 0           return undef;
84             };
85 0           %ssl_args = map { +"$_" => $arg{$_} } grep {/^SSL/} keys %arg;
  0            
  0            
86 0 0         $IO::Socket::SSL::DEBUG = (exists $arg{Debug} ? $arg{Debug} : undef);
87             }
88              
89             # OverSSL
90 0 0 0       if (defined($ssl) && $ssl =~ /ssl/i) {
91             $obj->ssl_start(\%ssl_args)
92 0 0         or do {
93 0           $obj->set_status(500, ["Cannot start SSL"]);
94 0           $obj->close;
95 0           return undef;
96             };
97             }
98              
99 0 0         unless ($obj->response() == CMD_OK) {
100 0           $obj->close();
101 0           return undef;
102             }
103              
104 0           ${*$obj}{'net_pop3_banner'} = $obj->message;
  0            
105              
106             # STARTTLS
107 0 0 0       if (defined($ssl) && $ssl =~ /starttls|stls/i ) {
108 0           my $capa;
109             ($capa = $obj->capa
110             and exists $capa->{STLS}
111             and ($obj->command('STLS')->response() == CMD_OK)
112             and $obj->ssl_start(\%ssl_args))
113 0 0 0       or do {
      0        
      0        
114 0           $obj->set_status(500, ["Cannot start SSL session"]);
115 0           $obj->close();
116 0           return undef;
117             };
118             }
119              
120 0           $obj;
121             }
122              
123             sub ssl_start {
124 0     0 0   my ($self, $args) = @_;
125 0           my $type = ref($self);
126              
127 0 0 0       (unshift @ISA, 'IO::Socket::SSL'
      0        
      0        
128             and IO::Socket::SSL->start_SSL($self, %$args)
129             and $self->isa('IO::Socket::SSL')
130             and bless $self, $type # re-bless 'cause IO::Socket::SSL blesses himself.
131             ) or return undef;
132             }
133              
134             # Override to specify a certain auth mechanism.
135             sub auth {
136 0     0 1   my ($self, $username, $password, $mechs) = @_;
137              
138 0 0         eval {
139 0           require MIME::Base64;
140 0           require Authen::SASL;
141             } or $self->set_status(500, ["Need MIME::Base64 and Authen::SASL todo auth"]), return 0;
142              
143 0           my $mechanisms = undef;
144 0 0         if ($mechs) {
145 0           $mechanisms = $mechs;
146             } else {
147 0           my $capa = $self->capa;
148 0   0       $mechanisms = $capa->{SASL} || 'CRAM-MD5';
149             }
150              
151 0           my $sasl;
152              
153 0 0 0       if (ref($username) and UNIVERSAL::isa($username, 'Authen::SASL')) {
154 0           $sasl = $username;
155 0   0       my $user_mech = $sasl->mechanism || '';
156 0           my @user_mech = split(/\s+/, $user_mech);
157 0           my %user_mech;
158 0           @user_mech{@user_mech} = ();
159              
160 0           my @server_mech = split(/\s+/, $mechanisms);
161 0           my @mech = @user_mech
162 0 0         ? grep { exists $user_mech{$_} } @server_mech
163             : @server_mech;
164 0 0         unless (@mech) {
165 0           $self->set_status(
166             500,
167             [ 'Client SASL mechanisms (',
168             join(', ', @user_mech),
169             ') do not match the SASL mechnism the server announces (',
170             join(', ', @server_mech), ')',
171             ]
172             );
173 0           return 0;
174             }
175              
176 0           $sasl->mechanism(join(" ", @mech));
177              
178 0           $sasl->mechanism($mechanisms);
179             }
180             else {
181 0 0         die "auth(username, password)" if not length $username;
182 0           $sasl = Authen::SASL->new(
183             mechanism => $mechanisms,
184             callback => {
185             user => $username,
186             pass => $password,
187             authname => $username,
188             }
189             );
190             }
191              
192             # We should probably allow the user to pass the host, but I don't
193             # currently know and SASL mechanisms that are used by smtp that need it
194 0           my ($hostname) = split /:/, ${*$self}{'net_pop3_host'}; # /
  0            
195 0           my $client = eval { $sasl->client_new('pop', $hostname, 0) };
  0            
196              
197 0 0         unless ($client) {
198 0           my $mech = $sasl->mechanism;
199 0           $self->set_status(
200             500,
201             [ " Authen::SASL failure: $@",
202             '(please check if your local Authen::SASL installation',
203             "supports mechanism '$mech'"
204             ]
205             );
206 0           return 0;
207             }
208              
209             my ($token) = $client->client_start
210 0 0         or do {
211 0           my $mech = $client->mechanism;
212 0           $self->set_status(
213             500,
214             [ ' Authen::SASL failure: $client->client_start ',
215             "mechanism '$mech' hostname #$hostname#",
216             $client->error
217             ]
218             );
219 0           return 0;
220             };
221              
222             # We dont support sasl mechanisms that encrypt the socket traffic.
223             # todo that we would really need to change the ISA hierarchy
224             # so we dont inherit from IO::Socket, but instead hold it in an attribute
225              
226 0           my @cmd = ("AUTH", $client->mechanism);
227 0           my $code;
228              
229 0 0 0       push @cmd, MIME::Base64::encode_base64($token, '')
230             if defined $token and length $token;
231              
232 0           while (($code = $self->command(@cmd)->response()) == CMD_MORE) {
233              
234 0 0         my ($token) = $client->client_step(MIME::Base64::decode_base64(($self->message)[0])) or do {
235 0           $self->set_status(
236             500,
237             [ ' Authen::SASL failure: $client->client_step ',
238             "mechanism '", $client->mechanism, " hostname #$hostname#, ",
239             $client->error
240             ]
241             );
242 0           return 0;
243             };
244              
245 0 0         @cmd = (MIME::Base64::encode_base64(defined $token ? $token : '', ''));
246             }
247              
248 0           $code == CMD_OK;
249              
250             }
251              
252             1;
253              
254             __END__