#!/usr/bin/perl # $Header$ ############################################################################ use Socket; use FileHandle; use Fcntl; use Getopt::Std; use IO::Socket; use MIME::Base64 qw(encode_base64); use CGI; use CGI::Carp qw(fatalsToBrowser); ################################################################# # Main program. # ################################################################# print STDOUT "Content-type: text/html\n\n"; print STDOUT "
"; init(); get_html(); sigprc(); if ( !$host or !$user or !$pwd or !$folder ) { # Put up the form display_form(); exit; } $mbx = $folder; connectToHost($host, \$conn); login($user,$pwd, $conn) or exit; $n = $startcount = $count = selectMbx( $mbx, $conn ); commafy( \$n ); print STDOUT "Click here to go back to the IMAP Purge web site."; print STDOUT "
Purging the $mbx folder Purging mailbox $mbx..." if $debug;
sendCommand ($conn, "1 SELECT \"$mbx\"");
while (1) {
readResponse ($conn);
last if ( $response =~ /1 OK/i );
}
sendCommand ( $conn, "1 EXPUNGE");
$expunged=0;
while (1) {
readResponse ($conn);
$expunged++ if $response =~ /\* (.+) Expunge/i;
last if $response =~ /^1 OK/;
if ( $response =~ /^1 BAD|^1 NO/i ) {
print STDOUT "Error purging messages: $response\n";
last;
}
}
$totalExpunged += $expunged;
# print STDOUT "$expunged messages purged\n" if $debug;
}
sub dieright {
local($sig) = @_;
print STDOUT "caught signal $sig\n";
logout( $conn );
exit(-1);
}
sub sigprc {
$SIG{'HUP'} = 'dieright';
$SIG{'INT'} = 'dieright';
$SIG{'QUIT'} = 'dieright';
$SIG{'ILL'} = 'dieright';
$SIG{'TRAP'} = 'dieright';
$SIG{'IOT'} = 'dieright';
$SIG{'EMT'} = 'dieright';
$SIG{'FPE'} = 'dieright';
$SIG{'BUS'} = 'dieright';
$SIG{'SEGV'} = 'dieright';
$SIG{'SYS'} = 'dieright';
$SIG{'PIPE'} = 'dieright';
$SIG{'ALRM'} = 'dieright';
$SIG{'TERM'} = 'dieright';
$SIG{'URG'} = 'dieright';
}
# getMailboxList
#
# get a list of the user's mailboxes
#
sub getMailboxList {
my $conn = shift;
my @mbxs;
my $mbx;
# Get a list of the user's mailboxes
#
Log("Get list of user's mailboxes") if $debug;
sendCommand ($conn, "1 LIST \"\" *");
undef @response;
while ( 1 ) {
readResponse ($conn);
if ( $response =~ /^1 OK/i ) {
last;
}
elsif ( $response !~ /^\*/ ) {
Log ("unexpected response: $response");
return 0;
}
}
undef @mbxs;
for $i (0 .. $#response) {
$response[$i] =~ s/\s+/ /;
($dmy,$mbx) = split(/"\/"/,$response[$i]);
$mbx =~ s/^\s+//; $mbx =~ s/\s+$//;
$mbx =~ s/"//g;
if ($response[$i] =~ /NOSELECT/i) {
if ($debugMode) { Log("$mbx is set NOSELECT,skip it",2); }
next;
}
if ($mbx =~ /^\./) {
# Skip mailboxes starting with a dot
next;
}
push ( @mbxs, $mbx ) if $mbx ne '';
}
return @mbxs;
}
sub selectMbx {
my $mbx = shift;
my $conn = shift;
my $count = 0;
# Some IMAP clients such as Outlook and Netscape) do not automatically list
# all mailboxes. The user must manually subscribe to them. This routine
# does that for the user by marking the mailbox as 'subscribed'.
# Now select the mailbox
sendCommand( $conn, "1 SELECT \"$mbx\"");
my $loops;
while ( 1 ) {
readResponse( $conn );
if ( $response =~ /^1 OK/i ) {
last;
} elsif ( $response =~ /\* (.+) EXISTS/i ) {
$count = $1;
} elsif ( $response =~ /^1 NO|^1 BAD|^\* BYE/i ) {
Log("Unexpected response to SELECT $mbx command: $response");
last;
}
last if $response =~ /\+ OK/i;
last if $loops++ > 99;
}
return $count;
}
sub get_html {
my $fields = shift;
my $formData=0;
# Get the HTML form values
#
my $query = new CGI;
$host = $query->param('host');
$user = $query->param('user');
$pwd = $query->param('pwd');
$folder = $query->param('folder');
}
sub display_form {
my @terms = split(/\//, $0);
my $me = $terms[$#terms];
# print STDOUT "Content-type: text/html\n\n";
print STDOUT "
";
print STDOUT "There are $n messages in the $mbx folder
";
Log("There are $n messages in the $mbx folder for $user");
exit if $n == 0;
$chunk = 1000;
$start -= $chunk; $end = 0;
$start++;
$start = 1;
$end = $start + $chunk;
$deleted=0;
while( 1 ) {
$range = "$start:$end";
deleteMsgs( $range, $conn );
expungeMbx( $mbx, $conn );
$start = $end+1;
$end = $start + $chunk;
$deleted += $chunk;
Log( "Deleted $deleted so far out of $startcount messages");
last if $start > $startcount;
}
$range = "1:$count";
deleteMsgs( $range, $conn );
Log("Expunging the mailbox");
expungeMbx( $mbx, $conn );
$count = selectMbx( $mbx, $conn );
print STDOUT "Finished, there are now $count messages in $mbx
";
Log("Finished, there are now $count messages in $mbx for $user");
logout( $conn );
exit;
sub init {
$version = 'V1.0.1';
$os = $ENV{'OS'};
processArgs();
# Determine whether we have SSL support via openSSL and IO::Socket::SSL
$ssl_installed = 1;
eval 'use IO::Socket::SSL';
if ( $@ ) {
$ssl_installed = 0;
}
$timeout = 60 unless $timeout;
# Open the logFile
#
if ( $logfile ) {
if ( !open(LOG, ">> $logfile")) {
print STDOUT "Can't open $logfile: $!\n";
}
select(LOG); $| = 1;
}
$total=0;
}
#
# sendCommand
#
# This subroutine formats and sends an IMAP protocol command to an
# IMAP server on a specified connection.
#
sub sendCommand
{
local($fd) = shift @_;
local($cmd) = shift @_;
print $fd "$cmd\r\n";
if ($showIMAP) { Log (">> $cmd",2); }
}
#
# readResponse
#
# This subroutine reads and formats an IMAP protocol response from an
# IMAP server on a specified connection.
#
sub readResponse
{
local($fd) = shift @_;
$response = <$fd>;
chop $response;
$response =~ s/\r//g;
push (@response,$response);
if ($showIMAP) { Log ("<< $response",2); }
}
#
# Log
#
# This subroutine formats and writes a log message to STDERR.
#
sub Log {
my $str = shift;
# If a logile has been specified then write the output to it
# Otherwise write it to STDOUT
if ( $logfile ) {
($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime;
if ($year < 99) { $yr = 2000; }
else { $yr = 1900; }
$line = sprintf ("%.2d-%.2d-%d.%.2d:%.2d:%.2d %s %s\n",
$mon + 1, $mday, $year + $yr, $hour, $min, $sec,$$,$str);
print LOG "$line";
}
}
# Make a connection to an IMAP host
sub connectToHost {
my $host = shift;
my $conn = shift;
Log("Connecting to $host") if $debug;
($host,$port) = split(/:/, $host);
$port = 143 unless $port;
# We know whether to use SSL for ports 143 and 993. For any
# other ones we'll have to figure it out.
$mode = sslmode( $host, $port );
if ( $mode eq 'SSL' ) {
unless( $ssl_installed == 1 ) {
warn("You must have openSSL and IO::Socket::SSL installed to use an SSL connection");
Log("You must have openSSL and IO::Socket::SSL installed to use an SSL connection");
exit;
}
Log("Attempting an SSL connection") if $debug;
$$conn = IO::Socket::SSL->new(
Proto => "tcp",
SSL_verify_mode => 0x00,
PeerAddr => $host,
PeerPort => $port,
Domain => AF_INET,
);
unless ( $$conn ) {
$error = IO::Socket::SSL::errstr();
Log("Error connecting to $host: $error");
warn("Error connecting to $host: $error");
exit;
}
} else {
# Non-SSL connection
Log("Attempting a non-SSL connection") if $debug;
$$conn = IO::Socket::INET->new(
Proto => "tcp",
PeerAddr => $host,
PeerPort => $port,
);
unless ( $$conn ) {
Log("Error connecting to $host:$port: $@");
warn "Error connecting to $host:$port: $@";
exit;
}
}
}
sub sslmode {
my $host = shift;
my $port = shift;
my $mode;
# Determine whether to make an SSL connection
# to the host. Return 'SSL' if so.
if ( $port == 143 ) {
# Standard non-SSL port
return '';
} elsif ( $port == 993 ) {
# Standard SSL port
return 'SSL';
}
unless ( $ssl_installed ) {
# We don't have SSL installed on this machine
return '';
}
# For any other port we need to determine whether it supports SSL
my $conn = IO::Socket::SSL->new(
Proto => "tcp",
SSL_verify_mode => 0x00,
PeerAddr => $host,
PeerPort => $port,
);
if ( $conn ) {
close( $conn );
$mode = 'SSL';
} else {
$mode = '';
}
return $mode;
}
# trim
#
# remove leading and trailing spaces from a string
sub trim {
local (*string) = @_;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return;
}
# login
#
# login in at the source host with the user's name and password
#
sub login {
my $user = shift;
my $pwd = shift;
my $conn = shift;
if ( $admin_user ) {
($admin_user,$admin_pwd) = split(/:/, $admin_user);
login_plain( $user, $admin_user, $admin_pwd, $conn ) or exit;
return 1;
}
sendCommand ($conn, "1 LOGIN $user $pwd");
while (1) {
readResponse ( $conn );
if ($response =~ /1 OK/i) {
last;
}
if ($response =~ /^(.+) NO|^(.+) BAD/i) {
Log ("unexpected LOGIN response: $response");
return 0;
}
}
Log("Logged in as $user") if $debug;
return 1;
}
# login_plain
#
# login in at the source host with the user's name and password. If provided
# with administrator credential, use them as this eliminates the need for the
# user's password.
#
sub login_plain {
my $user = shift;
my $admin = shift;
my $pwd = shift;
my $conn = shift;
# Do an AUTHENTICATE = PLAIN. If an admin user has been provided then use it.
if ( !$admin ) {
# Log in as the user
$admin = $user
}
$login_str = sprintf("%s\x00%s\x00%s", $user,$admin,$pwd);
$login_str = encode_base64("$login_str", "");
$len = length( $login_str );
# sendCommand ($conn, "1 AUTHENTICATE \"PLAIN\" {$len}" );
sendCommand ($conn, "1 AUTHENTICATE PLAIN" );
my $loops;
while (1) {
readResponse ( $conn );
last if $response =~ /\+/;
if ($response =~ /^1 NO|^1 BAD|^\* BYE/i) {
Log ("unexpected LOGIN response: $response");
exit;
}
$last if $loops++ > 5;
}
sendCommand ($conn, "$login_str" );
my $loops;
while (1) {
readResponse ( $conn );
if ( $response =~ /Microsoft Exchange/i and $conn eq $dst ) {
# The destination is an Exchange server
$exchange = 1;
Log("The destination is an Exchange server");
}
last if $response =~ /^1 OK/i;
if ($response =~ /^1 NO|^1 BAD|^\* BYE/i) {
Log ("unexpected LOGIN response: $response");
exit;
}
$last if $loops++ > 5;
}
return 1;
}
# logout
#
# log out from the host
#
sub logout {
my $conn = shift;
++$lsn;
undef @response;
sendCommand ($conn, "$lsn LOGOUT");
while ( 1 ) {
readResponse ($conn);
if ( $response =~ /^$lsn OK/i ) {
last;
}
elsif ( $response !~ /^\*/ ) {
Log ("unexpected LOGOUT response: $response");
last;
}
}
close $conn;
return;
}
# getMsgList
#
# Get a list of messages in a mailbox
#
sub getMsgList {
my $range = shift;
my $mailbox = shift;
my $msgs = shift;
my $conn = shift;
my $seen;
my $empty;
my $msgnum;
my $from;
my $flags;
trim( *mailbox );
sendCommand ($conn, "1 SELECT \"$mailbox\"");
undef @response;
$empty=0;
while ( 1 ) {
readResponse ( $conn );
if ( $response =~ / 0 EXISTS/i ) { $empty=1; }
if ( $response =~ /^1 OK/i ) {
# print STDERR "response $response\n";
last;
}
elsif ( $response !~ /^\*/ ) {
Log ("unexpected response: $response");
# print STDERR "Error: $response\n";
return 0;
}
}
sendCommand ( $conn, "1 FETCH $range (uid)");
undef @response;
while ( 1 ) {
readResponse ( $conn );
if ( $response =~ /^1 OK/i ) {
# print STDERR "response $response\n";
last;
}
last if $response =~ /^1 NO|^1 BAD/;
}
@msgs = ();
$flags = '';
for $i (0 .. $#response) {
last if $response[$i] =~ /^1 OK FETCH complete/i;
if ( $response[$i] =~ /\* (.+) FETCH/ ) {
($msgnum) = split(/\s+/, $1);
push( @$msgs, $msgnum );
}
}
}
sub fetchMsg {
my $msgnum = shift;
my $mbx = shift;
my $conn = shift;
my $message;
Log(" Fetching msg $msgnum...") if $debug;
sendCommand ($conn, "1 SELECT \"$mbx\"");
while (1) {
readResponse ($conn);
last if ( $response =~ /1 OK/i );
}
sendCommand( $conn, "1 FETCH $msgnum (rfc822)");
while (1) {
readResponse ($conn);
if ( $response =~ /1 OK/i ) {
$size = length($message);
last;
}
elsif ($response =~ /message number out of range/i) {
Log ("Error fetching uid $uid: out of range",2);
$stat=0;
last;
}
elsif ($response =~ /Bogus sequence in FETCH/i) {
Log ("Error fetching uid $uid: Bogus sequence in FETCH",2);
$stat=0;
last;
}
elsif ( $response =~ /message could not be processed/i ) {
Log("Message could not be processed, skipping it ($user,msgnum $msgnum,$destMbx)");
push(@errors,"Message could not be processed, skipping it ($user,msgnum $msgnum,$destMbx)");
$stat=0;
last;
}
elsif
($response =~ /^\*\s+$msgnum\s+FETCH\s+\(.*RFC822\s+\{[0-9]+\}/i) {
($len) = ($response =~ /^\*\s+$msgnum\s+FETCH\s+\(.*RFC822\s+\{([0-9]+)\}/i);
$cc = 0;
$message = "";
while ( $cc < $len ) {
$n = 0;
$n = read ($conn, $segment, $len - $cc);
if ( $n == 0 ) {
Log ("unable to read $len bytes");
return 0;
}
$message .= $segment;
$cc += $n;
}
}
}
return $message;
}
sub usage {
print STDOUT "usage:\n";
print STDOUT " purgeMbx.pl -S host/user/pwd -m