Ein POP3-Mailchecker überwacht ein eMail-Postfach auf das Eintreffen neuer Nachrichten. Dabei wird der Mail-Server in regelmäßigen Abständen nach der Anzahl der eMails im Postfach gefragt und anhand der Differenz zur vorherigen Abfrage festgestellt, ob eine oder mehrere neue Nachrichten eingetroffen sind. In diesem Artikel wird gezeigt, wie man mit Bordmitteln des .NET Frameworks unter C# einen solchen Mailchecker entwickelt. Dabei beschränken wir uns an dieser Stelle auf die Kommunikation mit dem Server und stellen ein vollständiges Programm zur Verfügung (siehe beiliegende CD), das jeder nach Belieben nutzen und ändern kann. Doch zuvor ein wenig Theorie.
Das Post Office Protocol
Mit dem Post Office Protocol in der Version 3, kurz POP3, können eMails von einem eMail-Postfach abgeholt und gelöscht werden. POP3 ist in dem RFC 1939 beschrieben, das im Internet [1] frei verfügbar ist. Eine Erweiterung des Protokolls ist im RFC 2449 spezifiziert und führt neue Kommandos und Fehlermeldungen ein. Eine gute Möglichkeit zum Kennenlernen des Protokolls ist das gute, alte Telnet, das auch unter Windows XP noch zum Standardumfang gehört. Am schnellsten erreicht man es unter Start | Ausführen ... und gibt dort direkt telnet ein. Vielleicht nehmen Sie Ihre persönlichen Anmeldedaten gleich zur Hand und versuchen den Verbindungsaufbau am lebenden Beispiel. Auf der Telnet-Konsole wird mit open die Verbindung zum Server hergestellt:WillkommenDas Escapezeichen ist 'CTRL++'Microsoft Telnet> open pop.meinprovider.de 110
+OK POP3 server ready
+OK POP3 Server ist bereitUSER PeterP+OK Bitte Kennwort eingebenPASS absolutgeheim+OK Postfach bereit
+OK POP3 Server ist bereitUSER PeterP+OK Bitte Kennwort eingebenPASS falscheskennwort-ERR Benutzer oder Kennwort falsch
STAT+OK 2 230
+OK POP3 Server ist bereitUSER PeterP+OK Bitte Kennwort eingebenPASS absolutgeheim+OK Postfach bereitSTAT+OK 2 230QUIT+OK
|
Die Programmierung
Was bislang nur über aufwändige API-Aufrufe möglich war, ist im .NET-Umfeld wirklich ein Kinderspiel. Zentrale Bestandteile unseres Programms sind die TcpClient- und NetworkStream-Klassen, die beide im Namensraum System.Net.Sockets liegen, sowie ein StreamReader und ein StreamWriter aus System.IO. Mehr wird nicht benötigt! Den noch nicht ausprogrammierten Klassenrumpf finden Sie im Listing 1. Listing 1using System;using System.IO;using System.Net.Sockets;namespace Mailchecker{public class POP3{private const int POP3_PORT = 110;private string _servername = string.Empty;private string _username = string.Empty;private string _password = string.Empty;private string _lastError = string.Empty;// Initialisierung der Klasse mit Server- und Anmeldedatenpublic POP3(string Servername, string Username, string Password){_servername = Servername;_username = Username;_password = Password;}// Liefert die Anzahl der Nachrichten im Postfach zurück.// Tritt ein Fehler ein, wird -1 zurückgegeben.public int Count{get { return GetMessageCount(); }}// Liefert die letzte Fehlermeldung zurück.public string LastError{get{string msg = _lastError;_lastError = string.Empty;return msg;}}// Interne Hilfsroutine zur Ermittlung der Nachrichtenanzahlprivate int GetMessageCount(){// TODO: Implementierung folgt!}// Ermittelt aus der Serverantwort die Anzahl der Nachrichtenprivate int GetParsedMessageCount(string serverResponse){// TODO: Implementierung folgt!}// Prüft, ob die Serverantwort positiv ist.private bool IsOK(string serverResponse){// TODO: Implementierung folgt!}}}
TcpClient tcpClient = new TcpClient(_servername, POP3_PORT);
NetworkStream networkStream = tcpClient.GetStream();StreamReader streamReader = new StreamReader(networkStream);StreamWriter streamWriter = new StreamWriter(networkStream);streamWriter.AutoFlush = true;
string response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("POP3-Server nicht bereit. Servermeldung: " + response);
// Benutzername mit dem USER-Befehl übergeben:streamWriter.WriteLine("USER {0}", _username);response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("Anmeldung fehlgeschlagen. Servermeldung: " + response);// Kennwort mit dem PASS-Befehl übergeben:streamWriter.WriteLine("PASS {0}", _password);response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("Anmeldung fehlgeschlagen. Servermeldung: " + response);
streamWriter.WriteLine("STAT");response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("STAT-Befehl fehlgeschlagen. Servermeldung: " + response);// Server-Antwort auswerten:int count = GetParsedMessageCount(response);
streamWriter.WriteLine("QUIT");streamReader.Close();streamWriter.Close();networkStream.Close();tcpClient.Close();
private int GetParsedMessageCount(string serverResponse){// Format: +OK Anzahl Groessetry{string temp = string.Empty;int start = -1;int end = -1;// Start- und Endpunkt findenstart = serverResponse.IndexOf(" ") + 1;end = serverResponse.IndexOf(" ", start);// Anzahl ausschneidenif (end > start)temp = serverResponse.Substring(start, end - start);return Convert.ToInt32(temp);}catch{throw new Exception("Auswertung fehlgeschlagen. Response: " + serverResponse);}}private bool private bool IsOK(string serverResponse){return serverResponse.StartsWith("+OK");}
private int GetMessageCount(){int count = -1;string response = string.Empty;try{// Verbindung zum Server aufbauen:TcpClient tcpClient = new TcpClient(_servername, POP3_PORT);// Netzwerk-Stream geben lassenNetworkStream networkStream = tcpClient.GetStream();// Auf dem Netzwerk-Stream einen Leser und einen Schreiber erstellen:StreamReader streamReader = new StreamReader(networkStream);StreamWriter streamWriter = new StreamWriter(networkStream);// Mit AutoFlush=true wird jeder Schreibefehl direkt an den Server gesendet.streamWriter.AutoFlush = true;// Wilkommensgruss abholen:response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("POP3-Server nicht bereit. Servermeldung: " + response);// Authentifizierung einleiten:streamWriter.WriteLine("USER {0}", _username);response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("Anmeldung fehlgeschlagen. Servermeldung: " + response);streamWriter.WriteLine("PASS {0}", _password);response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("Anmeldung fehlgeschlagen. Servermeldung: " + response);// Anzahl der Mailboxnachrichten ermitteln:streamWriter.WriteLine("STAT");response = streamReader.ReadLine();if (!IsOK(response))throw new Exception("STAT-Befehl fehlgeschlagen. Servermeldung: " + response);// Anzahl der Nachrichten ermitteln:count = GetParsedMessageCount(response);// Kommunikation beenden:streamWriter.WriteLine("QUIT");response = streamReader.ReadLine();// Verbindung schliessen:streamReader.Close();streamWriter.Close();networkStream.Close();tcpClient.Close();}catch (Exception ex){_lastError = ex.Message;}return count;}
POP3 pop3 = new POP3("pop.meinprovider.de", "PeterP", "absolutgeheim");int count = pop3.Count;
Sebastian Weber ist Software Engineer bei der Platinion GmbH und ist dort auf die Konzeption und Implementierung von .NET-Applikationen spezialisiert. Sie erreichen ihn per eMail unter: weber.sebastian@platinion.de. Links und Literatur
[1] RFC 1939: www.ietf.org/rfc/rfc1939.txt


