#!/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 "
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 () { chop; if ($_) { push (@names, $_); } } } else { # Get only the names of the entries this job is to migrate $skip = $total - 2; $x = 0; while () { chop; $name = $_; if ($start == $x) { last; } $x++; } push (@names,$name); $skip--; while () { chop; for $i (0 .. $skip) { $name = ; chop $name; } $name = ; chop $name; if ($name ne '') { push(@names,$name); } } } for $i (0 .. $#names) { if ($debug) { &Log("$names[$i]"); } } return; } sub usage { print STDERR "usage:\n"; print STDERR " -i \n"; print STDERR " -L \n"; print STDERR " -R \n"; print STDERR " -T \n"; print STDERR " -r \n"; print STDERR " -p migrate Public Mailboxes\n"; print STDERR " -u \n"; print STDERR " -pwd \n"; print STDERR " -P \n"; print STDERR " -m \n"; print STDERR " -s \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 \n"; print STDERR " source hostname or IP address\n"; print STDERR " destination hostname or IP address\n"; return; } sub processArgs { while ( $#ARGV >= 0 ) { $arg = shift @ARGV; $cmd .= " $arg "; if ($arg eq '-h') { &usage; exit 1; } elsif ( $arg eq "-i" ) { $infile = shift @ARGV; $cmd .= " $infile"; } elsif ( $arg eq "-L" ) { $logFile = shift @ARGV; $cmd =~ s/ -L //; } elsif ( $arg eq "-S" ) { $sourceHost = shift @ARGV; } elsif ( $arg eq "-D" ) { $destHost = shift @ARGV; } elsif ( $arg eq "-R" ) { $reportFile = shift @ARGV; $cmd =~ s/ -R //; } elsif ( $arg eq "-T" ) { $timeout = shift @ARGV; $cmd .= " $timeout"; } elsif ( $arg eq "-r" ) { $rootMbx = shift @ARGV; $cmd .= " $rootMbx"; } elsif ( $arg eq "-p" ) { $migrPublicMbxs = 1; } elsif ( $arg eq "-u" ) { $privUser = shift @ARGV; $cmd .= " $privUser"; } elsif ( $arg eq "-pwd" ) { $privUserPwd = shift @ARGV; $cmd .= " $privUserPwd"; } elsif ( $arg eq "-P" ) { $publicMbxList = shift @ARGV; $cmd .= " $publicMbxList"; &convertMbxName(*publicMbxList); @temp = split(/,/,$publicMbxList); foreach $mbx (@temp) { if (substr($mbx,0,8) ne '#Public/') { $mbx = '#Public/' . $mbx; } push (@publicMbxList,"$mbx"); } } elsif ( $arg eq "-m" ) { # Max message size $maxsize = shift @ARGV; $cmd .= " $maxsize"; } elsif ($arg eq "-s" ) { # Save a number of messages for inspection. Don't migrate them $saveMsgs = 1; $saveCount = shift @ARGV; $cmd .= " $saveCount"; } elsif ( $arg eq "-M" ) { $mbxList = shift @ARGV; $cmd .= "$mbxList"; @temp = split(/,/,$mbxList); foreach $mbx (@temp) { push (@mbxList,"$mbx"); } } elsif ( $arg eq "-d" ) { $debugMode = 1; } elsif ($arg eq "-I") { $showIMAP = 1; } elsif ($arg eq "-a") { # Autosubscribe folders $autoSubscribe = 1; } elsif ($arg eq '-UW') { # Destination is UW IMAP system $uw = 1; } elsif ($arg eq "-n") { # Number of migration jobs to run $numberOfJobs = shift @ARGV; } elsif ($arg eq "-N") { # Start position in users file $start = shift @ARGV; $jobNumber = $start; $childProcess = 1; } elsif ($arg eq "-j") { # Number of jobs $total = shift @ARGV; } elsif ($arg eq "-w") { # PID of main process $mainPID = shift @ARGV; } elsif (substr($arg,0,1) eq '-') { print STDERR "migrIMAP.cp5, Unrecognized argument $arg\n"; &usage; exit 1; } elsif ( $sourceHost eq "" ) { $sourceHost = $arg; if ($sourceHost =~ /:/) { # use the port number supplied ($sourceHost,$sourcePort) = split(/:/,$sourceHost); } } elsif ( $destHost eq "" ) { $destHost = $arg; if ($destHost =~ /:/) { # use the port number supplied ($destHost,$destPort) = split(/:/,$destHost); } } } return; } sub summary { local ($text) = @_; # Log the statistics ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; $endTime = 3600*$hour + 60*$min + $sec; $elapsed = ($endTime - $startTime)/60; ($a,$b) = split(/\./,$elapsed); $b = substr($b,0,2); $elapsed = $a . '.' . $b; # $grandTotalMsgs += $total; # $grandTotalBytes += $totalBytes; &Log("$text"); &Log("Elapsed time $elapsed minutes",3); &Log("Users migrated $usersMigrated",3); &Log("Total messages $grandTotalMsgs",3); &Log("Total Bytes $grandTotalBytes",3); &report("Users migrated $usersMigrated"); &report("Total messages $grandTotalMsgs"); &report("Total Bytes $grandTotalBytes"); # print STDERR "Total messages $grandTotalMsgs\n"; # print STDERR "Total Bytes $grandTotalBytes\n"; # print STDERR "Elapsed time $elapsed minutes\n\n"; # print STDERR "The summary report has been written to $reportFile\n\n"; if ($html) { print STDOUT "

"; print STDOUT "
$text
"; print STDOUT "Total messages $grandTotalMsgs
"; print STDOUT "Total bytes $grandTotalBytes
"; print STDOUT "Elapsed time $elapsed minutes

"; print STDOUT "The summary report has been written to $reportFile
"; print STDOUT "The logfile has been written to $logFile

"; print STDOUT ""; print STDOUT "\n"; } if ($#errors != -1) { if ($html) { print STDERR "\nErrors were detected. Please review the following:\n"; print STDERR " $logFile\n $reportFile\n\n"; print STDOUT ''; print STDOUT "Errors were detected. Please review $logFile
"; print STDOUT "and $reportFile for details

"; } &report("\nThe following errors were detected:\n"); for $i (0 .. $#errors) { &report("$errors[$i]"); } } return; } sub checkForHalt { $haltFn = "/tmp/stopMigration.$username"; if (-e $haltFn) { &Log("pid $$, Migration has been cancelled by operator command"); &logoutSource; &logoutDestination; close LOG; close RPT; &Log("exiting"); exit 1; } return; } sub validateParams { if (!$sourceHost) { &Log ("no remote host defined"); if ($html) { print STDOUT "Content-type: text/html\n\n\n"; print STDOUT "

You must specifiy a remote host"; } close LOG; close RPT; exit (1); } return; } sub runMigration { local ($start,$total) = @_; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; $startTime = 3600*$hour + 60*$min + $sec; if (($start+1 > $total) && (defined($pid))) { # More jobs than users, exit without doing anything &Log("More jobs than users"); close LOG; close RPT; exit 1; } if ($start == 0) { if ($html) { print STDOUT '

Summary of Migration
'; } } &processArgs; # Open the list of users to be migrated # $usersMigrated=0; if ($infile eq '') { if ($os eq 'Windows_NT') { $infile = "\\tmp\\users.dat.$$"; } else { $infile = "/tmp/users.dat.$$"; } if (-e $infile) { unlink $infile; } } if ($sourceName ne '') { # We have been supplied with the migration params from # the HTML form open (INFILE, ">>$infile"); print INFILE "$sourceName $sourcePwd $destUserName $destUserPwd\n"; close INFILE; } if ($fields{users} ne '') { if (!open (INFILE, ">>$infile")) { print STDOUT "Can't open input file $infile\n"; &Log("Can't open input file $infile"); close LOG; close RPT; exit 1; } @names = split(/%0D%0A/,$fields{users}); for $i (0 .. $#names) { $names[$i] =~ s/^\++//; $names[$i] =~ s/\++$//; $names[$i] =~ s/\+/ /g; print INFILE "$names[$i]\n"; } close INFILE; } &getUserList($infile, *users, $start, $total); if ($debug) { &Log("List of users to be migrated"); foreach $user (@users) { &Log("$user"); } } if ($fields{mbxList} ne '') { undef @mbxList; $mbxList =~ s/^\+\+$//g; @mbxList = split(/%0D%0A/,$mbxList); for $i (0 .. $#mbxList) { $mbxList[$i] =~ s/^\++//; $mbxList[$i] =~ s/\++$//; $mbxList[$i] =~ s/\+/ /g; } } # If Public Mailbox migration is selected then add anonymous user # if ($migrPublicMbxs) { push (@users,"anonymous xxxx $privUser $privUserPwd"); } for $line (@users) { $line =~ s/\s+/ /g; ($user,$pwd,$destUser,$destPwd) = split(/ /,$line); &trim(*user); &trim(*pwd); &trim(*destUser); &trim(*destPwd); if (!$user) { next; } if (/^\#/) { next; } &checkForHalt; if (! &connectToSource($sourceHost)) { &Log("\nCan't connect to host $sourceHost\n"); print STDOUT "Can't connect to host $sourceHost\n"; close LOG; close RPT; exit 1; } if (! &connectToDest($destHost)) { &Log("\nCan't connect to host $destHost\n"); print STDOUT "Can't connect to host $destHost\n"; close LOG; close RPT; exit 1; } # Log in at the source system if ($pwd eq '') { &Log("No password supplied for $user, skipping"); print STDOUT "No password supplied for $user
"; &report("No password supplied for $user, skipping\n"); push(@errors, "No password supplied for $user, skipping"); next; } if (! &loginSource($user,$pwd)) { if ($html) { print STDOUT "Unable to login to $sourceHost as $user"; } &Log("Unable to login to $sourceHost as $user"); &report("Unable to login to $sourceHost as $user\n"); push (@errors,"Unable to login to $sourceHost as $user, skipping"); next; } # See if we should the same name & password as on the source if (($destUser eq '*') || ($destUser eq '')) { $destUser = $user; } # if (($destPwd eq '*') || ($destPwd eq '')) { $destPwd = $pwd; } # Log in to the host system if ($destPwd eq '') { &Log("No password supplied for $destUser, skipping"); if ($html) { print STDOUT "No password supplied for $destUser\n"; } &report("No password supplied for $destUser, skipping\n"); push(@errors, "No password supplied for $destUser, skipping"); next; } if (! &loginDest($destUser,$destPwd)) { if ($html) { print STDOUT "Unable to login to $destHost as $destUser\n"; } &Log("Unable to login to $destHost as $destUser"); &report("Unable to login to $destHost as $destUser\n"); push (@errors,"Unable to login to $destHost as $destUser, skipping"); &logoutSource; next; } # If not supplied on the command line get list of the user's mailboxes # on the source server # &Log("Migrating $user"); if ($user eq 'anonymous') { # Public Mailbox migration if ($#publicMbxList == -1) { # Get a list of the public mbxs from the source undef @mbxs; &getMailboxList($user,*mbxs); } else { # Use the list supplied @mbxs = @publicMbxList; } } else { # Ordinary user migration if ($#mbxList == -1) { # Get a list of the public mbxs from the source undef @mbxs; if ($debugMode) { &Log("Get list of mailboxes",2); } &getMailboxList($user,*mbxs); } else { # Use the list supplied @mbxs = @mbxList; } } undef %destMbxs; undef %migratedMsgs; &migrate(*mbxs); if (($autoSubscribe) && ($user ne 'anonymous')) { # Add user's folders to the subscription list &subscribeFolders(*mbxs); } $total=0; # Summarize the results if ($user eq 'anonymous' ) { &report("Public Mailboxes migrated to $destHost"); # print STDERR "\nTotal Public Mailbox messages migrated \n"; } else { &report("$user migrated to $destHost"); # print STDERR "\nTotal messages migrated for $user\n"; } $total = $x = $y = 0; undef $notifyText; if ($html) { print STDOUT "Migrated $user on $sourceHost to $destUser on $destHost
\n"; } while (($x,$y) = each(%migratedMsgs)) { $total=$total+$y; # print STDERR " Migrated $y msgs from $x\n"; &report(" Migrated $y msgs from $x"); &Log("Migrated $y msgs from $x",2); &Log(" Migrated $y msgs from $x",1); $notifyText .= " Migrated $y msgs from $x\n"; if ($html) { print STDOUT ""; print STDOUT "Migrated $y msgs from $x
\n"; } } ¬ify($user,$notifyText); # print STDERR "\n Total $total msgs, $totalBytes bytes\n\n"; &report("\n Total $total msgs, $totalBytes bytes\n"); if ($html) { print STDOUT "\n Total $total msgs, $totalBytes bytes

\n\n"; } &Log("Total messages migrated $total, $totalBytes bytes"); $grandTotalMsgs += $total; $grandTotalBytes += $totalBytes; open (MON, ">>migrateSummary.log"); print MON "Migrated $user, $total messages, $totalBytes bytes\n"; close MON; $totalBytes=0; &Log("Logging out user $user from source",2); &logoutSource; &Log("Logging out user $destUser from destination",2); &logoutDest; $usersMigrated++; undef @mbxs; # The logout process drops the connection to the IMAP4 # server so we have to reconnect before doing the next user # which we will do at the top of the loop. } close RS; close LS; if ($numberOfJobs <= 0) { &Log("\n\nMigration completed.\n"); &Log("Users migrated $usersMigrated\n"); } else { &Log("Migration completed."); } return; } sub report { local ($line) = @_; # Lock the report files while writing to it. This prevents # multiple migration jobs from interleaving the report lines if ($os eq 'Windows_NT') { flock(RPT, 2); seek (RPT, 0, 2); print RPT "$line\n"; flock (RPT, 8); } else { print RPT "$line\n"; } if ($os eq 'Windows_NT') { flock(RPT2, 2); seek (RPT2, 0, 2); if ($line !~ /Summary of Migration for/) { print RPT2 "$line\n"; } flock (RPT2, 8); } return; } # subscribeFolders # # Subscribe the user's mailboxes # sub subscribeFolders { local (*mbxs) = @_; local ($i,$fn,$key,$folder); # Some clients, such as Outlook, do not automatically show a user # his folders. The user must first "subscribe" to each of them. # To save the user the trouble we will subscribe each folder for him. # That is, of course, if the autoSubscribe option is set. # # The Netscape or Internet Explorer Browers don't need this feature. foreach $mbx (@mbxs) { if ($debug) { &Log("Subscribing to $mbx",2); } &sendCommand (LS, "$lsn SUBSCRIBE \"$mbx\""); &readResponse (LS); if ( $response !~ /OK SUBSCRIBE complete/i ) { &Log ("unexpected SUBSCRIBE response: $response"); push(@errors,"Error subscribing mailbox $mbx"); } } return; } sub notify { local ($user,$text) = @_; local ($notifyFn); local ($message); $to = $user; $from = "\"IMAP Migration Tool\" "; ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; $mth = (Jan,Feb,Mar,Apr,May,Jun,Jul,Aug,Sep,Oct,Nov,Dec)[$mon]; $year = $year - 100; $year = 2000 + $year; $timestamp = "$mday $mth $year $hour:$min:00 -0400"; $msgID = $sec . $min . $hour . $mday . $mon . $year . $wday; $message = "Received: from localhost\n"; $message .= "X-Sender: IMAP-Migration-Tool\n"; $message .= "Message-Id: <$msgID\@$thisSystem>\n"; $message .= "From: $from\n"; $message .= "To: $to\n"; $message .= "Subject: Migration to $destHost\n"; $message .= "Date: $timestamp\n"; $message .= "\n"; $message .= "Your messages have been migrated to $destHost.\n"; $message .= "Here is a summary of the results.\n\n"; $message .= $text; $message .= "\nYour username on $destHost is $destUser and your password is $destPwd\n"; $message .= "\nFor additional information please contact the $destHost manager.\n"; $noteText = 'migrationNotification.txt'; if (-e $noteText) { # Add the text to the note we are sending open (N, "<$noteText"); while () { $message .= $_; } close N; } $lenx = length($message); if ($debug) { &Log("Inserting migration notification in $user's Inbox"); } &sendCommand (RS, "$lsn APPEND INBOX \{$lenx\}"); &readResponse (RS); if ( $response !~ /\+ ready to receive|\+ Ready for more data/i ) { &Log ("unexpected APPEND response: $response"); push(@errors,"Error appending message to INBOX for $user"); } print RS "$message\r\n"; undef @response; ++$rsn; while ( 1 ) { &readResponse (RS); if ( $response =~ /APPEND COMPLETE/i ) { last; } if ( $response =~ /^$rsn + Ready for more data/i ) { last; } elsif ( $response !~ /^\*/ ) { &Log ("unexpected APPEND response: $response"); last; } } &Log("Notify message inserted for user $user",2); return; } sub waitForChildren { local ($fn); # Watch the lock files written by the child processes (NT only) # and return when they have all disappeared for $i (0 .. $numberOfJobs-1) { $fn = '\\tmp\\migrateChild-' . $i . '.' .$$; while (-e $fn) { sleep 5; } } &Log("All children have finished"); return; } sub summaryNT { # Read the migration summary report and display the results # to the operator $rptfn = "\\tmp\\migrSum.tmp.$$"; if (!open(RPT2, "<$rptfn")) { &Log("Can't open report file $rptfn"); return; } print STDOUT '

Summary of Migration
'; while () { chop; $line = $_; print STDOUT "$line
"; } close RPT2; unlink $rptfn; return; } ################################################################# # Main program. # ################################################################# $version = 'V1.1.6'; $cmd = $0; $totalMsgs = $totalBytes = 0; $grandTotalMsgs = $grandTotalBytes = 0; $sourceCapability = $destCapability = 0; $os = $ENV{'OS'}; # If we've been called as a cgi program then pick up the # values passed to us # # while (($x,$y) = each(%ENV)) { print "$x $y\n"; } $username = $ENV{'USERNAME'}; $temp = $ENV{'QUERY_STRING'}; @pairs = split(/&/,$temp); if (@pairs) { foreach $item(@pairs) { ($key,$content) = split(/=/,$item,2); # $content =~ s/%(..)/pack("c",hex($1))/ge; # print "$key $content\n"; $fields{$key} = $content; } $destUserName = $fields{destUserName}; $destUserPwd = $fields{destUserPwd}; $sourceName = $fields{sourceName}; $sourcePwd = $fields{sourcePwd}; $sourceHost = $fields{sourceHost}; $sourcePort = $fields{sourcePort}; $showIMAP = $fields{showIMAP}; $debugMode = $fields{debugMode}; $infile = $fields{inputFile}; $logFile = $fields{logFile}; $reportFile = $fields{reportFile}; $mbxList = $fields{mbxList}; $autoSubscribe = $fields{autoSubscribe}; $numberOfJobs = $fields{numberOfJobs}; $operation = $fields{operation}; } if (%fields) { $html = 1; } $logFile =~ s/%(..)/pack("c",hex($1))/ge; $reportFile =~ s/%(..)/pack("c",hex($1))/ge; if ($numberOfJobs == 1) { $numberOfJobs = 0; } if ($debugMode eq 'on') { $debugMode = 0; } if ($showIMAP eq 'on') { $showIMAP = 1; } $infile =~ s/%(..)/pack("c",hex($1))/ge; &processArgs; &validateParams; # Some default values if ($timeout eq '') { $timeout = 60; } if (!$maxsize) { $maxsize = 99999999999999; } if ($os eq 'Windows_NT') { $thisSystem = `hostname`; } else { $thisSystem = `uname -n`; } chop $thisSystem; if (!$destHost) { $destHost = $thisSystem;} if ($destHost =~ /^$thisSystem$/i) { $localSystem = 1; } # Open the logFile # if ($logFile eq '') { if ($os eq 'Windows_NT') { $logFile = 'migrateIMAP.log'; } else { $logFile = 'migrateIMAP.log'; } } if ($os eq 'Windows_NT') { $logFile =~ s/\//\\/g; } if (!open(LOG, ">> $logFile")) { print STDERR "migrateIMAP.cp5, can't open logfile $logFile\n"; push(@errors, "Can't open logfile $logFile"); } select(LOG); $| = 1; if ($operation =~ /cancel/i) { # Order any migration jobs currently to shut down $username = $ENV{USERNAME}; open (STOP, ">/tmp/stopMigration.$username"); close STOP; print STDOUT "Content-type: text/html\n\n\n"; print STDOUT 'Migration has been cancelled'; close LOG; close RPT; exit 1; } else { $haltFn = "/tmp/stopMigration.$username"; if (-e $haltFn) { unlink $haltFn; } } if (!defined($jobNumber)) { # This is the main process &Log("migrateIMAP $version starting"); } else { # This is a child process &Log("Job number $jobNumber starting"); # print STDERR "jobNumber $jobNumber starting\n"; } $oldh = select(STDOUT); $| = 1; select ($oldh); if ($html) { print STDOUT "Content-type: text/html\n\n\n"; ### autoflush STDOUT 1; } # Open the summary report file # if ($reportFile eq '') { if ($os eq 'Windows_NT') { $reportFile = 'migrateIMAP.rpt'; } else { $reportFile = 'migrateIMAP.rpt'; } } if ($os eq 'Windows_NT') { $reportFile =~ s/\//\\/g; } if (!open(RPT, ">>$reportFile")) { push(@errors,"Can't open report file $reportFile"); } select(RPT); $| = 1; if (($os eq 'Windows_NT') && ($childProcess)) { $sumfn = "\\tmp\\migrSum.tmp.$mainPID"; if (!open (RPT2, ">$sumfn")) { &Log("error opening $sumfn"); } } ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime; if ($year < 99) { $yr = 2000; } else { $yr = 1900; } $date = sprintf ("%.2d-%.2d-%d.%.2d:%.2d:%.2d \n", $mon+1,$mday,$year+$yr,$hour,$min,$sec); &report("\nSummary of Migration for $date\n"); $j=0; if (($childProcess) && ($os eq 'Windows_NT')) { # This is a child process. Run the migration on the portion # of the users list assigned to us. $lockFn = "/tmp/migrateChild-$start.$mainPID"; open (LOCK, ">$lockFn"); close LOCK; &runMigration($start,$total); unlink $lockFn; close INFILE; if (($os eq 'Windows_NT') && (-e "\\tmp\\users.dat.$$")) { unlink "\\tmp\\users.dat.$$"; system("del \\tmp\\users.dat.$$"); unlink $infile; } close LOG; close RPT; exit 1; } if ($numberOfJobs == 0) { $singleJobMode = 1; } if (($numberOfJobs > 1) && (!$childProcess)) { if ($ENV{'WINDIR'} =~ /windows/i) { &Log("Multiple migration jobs is not support on Windows 95"); exit; } # Spin off the number of required jobs, then exit and let those # jobs do the migration work. $cmd =~ s/ -n //; for $i (0 .. $numberOfJobs-1) { $j++; $childLogfile = $logFile; ($part1,$part2) = split(/\./,$childLogfile); $childLogfile = $part1 . "-$j." . $part2; $childReportFile = $reportFile; ($part1,$part2) = split(/\./,$childReportfile); $childReportfile = $part1 . "-$j." . $part2; if ($os eq 'Windows_NT') { $cmd =~ s/\//\\/g; if ($infile) { system("start $cmd -S $sourceHost -D $destHost -N $i -j $numberOfJobs -i $infile -L $childLogfile -w $$ -R $childReportFile"); } else { system("start $cmd -S $sourceHost -D $destHost -N $i -j $numberOfJobs -L $childLogfile -w $$ -R $childReportFile"); } } else { $total = $numberOfJobs; $start = $jobNumber = $i; if ($pid = fork) { push(@jobs,$pid); &Log("Forked job $i, pid $pid, total $total, start $start"); } elsif (defined $pid) { ### &Log("This is job $i, starting migration"); &Log("This is job $i, starting migration, total $total, start $start"); $child = 1; &runMigration($start,$total); close LOG; close RPT; exit 1; } else { &Log("Error forking job $i"); } } } &Log("$numberOfJobs jobs spawned, waiting for their completion"); } if ($os eq 'Windows_NT') { if ($singleJobMode) { # We're running in single-job mode. Run the migration &runMigration(); } else { if ($debug) { &Log("$numberOfJobs jobs spawned, waiting for their completion"); } print STDERR "Wait for the child processes to end\n"; &waitForChildren; if (-e "\\tmp\null.tmp.$$") { unlink "\\tmp\null.tmp.$$"; } &summaryNT(); } } else { # This is Unix. if ($numberOfJobs < 2) { # We're running in single-job mode. Run the migration &runMigration(); &summary(); } else { # Wait for the jobs we forked to finish. for $job (@jobs) { &Log("Waiting for $job to finish"); waitpid($job,0); &Log("Migration completed"); } } } close LOG; close RPT; close INFILE; if ($os eq 'Windows_NT') { if (-e "\\tmp\\users.dat.$$") { unlink "\\tmp\\users.dat.$$"; } } else { if (-e "/tmp/users.dat.$$") { unlink "/tmp/users.dat.$$"; } } exit 1;