Skip to main content

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ßlichjedoch dasnur inAbsender aus SenderMailboxAllowedSenderAddresses festgelegtebeziehungsweise 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.
Es muss genau eine gültige 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 From wird immer auf die geprüfte dynamische SenderMailboxFrom:-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 Enabled wieder auf False setzen und alle echten Geheimnisse aus TestConfiguration.vb entfernen.

Produktive Kompilierung und Installation

  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 Ein gemeinsames Kennwort ausfür deralle Installationerlaubten Absender
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.

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.