GMX: Migration von POP3 zu IMAP mit imapsync und offlineimap

Vorbedingung: In einem lokalen Maildir sind Mails eines GMX-Accounts gespeichert, welche in der Vergangenheit über POP3 (mit fetchmail) runtergeladen wurden. Das lokale Maildir ist über einen lokalen Courier-IMAP-Server zugänglich (Maildir++). Lokal wurden die Mails in einer Ordnerstruktur verwaltet. Auf GMX-Seite wurden die Mails parallel analog verwaltet, wobei allerdings keine direkte Synchronisation erfolgte.

Nun sollen beide Seiten einmalig mit imapsync synchronisiert und später in den regulären offlineimap-Abgleich eingebunden werden. Verwendet wurde dabei imapsync in der Version 1.286 und offlineimap in der Version 6.0.3

Im Folgendenden wird beschrieben wie ein solches Vorhaben aussehen kann. Es ist kein komplettes Howto sondern spiegelt lediglich die Erfahrungen wieder, die während der Migration gemacht wurden.

imapsync wird zweimal aufgerufen: Zuerst wird von GMX in Richtung lokales Maildir abgeglichen, dann umgekehrt. Das ist notwendig, um auf beiden Seiten die jeweils nicht vorhandenen Mails zu kopieren. , dann lokal auf GMX, um unterschiedliche Header anzugleichen. Da Flags in beide Richtungen synchronisiert werden sollen und hier kein Mirror betrieben wird, ist der imapsync – +FLAGS-Fix nicht notwendig hier.

Bei der --folder-Option muß der ganze Pfad auf dem jeweiligen Quell-Server angegeben werden. Im Fall vom lokalen Courier-Server wäre das z.B. --folder 'INBOX.gmx.folderxyz'. Auf den angegebenen Foldernamen wendet imapsync nun zunächst das Präfix und den Seperator des Ziel-Servers an, bevor regextrans2 zum Einsatz kommt.

imapysync erkennt bei Courier automatisch den Prefix und den Seperator, da Courier ihn über das IMAP-Protokoll bekanntgibt. Nicht so bei GMX. Deshalb muß für den GMX-Server imapsync darüber manuell in Kenntnis setzen. Ist GMX z.B. der Quellserver, geschieht dies mit --prefix1 '' und --sep1 '/'.

Da auf dem Courier-Server in das Unterverzeichnis gmx synchronisiert werden soll, ist dort --prefix2 INBOX.gmx. anzugeben.

Beim Abholen der Mails über POP3 und IMAP werden unterschiedliche Header erzeugt. So fügt fetchmail z.B. seine eigenen Received- oder Content-Length-Header ein. Interessanterweise unterscheidet sich auch der Spezial-GMX-Header X-GMX-UID je nachdem über welchen Weg – POP3 oder IMAP – die Mail heruntergeladen wurde. Aus diesem Grund ist es notwendig die Identifizierung auf zwei Header zu beschränken, die normalerweise nicht geändert werden sollten und hoffentlich auch eindeutig sind (siehe aber auch den vorherigen Blogpost zu dem Thema): Date und Message-Id. Mit --useheader Message-ID --useheader Date zieht imapsync nur diese beiden Header für den Hashwert heran. Zusätzlich gibt man noch --skipsize --nofoldersizes an, weil sich wie gesagt die Größen der Mails unterscheiden können.

Die Verbindung zu GMX sollte natürlich verschlüsselt erfolgen, deshalb unbedingt die --ssl1 bzw. --ssl2 – Option benutzen. Lokal benötigen wir keine Verschlüsselung, allerdings kann es notwendig sein CRAM-MD5 mit --noauthmd5 abzuschalten, falls Courier nicht explizit dafür konfiguriert wurde.

Die vollständige Kommandozeilen lauten für die Richtung GMX -> lokal:

