File Coverage

blib/lib/Mail/Transport/POP3.pm
Criterion Covered Total %
statement 21 210 10.0
branch 0 122 0.0
condition 0 27 0.0
subroutine 7 28 25.0
pod 17 19 89.4
total 45 406 11.0


line stmt bran cond sub pod time code
1             # Copyrights 2001-2018 by [Mark Overmeer].
2             # For other contributors see ChangeLog.
3             # See the manual pages for details on the licensing terms.
4             # Pod stripped from pm file by OODoc 2.02.
5             # This code is part of distribution Mail-Box-POP3. Meta-POD processed with
6             # OODoc into POD and HTML manual-pages. See README.md
7             # Copyright Mark Overmeer. Licensed under the same terms as Perl itself.
8              
9             package Mail::Transport::POP3;
10 4     4   20 use vars '$VERSION';
  4         7  
  4         149  
11             $VERSION = '3.004';
12              
13 4     4   16 use base 'Mail::Transport::Receive';
  4         6  
  4         1437  
14              
15 4     4   11816 use strict;
  4         6  
  4         55  
16 4     4   16 use warnings;
  4         4  
  4         67  
17              
18 4     4   1562 use IO::Socket ();
  4         70593  
  4         99  
19 4     4   21 use Socket qw/$CRLF/;
  4         6  
  4         656  
20 4     4   21 use Digest::MD5 ();
  4         8  
  4         7686  
