Convert Maildir to IMAP for all users with perl
This script needs to be run directly from the main-maildir, so first change into it with "cd". For kmail you would need to execute "cd $HOME/.kde/share/apps/kmail/mail". This script works recursive, but as maildir seems not to handle subdirs, kmail creates subfolders in parallel with prefix and suffix. For example: The maildir "Work/Administration" is located in the path ".Work.directory/Administration". I don't know, if other mail programs work the same way, so you should check that before you begin.
The script will report some details about the message if the import fails. Major problems I encountered while importing (you should check too): - The maximum message size accepted by the server was to small - The folder could not be created due to some special characters used on filesystem entries
To be able to run this script, you also need to enable the imap clear text login with:
su - zimbra -c "zmprov mcf zimbraImapCleartextLoginEnabled TRUE"
You can/should disable this option after migration!
#!/usr/bin/perl # by Andreas Kammlott, thanks to perl and its community use HTTP::Date qw(str2time); use HTTP::Date qw(parse_date); use Date::Format; use Mail::IMAPClient; use Mail::Box::Maildir; use Mail::Message::Convert::MailInternet; # Open imap connection, adjust values to your needs my $imap = Mail::IMAPClient->new( Server => 'zimbra.domain.com', User => 'test', Password => 'test12') || die "Could not connect to IMAP server.\n"; # A maildir contains a subdir "cur" foreach my $dir (`find ./ -type d -name cur|sort`) { my $imdir = ""; # to store the path on the server (imap-directory) my $fsdir = ""; # to store the path of the maildir (filesystem) # Bring the path to an array, each directory needs to be handled my @fspath = split(/\//, $dir); # Replace the leading point and the tailing .directory to acquire # the path, which has to be created in imap, the "cur" don't need # to be stored for (my $i = 1; $i < @fspath-1; $i++) { $fsdir = "$fsdir$fspath[$i]/"; if ($fspath[$i] =~ /\..*\.directory/) { $fspath[$i] =~ s/^\.(.+)\.directory$/$1/; } $imdir = "$imdir$fspath[$i]/"; } # Create the mdir object, this object contains all the messages from # the filesystem's maildir my $mdir = Mail::Box::Maildir->new(folder => $fsdir) or print "Failed to create mdir object from \"$fsdir\"\n"; # Process this directory only if it contains messages if (!$mdir->messages() > 0) { #print "Skipping... folder has been detected as empty.\n"; next; } # Check for the folder on the server (imap) and create if necessary if (!$imap->exists($imdir)) { # Jump to next folder if creation of this one fails if (!$imap->create($imdir)) { print "ERROR: Could not create folder \"$imdir\".\n"; next; } } # Now begin to push all messages foreach my $msg ($mdir->messages()) { my $subject = $msg->subject; my $msgdate = $msg->head->get('Date'); my $msgid = $msg->messageId; # Required date as RFC2060 dd-Mon-yyyy hh:mm:ss +0000 # Provided format is Tue, 20 Jul 2004 09:55:16 +0200 my $time = str2time($msgdate); (undef, undef, undef, undef, undef, undef, $tz) = parse_date($msgdate); my $date = time2str("%d-%h-%Y %H:%M:%S %z",$time,$tz) or print "problem with time: $time and tz: $tz\n"; # Create a converter object my $conv = Mail::Message::Convert::MailInternet->new; # Don't understand why the $conv->export($msg)->as_string() has to be called twice! my $exp = $conv->export($msg)->as_string(); if (!$imap->append_string($imdir, $conv->export($msg)->as_string(), undef, $date)) { print "Failed to append message!\n" ."ID:$msgid\n" ."SUBJECT:$subject\n" ."IMAPDIR:$imdir\n" ."MESSAGEDATE:$msgdate\n" ."IMPORTDATE:$date\n\n"; } } } $imap->close();