#!/usr/bin/perl ####################################################################### # Program name migrateIMAP.pl # # Date 8/05/99 # # # ####################################################################### use Socket; use FileHandle; use Fcntl; $debugMode = 0; $SIG{'ALRM'} = 'alarmHandler'; # # 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 @_; &alarmSet ($timeout); $response = <$fd>; &alarmSet (0); 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 { local ($str,$level) = @_; local ($line); # Definition of "level" argument: # 1 = Write to screen only # 2 = Write to logfile only # 3 = Write to both if (!$level) { $level = 3; } if ((!$html) && ($level =~ /1|3/)){ print STDERR "$str\n"; } ($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); if ($level =~ /2|3/) { print LOG "$line"; } } # # alarmHandler # # This subroutine catches response timeouts and attempts to reconnect # to the host so that processing can continue # sub alarmHandler { &Log ("Timeout - no response from server after $timeout seconds"); print STDERR "\nNo response from server after $timeout seconds\n"; $response = "TIMEOUT\n"; &Log("Reconnect to server and continue"); print STDERR "Reconnect to server and continue\n\n"; if (! &connectToSource($sourceHost)) { print STDERR "\nCan't reconnect to $sourceHost, aborting migration\n"; close LOG; close RPT; exit 1; } if (! &connectToDest($destHost)) { print STDERR "\nCan't reconnect to $destHost, aborting migration\n"; close LOG; close RPT; exit 1; } # Log in at the source system if (! &loginSource($user,$pwd)) { print STDERR "Unable to login to $sourceHost as $user, aborting migration\n"; &report("Unable to login to $sourceHost as $user, aborting migration\n"); close LOG; close RPT; exit 1; } if (! &loginDest($destUser,$destPwd)) { print STDERR "Unable to login to $destHost as $destUser, aborting migration\n"; &report("Unable to login to $destHost as $destUser, aborting migration\n"); close LOG; close RPT; exit 1; } print STDERR "\n"; return; } # insertMsg # # This routine inserts an RFC822 messages into a user's folder # sub insertMsg { local (*message, $mbx, $flags, $date, $createMbxOnly) = @_; local ($lsn,$lenx); # Rename .mail to Inbox if ($mbx =~ /\.mail/) { $mbx = 'Inbox'; } $lenx = length($message); $totalBytes = $totalBytes + $lenx; $totalMsgs++; if ( $uw ) { # UW IMAP doesn't permit a folder to have both messages # and subfolders. Flatten the sufolder hierarchy so that # subfolders can be migrated. $mbx =~ s/\//-/g; } # Create the mailbox unless we have already done so ++$lsn; if ($destMbxs{"$mbx"} eq '') { &sendCommand (LS, "$lsn CREATE \"$mbx\""); while ( 1 ) { &readResponse (LS); if ( $response =~ /^$rsn OK/i ) { last; } elsif ( $response !~ /^\*/ ) { if (!($response =~ /already exists|reserved mailbox name/i)) { &Log ("WARNING: $response"); } last; } } } $destMbxs{"$mbx"} = '1'; if ($createMbxOnly) { return; } ++$lsn; $flags =~ s/\\Recent//; &sendCommand (LS, "$lsn APPEND \"$mbx\" ($flags) \"$date\" \{$lenx\}"); &readResponse (LS); if ( $response !~ /^\+/ ) { &Log ("unexpected APPEND response: $response"); # next; push(@errors,"Error appending message to $mbx for $user"); return 0; } print LS "$message\r\n"; undef @response; while ( 1 ) { &readResponse (LS); if ( $response =~ /^$lsn OK/i ) { last; } elsif ( $response !~ /^\*/ ) { &Log ("unexpected APPEND response: $response"); # next; return 0; } } return; } # connectToSource # # Make an IMAP4 connection to the source host # sub connectToSource { local ($host) = @_; &Log("Connecting to $host"); $sockaddr = 'S n a4 x8'; ($name, $aliases, $proto) = getprotobyname('tcp'); if (! $sourcePort) { $sourcePort = 143; } if ($debugMode) { &Log("Using port $sourcePort",2); } if ($host eq "") { &Log ("no remote host defined"); print STDOUT "
You must specifiy a remote host";
close LOG; close RPT;
exit (1);
}
($name, $aliases, $type, $len, $serverAddr) = gethostbyname ($host);
if (!$serverAddr) {
&Log ("$host: unknown host");
print STDOUT " ";
print STDOUT " ";
print STDOUT "The summary report has been written to $reportFile ";
print STDOUT "";
print STDOUT "
Host $host is unknown\n";
close LOG; close RPT;
exit (1);
}
# Connect to the remote IMAP4 server
#
$server = pack ($sockaddr, &AF_INET, $sourcePort, $serverAddr);
if (! socket(RS, &PF_INET, &SOCK_STREAM, $proto) ) {
&Log ("socket: $!");
close LOG; close RPT;
exit (1);
}
if ( ! connect(RS, $server) ) {
print STDOUT "
$!. ";
&Log ("connect: $!");
return 0;
}
select(RS); $| = 1;
while (1) {
&readResponse (RS);
if ( $response =~ /^\* OK/i ) {
last;
}
else {
&Log ("Can't connect to host on port $sourcePort: $response");
return 0;
}
}
# print STDERR "Connected to $host\n";
&Log ("connected to $host (port $sourcePort)",2);
select(RS); $| = 1;
return 1;
}
# connectToDest
#
# Make an IMAP4 connection to the Destination Host
#
sub connectToDest {
local ($host) = @_;
&Log("Connecting to $host");
$sockaddr = 'S n a4 x8';
($name, $aliases, $proto) = getprotobyname('tcp');
if ($html) { $destPort = $IMAPport; }
if (! $destPort) {
$destPort = 143;
}
if ($debugMode) { &Log("Using port $destPort",2); }
if ($host eq "") {
&Log ("no remote host defined");
close LOG; close RPT;
exit (1);
}
($name, $aliases, $type, $len, $serverAddr) = gethostbyname ($host);
if (!$serverAddr) {
&Log ("$host: unknown host");
close LOG; close RPT;
exit (1);
}
# Connect to the destination IMAP4 server
#
# print STDERR "Connecting to host $host\n";
$server = pack ($sockaddr, &AF_INET, $destPort, $serverAddr);
if (! socket(LS, &PF_INET, &SOCK_STREAM, $proto) ) {
&Log ("socket: $!");
close LOG; close RPT;
exit (1);
}
if ( ! connect(LS, $server) ) {
print STDOUT "
$!. ";
&Log ("connect: $!");
return 0;
}
select(LS); $| = 1;
while (1) {
&readResponse (LS);
if ( $response =~ /^\* OK/i ) {
last;
}
else {
&Log ("Can't connect to host on port $destPort: $response");
return 0;
}
}
&Log ("connected to $host (port $destPort)",2);
# print STDERR "Connected to $host\n";
select(LS); $| = 1;
return 1;
}
# trim
#
# remove leading and trailing spaces from a string
sub trim {
local (*string) = @_;
$string =~ s/^\s+//;
$string =~ s/\s+$//;
return;
}
# loginSource
#
# login in at the source host with the user's name and password
#
sub loginSource {
local ($user,$pwd) = @_;
$rsn = 1;
# print STDERR "login $user $pwd\n";
&sendCommand (RS, "$rsn LOGIN $user $pwd");
while (1) {
&readResponse (RS);
if ($response =~ /^$rsn OK/i) {
last;
}
elsif ($response !~ /^\*/) {
&Log ("unexpected LOGIN response: $response");
return 0;
}
}
&Log("Logged in at $sourceHost as $user",2);
if (!$sourceCapability) {
# List the IMAP4 capability info about the source system
&sendCommand (RS, "$rsn CAPABILITY");
undef @response;
while (1) {
&readResponse (RS);
for $i (0 .. $#response) {
if ($response[$i] =~ /CAPABILITY/) {
# &Log ("$i $response[$i]");
$sourceCapability = $response[$i];
last;
}
}
if ( $response =~ /^$rsn OK/i ) {
last;
}
}
&Log("$sourceHost $sourceCapability",2);
}
return 1;
}
# loginDest
#
# log in at the destination host with the user's name and password
#
sub loginDest {
local ($user,$pwd) = @_;
$lsn = 1;
if ($debugMode) { &Log("Logging in at destination as $user",2); }
&sendCommand (LS, "$lsn LOGIN $user $pwd");
while (1) {
&readResponse (LS);
if ($response =~ /^$lsn OK/i) {
last;
}
elsif ($response !~ /^\*/) {
&Log ("unexpected LOGIN response: $response");
return 0;
}
}
&Log("Logged in at $destHost as $user",2);
if (!$destCapability) {
# List the IMAP4 capability info about the dest system
&sendCommand (LS, "$rsn CAPABILITY");
undef @response;
while (1) {
&readResponse (LS);
for $i (0 .. $#response) {
if ($response[$i] =~ /CAPABILITY/) {
# &Log ("$i $response[$i]");
$destCapability = $response[$i];
last;
}
}
if ( $response =~ /^$rsn OK/i ) {
last;
}
}
&Log("$destHost $destCapability",2);
}
return 1;
}
# logoutSource
#
# log out from the source host
#
sub logoutSource {
++$rsn;
&sendCommand (RS, "$rsn LOGOUT");
while ( 1 ) {
&readResponse (RS);
if ( $response =~ /^$rsn OK/i ) {
last;
}
elsif ( $response !~ /^\*/ ) {
&Log ("unexpected LOGOUT response: $response");
last;
}
}
close RS;
return;
}
# logoutDest
#
# log out from the destination host
#
sub logoutDest {
++$lsn;
undef @response;
&sendCommand (LS, "$lsn LOGOUT");
while ( 1 ) {
&readResponse (LS);
if ( $response =~ /^$lsn OK/i ) {
last;
}
elsif ( $response !~ /^\*/ ) {
&Log ("unexpected LOGOUT response: $response");
last;
}
}
close LS;
return;
}
# getMailboxList
#
# get a list of the user's mailboxes from the source host
#
sub getMailboxList {
local ($user,*mbxs) = @_;
# Get a list of the user's mailboxes
#
if ($debugMode) { &Log("Get list of user's mailboxes",2); }
&sendCommand (RS, "$rsn LIST \"\" *");
undef @response;
while ( 1 ) {
&readResponse (RS);
for $i (0 .. $#response) {
# print "$i- $response[$i]\n";
}
if ( $response =~ /^$rsn OK/i ) {
last;
}
elsif ( $response !~ /^\*/ ) {
&Log ("unexpected response: $response");
return 0;
}
}
undef @mbxs;
for $i (0 .. $#response) {
# print STDERR "$response[$i]\n";
$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 =~ /^\#/) && ($user ne 'anonymous')) {
# Skip public mbxs unless we are migrating them
next;
}
if ($mbx =~ /^\./) {
# Skip mailboxes starting with a dot
next;
}
if ($mbx =~ /^\Di&AOE-rio/) {
# Skip mailboxes starting with a dot
next;
}
if ($mbx =~ /^\Journal/) {
# Skip mailboxes starting with a dot
next;
}
# if ($mbx eq 'Mail') {
# # Skip the 'root' mailbox
# next;
# }
if ($rootMbx) {
if ($mbx !~ /^$rootMbx/i) {
# Skip any file not under the root mailbox
if ($debugMode) { &Log("Skip mailbox not under $rootMbx",2); }
next;
}
}
if (($user eq 'anonymous') && ($#publicMbxList > -1)) {
# If we are migrating Public Mailboxes and a list of
# the Public Mailboxes to be migrated was supplied on
# the command line, substitute that list
undef @mbxs;
@mbxs = @publicMbxList;
}
# print STDERR "mailbox >$mbx<\n";
if ($mbx ne '') {
push(@mbxs,$mbx);
if ($debugMode) { &Log("$mbx",2); }
}
}
return 1;
}
# getMsgList
#
# Get a list of the user's messages in the indicated mailbox on
# the source host
#
sub getMsgList {
local ($mailbox,*msgs) = @_;
local ($seen,$empty);
# print STDERR "List of messages in $mailbox\n";
# Select the mailbox in read-only mode
&sendCommand (RS, "$rsn EXAMINE \"$mailbox\"");
undef @response;
$empty=0;
while ( 1 ) {
&readResponse (RS);
if ( $response =~ / 0 EXISTS/i ) { $empty=1; }
if ( $response =~ /^$rsn OK/i ) {
# print STDERR "response $response\n";
last;
}
elsif ( $response !~ /^\*/ ) {
&Log ("unexpected response: $response");
# print STDERR "Error: $response\n";
push(@errors,"Error getting list of msgs in $mailbox ($user): $response");
return 0;
}
}
if ($empty) {
# No msgs in this mailbox
&Log("$mailbox is empty",2);
undef @msgs;
return 1;
}
&sendCommand (RS, "$rsn FETCH 1:* (UID FLAGS RFC822.SIZE INTERNALDATE)");
undef @response;
while ( 1 ) {
&readResponse (RS);
if ( $response =~ /^$rsn OK/i ) {
# print STDERR "response $response\n";
last;
}
elsif ( $response !~ /^\*/ ) {
&Log ("unexpected response: $response");
&Log ("Unable to get list of messages in this mailbox");
push(@errors,"Error getting list of $user's msgs");
return 0;
}
}
# Get a list of the msgs in the mailbox
#
undef @msgs;
undef $flags;
for $i (0 .. $#response) {
$seen=0;
$_ = $response[$i];
if (/OK FETCH complete/) { last; }
# response will be: * 1 FETCH (UID 1 FLAGS (\Seen \Recent))
#### $_ = ~/UID ([^FLAGS]*)/;
$_ =~ /\* ([^FETCH]*)/;
$uid = $1;
$uid =~ s/\s+$//;
if ($response[$i] =~ /FLAGS/) {
# Get the list of flags
$response[$i] =~ /FLAGS \(([^\)]*)/;
$flags = $1;
}
$_ =~ /INTERNALDATE ([^\)]*)/;
$date = $1;
$date =~ s/"//g;
$_ =~ /RFC822.SIZE ([^\)]*)/;
$size = $1;
if ($size > $maxsize) {
if ($debugMode) { &Log("UID $uid (size $size) exceeds max, skip it"); }
} else {
if ($response[$i] =~ /\\Seen/) { $seen = 1; }
if (($uid ne 'OK') && ($uid ne '') && ($response[$i] =! /OK FETCH/)) {
push (@msgs, "$uid|$flags|$date");
}
}
}
return 1;
}
# migrateMsg
#
# Fetch a message from the source host and migrate it to the
# destination host
#
sub migrateMsg {
local ($msgnum, $destMbx, $flags, $date) = @_;
local ($stat=1);
# print STDERR "migrating message $msgnum ($seen)\n";
if ($debugMode) { &Log("Fetching msgnum $msgnum",2); }
&sendCommand (RS, "$rsn FETCH $msgnum (RFC822)");
while (1) {
&readResponse (RS);
if ( $response =~ /^$rsn OK/i ) {
$size = length($message);
if (!$saveMsgs) {
if ($debugMode) { &Log("Inserting msgnum $msgnum, size $size",2); }
&insertMsg(*message, $destMbx, $flags, $date);
}
last;
}
elsif ($response =~ /message number out of range/i) {
&Log ("Error fetching msgnum $msgnum: out of range",2);
$stat=0;
last;
}
elsif ($response =~ /Bogus sequence in FETCH/i) {
&Log ("Error fetching msgnum $msgnum: 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 ) {
&alarmSet ($timeout);
$n = 0;
$n = read (RS, $segment, $len - $cc);
&alarmSet ($timeout);
if ( $n == 0 ) {
&Log ("unable to read $len bytes");
return 0;
}
$message .= $segment;
$cc += $n;
}
}
}
if ($saveMsgs) {
if ($os eq 'Windows_NT') { $saveFn = ">\\tmp\\saveMsg.tmp." . $saveCount; }
else { $saveFn = ">/tmp/saveMsg.tmp." . $saveCount; }
open (SAVE, ">$saveFn");
print SAVE "$message\n";
close SAVE;
$saveCount--;
if ($saveCount == 0) {
&Log("Saved messages written to /tmp, exiting");
close LOG; close RPT;
exit 1;
}
}
return $stat;
}
#
# alarmSet
#
# This subroutine sets an alarm
#
sub alarmSet {
local ($timeout) = @_;
if ($ENV{'WINDIR'} =~ /windows/i) { return; }
if ( $ENV{'OS'} ne 'Windows_NT' ) {
alarm $timeout;
}
}
# migrate
#
# Migrate the user's messages to the destination system
#
sub migrate {
local (*mbxs) = @_;
for $i (0 .. $#mbxs) {
&Log("Migrating mailbox $mbxs[$i]",2);
&checkForHalt;
$mbxs[$i] =~ s/"//g;
if ($debugMode) {
&Log("Migrating mailbox $mbxs[$i]",2);
}
# Get a list of the messages in the source mbx
#
if (!&getMsgList($mbxs[$i],*msgList)) {
# Error getting list of msgs in mailbox,
# go on to next mailbox
next;
}
if ($#msgList == -1) {
# Mailbox is empty, create it anyway
&insertMsg(*message, $mbxs[$i], $flags, $date, 1);
$migratedMsgs{"$mbxs[$i]"} = 0;
next;
}
$migrated=0;
undef @flags;
for $j (0 .. $#msgList) {
($msgnum,$flags,$date) = split(/\|/,$msgList[$j]);
$j++;
if ($j >= 50) {
&checkForHalt;
$j=0;
}
if ($debugMode) {
&Log("Migrating msgnum $msgnum",2);
}
$stat = &migrateMsg($msgnum, $mbxs[$i], $flags, $date);
if (!$stat) { next; }
if ($migratedMsgs{"$mbxs[$i]"} eq '') {
$migratedMsgs{"$mbxs[$i]"} = '1';
} else {
$count = $migratedMsgs{"$mbxs[$i]"};
$count++;
$migratedMsgs{"$mbxs[$i]"} = $count;
}
}
# &setFlags($mbxs[$i], *flags);
}
return;
}
# getUserList
#
# Get a list of the users this migration job is to process. If only
# one job is executed ($total=0) then we will do the whole list.
# Othewise use the $start and $total arguments to extract our share
# of the users from the list.
#
# If start=1 and total=5 then we will select the 1st entry, the 6th
# entry, the 11th entry, and so on.
#
sub getUserList {
local ($fn,*names,$start,$total) = @_;
local ($name,$x,$skip);
if ($debug) { &Log("This is getUserList",2); }
undef @names;
if (!open (IN, "<$fn")) {
&Log("Can't open input file $fn");
print STDOUT "Can't open input file $fn\n";
close LOG; close RPT;
exit 1;
}
if ($total <= 1) {
# Get the whole list
while (\n";
print STDERR " -m
\n";
print STDERR " -d debugMode mode\n";
print STDERR " -I record IMAP4 protocol messages\n";
print STDERR " -t test mode, don't migrate any messages\n";
print STDERR " -a autosubscribe mailboxes\n";
print STDERR " -n
$text
";
print STDOUT "Total messages $grandTotalMsgs
";
print STDOUT "Total bytes $grandTotalBytes
";
print STDOUT "Elapsed time $elapsed minutes
";
print STDOUT "The logfile has been written to $logFile