#-*- perl -*-
#
# Copyright (C) 2005,2006,2007,2008 Ken'ichi Fukamachi
# All rights reserved. This program is free software; you can
# redistribute it and/or modify it under the same terms as Perl itself.
#
# $FML: POP3.pm,v 1.8 2007/01/16 12:16:52 fukachan Exp $
#
package FML::MUA::POP3;
use strict;
use vars qw(@ISA @EXPORT @EXPORT_OK $AUTOLOAD);
use Carp;
use FileHandle;
# optional queue class.
my $opt_class = [ "article_post", "command_mail", "error_mail_analyzer" ];
=head1 NAME
FML::MUA::POP3 - retrieve a message by pop3 protocol.
=head1 SYNOPSIS
use FML::MUA::POP3;
my $mua = new FML::MUA::POP3 $curproc;
MUA:
for my $server (@$servers) {
if (defined $mua) {
$mua->login({
server => $server,
username => $username,
password => $password,
});
}
else {
$curproc->logerror("object undefined.");
}
if ($mua->error()) {
$curproc->logerror($mua->error());
next MUA;
}
}
if ($mua->error()) {
$curproc->logerror($mua->error());
$curproc->stop_this_process();
return;
}
$mua->retrieve( { class => $class } );
if ($mua->error()) {
$curproc->logerror($mua->error());
$curproc->stop_this_process();
return;
}
$mua->quit();
if ($mua->error()) {
$curproc->logerror($mua->error());
}
=head1 DESCRIPTION
This class provides POP3 protocol interface.
It behaves a MUA.
=head1 METHODS
=head2 new()
constructor.
=cut
# Descriptions: constructor.
# Arguments: OBJ($self) OBJ($curproc)
# Side Effects: none
# Return Value: OBJ
sub new
{
my ($self, $curproc) = @_;
my ($type) = ref($self) || $self;
my $me = { _curproc => $curproc };
return bless $me, $type;
}
=head2 login($r_args)
login to pop3 server.
=cut
# Descriptions: login to pop3 server.
# Arguments: OBJ($self) HASH_REF($r_args);
# Side Effects: none
# Return Value: OBJ
sub login
{
my ($self, $r_args) = @_;
my $server = $r_args->{ server } || '';
my $username = $r_args->{ username } || '';
my $password = $r_args->{ password } || '';
my $timeout = $r_args->{ timeout } || 60;
if ($server && $username && $password) {
use Net::POP3;
my $pop = Net::POP3->new($server, Timeout => $timeout);
if (defined $pop) {
my $status = undef;
my $capa = $pop->capa();
# 1. try APOP if APOP is supported (we can know it via CAPA).
# 2. try ordinary POP if APOP fails or not supported.
if ($capa->{ APOP }) {
$status = $pop->apop($username, $password);
}
unless ($status) {
$status = $pop->login($username, $password);
}
if (defined $status) {
$self->{ _pop } = $pop;
return $pop;
}
else {
$self->error_set("login failed.");
}
}
else {
$self->error_set("undefined object.");
}
}
else {
$self->error_set("invalid login arguments");
}
}
=head2 retrieve($r_args)
retrieve messages.
=cut
# Descriptions: retrieve messages.
# Arguments: OBJ($self) HASH_REF($r_args);
# Side Effects: none
# Return Value: OBJ
sub retrieve
{
my ($self, $r_args) = @_;
my $curproc = $self->{ _curproc };
my $pop = $self->{ _pop } || undef;
my $class = $r_args->{ class } || undef;
my $tmp_queue = "incoming";
# ASSERT
unless (defined $pop) {
$self->error_set("invalid state");
return undef;
}
unless (defined $class) {
$self->error_set("invalid class");
return undef;
}
my $msgnums = $pop->list; # hashref of msgnum => size
MSG:
foreach my $msgnum (keys %$msgnums) {
my $q = $self->_new_queue_file($r_args);
if (defined $q) {
my $wh = $q->open($tmp_queue, { mode => "w" });
if (defined $wh) {
$wh->autoflush(1);
$wh->clearerr();
$pop->get($msgnum, $wh);
if ($wh->error()) {
$curproc->logerror("failed to retrieve NUM=$msgnum.");
$q->remove();
}
else {
my $id = $q->id();
$q->dup_content($tmp_queue, $class);
$q->remove();
$pop->delete($msgnum);
$curproc->log("fetched: qid=$id");
}
$wh->close();
}
else {
last MSG;
}
}
else {
$curproc->logerror("queue not prepared");
}
}
}
=head2 quit($r_args)
close pop session.
=cut
# Descriptions: close pop session.
# Arguments: OBJ($self) HASH_REF($r_args)
# Side Effects: close pop session.
# Return Value: none
sub quit
{
my ($self, $r_args) = @_;
my $pop = $self->{ _pop };
if (defined $pop) {
$pop->quit();
}
}
=head1 UTILITY
=cut
# Descriptions: create a new queue and return the object.
# Arguments: OBJ($self) HASH_REF($r_args);
# Side Effects: none
# Return Value: OBJ
sub _new_queue_file
{
my ($self, $r_args) = @_;
my $curproc = $self->{ _curproc };
my $config = $curproc->config();
my $queue_dir = $config->{ fetchfml_queue_dir } || '';
my $class = $r_args->{ class } || '';
# ASSERT
unless ($class) {
$self->error_set("invalid class");
return undef;
}
unless ($queue_dir) {
$self->error_set("queue_dir undefined");
return undef;
}
use Mail::Delivery::Queue;
my $queue = new Mail::Delivery::Queue {
directory => $queue_dir,
local_class => $opt_class,
};
return $queue;
}
=head2 pickup_queue($r_args)
=cut
# Descriptions: pick up one queue and return the queue id.
# Arguments: OBJ($self) HASH_REF($r_args)
# Side Effects: none
# Return Value: OBJ
sub pickup_queue
{
my ($self, $r_args) = @_;
my $curproc = $self->{ _curproc };
my $config = $curproc->config();
my $class = $r_args->{ class } || $opt_class->[ 0 ] || '';
my $queue_dir = $config->{ fetchfml_queue_dir } || '';
# ASSERT
unless ($class) {
$self->error_set("invalid class");
return undef;
}
unless ($queue_dir) {
$self->error_set("queue_dir undefined");
return undef;
}
use Mail::Delivery::Queue;
my $queue = new Mail::Delivery::Queue {
directory => $queue_dir,
local_class => $opt_class,
};
my $list = $queue->list($class, "oldest");
my $queue_id = $list->[ 0 ] || '';
if (defined $queue_id && $queue_id) {
my $queue = new Mail::Delivery::Queue {
id => $queue_id,
directory => $queue_dir,
local_class => $opt_class,
};
return $queue;
}
else {
return undef;
}
}
=head1 ERROR HADNLING
=head2 error_set($reason)
save error reason.
=head2 error()
return the last error reason.
=cut
# Descriptions: save error reason.
# Arguments: OBJ($self) STR($reason)
# Side Effects: update $self.
# Return Value: none
sub error_set
{
my ($self, $reason) = @_;
$self->{ _error_reason } = $reason || '';
}
# Descriptions: return the last error reason.
# Arguments: OBJ($self)
# Side Effects: none
# Return Value: STR
sub error
{
my ($self) = @_;
return( $self->{ _error_reason } || '' );
}
#
# debug
#
if ($0 eq __FILE__) {
my ($username, $password, $buf) = ();
my $server = shift @ARGV || croak("usage: $0 server\n");
use Term::ReadLine;
my $term = new Term::ReadLine 'Simple Perl calc';
my $prompt = "Username: ";
LINE:
while ( defined ($buf = $term->readline($prompt)) ) {
system "stty -echo";
eval( $username = $buf );
last LINE if $@;
last LINE if $username;
}
$username =~ s/\s*$//;
$prompt = "Password: ";
LINE:
while ( defined ($buf = $term->readline($prompt)) ) {
system "stty -echo";
eval( $password = $buf );
last LINE if $@;
last LINE if $password;
}
$password =~ s/\s*$//;
print "\n";
if ($username && $password) {
use Net::POP3;
my $pop = Net::POP3->new($server, Timeout => 60);
my $capa = $pop->capa();
my $status = 0;
if ($capa->{ APOP }) {
print "try apop ...\n";
$status = $pop->apop($username, $password) || undef;
}
unless ($status) {
print "try usual login ...\n";
$status = $pop->login($username, $password) || undef;
}
if (defined $status) {
if ($status > 0) {
my $msgnums = $pop->list; # hashref of msgnum => size
foreach my $msgnum (keys %$msgnums) {
print ">>> $msgnum\n";
$pop->get($msgnum, \*STDOUT);
# $pop->delete($msgnum);
}
}
}
else {
croak("login failed.");
}
$pop->quit;
}
else {
croak("username and password unspecified.");
}
}
=head1 CODING STYLE
See C on fml coding style guide.
=head1 AUTHOR
Ken'ichi Fukamachi
=head1 COPYRIGHT
Copyright (C) 2005,2006,2007,2008 Ken'ichi Fukamachi
All rights reserved. This program is free software; you can
redistribute it and/or modify it under the same terms as Perl itself.
=head1 HISTORY
FML::MUA::POP3 appeared in fml8 mailing list driver package.
See C for more details.
=cut
1;