21              
22              
23             sub init($)
24 0     0 0   { my ($self, $args) = @_;
25 0           $args->{via} = 'pop3';
26 0   0       $args->{port} ||= 110;
27              
28 0 0         $self->SUPER::init($args) or return;
29              
30 0   0       $self->{MTP_auth} = $args->{authenticate} || 'AUTO';
31 0           $self->{MTP_ssl} = $args->{use_ssl};
32 0 0         return unless $self->socket; # establish connection
33              
34 0           $self;
35             }
36              
37             #------------------------------------------
38              
39              
40             sub ids(;@)
41 0     0 1   { my $self = shift;
42 0 0         return unless $self->socket;
43 0 0         wantarray ? @{$self->{MTP_n2uidl}} : $self->{MTP_n2uidl};
  0            
44             }
45              
46             #------------------------------------------
47              
48              
49             sub messages()
50 0     0 1   { my $self = shift;
51              
52 0 0         $self->log(ERROR =>"Cannot get the messages of pop3 via messages()."), return ()
53             if wantarray;
54              
55 0           $self->{MTP_messages};
56             }
57              
58              
59 0     0 1   sub folderSize() { shift->{MTP_total} }
60              
61              
62             sub header($;$)
63 0     0 1   { my ($self, $uidl) = (shift, shift);
64 0 0         return unless $uidl;
65 0   0       my $bodylines = shift || 0;;
66              
67 0 0         my $socket = $self->socket or return;
68 0 0         my $n = $self->id2n($uidl) or return;
69              
70 0           $self->sendList($socket, "TOP $n $bodylines$CRLF");
71             }
72              
73              
74             sub message($;$)
75 0     0 1   { my ($self, $uidl) = @_;
76 0 0         return unless $uidl;
77              
78 0 0         my $socket = $self->socket or return;
79 0 0         my $n = $self->id2n($uidl) or return;
80 0           my $message = $self->sendList($socket, "RETR $n$CRLF");
81              
82 0 0         return unless $message;
83              
84             # Some POP3 servers add a trailing empty line
85 0 0 0       pop @$message if @$message && $message->[-1] =~ m/^[\012\015]*$/;
86              
87             $self->{MTP_fetched}{$uidl} = undef # mark this ID as fetched
88 0 0         unless exists $self->{MTP_nouidl};
89              
90 0           $message;
91             }
92              
93              
94             sub messageSize($)
95 0     0 1   { my ($self, $uidl) = @_;
96 0 0         return unless $uidl;
97              
98 0           my $list;
99 0 0         unless($list = $self->{MTP_n2length})
100 0 0         { my $socket = $self->socket or return;
101 0 0         my $raw = $self->sendList($socket, "LIST$CRLF") or return;
102 0           my @n2length;
103 0           foreach (@$raw)
104 0           { m#^(\d+) (\d+)#;
105 0           $n2length[$1] = $2;
106             }
107 0           $self->{MTP_n2length} = $list = \@n2length;
108             }
109              
110 0 0         my $n = $self->id2n($uidl) or return;
111 0           $list->[$n];
112             }
113              
114              
115             sub deleted($@)
116 0   0 0 1   { my $dele = shift->{MTP_dele} ||= {};
117 0 0         (shift) ? @$dele{ @_ } = () : delete @$dele{ @_ };
118             }
119              
120              
121             sub deleteFetched()
122 0     0 1   { my $self = shift;
123 0           $self->deleted(1, keys %{$self->{MTP_fetched}});
  0            
124             }
125              
126              
127             sub disconnect()
128 0     0 1   { my $self = shift;
129              
130 0           my $quit;
131 0 0         if($self->{MTP_socket}) # can only disconnect once
132 0 0         { if(my $socket = $self->socket)
133 0   0       { my $dele = $self->{MTP_dele} || {};
134 0           while(my $uidl = each %$dele)
135 0 0         { my $n = $self->id2n($uidl) or next;
136 0 0         $self->send($socket, "DELE $n$CRLF") or last;
137             }
138              
139 0           $quit = $self->send($socket, "QUIT$CRLF");
140 0           close $socket;
141             }
142             }
143              
144 0           delete @$self{ qw(
145             MTP_socket
146             MTP_dele
147             MTP_uidl2n
148             MTP_n2uidl
149             MTP_n2length
150             MTP_fetched
151             ) };
152              
153 0           OK($quit);
154             }
155              
156              
157             sub fetched(;$)
158 0     0 1   { my $self = shift;
159 0 0         return if exists $self->{MTP_nouidl};
160 0           $self->{MTP_fetched};
161             }
162              
163              
164 0     0 1   sub id2n($;$) { shift->{MTP_uidl2n}{shift()} }
165              
166             #------------------------------------------
167              
168              
169             sub socket(;$)
170 0     0 1   { my $self = shift;
171              
172 0           my $socket = $self->_connection;
173 0 0         return $socket if defined $socket;
174              
175 0 0         if(exists $self->{MTP_nouidl})
176 0           { $self->log(ERROR =>
177             "Can not re-connect reliably to server which doesn't support UIDL");
178 0           return;
179             }
180              
181 0 0         return unless $socket = $self->login;
182 0 0         return unless $self->status( $socket );
183              
184             # Save socket in the object and return it
185              
186 0           $self->{MTP_socket} = $socket;
187             }
188              
189              
190             sub send($$)
191 0     0 1   { my $self = shift;
192 0           my $socket = shift;
193 0           my $response;
194            
195 0 0         if(eval {print $socket @_})
  0            
196 0           { $response = <$socket>;
197 0 0         $self->log(ERROR => "Cannot read POP3 from socket: $!")
198             unless defined $response;
199             }
200             else
201 0           { $self->log(ERROR => "Cannot write POP3 to socket: $@");
202             }
203 0           $response;
204             }
205              
206              
207             sub sendList($$)
208 0     0 1   { my $self = shift;
209 0           my $socket = shift;
210 0 0         my $response = $self->send($socket, @_) or return;
211              
212 0 0         return unless OK($response);
213              
214 0           my @list;
215 0           local $_;
216 0           while(<$socket>)
217 0 0         { last if m#^\.\r?\n#s;
218 0           s#^\.##;
219 0           push @list, $_;
220             }
221              
222 0           \@list;
223             }
224              
225             sub DESTROY()
226 0     0     { my $self = shift;
227 0           $self->SUPER::DESTROY;
228 0 0         $self->disconnect if $self->{MTP_socket}; # only do if not already done
229             }
230              
231 0   0 0 0   sub OK($;$) { substr(shift || '', 0, 3) eq '+OK' }
232              
233             sub _connection(;$)
234 0     0     { my $self = shift;
235              
236 0           my $socket = $self->{MTP_socket};
237 0 0         defined $socket or return undef;
238              
239             # Check if we (still) got a connection
240 0           eval {print $socket "NOOP$CRLF"};
  0            
241 0 0 0       if($@ || ! <$socket> )
242 0           { delete $self->{MTP_socket};
243 0           return undef;
244             }
245              
246 0           $socket;
247             }
248              
249              
250             sub login(;$)
251 0     0 1   { my $self = shift;
252              
253             # Check if we can make a TCP/IP connection
254              
255 0           local $_; # make sure we don't spoil $_ for the outside world
256 0           my ($interval, $retries, $timeout) = $self->retry;
257 0           my ($host, $port, $username, $password) = $self->remoteHost;
258 0 0 0       unless($username && $password)
259 0           { $self->log(ERROR => "POP3 requires a username and password.");
260 0           return;
261             }
262              
263 0 0         my $net = $self->{MTP_ssl} ? 'IO::Socket::SSL' : 'IO::Socket::INET';
264 0 0         eval "require $net" or die $@;
265              
266 0           my $socket = eval {$net->new("$host:$port")};
  0            
267 0 0         unless($socket)
268 0           { $self->log(ERROR => "Cannot connect to $host:$port for POP3: $!");
269 0           return;
270             }
271              
272             # Check if it looks like a POP server
273              
274 0           my $connected;
275 0           my $authenticate = $self->{MTP_auth};
276 0           my $welcome = <$socket>;
277 0 0         unless(OK($welcome))
278 0           { $self->log(ERROR =>
279             "Server at $host:$port does not seem to be talking POP3.");
280 0           return;
281             }
282              
283             # Check APOP login if automatic or APOP specifically requested
284              
285 0 0 0       if($authenticate eq 'AUTO' || $authenticate eq 'APOP')
286 0 0         { if($welcome =~ m#^\+OK .*(<\d+\.\d+\@[^>]+>)#)
287 0           { my $md5 = Digest::MD5::md5_hex($1.$password);
288 0           my $response = $self->send($socket, "APOP $username $md5$CRLF");
289 0 0         $connected = OK($response) if $response;
290             }
291             }
292              
293             # Check USER/PASS login if automatic and failed or LOGIN specifically requested
294              
295 0 0         unless($connected)
296 0 0 0       { if($authenticate eq 'AUTO' || $authenticate eq 'LOGIN')
297 0 0         { my $response = $self->send($socket, "USER $username$CRLF")
298             or return;
299              
300 0 0         if(OK($response))
301 0 0         { $response = $self->send($socket, "PASS $password$CRLF")
302             or return;
303 0           $connected = OK($response);
304             }
305             }
306             }
307              
308             # If we're still not connected now, we have an error
309              
310 0 0         unless($connected)
311 0 0         { $self->log(ERROR => $authenticate eq 'AUTO' ?
312             "Could not authenticate using any login method" :
313             "Could not authenticate using '$authenticate' method");
314 0           return;
315             }
316              
317 0           $socket;
318             }
319              
320             #------------------------------------------
321              
322              
323             sub status($;$)
324 0     0 1   { my ($self,$socket) = @_;
325              
326             # Check if we can do a STAT
327              
328 0 0         my $stat = $self->send($socket, "STAT$CRLF") or return;
329 0 0         if($stat =~ m#^\+OK (\d+) (\d+)#)
330 0           { @$self{qw(MTP_messages MTP_total)} = ($1,$2);
331             }
332             else
333 0           { delete $self->{MTP_messages};
334 0           delete $self->{MTP_size};
335 0           $self->log(ERROR => "POP3 Could not do a STAT");
336 0           return;
337             }
338              
339             # Check if we can do a UIDL
340              
341 0 0         my $uidl = $self->send($socket, "UIDL$CRLF") or return;
342 0           $self->{MTP_nouidl} = undef;
343 0           delete $self->{MTP_uidl2n}; # lose the reverse lookup: UIDL -> number
344 0 0         if(OK($uidl))
345 0           { my @n2uidl;
346 0           $n2uidl[$self->{MTP_messages}] = undef; # optimization, sets right size
347              
348 0           local $_; # protect global $_
349 0           while(<$socket>)
350 0 0         { last if substr($_, 0, 1) eq '.';
351 0           s#\r?\n$##;
352 0 0         $n2uidl[$1] = $2 if m#^(\d+) (.+)#;
353             }
354              
355 0           shift @n2uidl; # make message 1 into index 0
356 0           $self->{MTP_n2uidl} = \@n2uidl;
357 0           delete $self->{MTP_n2length};
358 0           delete $self->{MTP_nouidl};
359             }
360              
361             # We can't do UIDL, we need to fake it
362              
363             else
364 0 0         { my $list = $self->send($socket, "LIST$CRLF") or return;
365 0           my @n2length;
366             my @n2uidl;
367 0 0         if(OK($list))
368 0           { my $messages = $self->{MTP_messages};
369 0           my ($host, $port) = $self->remoteHost;
370 0           $n2length[$messages] = $n2uidl[$messages] = undef; # optimization
371 0           while(<$socket>)
372 0 0         { last if substr($_, 0, 1) eq '.';
373 0           m#^(\d+) (\d+)#;
374 0           $n2length[$1] = $2;
375 0           $n2uidl[$1] = "$host:$port:$1"; # fake UIDL, for id only
376             }
377 0           shift @n2length; shift @n2uidl; # make 1st message in index 0
  0            
378             }
379 0           $self->{MTP_n2length} = \@n2length;
380 0           $self->{MTP_n2uidl} = \@n2uidl;
381             }
382              
383 0           my $i = 1;
384 0           my %uidl2n;
385 0           foreach(@{$self->{MTP_n2uidl}})
  0            
386 0           { $uidl2n{$_} = $i++;
387             }
388 0           $self->{MTP_uidl2n} = \%uidl2n;
389 0           1;
390             }
391              
392             #------------------------------------------
393              
394              
395             sub url(;$)
396 0     0 1   { my ($host, $port, $user, $pwd) = shift->remoteHost;
397 0           "pop3://$user:$pwd\@$host:$port";
398             }
399              
400             #------------------------------------------
401              
402              
403             1;