PHXSMTPProxy – SMTP-Proxy für Microsoft Graph
Zweck
PHXSMTPProxy ermöglicht einer älteren Anwendung den Mailversand über Microsoft 365, obwohl die Anwendung selbst kein OAuth2 unterstützt.
Alte Anwendung
│ SMTP AUTH LOGIN/PLAIN
▼
PHXSMTPProxy Windows-Dienst
│ persistente lokale Warteschlange
▼
Microsoft Graph mit App-only-OAuth2
│ POST /users/{Postfach}/sendMail
▼
Exchange Online
Es wird keine SMTP-Verbindung zu Exchange Online aufgebaut. Der Ausgang erfolgt ausschließlich über HTTPS und Microsoft Graph.
Systemvoraussetzungen
- Windows Server beziehungsweise RDS-Server
- .NET Framework 4.8
- Visual Studio 2022 zum Kompilieren
- Microsoft-365-Mandant mit Exchange Online
- Berechtigung zum Erstellen einer Entra-App
- Ausgehender HTTPS-Zugriff auf Port 443
- Zugriff auf
login.microsoftonline.comundgraph.microsoft.com
Funktionsweise der Warteschlange
Nachrichten werden zuerst dauerhaft gespeichert:
%ProgramData%\Phoenix_Data_Systems\PHXSMTPProxy\Queue
├── Incoming
├── Pending
└── DeadLetter
Erst danach erhält die alte Anwendung 250 Queued. Ist Graph kurzzeitig nicht
erreichbar oder antwortet mit 429 Too Many Requests, erfolgt ein späterer
Versuch. Ein von Graph gesendeter Retry-After-Wert wird berücksichtigt.
Nach dauerhaften Graph-Fehlern oder zu vielen Versuchen wird die Nachricht nach
DeadLetter verschoben. Dieser Ordner muss überwacht werden.
1. Anwendung in Microsoft Entra anlegen
- Microsoft Entra Admin Center öffnen.
- App-Registrierungen öffnen.
- Eine mandanteninterne Anwendung
PHXSMTPProxyerstellen. - Anwendungs-ID (Client-ID) notieren.
- Verzeichnis-ID (Tenant-ID) notieren.
- Unter Zertifikate und Geheimnisse ein Client Secret erstellen.
- Den Secret-Wert sicher notieren.
Das Client Secret wird später DPAPI-verschlüsselt gespeichert und niemals in
App.config eingetragen.
2. Globale Graph-Berechtigung vergeben
In der Entra-App:
- API-Berechtigungen öffnen.
- Berechtigung hinzufügen wählen.
- Microsoft Graph auswählen.
- Anwendungsberechtigungen auswählen.
Mail.Sendhinzufügen.- Die Administratorzustimmung für den Mandanten erteilen.
Mail.Send als Anwendungsberechtigung erlaubt den Versand ohne angemeldeten
Benutzer und gilt standardmäßig für alle Postfächer. Der Dienst selbst verwendet ausschließlichjedoch
dasnur inAbsender aus SenderMailboxAllowedSenderAddressesfestgelegtebeziehungsweise Postfach.der optionalen
Domänenfreigabe.
Der Graph-Aufruf erfolgt immer exakt über dieses Postfach:
From: test@kunde.de
→ POST /users/{SenderMailbox}/test@kunde.de/sendMail
DerDie vorhandeneaus From: gelesene Adresse wird als Graph-Zielpostfach verwendet und erneut
als MIME-Absender der alten Anwendung wird verpflichtend durch
SenderMailbox ersetzt.eingesetzt. Microsoft Graph speichert die versendete Nachricht
automatisch im Ordner Gesendete Elemente genau dieses Postfachs.
Microsoft: Graph-Berechtigungen – Mail.Send
3. Empfohlene Einschränkung auf ein Postfach
Die globale Entra-Berechtigung ist einfach einzurichten, besitzt aber einen sehr großen Berechtigungsumfang. Für eine zukunftsorientierte Least-Privilege- Konfiguration empfiehlt Microsoft Role Based Access Control for Applications in Exchange Online. Dieses Verfahren ersetzt die älteren Application Access Policies.
Microsoft: RBAC for Applications in Exchange Online
Wichtig: Berechtigungen aus Entra und Exchange-RBAC wirken additiv. Wenn du eine
echte Postfachbeschränkung über Application RBAC verwendest, muss die globale
Mail.Send-Anwendungsberechtigung in Entra anschließend entfernt werden.
Schematisches Beispiel:
Connect-MgGraph -Scopes "Application.Read.All"
Connect-ExchangeOnline
$clientId = "CLIENT-ID-DER-APP"
$mailbox = "smtp@firma.at"
$entraServicePrincipal = Get-MgServicePrincipal `
-Filter "appId eq '$clientId'"
New-ServicePrincipal `
-AppId $clientId `
-ObjectId $entraServicePrincipal.Id `
-DisplayName "PHXSMTPProxy"
Set-Mailbox -Identity $mailbox -CustomAttribute15 "PHXSMTPProxy"
New-ManagementScope `
-Name "PHXSMTPProxy-Mailbox" `
-RecipientRestrictionFilter "CustomAttribute15 -eq 'PHXSMTPProxy'"
New-ManagementRoleAssignment `
-Name "PHXSMTPProxy-Mail.Send" `
-App $entraServicePrincipal.Id `
-Role "Application Mail.Send" `
-CustomResourceScope "PHXSMTPProxy-Mailbox"
Test-ServicePrincipalAuthorization `
-Identity $entraServicePrincipal.Id `
-Resource $mailbox
Prüfe vorher, ob CustomAttribute15 bereits anderweitig verwendet wird.
RBAC-Änderungen können wegen Zwischenspeicherung verzögert wirksam werden.
Bei mehreren erlaubten Absendepostfächern muss das gewählte Attribut bei jedem
dieser Postfächer gesetzt werden, damit alle im Management Scope enthalten sind.
4. Anwendung konfigurieren
In App.config mindestens anpassen:
<add key="TenantId" value="TENANT-ID" />
<add key="ClientId" value="CLIENT-ID" />
<add key="SenderMailbox"AllowedSenderAddresses"
value="smtp@firma.at"test@kunde.de,rechnung@kunde.de,bestellung@kunde.de" />
<add key="AllowedSenderDomains" value="firma.at"" />
<add key="OAuthScope" value="https://graph.microsoft.com/.default" />
Nach dem Kompilieren heißt die aktive Datei PHXSMTPProxy.exe.config.
Betrieb auf demselben RDS-Server
<add key="ListenAddress" value="127.0.0.1" />
<add key="ListenPort" value="2525" />
<add key="AllowedClientAddresses" value="127.0.0.1,::1" />
<add key="RequireStartTls" value="false" />
Zugriff aus dem LAN
<add key="ListenAddress" value="0.0.0.0" />
<add key="AllowedClientAddresses" value="192.168.10.25" />
<add key="RequireStartTls" value="true" />
<add key="TlsCertificateThumbprint" value="ZERTIFIKAT-FINGERABDRUCK" />
Das Zertifikat muss mit privatem Schlüssel unter LocalMachine\My liegen. Das
Dienstkonto NETWORK SERVICE benötigt Leserechte auf den privaten Schlüssel.
Unterstützt wird STARTTLS, aber kein implizites SMTPS auf Port 465.
5. Absender und Empfänger
Microsoft Graph ermittelt Empfänger aus den MIME-Kopfzeilen, klassisches SMTP
dagegen aus RCPT TO. Der Proxy gleicht beide Verfahren an:
- Das Graph-Absendepostfach wird dynamisch aus dem MIME-Header
From:ermittelt.
From:-Adresse vorhanden sein.
Die Adresse muss in AllowedSenderAddresses freigegeben sein.
Für alle erlaubten Absender wird dasselbe lokale SMTP-Kennwort verwendet.
Nur mit RCPT TO angenommene Empfänger werden an Graph übergeben.
Passende ursprüngliche To- und Cc-Empfänger bleiben sichtbar.
Verdeckte Empfänger werden als Bcc eingesetzt.
Andere Empfängerangaben in MIME-Headern werden nicht ungeprüft übernommen.
Der MIME-Absender FromSenderMailboxFrom:-Adresse gesetzt.
Beispiel:
From: rechnung@kunde.de
→ POST /users/rechnung@kunde.de/sendMail
→ Gesendete Elemente von rechnung@kunde.de
Ist AllowedSenderAddresses nicht leer, hat diese Liste Vorrang. Nur wenn sie
leer ist, wird AllowedSenderDomains als weniger strenger Fallback verwendet.
6. Größenlimit
Der Dienst sendet die vollständige MIME-Nachricht base64-kodiert an sendMail:
<add key="MaxMessageBytes" value="2800000" />
Für größere Anhänge sieht Graph einen mehrstufigen Ablauf mit Entwurf und Upload
Session vor. Dieser benötigt eine vollständige Zerlegung der MIME-Nachricht und
ist in Version 1.24 nicht enthalten. Erhöhe das Limit daher nicht ungetestet.
Microsoft: Große Anhänge mit Upload Sessions
7. Kompilieren und installieren
Schneller Test mit Klartextparametern
Für Tests steht TestConfiguration.vb zur Verfügung. Dort zuerst aktivieren:
Public Const Enabled As Boolean = True
Anschließend können alle relevanten Werte direkt im Modul eingetragen werden:
Public Const SmtpUsername As String = "alte-anwendung"
Public Const SmtpPassword As String = "lokales-testkennwort"
Public Const AllowedSenderAddresses As String =
"test@kunde.de,rechnung@kunde.de"
Public Const TenantId As String = "Tenant-ID"
Public Const ClientId As String = "Client-ID"
Public Const ClientSecret As String = "Client-Secret"
Bei aktiviertem Testmodus werden die entsprechenden Werte aus App.config und
die DPAPI-Dateien nicht verwendet. Die Testwarteschlange liegt standardmäßig in:
%LocalAppData%\Phoenix_Data_Systems\PHXSMTPProxy-Test
Das Programm kann in Visual Studio gestartet oder direkt im Konsolenmodus ausgeführt werden:
.\PHXSMTPProxy.exe --console
Während des Starts erscheint eine Warnung und im Log wird der unsichere Testmodus vermerkt.
Wichtig: Das Client Secret und die Kennwörter stehen im Klartext im Quellcode und auch in der kompilierten EXE. Vor der produktiven Kompilierung
Enabledwieder aufFalsesetzen und alle echten Geheimnisse ausTestConfiguration.vbentfernen.
Produktive Kompilierung und Installation
PHXSMTPProxy.slnin Visual Studio 2022 öffnen.ReleaseundAny CPUauswählen.- Projektmappe erstellen ausführen.
- PowerShell als Administrator öffnen.
- Installation starten:
Set-ExecutionPolicy -Scope Process Bypass
.\Install-Service.ps1
Das Skript fragt verdeckt nach dem lokalen SMTP-Kennwort und dem Entra Client Secret. Beide Werte werden mit Windows DPAPI verschlüsselt gespeichert:
%ProgramData%\Phoenix_Data_Systems\PHXSMTPProxy\Secrets\smtp-password.bin
%ProgramData%\Phoenix_Data_Systems\PHXSMTPProxy\Secrets\oauth-client-secret.bin
8. Alte Anwendung einstellen
| Einstellung | Wert |
|---|---|
| SMTP-Server | 127.0.0.1 |
| Port | 2525 |
| Authentifizierung | SMTP AUTH LOGIN oder PLAIN |
| Benutzer | Wert aus SmtpUsername |
| Kennwort | Ein gemeinsames Kennwort |
| TLS | entsprechend RequireStartTls |
9. Testnachricht
.\Test-SmtpProxy.ps1 `
-From "smtp@firma.at" `
-To "empfaenger@firma.at"
Bei lokal aktiviertem STARTTLS zusätzlich -EnableSsl verwenden.
Protokollierung und Zustellstatus
%ProgramData%\Phoenix_Data_Systems\PHXSMTPProxy\Logs
Graph bestätigt sendMail mit 202 Accepted. Das bedeutet Annahme, aber noch
keine endgültige Zustellbestätigung. Exchange-Transportregeln, Spamprüfung und
Empfängergrenzen gelten weiterhin.
Nach der Annahme wird eine Kopie im Ordner Gesendete Elemente des unterdynamisch
aus SenderMailboxFrom:konfiguriertenermittelten Postfachs gespeichert. Eine zusätzliche
Mail.ReadWrite-Berechtigung ist dafür nicht erforderlich.
In einem seltenen Grenzfall kann eine Nachricht doppelt versendet werden, wenn Microsoft sie annimmt, aber die HTTP-Antwort den Proxy nicht mehr erreicht.
Sicherheit
- SMTP-Port niemals öffentlich ins Internet stellen.
AllowedClientAddresses=*nicht produktiv verwenden.- Im LAN STARTTLS und Windows-Firewall-Regeln verwenden.
- Client Secret rechtzeitig erneuern.
- Wenn möglich Exchange Application RBAC statt globalem
Mail.Sendverwenden. - Dead-Letter-Ordner und Logs überwachen.
Deinstallation
.\Uninstall-Service.ps1
Auch verschlüsselte Geheimnisse entfernen:
.\Uninstall-Service.ps1 -RemoveSecrets
Programmdateien, Warteschlange und Logs werden nicht automatisch gelöscht.