Dateien nach "/" hochladen
This commit is contained in:
+102
-20
@@ -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."
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user