Skip Menu |

This queue is for tickets about the Net-Server-POP3proxy CPAN distribution.

Report information
The Basics
Id: 12287
Status: new
Priority: 0/
Queue: Net-Server-POP3proxy

People
Owner: Nobody in particular
Requestors: barborak [...] basikgroup.com
Cc:
AdminCc:

Bug Information
Severity: Wishlist
Broken in: 0.1
Fixed in: (no value)



Subject: nice to have more control of looper
This is a very nice module. To allow more customization, it would be nice if the looper method allowed you to set the select timeout and if it returned a status indicating if there was any activity during the last loop or not. For example, sub looper { my $self = shift; my $timeout = shift; ... my ($toread, $towrite) = IO::Select->select($self->{read_sockets}, $self->{write_sockets},undef, $timeout); my $activity = $#$toread + $#$towrite > -2 ? 1 : undef; ... return $activity; } The reason for doing this can be see in the attached script that uses this module as the basis for a SpamAssassin proxy. This script is based on the pop3proxy.pl script and includes its exit port technique. It also supports an HTTP interface at localhost:8800 to the Bayesian learning system. By having better control of the looper method, I can support these extra features better. The activity return allows me to play a sound on receiving some ham. Thanks again for a great module, Mike P.S. To setup the attached script, follow the pop3proxy.pl instructions except there is no hostmap needed.
use Getopt::Long; use Mail::SpamAssassin; use Win32::Sound; use Net::Server::POP3proxy; use IO::Socket; use IO::Select; use FindBin; my $logfile = 'pop3proxy.log'; my $run_learning_interface = 1; my $exit_port = 9625; my $maxscan = 250000; my $helpflag = 0; usage() unless GetOptions( "logfile:s" => \$logfile, "maxscan=i" => \$max_scan_size, "exitport=i" => \$exit_port, "help" => \$helpflag, "learn" => \$run_learning_interface, ); usage() if $helpflag; chdir "$FindBin::RealBin"; # Prevent concurrent proxies - kill any previous instance # if ( IO::Socket::INET->new ( PeerAddr => 'localhost', PeerPort => $exit_port, Proto => "tcp", Type => SOCK_STREAM ) ) { sleep ( 10 ); warn "WARNING: Existing proxy killed\n"; } if ( $logfile ) { # Redirect stdout and stderr to logfile if specified. # Windows strangeness - you can't reopen STDOUT/STDERR successfully # under wperl.exe unless you've already closed it. Go figure. close STDOUT; close STDERR; open(STDOUT, "> $logfile") or die "Can't redirect stdout: $!"; open(STDERR, ">&STDOUT") or die "Can't dup stdout: $!"; } $| = 1; my $spamtest = Mail::SpamAssassin->new ( { userprefs_filename => './user_prefs', dont_copy_prefs => 1 } ); $spamtest->init_learner ( ); my $readable = IO::Select->new; my $writeable = IO::Select->new; # Create the "exit socket" - any connection on this socket from # localhost will cause us to exit. # my $exit_socket; if ( $exit_port ) { $exit_socket = IO::Socket::INET->new(LocalPort => $exit_port, Listen => 1, Reuse => 1); $readable->add ( $exit_socket ); } my @hamQueue; my $learning_port = 8800; my $learning_socket; my %URLESCAPE_MAP; if ( $run_learning_interface ) { require HTTP::Daemon; require HTTP::Status; require HTTP::Response; require URI::Escape; $HTTP::Status::RC_FORBIDDEN = $HTTP::Status::RC_FORBIDDEN; $HTTP::Status::RC_OK = $HTTP::Status::RC_OK; $learning_socket = HTTP::Daemon->new ( LocalPort => $learning_port ) || die "cannot start the learning interface: $!"; $readable->add ( $learning_socket ); } my $popproxy = new Net::Server::POP3proxy ( # Port => $port, Debug => sub { print ($_[0]) . "\n"; }, Action => sub { filterAction ( $_[0] ); }, Error => sub { die ( $_[0] ); }, MaxSize => $maxscan ) or die ( "Cannot init POP3 proxy server" ); my $ding = undef; my $dingGrace = 5; while ( 1 ) { my $activity = $popproxy->looper ( 1 ); if ( $activity < 0 ) { last; } if ( $ding ) { if ( ! $activity ) { $dingGrace--; } else { $dingGrace = 5; } if ( ! $dingGrace ) { Win32::Sound::Play ( "/winnt/media/notify.wav" ); $ding = undef; } } my ( $toread ) = IO::Select->select ( $readable, undef, undef, 0 ); foreach my $socket ( @$toread ) { if ( $socket == $exit_socket ) { all_done ( $socket ); next; } if ( $socket->sockport == $learning_port ) { handleLearningRequest ( $socket ); next; } unless ($socket && $socket->connected()) { undef $socket; } } # cleanup undef $toread; } my @mail; sub filterAction { my $mailMsg = shift; my $bytecount = length $mailMsg; $mailMsg =~ s/\012\.\./\012\./g; # un-byte-stuff @mail = split /^/, $mailMsg; my $response = shift @mail; # SpamAssassin::NoMailAudit adds a Unix mbox From_ line, unless # you construct your NoMailAudit message with the (ahem, # undocumented) add_From_line param set to false. That From_ # kinda breaks the protocol - the client isn't expecting mbox, # he's expecting raw 822 mail - so we leave it out. my $message = Mail::SpamAssassin::Message->new({message => \@mail}); my $status = $spamtest->check($message); my $id = $message->get_pristine_header('Message-id') || '*none*'; print "$bytecount bytes, ", $status->is_spam() ? 'SPAM' : 'NOT spam', ", Message-id: $id\n"; if ( ! $status->is_spam() ) { $ding = 1; # Notify ham reception. if ( $id ne '*none*' ) { if ( $#hamQueue + 1 >= 50 ) { my $oldMessage = shift @hamQueue; $oldMessage->finish ( ); } # Save the ham for learning. push @hamQueue, $message; } } else { # learn it as spam $spamtest->learn ( $message, undef, 1, undef ); } print $status->get_report(); my $rewritten = $status->rewrite_mail(); $mailMsg = $response; $mailMsg .= $rewritten; $mailMsg =~ s|(?<!\015)\012|\015\012|g; $status->finish(); $mailMsg =~ s/\012\./\012\.\./g; # byte-stuff return $mailMsg; } sub handleLearningRequest { my $socket = shift; if ( $socket == $learning_socket ) { my $client = $learning_socket->accept; if ( ! $client ) { $learning_socket->close; $readable->remove($learning_socket); $learning_socket = HTTP::Daemon->new ( LocalPort => $learning_port ) || die "cannot restart the learning interface: $!"; $readable->add($learning_socket); } else { $readable->add ( $client ); } } else { if ( my $request = $socket->get_request ) { if ( $request->method eq 'GET' and $request->url->path =~ "/learn_ham(/(ham|spam)){0,1}" ) { my $command = $2; my $response = HTTP::Response->new ( $HTTP::Status::RC_OK ); $response->header ( 'Content-type' => 'text/html' ); my $content = "<html><head><title>Ham Learner</title></head><body>"; if ( $command eq "ham" || $command eq "spam" ) { my $id = $request->url->query; if ( $id =~ /id=(.*)$/ ) { $id = URI::Escape::uri_unescape ( $1 ); foreach ( my $index = 0; $index <= $#hamQueue; $index++ ) { my $message = $hamQueue [ $index ]; my $msgId = $message->get_pristine_header ( 'Message-id' ); if ( $msgId eq $id ) { my $status = $spamtest->learn ( $message, undef, $command eq "ham" ? undef : 1, undef ); if ( $status->did_learn ( ) ) { $content .= "Marking message \"" . $message->get_pristine_header( 'Subject' ) . "\" as $command.<br /><br />"; } else { $content .= "The message \"" . $message->get_pristine_header( 'Subject' ) . "\" was not learned.<br /><br />"; } $status->finish ( ); $message->finish ( ); splice @hamQueue, $index, 1; } } } } if ( $#hamQueue < 0 ) { $content .= "There is no more ham to rate."; } else { $content .= "<table cellpadding=\"3\">"; $content .= "<tr><td></td><td></td><td bgcolor=\"#CCCCCC\"><b>From</b></td><td><b>Subject</b></td></tr>"; foreach my $message ( @hamQueue ) { my $id = $message->get_pristine_header ( 'Message-id' ) || next; $id = URI::Escape::uri_escape ( $id ); $content .= "<tr><td bgcolor=\"#00CC00\"><a href=\"/learn_ham/ham?id=$id\">HAM</a></td><td bgcolor=\"#CC0000\"><a href=\"/learn_ham/spam?id=$id\">SPAM</a></td><td bgcolor=\"#CCCCCC\">" . $message->get_pristine_header( 'From' ) . "</td><td>" . $message->get_pristine_header( 'Subject' ) . "</td></tr>"; } $content .= "</table>"; } $content .= "</body></html>"; $response->content ( $content ); $socket->send_response ( $response ); } else { $socket->send_error ( $HTTP::Status::RC_FORBIDDEN ) } } else { $readable->remove($socket); $socket->close; } } } sub usage { print <<EOT; Usage: $0 [options] Options include: --logfile filename Use filename as the log file. Default is pop3proxy.log. If the filename is omitted, log to STDOUT. --maxscan bytes Messages which exceed this size will not be scanned for spam. The default is 250000. Setting this to zero disables this behavior. --exitport port Any connection from localhost on this port will cause us to exit. The default is 9625. Setting this to zero disables this behavior. --learn Run the spam learning interface. EOT exit; } sub all_done { my $socket = shift; my $new_sock = $socket->accept; if ($new_sock->peerhost eq '127.0.0.1') { print "Connection on exit socket, exiting\n"; exit; } else { print "Connection on exit socket from non-local host!\n"; $new_sock->close; } cleanup ( ); } sub cleanup { if ( $popproxy ) { $popproxy->cleanup ( ); # run last buffers - with a grace of 50 # communications for the rest buffer my $gracecounter = 50; while ($popproxy->looper() && $gracecounter ) { $gracecounter--; } $popproxy->cleanup ( 1 ); } }