PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Das IMAP Protokoll im Detail betrachtet

with 3 comments

IMAP ist heutzutage das Email-Postfach-Zugriffsverfahren der Wahl. Bei IMAP bleiben die Emails auf dem Server (es sei denn man löscht sie), es werden Ordner unterstützt in die man die Emails verschieben kann. Außerdem werden zum Beispiel Flags auf dem Server gespeichert wenn eine Email gelesen wurde. All das ist bei POP3 anders, weshalb IMAP in Version 4rev1 heutzutage bevorzugt wird.

Um das IMAP Protokoll etwas besser zu verstehen werden wir hier die wichtigsten Befehle via Telnet durchgehen und schauen was dabei herauskommt.

Zuerst müssen wir eine TCP-Verbindung aufbauen. Das macht man entweder unverschlüsselt oder SSL-verschlüsselt falls dies der Server unterstützt.

telnet imap.phpgangsta.de 143
Trying 85.214.28.26...
Connected to imap.phpgangsta.de.
Escape character is '^]'.
* OK Dovecot ready.

und hier die SSL-verschlüsselte Verbindung:

openssl s_client -connect imap.phpgangsta.de:993
CONNECTED(00000003)
depth=0 /O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
verify error:num=18:self signed certificate
verify return:1
depth=0 /O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
verify error:num=10:certificate has expired
notAfter=Dec  4 21:15:04 2009 GMT
verify return:1
depth=0 /O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
notAfter=Dec  4 21:15:04 2009 GMT
verify return:1
---
Certificate chain
0 s:/O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
i:/O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
---
Server certificate
-----BEGIN CERTIFICATE-----
MIIDQDCCAqmgAwIBAgIJAPqlHcMGI8LLMA0GCSqGSIb3DQEBBQUAMHQxHDAaBgNV
BAoTE0RvdmVjb3QgbWFpbCBzZXJ2ZXIxEjAQBgNVBAsTCWgxNDQwNjgyLjEiMCAG
.......
+Bzs3mhttbaTq0zq7LO/xvTRBvY=
-----END CERTIFICATE-----
subject=/O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
issuer=/O=Dovecot mail server/OU=h1440682./CN=h1440682.stratoserver.net/emailAddress=root@hwn53662
---
No client certificate CA names sent
---
SSL handshake has read 1400 bytes and written 316 bytes
---
New, TLSv1/SSLv3, Cipher is DHE-RSA-AES256-SHA
Server public key is 1024 bit
Compression: NONE
Expansion: NONE
SSL-Session:
Protocol  : TLSv1
Cipher    : DHE-RSA-AES256-SHA
Session-ID: 660DD8FA520E6126F810ABE9E109051A76EB85B7DE151241429BE428F4D9ACB3
Session-ID-ctx:
Master-Key: D20D1D839B7F194CB99AE2601CF2DEE9D4F717254C01A2102ECCA1798756ABF23E1449B10165FD59F774B1E2D36C17FE
Key-Arg   : None
Start Time: 1267320387
Timeout   : 300 (sec)
Verify return code: 10 (certificate has expired)
---
* OK Dovecot ready.

Wie man sieht findet ein Zertifikatsaustausch und SSL-Verbindungshandshake statt. Danach können wir zuerst die unterstützten Features des Servers abfragen und schauen, welche Authentifizierungsmöglichkeiten wir haben:

TAG1 CAPABILITY
* CAPABILITY IMAP4rev1 SASL-IR SORT THREAD=REFERENCES MULTIAPPEND UNSELECT LITERAL+ IDLE CHILDREN NAMESPACE LOGIN-REFERRALS QUOTA STARTTLS AUTH=PLAIN AUTH=DIGEST-MD5 AUTH=CRAM-MD5 AUTH=NTLM AUTH=RPA
TAG1 OK Capability completed.

Hier sieht man eine der wichtigsten Eigenschaften des Protokolls: Jeder Befehl wird durch einen Tag begonnen, häufig ist darin eine Zahl enthalten die erhöht wird. Dieser Tag ist deshalb nötig da man mehrere Befehle senden kann bevor die Antwort des vorherigen Befehls zurückgesendet worden ist. Jedenfalls theoretisch. Außerdem ist es wichtig zu wissen dass alle Befehle case-insensitiv sind, man kann sie also sowohl groß oder auch klein schreiben.

Hier sehen wir also dass der IMAP-Server sortieren kann (nicht jeder Server unterstützt das), ebenso ist die Quota-Extension aktiviert, TLS wird unterstützt sowie 5 Authentifizierungsmethoden.