$ imapsync --host1 imap.gmx.net --user1 user@gmx.de --sep1 '/' --prefix1 '' -ssl1 \
    --host2 localhost --user2 localuser --prefix2 INBOX.gmx. \
    --skipsize --nofoldersizes --noauthmd5 --useheader Message-ID --useheader Date --debug \
    --folder ablage/2009  2>&1 | tee out.txt

und für die Richtung lokal -> GMX:

$ imapsync --host1 localhost --user1 gal \
    --host2 imap.gmx.net --user2 user@gmx.de 
    --sep2 '/' --prefix2 '' --regextrans2 's/gmx\///'  --ssl2 \
    --skipsize --nofoldersizes --noauthmd5 --useheader Message-ID --useheader Date --debug \
    --folder 'INBOX.gmx.ablage.2009' 2>&1 | tee out.txt

Zunächst bietet es sich aber an, zusätzlich --dry zu setzen und die Ausgabe in einer Datei zu speichern. Verdächte Stellen sind auffindbar, indem man darin nach "Skip" oder "Warn" sucht.

Nachdem durch das wechselseitige Synchronisieren GMX->lokal, lokal->GMX aller Ordner zunächst für eine einheitliche Datenbasis gesorgt wurde, kann man nun daran gehen eine laufende Synchronisation mit offlineimap einzurichten.

offlineimap synchronisiert Mails nicht anhand ihrer Inhalte (die Message-ID ist letztlich auch Inhalt der Mail) sondern anhand der UIDs in IMAP. Deshalb müssen nun (nach vorheriger Sicherung!) die lokalen Ordner gelöscht werden, damit offlineimap initial den Stand des GMX-IMAP-Servers lokal kopieren kann.

Konfiguration für offlineimap:

In .offlineimaprc:

[general]
pythonfile = ~/bin/oimaptransfolder.py

...

[Repository gmx-local]
type = Maildir
localfolders = ~/Maildir
sep = .

[Repository gmx-remote]
type = IMAP
remotehost = imap.gmx.net
ssl = yes
remoteuser = user@gmx.de
remotepass = **
maxconnections = 1
holdconnectionopen = no
nametrans = oimaptransfolder_gmx
folderfilter = lambda foldername: foldername in [
    'INBOX', 'Gesendet',
    'Spamverdacht', 'Entw&APw-rfe',
    'ablage/2000', 'ablage/2001', 'ablage/2002', 'ablage/2003', 'ablage/2004',
    'ablage/2005', 'ablage/2006', 'ablage/2007', 'ablage/2008', 'ablage/2009',
    'gesendet_ablage/2000-2002', 'gesendet_ablage/2003',
    'gesendet_ablage/2004', 'gesendet_ablage/2005',
    'gesendet_ablage/2006', 'gesendet_ablage/2007',
    'gesendet_ablage/2008', 'gesendet_ablage/2009' ]

"Entw&APw-rfe" ist die UTF-7-Codierung für "Entwürfe". IMAP verwendet UTF-7, um Umlaute zu kodieren. Ein entsprechend kodierter Ordner wird auch im lokalen Maildir benutzt, was von Vorteil ist, da Courier auch für die Speicherung der Ordnernamen im Dateisystem UTF-7 verwendet.

oimaptransfolder_gmx ist die Funktion, die die Folder-Namen umsetzt vom GMX-IMAP-Server auf das lokale Maildir-Datei-System. Ich habe sie zusammen mit anderen Mappings in ~bin/oimaptransfolder.py definiert. Die Datei sind in etwa so aus:

import re

def oimaptransfolder_webde(foldername):
    if(foldername == "INBOX"):
	retval = ".webde"
    else:
	retval = ".webde." + foldername

    return retval

def oimaptransfolder_gmx(foldername):
    if(foldername == "INBOX"):
	retval = ".gmx"
    else:
	retval = ".gmx." + foldername
    retval = re.sub("/", ".", retval)
    return retval