File Coverage

blib/lib/Mail/Transport/POP3.pm
Criterion Covered Total %
statement 30 232 12.9
branch 0 132 0.0
condition 0 39 0.0
subroutine 10 33 30.3
pod 19 20 95.0
total 59 456 12.9


line stmt bran cond sub pod time code
1             # Copyrights 2001-2023 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.03.
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   26 use vars '$VERSION';
  4         8  
  4         198  
11             $VERSION = '3.006';
12              
13 4     4   22 use base 'Mail::Transport::Receive';
  4         8  
  4         2114  
14              
15 4     4   16484 use strict;
  4         9  
  4         68  
16 4     4   20 use warnings;
  4         6  
  4         148  
17              
18 4     4   2126 use IO::Socket ();
  4         96234  
  4         102  
19 4     4   27 use IO::Socket::INET ();
  4         8  
  4         75  
20 4     4   3700 use IO::Socket::SSL qw(SSL_VERIFY_NONE);
  4         277653  
  4         41  
21 4     4   459 use Socket qw/$CRLF/;
  4         10  
  4         529  
22 4     4   31 use Digest::MD5 qw/md5_hex/;
  4         10  
  4         262  
23 4     4   2215 use MIME::Base64 qw/encode_base64/;
  4         2788  
  4         12189  