Bevor wir nun unsere Emails auflisten oder lesen können müssen wir uns authentifizieren. Der Einfachkeit halber nutzen wir hier die Klartext-Methode:

TAG2 LOGIN username@phpgangsta.de MeinImapPasswort
TAG2 OK Logged in.

Das ging schnell und unkompliziert. Als nächstes möchten wir alle Ordner anzeigen lassen die auf dem Server existieren:

TAG3 LIST "" "*"
* LIST (\HasNoChildren) "." "Sent Messages"
* LIST (\HasNoChildren) "." "Apple Mail To Do"
* LIST (\HasNoChildren) "." "Sent"
* LIST (\HasNoChildren) "." "Drafts"
* LIST (\HasNoChildren) "." "Trash"
* LIST (\HasNoChildren) "." "INBOX"
TAG3 OK List completed.

Mein IMAP-Server nutzt Punkte . zur Trennung der verschachtelten Ordner, es gibt auch IMAP-Server die den Slash / nutzen.

Ohne ein Verzeichnis zu betreten wollen wir erst einmal einige Informationen über die INBOX haben:

TAG4 STATUS INBOX (MESSAGES)
* STATUS "INBOX" (MESSAGES 259)
TAG4 OK Status completed.
TAG5 STATUS INBOX (RECENT)
* STATUS "INBOX" (RECENT 0)
TAG5 OK Status completed.
TAG6 STATUS INBOX (UNSEEN)
* STATUS "INBOX" (UNSEEN 0)
TAG6 OK Status completed.

Wir sehen dass in meinem Posteingang insgeasmt 259 Emails liegen, davon sind keine „noch nie gesehen“ und keine tragen das Flag UNSEEN, ich habe also alle Emails bereits gelesen und als \SEEN geflaggt. Weitere Informationen erhalten wir mit den Befehlen EXAMINE bzw. SELECT. Im Unterschied zu EXAMINE (READ-ONLY) öffnet SELECT die INBOX im READ-WRITE Modus.

TAG7 EXAMINE INBOX
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk $Forwarded)
* OK [PERMANENTFLAGS ()] Read-only mailbox.
* 259 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1259493142] UIDs valid
* OK [UIDNEXT 366] Predicted next UID
TAG7 OK [READ-ONLY] Select completed.
TAG8 SELECT INBOX
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk $Forwarded)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk $Forwarded \*)] Flags permitted.
* 259 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1259493142] UIDs valid
* OK [UIDNEXT 366] Predicted next UID
TAG8 OK [READ-WRITE] Select completed.

Als nächstes möchten wir einen neuen Ordner erstellen. Gesagt, getan:

TAG9 CREATE Test3
TAG9 OK Create completed.
TAG10 LIST "" "*"
* LIST (\HasNoChildren) "." "Test3"
* LIST (\HasNoChildren) "." "Sent Messages"
* LIST (\HasNoChildren) "." "Apple Mail To Do"
* LIST (\HasNoChildren) "." "Sent"
* LIST (\HasNoChildren) "." "Drafts"
* LIST (\HasNoChildren) "." "Trash"
* LIST (\HasNoChildren) "." "INBOX"
TAG10 OK List completed.

Einen Ordner umbenennen könnte nicht einfacher sein:

TAG11 RENAME Test3 Test4
TAG11 OK Rename completed.
TAG10 LIST "" "*"
* LIST (\HasNoChildren) "." "Sent Messages"
* LIST (\HasNoChildren) "." "Apple Mail To Do"
* LIST (\HasNoChildren) "." "Test4"
* LIST (\HasNoChildren) "." "Sent"
* LIST (\HasNoChildren) "." "Drafts"
* LIST (\HasNoChildren) "." "Trash"
* LIST (\HasNoChildren) "." "INBOX"
TAG10 OK List completed.

Und wir löschen ihn wieder:

TAG11 DELETE Test3
TAG11 OK Delete completed.
TAG10 LIST "" "*"
* LIST (\HasNoChildren) "." "Sent Messages"
* LIST (\HasNoChildren) "." "Apple Mail To Do"
* LIST (\HasNoChildren) "." "Sent"
* LIST (\HasNoChildren) "." "Drafts"
* LIST (\HasNoChildren) "." "Trash"
* LIST (\HasNoChildren) "." "INBOX"
TAG10 OK List completed.

Eine Email abrufen ist auch sehr einfach, man betritt den Ordner und benötigt nur die Sequenz-Nummer der Email. Als erstes möchten wir nur die Flags der ersten Email abrufen:

