Skip to main content

PHXSMTPProxy – SMTP-Proxy für Microsoft Graph

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.com und graph.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

  1. Microsoft Entra Admin Center öffnen.
  2. App-Registrierungen öffnen.
  3. Eine mandanteninterne Anwendung PHXSMTPProxy erstellen.
  4. Anwendungs-ID (Client-ID) notieren.
  5. Verzeichnis-ID (Tenant-ID) notieren.
  6. Unter Zertifikate und Geheimnisse ein Client Secret erstellen.
  7. 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:

  1. API-Berechtigungen öffnen.
  2. Berechtigung hinzufügen wählen.
  3. Microsoft Graph auswählen.
  4. Anwendungsberechtigungen auswählen.
  5. Mail.Send hinzufügen.
  6. 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ßlich das in SenderMailbox festgelegte Postfach.

Der Graph-Aufruf erfolgt immer exakt über dieses Postfach:

POST /users/{SenderMailbox}/sendMail

Der vorhandene MIME-Absender der alten Anwendung wird verpflichtend durch SenderMailbox ersetzt. 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.

4. Anwendung konfigurieren

In App.config mindestens anpassen:

<add key="TenantId" value="TENANT-ID" />
<add key="ClientId" value="CLIENT-ID" />
<add key="SenderMailbox" value="smtp@firma.at" />
<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:

  • 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 From wird immer auf SenderMailbox gesetzt.

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.2 nicht enthalten. Erhöhe das Limit daher nicht ungetestet.

Microsoft: Große Anhänge mit Upload Sessions

7. Kompilieren und installieren

  1. PHXSMTPProxy.sln in Visual Studio 2022 öffnen.
  2. Release und Any CPU auswählen.
  3. Projektmappe erstellen ausführen.
  4. PowerShell als Administrator öffnen.
  5. 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 Kennwort aus der Installation
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 unter SenderMailbox konfigurierten Postfachs gespeichert. Eine zusätzliche Mail.ReadWrite-Berechtigung ist dafür nicht erforderlich.

Microsoft: user sendMail

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.Send verwenden.
  • 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.