Dateien nach "/" hochladen

This commit is contained in:
2026-08-14 00:21:23 +02:00
parent 8bb4237793
commit 9d49321abc
4 changed files with 591 additions and 246 deletions
+102 -20
View File
@@ -7,7 +7,7 @@
Nimmt lokale SMTP-Mails an, speichert sie als .eml in einer Queue und sendet sie
anschließend per Microsoft Graph sendMail mit App-only Zertifikatsauthentifizierung.
V1.7: SMTP AUTH LOGIN/PLAIN mit PBKDF2-SHA256, Benutzer-/Session-Schutz und V1.6 Queue-/Backpressure-Funktionen
V1.8: Failed-Queue-Verwaltung und kooperativer Graceful Shutdown; inklusive SMTP AUTH und V1.6 Queue-/Backpressure-Funktionen
#>
[CmdletBinding()]
@@ -697,6 +697,12 @@ function Process-Queue {
$dirs = Get-QueueDirectories
foreach ($file in Get-ChildItem -LiteralPath $dirs.Pending -Filter "*.eml" -File | Sort-Object CreationTimeUtc) {
if ($script:Config.PSObject.Properties.Name -contains "ShutdownSignalPath" -and
(Test-Path -LiteralPath ([string]$script:Config.ShutdownSignalPath))) {
Write-Log "Queue-Worker: Shutdown angefordert; starte keine weitere Queue-Mail."
break
}
$metaPath = "$($file.FullName).json"
if (Test-Path -LiteralPath $metaPath) {
@@ -1733,6 +1739,10 @@ function New-WorkerConfig {
$copy.Paths.Failed = Resolve-PathFromConfig $script:Config.Paths.Failed
$copy.Paths.Logs = Resolve-PathFromConfig $script:Config.Paths.Logs
if ($script:ShutdownSignalPath) {
$copy | Add-Member -NotePropertyName ShutdownSignalPath -NotePropertyValue $script:ShutdownSignalPath -Force
}
return $copy
}
@@ -1827,6 +1837,22 @@ if (-not (Test-Path -LiteralPath $ConfigPath)) {
$script:Config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | ConvertFrom-Json
$script:InstallRoot = Split-Path -Parent ([IO.Path]::GetFullPath($ConfigPath))
$script:ShutdownSignalPath = Join-Path $script:InstallRoot "shutdown.request"
$script:GracefulShutdownSeconds = 30
if ($script:Config.Smtp.PSObject.Properties.Name -contains "GracefulShutdownSeconds") {
try {
$configuredGrace = [int]$script:Config.Smtp.GracefulShutdownSeconds
if ($configuredGrace -ge 5 -and $configuredGrace -le 300) {
$script:GracefulShutdownSeconds = $configuredGrace
}
} catch {}
}
# Ein altes Signal darf einen frisch gestarteten Relay nicht sofort wieder beenden.
Remove-Item -LiteralPath $script:ShutdownSignalPath -Force -ErrorAction SilentlyContinue
New-Item -ItemType Directory -Path (Resolve-PathFromConfig $script:Config.Paths.Logs) -Force | Out-Null
Initialize-QueueDirectories
@@ -1946,6 +1972,12 @@ $queueBootstrap
try {
while (`$true) {
if (`$script:Config.PSObject.Properties.Name -contains "ShutdownSignalPath" -and
(Test-Path -LiteralPath ([string]`$script:Config.ShutdownSignalPath))) {
Write-Log "Queue-Worker: Shutdown-Signal erkannt."
break
}
try {
Process-Queue
}
@@ -1953,7 +1985,15 @@ try {
Write-Log ("Queue-Worker: {0}" -f `$_.Exception.Message) "ERROR"
}
Start-Sleep -Seconds ([int]`$script:Config.Queue.PollSeconds)
# In kleinen Schritten schlafen, damit Shutdown nicht erst nach PollSeconds reagiert.
`$sleepUntil = (Get-Date).AddSeconds([int]`$script:Config.Queue.PollSeconds)
while ((Get-Date) -lt `$sleepUntil) {
if (`$script:Config.PSObject.Properties.Name -contains "ShutdownSignalPath" -and
(Test-Path -LiteralPath ([string]`$script:Config.ShutdownSignalPath))) {
break
}
Start-Sleep -Milliseconds 250
}
}
}
finally {
@@ -1973,7 +2013,7 @@ $listenIp = [System.Net.IPAddress]::Parse($script:Config.Smtp.ListenAddress)
$listener = [System.Net.Sockets.TcpListener]::new($listenIp, [int]$script:Config.Smtp.Port)
$listener.Start()
Write-Log "SMTPGraphRelay V1.7 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
Write-Log "SMTPGraphRelay V1.8 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
Write-Log "Graph-Absender: $($script:Config.Graph.SenderMailbox)"
Write-Log "Maximale parallele SMTP-Verbindungen: $script:MaxConcurrentClients"
Write-Log "Queue-/Graph-Worker läuft separat vom SMTP-Listener."
@@ -1989,6 +2029,9 @@ $authMode = if ($authSettings.RequireAuth) { "erforderlich" } else { "optional/d
Write-Log ("SMTP-AUTH: {0}; {1} Benutzer; max. {2} Fehlversuche/Verbindung." -f `
$authMode, @($authSettings.Users).Count, $authSettings.AuthMaxFailures)
Write-Log ("Graceful Shutdown: bis zu {0} Sekunden für aktive Sessions/Worker." -f `
$script:GracefulShutdownSeconds)
$logSettings = Get-LogSettings
Write-Log ("Log-Rotation: max. {0} MB pro Datei, Aufbewahrung {1} Tage." -f `
$logSettings.MaxFileSizeMB, $logSettings.RetentionDays)
@@ -1997,14 +2040,24 @@ try {
while ($true) {
Remove-CompletedSmtpWorkers
if (Test-Path -LiteralPath $script:ShutdownSignalPath) {
Write-Log "Graceful Shutdown angefordert. SMTP-Listener wird geschlossen."
try { $listener.Stop() } catch {}
break
}
if ((Get-Date) -ge $script:NextCertificateCheck) {
[void](Test-RelayCertificateExpiry)
$script:NextCertificateCheck = (Get-Date).AddHours($script:CertificateCheckHours)
}
# Sollte der Queue-Worker unerwartet beendet werden, Relay nicht still
# ohne Versand weiterlaufen lassen.
# Sollte der Queue-Worker unerwartet beendet werden, ist das nur dann ein
# Fehler, wenn gerade KEIN geplanter Shutdown läuft.
if ($script:QueueAsyncResult.IsCompleted) {
if (Test-Path -LiteralPath $script:ShutdownSignalPath) {
break
}
try {
[void]$script:QueuePowerShell.EndInvoke($script:QueueAsyncResult)
throw "Queue-Worker wurde unerwartet beendet."
@@ -2024,34 +2077,63 @@ try {
}
}
finally {
Write-Log "SMTPGraphRelay wird beendet..." "INFO"
Write-Log "SMTPGraphRelay fährt kontrolliert herunter..."
try { $listener.Stop() } catch {}
# Neue Clients werden nicht mehr angenommen; bestehende Sessions schließen.
foreach ($worker in @($script:ActiveSmtpWorkers)) {
try { $worker.Client.Close() } catch {}
try { $worker.PowerShell.Stop() } catch {}
try {
if ($worker.AsyncResult) {
[void]$worker.PowerShell.EndInvoke($worker.AsyncResult)
}
} catch {}
try { $worker.PowerShell.Dispose() } catch {}
$deadline = (Get-Date).AddSeconds($script:GracefulShutdownSeconds)
# Laufende SMTP-Sessions bekommen Zeit, ihre aktuelle Nachricht/Session sauber
# abzuschließen. Es werden bereits keine neuen Verbindungen mehr angenommen.
while ($script:ActiveSmtpWorkers.Count -gt 0 -and (Get-Date) -lt $deadline) {
Remove-CompletedSmtpWorkers
if ($script:ActiveSmtpWorkers.Count -gt 0) {
Start-Sleep -Milliseconds 200
}
}
if ($script:ActiveSmtpWorkers.Count -gt 0) {
Write-Log ("Graceful-Shutdown-Timeout: {0} SMTP-Session(s) werden jetzt beendet." -f `
$script:ActiveSmtpWorkers.Count) "WARN"
foreach ($worker in @($script:ActiveSmtpWorkers)) {
try { $worker.Client.Close() } catch {}
try { $worker.PowerShell.Stop() } catch {}
try {
if ($worker.AsyncResult) {
[void]$worker.PowerShell.EndInvoke($worker.AsyncResult)
}
} catch {}
try { $worker.PowerShell.Dispose() } catch {}
}
$script:ActiveSmtpWorkers.Clear()
}
$script:ActiveSmtpWorkers.Clear()
try { $script:SmtpRunspacePool.Close() } catch {}
try { $script:SmtpRunspacePool.Dispose() } catch {}
try { $script:QueuePowerShell.Stop() } catch {}
# Queue-Worker darf den aktuell laufenden Graph-Aufruf beenden. Durch das
# Shutdown-Signal startet Process-Queue anschließend keine weitere Mail.
while (-not $script:QueueAsyncResult.IsCompleted -and (Get-Date) -lt $deadline) {
Start-Sleep -Milliseconds 200
}
if (-not $script:QueueAsyncResult.IsCompleted) {
Write-Log "Graceful-Shutdown-Timeout: Queue-/Graph-Worker wird jetzt beendet." "WARN"
try { $script:QueuePowerShell.Stop() } catch {}
}
try {
if ($script:QueueAsyncResult) {
[void]$script:QueuePowerShell.EndInvoke($script:QueueAsyncResult)
}
} catch {}
try { $script:QueuePowerShell.Dispose() } catch {}
try { $script:QueuePowerShell.Dispose() } catch {}
try { Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null } catch {}
Write-Log "SMTPGraphRelay beendet."
Remove-Item -LiteralPath $script:ShutdownSignalPath -Force -ErrorAction SilentlyContinue
Write-Log "SMTPGraphRelay sauber beendet."
}