TAG11 SELECT INBOX
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk $Forwarded)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft $NotJunk $Junk $Forwarded \*)] Flags permitted.
* 259 EXISTS
* 0 RECENT
* OK [UIDVALIDITY 1259493142] UIDs valid
* OK [UIDNEXT 366] Predicted next UID
TAG11 OK [READ-WRITE] Select completed.
TAG12 FETCH 1 FLAGS
* 1 FETCH (FLAGS (\Seen))
TAG12 OK Fetch completed.

Diese Email hat also nur ein Flag, und zwar das System-Flag \Seen. Dies ist ein Standard-Flag und ist auf jedem IMAP-Server verfügbar.

Bei vielen Kommandos kann man auch Bereiche angeben, mit dem folgenden Befehl rufen wir zum Beispiel die Header der Emails mit den Sequenz-Nummern 1-2 ab:

TAG13 FETCH 1:2 RFC822.HEADER
* 1 FETCH (RFC822.HEADER {1355}
Return-Path: <www-data@h1440682.stratoserver.net>
Delivered-To: username@phpgangsta.de
Received: from localhost (h1440682.stratoserver.net [127.0.0.1])
        by h1440682.stratoserver.net (Postfix) with ESMTP id 01E4FEC9A84
        for <username@phpgangsta.de>; Sun, 29 Nov 2009 11:12:22 +0000 (UTC)
X-Virus-Scanned: Debian amavisd-new at h1440682.stratoserver.net
X-Spam-Score: -2.167
X-Spam-Level:
X-Spam-Status: No, score=-2.167 tagged_above=-999 required=5 tests=[AWL=0.433,
        BAYES_00=-2.599, NO_RELAYS=-0.001]
Received: from h1440682.stratoserver.net ([127.0.0.1])
        by localhost (h1440682.stratoserver.net [127.0.0.1]) (amavisd-new, port 10024)
        with ESMTP id ao3Q3pSyEY6z for <username@phpgangsta.de>;
        Sun, 29 Nov 2009 11:12:16 +0000 (UTC)
Received: by h1440682.stratoserver.net (Postfix, from userid 33)
        id 1B0F7EC9A8F; Sun, 29 Nov 2009 11:12:16 +0000 (UTC)
To: Michael Kliewe <username@phpgangsta.de>
Subject: E-Mail-Konto erfolgreich eingerichtet
Date: Sun, 29 Nov 2009 12:12:15 +0100
From: Siteadmin <admin@h1440682.stratoserver.net>
Message-ID: <184c494986d163b4ffb02ea29a4c174b@85.214.28.26>
X-Priority: 3
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset="iso-8859-1"

)
* 2 FETCH (RFC822.HEADER {1716}
Return-Path: <username@gmx.de>
Delivered-To: username@phpgangsta.de
Received: from localhost (h1440682.stratoserver.net [127.0.0.1])
        by h1440682.stratoserver.net (Postfix) with ESMTP id 0EBBAEC9A8F
        for <username@phpgangsta.de>; Sun, 29 Nov 2009 11:14:26 +0000 (UTC)
X-Virus-Scanned: Debian amavisd-new at h1440682.stratoserver.net
X-Spam-Score: -0.948
X-Spam-Level:
X-Spam-Status: No, score=-0.948 tagged_above=-999 required=5 tests=[AWL=1.651,
        BAYES_00=-2.599]
Received: from h1440682.stratoserver.net ([127.0.0.1])
        by localhost (h1440682.stratoserver.net [127.0.0.1]) (amavisd-new, port 10024)
        with ESMTP id sMs8yK0tkHzy for <username@phpgangsta.de>;
        Sun, 29 Nov 2009 11:14:20 +0000 (UTC)
Received: by h1440682.stratoserver.net (Postfix, from userid 1000)
        id 5E25CEC9A84; Sun, 29 Nov 2009 11:14:20 +0000 (UTC)
Received: from mail.gmx.net (mail.gmx.net [213.165.64.20])
        by h1440682.stratoserver.net (Postfix) with SMTP id AAF41EC9A84
        for <username@phpgangsta.de>; Sun, 29 Nov 2009 11:14:14 +0000 (UTC)
Received: (qmail invoked by alias); 29 Nov 2009 11:14:14 -0000
Received: from blfd-4d0880a4.pool.mediaWays.net (EHLO [192.168.1.35]) [77.8.128.164]
  by mail.gmx.net (mp031) with SMTP; 29 Nov 2009 12:14:14 +0100
X-Authenticated: #1327535
X-Provags-ID: V01U2FsdGVkX1+wU+dRnXGw7/dirf9XFJQ/K1yjw99mW6UXVHyqRf
        yuU4LQtce8Tz9H
Message-ID: <4B125783.9050508@gmx.de>
Date: Sun, 29 Nov 2009 12:14:11 +0100
From: Michael Kliewe <username@gmx.de>
User-Agent: Thunderbird 2.0.0.23 (Windows/20090812)
MIME-Version: 1.0
To: username@phpgangsta.de
Subject: test neues Konto
Content-Type: text/plain; charset=ISO-8859-15; format=flowed
Content-Transfer-Encoding: 7bit
X-Y-GMX-Trusted: 0
X-FuHaFi: 0.00

)
TAG13 OK Fetch completed.