24              
25              
26 0   0 0     sub _OK($) { substr(shift // '', 0, 3) eq '+OK' }
27              
28             sub init($)
29 0     0 0   { my ($self, $args) = @_;
30 0           $args->{via} = 'pop3';
31 0   0       $args->{port} ||= 110;
32              
33 0 0         $self->SUPER::init($args) or return;
34              
35 0   0       $self->{MTP_auth} = $args->{authenticate} || 'AUTO';
36 0           $self->{MTP_ssl} = $args->{use_ssl};
37              
38 0   0       my $opts = $self->{MTP_ssl_opts} = $args->{ssl_options} || {};
39 0   0       $opts->{verify_hostname} ||= 0;
40 0   0       $opts->{SSL_verify_mode} ||= SSL_VERIFY_NONE;
41              
42 0 0         $self->socket or return; # establish connection
43              
44 0           $self;
45             }
46              
47             #------------------------------------------
48              
49 0     0 1   sub useSSL() { shift->{MTP_ssl} }
50              
51              
52 0     0 1   sub SSLOptions() { shift->{MTP_ssl_opts} }
53              
54             #------------------------------------------
55              
56             sub ids(;@)
57 0     0 1   { my $self = shift;
58 0 0         $self->socket or return;
59 0 0         wantarray ? @{$self->{MTP_n2uidl}} : $self->{MTP_n2uidl};
  0            
60             }
61              
62              
63             sub messages()
64 0     0 1   { my $self = shift;
65              
66 0 0         $self->log(ERROR =>"Cannot get the messages of pop3 via messages()."), return ()
67             if wantarray;
68              
69 0           $self->{MTP_messages};
70             }
71              
72              
73 0     0 1   sub folderSize() { shift->{MTP_folder_size} }
74              
75              
76             sub header($;$)
77 0     0 1   { my ($self, $uidl) = (shift, shift);
78 0 0         return unless $uidl;
79 0   0       my $bodylines = shift || 0;;
80              
81 0 0         my $socket = $self->socket or return;
82 0 0         my $n = $self->id2n($uidl) or return;
83              
84 0           $self->sendList($socket, "TOP $n $bodylines$CRLF");
85             }
86              
87              
88             sub message($;$)
89 0     0 1   { my ($self, $uidl) = @_;
90 0 0         return unless $uidl;
91              
92 0 0         my $socket = $self->socket or return;
93 0 0         my $n = $self->id2n($uidl) or return;
94 0           my $message = $self->sendList($socket, "RETR $n$CRLF");
95              
96 0 0         return unless $message;
97              
98             # Some POP3 servers add a trailing empty line
99 0 0 0       pop @$message if @$message && $message->[-1] =~ m/^[\012\015]*$/;
100              
101             $self->{MTP_fetched}{$uidl} = undef # mark this ID as fetched
102 0 0         unless exists $self->{MTP_nouidl};
103              
104 0           $message;
105             }
106              
107              
108             sub messageSize($)
109 0     0 1   { my ($self, $uidl) = @_;
110 0 0         return unless $uidl;
111              
112 0           my $list;
113 0 0         unless($list = $self->{MTP_n2length})
114 0 0         { my $socket = $self->socket or return;
115 0 0         my $raw = $self->sendList($socket, "LIST$CRLF") or return;
116 0           my @n2length;
117 0           foreach (@$raw)
118 0           { m#^(\d+) (\d+)#;
119 0           $n2length[$1] = $2;
120             }
121 0           $self->{MTP_n2length} = $list = \@n2length;
122             }
123              
124 0 0         my $n = $self->id2n($uidl) or return;
125 0           $list->[$n];
126             }
127              
128              
129             sub deleted($@)
130 0   0 0 1   { my $dele = shift->{MTP_dele} ||= {};
131 0 0         (shift) ? @$dele{ @_ } = () : delete @$dele{ @_ };
132             }
133              
134              
135             sub deleteFetched()
136 0     0 1   { my $self = shift;
137 0           $self->deleted(1, keys %{$self->{MTP_fetched}});
  0            
138             }
139              
140              
141             sub disconnect()
142 0     0 1   { my $self = shift;
143              
144 0           my $quit;
145 0 0         if($self->{MTP_socket}) # can only disconnect once
146 0 0         { if(my $socket = $self->socket)
147 0   0       { my $dele = $self->{MTP_dele} || {};
148 0           while(my $uidl = each %$dele)
149 0 0         { my $n = $self->id2n($uidl) or next;
150 0 0         $self->send($socket, "DELE $n$CRLF") or last;
151             }
152              
153 0           $quit = $self->send($socket, "QUIT$CRLF");
154 0           close $socket;
155             }
156             }
157              
158 0           delete @$self{ qw(
159             MTP_socket
160             MTP_dele
161             MTP_uidl2n
162             MTP_n2uidl
163             MTP_n2length
164             MTP_fetched
165             ) };
166              
167 0           _OK $quit;
168             }
169              
170              
171             sub fetched(;$)
172 0     0 1   { my $self = shift;
173 0 0         return if exists $self->{MTP_nouidl};
174 0           $self->{MTP_fetched};
175             }
176              
177              
178 0     0 1   sub id2n($;$) { shift->{MTP_uidl2n}{shift()} }
179              
180             #------------------------------------------
181              
182              
183             sub socket()
184 0     0 1   { my $self = shift;
185              
186             # Do we (still) have a working connection which accepts commands?
187 0           my $socket = $self->_connection;
188 0 0         return $socket if defined $socket;
189              
190 0 0         if(exists $self->{MTP_nouidl})
191 0           { $self->log(ERROR =>
192             "Can not re-connect reliably to server which doesn't support UIDL");
193 0           return;
194             }
195              
196             # (Re-)establish the connection
197 0 0         $socket = $self->login or return;
198 0 0         $self->status($socket) or return;
199 0           $self->{MTP_socket} = $socket;
200             }
201              
202              
203              
204             sub send($$)
205 0     0 1   { my $self = shift;
206 0           my $socket = shift;
207 0           my $response;
208            
209 0 0         if(eval {print $socket @_})
  0            
210 0           { $response = <$socket>;
211 0 0         $self->log(ERROR => "Cannot read POP3 from socket: $!")
212             unless defined $response;
213             }
214             else
215 0           { $self->log(ERROR => "Cannot write POP3 to socket: $@");
216             }
217 0           $response;
218             }
219              
220              
221             sub sendList($$)
222 0     0 1   { my ($self, $socket) = (shift, shift);
223 0           my $response = $self->send($socket, @_);
224 0 0 0       $response && _OK $response or return;
225              
226 0           my @list;
227 0           while(my $line = <$socket>)
228 0 0         { last if $line =~ m#^\.\r?\n#s;
229 0           $line =~ s#^\.##;
230 0           push @list, $line;
231             }
232              
233 0           \@list;
234             }
235              
236             sub DESTROY()
237 0     0     { my $self = shift;
238 0           $self->SUPER::DESTROY;
239 0 0         $self->disconnect if $self->{MTP_socket}; # only when open
240             }
241              
242             sub _connection()
243 0     0     { my $self = shift;
244              
245 0           my $socket = $self->{MTP_socket};
246 0 0         defined $socket or return;
247              
248             # Check if we (still) got a connection
249 0           eval { print $socket "NOOP$CRLF" };
  0            
250 0 0 0       if($@ || ! <$socket> )
251 0           { delete $self->{MTP_socket};
252 0           return undef;
253             }
254              
255 0           $socket;
256             }
257              
258              
259              
260             sub login(;$)
261 0     0 1   { my $self = shift;
262              
263             # Check if we can make a connection
264              
265 0           my ($host, $port, $username, $password) = $self->remoteHost;
266 0 0 0       unless($username && $password)
267 0           { $self->log(ERROR => "POP3 requires a username and password.");
268 0           return;
269             }
270              
271 0           my $socket;
272 0 0         if($self->useSSL)
273 0           { my $opts = $self->SSLOptions;
274 0           $socket = eval { IO::Socket::SSL->new(PeerAddr => "$host:$port", %$opts) };
  0            
275             }
276             else
277 0           { $socket = eval { IO::Socket::INET->new("$host:$port") };
  0            
278             }
279              
280 0 0         unless($socket)
281 0           { $self->log(ERROR => "Cannot connect to $host:$port for POP3: $!");
282 0           return;
283             }
284              
285             # Check if it looks like a POP server
286              
287 0           my $connected;
288 0           my $authenticate = $self->{MTP_auth};
289 0           my $welcome = <$socket>;
290 0 0         unless(_OK $welcome)
291 0           { $self->log(ERROR =>
292             "Server at $host:$port does not seem to be talking POP3.");
293 0           return;
294             }
295              
296             # Check APOP login if automatic or APOP specifically requested
297 0 0 0       if($authenticate eq 'AUTO' || $authenticate eq 'APOP')
298 0 0         { if($welcome =~ m#^\+OK .*(<\d+\.\d+\@[^>]+>)#)
299 0           { my $md5 = md5_hex $1.$password;
300 0           my $response = $self->send($socket, "APOP $username $md5$CRLF");
301 0           $connected = _OK $response;
302             }
303             }
304              
305             # Check USER/PASS login if automatic and failed or LOGIN specifically
306             # requested.
307 0 0         unless($connected)
308 0 0 0       { if($authenticate eq 'AUTO' || $authenticate eq 'LOGIN')
309 0 0         { my $response = $self->send($socket, "USER $username$CRLF")
310             or return;
311              
312 0 0         if(_OK $response)
313 0 0         { my $response2 = $self->send($socket, "PASS $password$CRLF")
314             or return;
315 0           $connected = _OK $response2;
316             }
317             }
318             }
319              
320             # Try OAUTH2 login
321 0 0 0       if(! $connected && $authenticate =~ /^OAUTH2/)
322             { # Borrowed from Net::POP3::XOAuth2 0.0.2 by Kizashi Nagata (also Perl license)
323 0           my $token = encode_base64 "user=$username\001auth=Bearer $password\001\001";
324 0           $token =~ s/[\r\n]//g; # no base64 newlines, anywhere
325              
326 0 0         if($authenticate eq 'OAUTH2_SEP')
327             { # Microsofts way
328             # https://learn.microsoft.com/en-us/exchange/client-developer/legacy-protocols/how-to-authenticate-an-imap-pop-smtp-application-by-using-oauth
329 0 0         my $response = $self->send($socket, "AUTH XOAUTH2$CRLF")
330             or return;
331              
332 0 0         if($response =~ /^\+/) # Office365 sends + here, not +OK
333 0 0         { my $response2 = $self->send($socket, "$token$CRLF")
334             or return;
335 0           $connected = _OK $response2;
336             }
337             }
338             else
339 0 0         { my $response = $self->send($socket, "AUTH XOAUTH2 $token$CRLF")
340             or return;
341              
342 0           $connected = _OK $response;
343             }
344             }
345              
346             # If we're still not connected now, we have an error
347 0 0         unless($connected)
348 0 0         { $self->log(ERROR => $authenticate eq 'AUTO' ?
349             "Could not authenticate using any login method" :
350             "Could not authenticate using '$authenticate' method");
351 0           return;
352             }
353              
354 0           $socket;
355             }
356              
357              
358              
359             sub status($;$)
360 0     0 1   { my ($self, $socket) = @_;
361              
362             # Check if we can do a STAT
363              
364 0 0         my $stat = $self->send($socket, "STAT$CRLF") or return;
365 0 0         if($stat !~ m#^\+OK (\d+) (\d+)#)
366 0           { delete $self->{MTP_messages};
367 0           delete $self->{MTP_size};
368 0           $self->log(ERROR => "POP3 Could not do a STAT");
369 0           return;
370             }
371 0           $self->{MTP_messages} = my $nr_msgs = $1;
372 0           $self->{MTP_folder_size} = $2;
373              
374             # Check if we can do a UIDL
375              
376 0 0         my $uidl = $self->send($socket, "UIDL$CRLF") or return;
377 0           $self->{MTP_nouidl} = undef;
378 0           delete $self->{MTP_uidl2n}; # drop the reverse lookup: UIDL -> number
379              
380 0 0         if(_OK $uidl)
381 0           { my @n2uidl;
382 0           $n2uidl[$nr_msgs] = undef; # pre-alloc
383              
384 0           while(my $line = <$socket>)
385 0 0         { last if substr($line, 0, 1) eq '.';
386 0 0         $line =~ m#^(\d+) (.+?)\r?\n# or next;
387 0           $n2uidl[$1] = $2;
388             }
389              
390 0           shift @n2uidl; # make message 1 into index 0
391 0           $self->{MTP_n2uidl} = \@n2uidl;
392 0           delete $self->{MTP_n2length};
393 0           delete $self->{MTP_nouidl};
394             }
395             else
396             { # We can't do UIDL, we need to fake it
397 0 0         my $list = $self->send($socket, "LIST$CRLF") or return;
398 0           my (@n2length, @n2uidl);
399              
400 0 0         if(_OK $list)
401 0           { $n2length[$nr_msgs] = $n2uidl[$nr_msgs] = undef; # alloc all
402              
403 0           my ($host, $port) = $self->remoteHost;
404 0           while(my $line = <$socket>)
405 0 0         { last if substr($line, 0, 1) eq '.';
406 0 0         $line =~ m#^(\d+) (\d+)# or next;
407 0           $n2length[$1] = $2;
408 0           $n2uidl[$1] = "$host:$port:$1"; # fake UIDL, for id only
409             }
410 0           shift @n2length; shift @n2uidl; # make 1st message in index 0
  0            
411             }
412 0           $self->{MTP_n2length} = \@n2length;
413 0           $self->{MTP_n2uidl} = \@n2uidl;
414             }
415              
416 0           my $i = 1;
417 0           my %uidl2n = map +($_ => $i++), @{$self->{MTP_n2uidl}};
  0            
418 0           $self->{MTP_uidl2n} = \%uidl2n;
419              
420 0           1;
421             }
422              
423             #------------------------------------------
424              
425              
426             sub url(;$)
427 0     0 1   { my $self = shift;
428 0           my ($host, $port, $user, $pwd) = $self->remoteHost;
429 0 0         my $proto = $self->useSSL ? 'pop3s' : 'pop3';
430 0           "$proto://$user:$pwd\@$host:$port";
431             }
432              
433             #------------------------------------------
434              
435              
436             1;