Dateien nach "/" hochladen

This commit is contained in:
2026-08-13 23:15:08 +02:00
parent 07f7cea199
commit c4ffc9b0cf
2 changed files with 243 additions and 75 deletions
+140 -5
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.4: Zertifikatsüberwachung, parallele SMTP-Clients, separater Queue-Worker, robuste Queue und Graph-Retry
V1.5: Log-Rotation, Zertifikatsüberwachung, parallele SMTP-Clients, Queue-Worker und robuste Graph-Retry-Logik
#>
[CmdletBinding()]
@@ -18,6 +18,114 @@ param(
$ErrorActionPreference = "Stop"
[Console]::OutputEncoding = [System.Text.Encoding]::UTF8
function Get-LogSettings {
$maxSizeMB = 10
$retentionDays = 30
$cleanupHours = 12
try {
if ($script:Config.Logging) {
if ($script:Config.Logging.PSObject.Properties.Name -contains "MaxFileSizeMB") {
$v = [int]$script:Config.Logging.MaxFileSizeMB
if ($v -ge 1 -and $v -le 1024) { $maxSizeMB = $v }
}
if ($script:Config.Logging.PSObject.Properties.Name -contains "RetentionDays") {
$v = [int]$script:Config.Logging.RetentionDays
if ($v -ge 1 -and $v -le 3650) { $retentionDays = $v }
}
if ($script:Config.Logging.PSObject.Properties.Name -contains "CleanupHours") {
$v = [int]$script:Config.Logging.CleanupHours
if ($v -ge 1 -and $v -le 168) { $cleanupHours = $v }
}
}
} catch {}
return [pscustomobject]@{
MaxFileSizeMB = $maxSizeMB
RetentionDays = $retentionDays
CleanupHours = $cleanupHours
}
}
function Invoke-LogMaintenance {
param(
[switch]$Force
)
if (-not $script:Config -or -not $script:Config.Paths.Logs) {
return
}
$settings = Get-LogSettings
$logDir = $script:Config.Paths.Logs
if (-not [IO.Path]::IsPathRooted($logDir)) {
if ($PSScriptRoot) {
$logDir = Join-Path $PSScriptRoot $logDir
} else {
return
}
}
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
if (-not $script:NextLogCleanup) {
$script:NextLogCleanup = [DateTime]::MinValue
}
if (-not $Force -and (Get-Date) -lt $script:NextLogCleanup) {
return
}
$cutoff = (Get-Date).AddDays(-1 * $settings.RetentionDays)
foreach ($file in Get-ChildItem -LiteralPath $logDir -File -Filter "SMTPGraphRelay-*.log" -ErrorAction SilentlyContinue) {
if ($file.LastWriteTime -lt $cutoff) {
try {
Remove-Item -LiteralPath $file.FullName -Force -ErrorAction Stop
} catch {}
}
}
$script:NextLogCleanup = (Get-Date).AddHours($settings.CleanupHours)
}
function Rotate-LogIfNeeded {
param(
[Parameter(Mandatory)][string]$LogFile
)
if (-not (Test-Path -LiteralPath $LogFile)) {
return
}
$settings = Get-LogSettings
try {
$file = Get-Item -LiteralPath $LogFile -ErrorAction Stop
$maxBytes = [int64]$settings.MaxFileSizeMB * 1024 * 1024
if ($file.Length -lt $maxBytes) {
return
}
$stamp = Get-Date -Format "yyyyMMdd-HHmmss"
$dir = Split-Path $LogFile -Parent
$archive = Join-Path $dir "SMTPGraphRelay-$stamp.log"
$counter = 1
while (Test-Path -LiteralPath $archive) {
$archive = Join-Path $dir ("SMTPGraphRelay-{0}-{1}.log" -f $stamp, $counter)
$counter++
}
Move-Item -LiteralPath $LogFile -Destination $archive -Force
}
catch {}
}
function Write-Log {
param(
[Parameter(Mandatory)][string]$Message,
@@ -31,28 +139,42 @@ function Write-Log {
try {
if ($script:Config -and $script:Config.Paths.Logs) {
$logDir = $script:Config.Paths.Logs
if (-not [IO.Path]::IsPathRooted($logDir)) { $logDir = Join-Path $PSScriptRoot $logDir }
if (-not [IO.Path]::IsPathRooted($logDir)) {
if ($PSScriptRoot) {
$logDir = Join-Path $PSScriptRoot $logDir
} else {
return
}
}
New-Item -ItemType Directory -Path $logDir -Force | Out-Null
$logFile = Join-Path $logDir "SMTPGraphRelay.log"
# Mehrere SMTP-Runspaces und der Queue-Worker können gleichzeitig loggen.
# Ein benannter Mutex verhindert kollidierende Schreibzugriffe.
# Ein benannter Mutex schützt Rotation und Schreibzugriff gemeinsam.
$mutex = New-Object System.Threading.Mutex($false, "Local\SMTPGraphRelay-Log")
$lockTaken = $false
try {
$lockTaken = $mutex.WaitOne(5000)
if ($lockTaken) {
Rotate-LogIfNeeded -LogFile $logFile
Add-Content -LiteralPath $logFile -Value $line -Encoding UTF8
Invoke-LogMaintenance
}
}
finally {
if ($lockTaken) {
try { $mutex.ReleaseMutex() } catch {}
}
$mutex.Dispose()
}
}
} catch {}
}
catch {}
}
function Resolve-PathFromConfig {
@@ -999,6 +1121,9 @@ $script:Config = Get-Content -LiteralPath $ConfigPath -Raw -Encoding UTF8 | Conv
New-Item -ItemType Directory -Path (Resolve-PathFromConfig $script:Config.Paths.Logs) -Force | Out-Null
Initialize-QueueDirectories
# Alte Logarchive beim Start direkt bereinigen.
Invoke-LogMaintenance -Force
# Zertifikat beim Start immer prüfen und Status protokollieren.
[void](Test-RelayCertificateExpiry -ForceLog)
@@ -1039,6 +1164,9 @@ $script:WorkerConfig = New-WorkerConfig
# Nur die Funktionen, die ein SMTP-Client wirklich benötigt, werden in die
# Worker-Runspaces kopiert. Graph-/Queue-Versand bleibt in einem separaten Worker.
$smtpFunctionNames = @(
"Get-LogSettings",
"Invoke-LogMaintenance",
"Rotate-LogIfNeeded",
"Write-Log",
"Resolve-PathFromConfig",
"Test-IPv4InCidr",
@@ -1072,6 +1200,9 @@ $script:ActiveSmtpWorkers = New-Object System.Collections.ArrayList
# Damit ein langsamer Graph-Aufruf nicht mehr die Annahme neuer SMTP-Verbindungen
# blockiert, läuft Process-Queue dauerhaft in einem eigenen Runspace.
$queueFunctionNames = @(
"Get-LogSettings",
"Invoke-LogMaintenance",
"Rotate-LogIfNeeded",
"Write-Log",
"Resolve-PathFromConfig",
"Get-GraphConnection",
@@ -1120,11 +1251,15 @@ $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.3 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
Write-Log "SMTPGraphRelay V1.5 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."
$logSettings = Get-LogSettings
Write-Log ("Log-Rotation: max. {0} MB pro Datei, Aufbewahrung {1} Tage." -f `
$logSettings.MaxFileSizeMB, $logSettings.RetentionDays)
try {
while ($true) {
Remove-CompletedSmtpWorkers