Es gibt noch weitere Parameter, mit denen man Zusammenfassungen von Emails erhalten kann, nur die Struktur einer MIME-Email (BODYSTRUCTURE) oder den Body einer Email abrufen kann.

Mit dem STORE-Befehl können wir Flags setzen oder entfernen, zum Beispiel setzen wir hier das \SEEN-Flag zurück:

TAG14 STORE 1 -FLAGS \SEEN
* 1 FETCH (FLAGS ())
TAG14 OK Store completed.

Mit +FLAGS setzen wir zusätzliche Flags, und mit FLAGS ersetzen wir die aktuell vorhandenen Flags.

Emails löschen läuft etwas anders als man erwartet. Zuerst setzt man das Flag \DELETED und dann ruft man EXPUNGE auf. Damit werden alle zum Löschen markierten Mails gelöscht und die Sequenz-IDs neu zugeteilt.

TAG15 STORE 1 FLAGS \DELETED
* 1 FETCH (FLAGS (\Deleted))
TAG15 OK Store completed.
TAG16 EXPUNGE
* 1 EXPUNGE
TAG16 OK Expunge completed.

Um Emails zu verschieben nutzen wir den COPY-Befehl, da es keinen Befehl zum Verschieben gibt. Nach dem Kopieren müssen wir das Original noch löschen:

TAG17 COPY 1 Test4
TAG17 OK Copy completed.
TAG18 SELECT Test4
* FLAGS (\Answered \Flagged \Deleted \Seen \Draft)
* OK [PERMANENTFLAGS (\Answered \Flagged \Deleted \Seen \Draft \*)] Flags permitted.
* 1 EXISTS
* 1 RECENT
* OK [UIDVALIDITY 1267323470] UIDs valid
* OK [UIDNEXT 2] Predicted next UID
TAG18 OK [READ-WRITE] Select completed.
TAG19 STORE 1 FLAGS \DELETED
* 1 FETCH (FLAGS (\Deleted \Recent))
TAG19 OK Store completed.
TAG20 EXPUNGE
* 1 EXPUNGE
* 0 RECENT
TAG20 OK Expunge completed.

Häufig befindet sich eine Verbindung um IDLE Modus. Dann wartet der Client auf Änderungen des Servers, von denen er dann sofort unterrichtet wird (zum Beispiel eine neue Nachricht in der INBOX):

TAG21 IDLE
+ idling
<Nichts passiert bis eine neue Mail eintrifft>
* 259 EXISTS
* 1 RECENT
DONE
TAG21 OK Idle completed.

Mit DONE beenden wir den IDLE-Modus. Mit dieser Möglichkeit haben wir also eine Push-Technik des Servers zur Verfügung, wir müssen so nicht periodisch nachfragen ob es etwas neues gibt. Dadurch ist IMAP so schnell!

Wir können auch serverseitig in Emails suchen. Dabei spezifizieren wir Suchbegriffe, wo gesucht werden soll und evtl. noch Filter. Hier 2 Beispiele:

TAG22 SEARCH BODY "Michael"
* SEARCH 6 18 19 74 85 158 160 161 162 180 184 185 186 191 206 208 209 210 219 220 224 233 234 235 236 237 238 239 243 245 246 247 248 249 257
TAG22 OK Search completed.
TAG23 SEARCH SEEN SINCE 1-Nov-2009 NOT FROM "Schmitt" SMALLER 10000 SUBJECT "Blog"
* SEARCH 6 155 188 189 197 198 199 212 213 214 215 216 217 218 221 222 224 225 226 227 228 229 231 232 254
TAG23 OK Search completed.

Man kann also auch komplexe Suchanfragen starten und erhält als Ergebnis immer eine Liste von Sequenz-Nummern (oder UIDs im Fall von einem UID Kommando).

Wenn man eine sortierte Liste braucht kann man den SORT-Befehl nutzen wenn die SORT-Extension auf dem IMAP-Server installiert ist:

TAG24 SORT (SUBJECT) UTF-8 SINCE 12-Dec-2009
* SORT 108 182 144 109 230 234 235 236 237 238 239 243 245 246 247 248 249 178 149 150 151 152 153 154 157 159 164 148 207 254 233 208 209 210 242 175 177 181 225 226 227 156 167 168 183 62 63 64 65 66 67 68 69 70 78 188 187 163 165 166 113 114 115 116 117 142 143 251 170 255 160 161 107 94 106 95 111 147 92 91 112 71 72 76 77 122 123 124 125 126 145 146 119 79 80 241 240 118 155 110 253 252 73 127 129 130 132 133 134 135 136 137 138 139 141 99 100 101 102 103 104 105 257 59 60 61 75 258 96 97 98 176 186 158 162 184 185 190 191 192 194 195 204 205 189 212 213 214 215 216 217 218 221 224 229 219 220 202 203 211 223 193 200 197 198 199 222 231 232 196 172 120 128 131 244 81 82 83 84 87 88 89 90 86 140 171 228 201 259 121 169 74 85 250 256 179 174 173 180 206 93
TAG24 OK Sort completed.

Wie können wir eine neue Email im Drafts-Ordner abspeichern? Dazu gibt es den APPEND-Befehl:

TAG25 APPEND Drafts (\SEEN \DRAFT) {10}
+ OK
Hallo Kai
TAG25 OK Append completed.

Das ist natürlich keine gültige Email, denn es fehlen die Header, aber zur Anschauung soll es reichen.

Die Verbindung trennen wir nicht einfach so, sondern wir loggen uns brav aus:

TAG25 LOGOUT
* BYE Logging out
TAG25 OK Logout completed.
Connection closed by foreign host.

Wie bereits erwähnt arbeitet man normalerweise stets mit Sequenznummern. Da diese aber nicht gleich bleiben und sich zum Beispiel nach dem EXPUNGE oder einer neuen Verbindung ändern können kann man auch mit gleichbleibenden UniqueIDs (UIDs) arbeiten. Jede Mail innerhalb eines Ordners erhält eine UID, und diese ändert sich auch Zeit ihres Lebens nicht. Damit kann man dann schneller herausfinden ob neue Emails angekommen sind, welche noch nicht heruntergeladen wurden usw. Um mit UIDs zu arbeiten stellt man einfach dem entsprechenden Kommando ein UID voran, also beispielsweise:

TAG25 SEARCH BODY "Michael"
* SEARCH 6 18 19 74 85 158 160 161 162 180 184 185 186 191 206 208 209 210 219 220 224 233 234 235 236 237 238 239 243 245 246 247 248 249 257
TAG25 OK Search completed.
TAG26 UID SEARCH BODY "Michael"
* SEARCH 7 20 21 84 96 187 189 190 193 212 217 218 219 224 241 243 244 245 258 259 266 282 283 284 285 286 287 288 292 294 295 296 308 309 347
TAG26 OK Search completed.

Da ich bereits einige Emails gelöscht habe sind die Sequenz-Nummern etwas kleiner. Die Anzahl der gefundenen Emails ist wie erwartet gleich.

Ich habe natürlich noch einige Befehle ausgelassen, die man sich aber gern in den entsprechenden RFCs zu IMAP und den Extensions durchgelesen kann. Stichworte sind LSUB, SUBSCRIBE, UNSUBSCRIBE, GETACL, GETQUOTAROOT, STARTTLS, AUTHENTICATE, CLOSE, UIDVALIDITY und einige mehr.

Written by Michael Kliewe

März 2nd, 2010 at 9:34 am

3 Responses to 'Das IMAP Protokoll im Detail betrachtet'

Subscribe to comments with RSS or TrackBack to 'Das IMAP Protokoll im Detail betrachtet'.

  1. Dankeschön, sehr gut Dokumentiert.

    LN

    23 Aug 11 at 12:28

  2. Hallo Michael,

    TAG25 APPEND Drafts (\SEEN \DRAFT) {10}

    Die 10 in der geschweiften Klammer, ist das die Mailgröße und wie errechne ich diese?

    Danke
    Peter

    Peter

    17 Jan 12 at 10:34

  3. @Peter Die Zahl ist die Anzahl an Octets die danach folgt. Siehe auch im RFC unter 4.3: Strings
    http://tools.ietf.org/html/rfc3501#section-4.3

    In den meisten Fällen ist Octets = Bytes.

    Michael Kliewe

    17 Jan 12 at 12:12

Leave a Reply

You can add images to your comment by clicking here.