Dateien nach "/" hochladen
This commit is contained in:
+280
-20
@@ -7,7 +7,7 @@
|
|||||||
Nimmt lokale SMTP-Mails an, speichert sie als .eml in einer Queue und sendet sie
|
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.
|
anschließend per Microsoft Graph sendMail mit App-only Zertifikatsauthentifizierung.
|
||||||
|
|
||||||
V1.5: Log-Rotation, Zertifikatsüberwachung, parallele SMTP-Clients, Queue-Worker und robuste Graph-Retry-Logik
|
V1.6: Queue-ID/Received/Message-ID sowie SMTP-/Queue-Backpressure und Session-Limits
|
||||||
#>
|
#>
|
||||||
|
|
||||||
[CmdletBinding()]
|
[CmdletBinding()]
|
||||||
@@ -550,6 +550,7 @@ function Send-QueuedMail {
|
|||||||
$dirs = Get-QueueDirectories
|
$dirs = Get-QueueDirectories
|
||||||
$metaPath = "$FilePath.json"
|
$metaPath = "$FilePath.json"
|
||||||
$meta = $null
|
$meta = $null
|
||||||
|
$queueId = [IO.Path]::GetFileNameWithoutExtension($FilePath)
|
||||||
|
|
||||||
if (Test-Path -LiteralPath $metaPath) {
|
if (Test-Path -LiteralPath $metaPath) {
|
||||||
try {
|
try {
|
||||||
@@ -561,12 +562,17 @@ function Send-QueuedMail {
|
|||||||
|
|
||||||
if (-not $meta) {
|
if (-not $meta) {
|
||||||
$meta = [pscustomobject]@{
|
$meta = [pscustomobject]@{
|
||||||
|
QueueId = $queueId
|
||||||
ReceivedUtc = [DateTime]::UtcNow.ToString("o")
|
ReceivedUtc = [DateTime]::UtcNow.ToString("o")
|
||||||
RetryCount = 0
|
RetryCount = 0
|
||||||
NextAttemptUtc = [DateTime]::UtcNow.ToString("o")
|
NextAttemptUtc = [DateTime]::UtcNow.ToString("o")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($meta -and $meta.PSObject.Properties.Name -contains "QueueId" -and -not [string]::IsNullOrWhiteSpace([string]$meta.QueueId)) {
|
||||||
|
$queueId = [string]$meta.QueueId
|
||||||
|
}
|
||||||
|
|
||||||
$bytes = [IO.File]::ReadAllBytes($FilePath)
|
$bytes = [IO.File]::ReadAllBytes($FilePath)
|
||||||
|
|
||||||
if ($script:Config.Graph.ForceSender) {
|
if ($script:Config.Graph.ForceSender) {
|
||||||
@@ -589,7 +595,7 @@ function Send-QueuedMail {
|
|||||||
|
|
||||||
# Graph sendMail liefert bei Annahme 202 Accepted.
|
# Graph sendMail liefert bei Annahme 202 Accepted.
|
||||||
# Invoke-MgGraphRequest wirft bei Fehlern eine Exception; kein Fehler bedeutet hier angenommen.
|
# Invoke-MgGraphRequest wirft bei Fehlern eine Exception; kein Fehler bedeutet hier angenommen.
|
||||||
Write-Log "Mail von Graph angenommen: $(Split-Path $FilePath -Leaf)"
|
Write-Log ("[{0}] Mail von Graph angenommen (202 Accepted)." -f $queueId)
|
||||||
|
|
||||||
Remove-Item -LiteralPath $FilePath -Force
|
Remove-Item -LiteralPath $FilePath -Force
|
||||||
if (Test-Path -LiteralPath $metaPath) {
|
if (Test-Path -LiteralPath $metaPath) {
|
||||||
@@ -616,9 +622,10 @@ function Send-QueuedMail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $decision.Retry) {
|
if (-not $decision.Retry) {
|
||||||
Write-Log ("Graph-Versand permanent fehlgeschlagen ({0}, {1}): {2}" -f $statusText, $decision.Reason, $failure.Message) "ERROR"
|
Write-Log ("[{0}] Graph-Versand permanent fehlgeschlagen ({1}, {2}): {3}" -f $queueId, $statusText, $decision.Reason, $failure.Message) "ERROR"
|
||||||
|
|
||||||
$newMeta = [ordered]@{
|
$newMeta = [ordered]@{
|
||||||
|
QueueId = $queueId
|
||||||
ReceivedUtc = $meta.ReceivedUtc
|
ReceivedUtc = $meta.ReceivedUtc
|
||||||
RemoteAddress = $meta.RemoteAddress
|
RemoteAddress = $meta.RemoteAddress
|
||||||
EnvelopeFrom = $meta.EnvelopeFrom
|
EnvelopeFrom = $meta.EnvelopeFrom
|
||||||
@@ -636,9 +643,10 @@ function Send-QueuedMail {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ($retryCount -ge $maxRetries) {
|
if ($retryCount -ge $maxRetries) {
|
||||||
Write-Log ("Graph-Versand nach {0} Versuchen endgültig fehlgeschlagen ({1}): {2}" -f $retryCount, $statusText, $failure.Message) "ERROR"
|
Write-Log ("[{0}] Graph-Versand nach {1} Versuchen endgültig fehlgeschlagen ({2}): {3}" -f $queueId, $retryCount, $statusText, $failure.Message) "ERROR"
|
||||||
|
|
||||||
$newMeta = [ordered]@{
|
$newMeta = [ordered]@{
|
||||||
|
QueueId = $queueId
|
||||||
ReceivedUtc = $meta.ReceivedUtc
|
ReceivedUtc = $meta.ReceivedUtc
|
||||||
RemoteAddress = $meta.RemoteAddress
|
RemoteAddress = $meta.RemoteAddress
|
||||||
EnvelopeFrom = $meta.EnvelopeFrom
|
EnvelopeFrom = $meta.EnvelopeFrom
|
||||||
@@ -657,6 +665,7 @@ function Send-QueuedMail {
|
|||||||
|
|
||||||
$next = (Get-Date).AddSeconds([int]$decision.DelaySeconds)
|
$next = (Get-Date).AddSeconds([int]$decision.DelaySeconds)
|
||||||
$newMeta = [ordered]@{
|
$newMeta = [ordered]@{
|
||||||
|
QueueId = $queueId
|
||||||
ReceivedUtc = $meta.ReceivedUtc
|
ReceivedUtc = $meta.ReceivedUtc
|
||||||
RemoteAddress = $meta.RemoteAddress
|
RemoteAddress = $meta.RemoteAddress
|
||||||
EnvelopeFrom = $meta.EnvelopeFrom
|
EnvelopeFrom = $meta.EnvelopeFrom
|
||||||
@@ -676,8 +685,8 @@ function Send-QueuedMail {
|
|||||||
"$($decision.DelaySeconds) Sek."
|
"$($decision.DelaySeconds) Sek."
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log ("Graph-Versand fehlgeschlagen (Versuch {0}/{1}, {2}, {3}). Neuer Versuch in {4}: {5}" -f `
|
Write-Log ("[{0}] Graph-Versand fehlgeschlagen (Versuch {1}/{2}, {3}, {4}). Neuer Versuch in {5}: {6}" -f `
|
||||||
$retryCount, $maxRetries, $statusText, $decision.Reason, $delayText, $failure.Message) "WARN"
|
$queueId, $retryCount, $maxRetries, $statusText, $decision.Reason, $delayText, $failure.Message) "WARN"
|
||||||
|
|
||||||
[void](Move-QueueItem -SourceEml $FilePath -DestinationDirectory $dirs.Pending)
|
[void](Move-QueueItem -SourceEml $FilePath -DestinationDirectory $dirs.Pending)
|
||||||
return $false
|
return $false
|
||||||
@@ -729,6 +738,185 @@ function Process-Queue {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function New-QueueId {
|
||||||
|
# Kurz genug für Logs/SMTP-Antworten, aber mit sehr hoher Kollisionsreserve.
|
||||||
|
return ([guid]::NewGuid().ToString("N").Substring(0,12).ToUpperInvariant())
|
||||||
|
}
|
||||||
|
|
||||||
|
function Add-RelayMessageHeaders {
|
||||||
|
param(
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[AllowEmptyCollection()]
|
||||||
|
[AllowEmptyString()]
|
||||||
|
[System.Collections.Generic.List[string]]$Lines,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$QueueId,
|
||||||
|
|
||||||
|
[Parameter(Mandatory)]
|
||||||
|
[string]$RemoteAddress
|
||||||
|
)
|
||||||
|
|
||||||
|
$result = New-Object System.Collections.Generic.List[string]
|
||||||
|
|
||||||
|
# Header/Body-Grenze suchen. Falls eine kaputte Minimalnachricht keine Leerzeile
|
||||||
|
# besitzt, behandeln wir den kompletten Inhalt als Headerblock und ergänzen die
|
||||||
|
# notwendige Trennzeile.
|
||||||
|
$separatorIndex = -1
|
||||||
|
for ($i = 0; $i -lt $Lines.Count; $i++) {
|
||||||
|
if ($Lines[$i] -eq "") {
|
||||||
|
$separatorIndex = $i
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$headerEnd = if ($separatorIndex -ge 0) { $separatorIndex } else { $Lines.Count }
|
||||||
|
|
||||||
|
$hasMessageId = $false
|
||||||
|
for ($i = 0; $i -lt $headerEnd; $i++) {
|
||||||
|
if ($Lines[$i] -match '^(?i)Message-ID\s*:') {
|
||||||
|
$hasMessageId = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$hostname = [string]$script:Config.Smtp.Hostname
|
||||||
|
if ([string]::IsNullOrWhiteSpace($hostname)) {
|
||||||
|
$hostname = $env:COMPUTERNAME
|
||||||
|
}
|
||||||
|
|
||||||
|
$date = [DateTimeOffset]::Now.ToString("ddd, dd MMM yyyy HH:mm:ss zzz", [Globalization.CultureInfo]::InvariantCulture)
|
||||||
|
$received = "Received: from [$RemoteAddress] by $hostname with SMTPGraphRelay; $date"
|
||||||
|
|
||||||
|
# Received immer ganz oben vor die ursprünglichen Header setzen.
|
||||||
|
$result.Add($received)
|
||||||
|
|
||||||
|
if (-not $hasMessageId) {
|
||||||
|
$safeHost = ($hostname -replace '[^A-Za-z0-9._-]', '-')
|
||||||
|
$messageId = "<$QueueId.$([DateTime]::UtcNow.ToString('yyyyMMddHHmmss'))@$safeHost>"
|
||||||
|
$result.Add("Message-ID: $messageId")
|
||||||
|
}
|
||||||
|
|
||||||
|
for ($i = 0; $i -lt $Lines.Count; $i++) {
|
||||||
|
$result.Add($Lines[$i])
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($separatorIndex -lt 0) {
|
||||||
|
$result.Add("")
|
||||||
|
}
|
||||||
|
|
||||||
|
return $result
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-SmtpLimits {
|
||||||
|
$maxRecipients = 50
|
||||||
|
$maxMessagesPerConnection = 25
|
||||||
|
$maxPendingMessages = 5000
|
||||||
|
$minFreeDiskSpaceMB = 1024
|
||||||
|
|
||||||
|
try {
|
||||||
|
if ($script:Config.Smtp.PSObject.Properties.Name -contains "MaxRecipients") {
|
||||||
|
$v = [int]$script:Config.Smtp.MaxRecipients
|
||||||
|
if ($v -ge 1 -and $v -le 1000) { $maxRecipients = $v }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:Config.Smtp.PSObject.Properties.Name -contains "MaxMessagesPerConnection") {
|
||||||
|
$v = [int]$script:Config.Smtp.MaxMessagesPerConnection
|
||||||
|
if ($v -ge 1 -and $v -le 10000) { $maxMessagesPerConnection = $v }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:Config.Queue.PSObject.Properties.Name -contains "MaxPendingMessages") {
|
||||||
|
$v = [int]$script:Config.Queue.MaxPendingMessages
|
||||||
|
if ($v -ge 1 -and $v -le 1000000) { $maxPendingMessages = $v }
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($script:Config.Queue.PSObject.Properties.Name -contains "MinFreeDiskSpaceMB") {
|
||||||
|
$v = [int]$script:Config.Queue.MinFreeDiskSpaceMB
|
||||||
|
if ($v -ge 0 -and $v -le 1048576) { $minFreeDiskSpaceMB = $v }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {}
|
||||||
|
|
||||||
|
return [pscustomobject]@{
|
||||||
|
MaxRecipients = $maxRecipients
|
||||||
|
MaxMessagesPerConnection = $maxMessagesPerConnection
|
||||||
|
MaxPendingMessages = $maxPendingMessages
|
||||||
|
MinFreeDiskSpaceMB = $minFreeDiskSpaceMB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-QueuePressure {
|
||||||
|
$dirs = Get-QueueDirectories
|
||||||
|
$limits = Get-SmtpLimits
|
||||||
|
|
||||||
|
try {
|
||||||
|
$pendingCount = @(
|
||||||
|
Get-ChildItem -LiteralPath $dirs.Pending -Filter "*.eml" -File -ErrorAction Stop
|
||||||
|
).Count
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Accept = $false
|
||||||
|
SmtpCode = "451 4.3.0 SMTPGraphRelay queue temporarily unavailable"
|
||||||
|
Reason = "Pending-Queue konnte nicht gelesen werden: $($_.Exception.Message)"
|
||||||
|
PendingCount = $null
|
||||||
|
FreeSpaceMB = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($pendingCount -ge $limits.MaxPendingMessages) {
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Accept = $false
|
||||||
|
SmtpCode = "452 4.3.1 SMTPGraphRelay queue limit reached"
|
||||||
|
Reason = "Pending-Queue-Limit erreicht ($pendingCount/$($limits.MaxPendingMessages))"
|
||||||
|
PendingCount = $pendingCount
|
||||||
|
FreeSpaceMB = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$freeSpaceMB = $null
|
||||||
|
|
||||||
|
try {
|
||||||
|
$fullQueuePath = [IO.Path]::GetFullPath($dirs.Root)
|
||||||
|
$root = [IO.Path]::GetPathRoot($fullQueuePath)
|
||||||
|
|
||||||
|
if ($root) {
|
||||||
|
$driveInfo = New-Object System.IO.DriveInfo($root)
|
||||||
|
if ($driveInfo.IsReady) {
|
||||||
|
$freeSpaceMB = [Math]::Floor($driveInfo.AvailableFreeSpace / 1MB)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Accept = $false
|
||||||
|
SmtpCode = "451 4.3.0 SMTPGraphRelay storage status unavailable"
|
||||||
|
Reason = "Freier Speicher konnte nicht ermittelt werden: $($_.Exception.Message)"
|
||||||
|
PendingCount = $pendingCount
|
||||||
|
FreeSpaceMB = $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($null -ne $freeSpaceMB -and $freeSpaceMB -lt $limits.MinFreeDiskSpaceMB) {
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Accept = $false
|
||||||
|
SmtpCode = "452 4.3.1 Insufficient system storage"
|
||||||
|
Reason = "Freier Speicher zu niedrig (${freeSpaceMB}MB < $($limits.MinFreeDiskSpaceMB)MB)"
|
||||||
|
PendingCount = $pendingCount
|
||||||
|
FreeSpaceMB = $freeSpaceMB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [pscustomobject]@{
|
||||||
|
Accept = $true
|
||||||
|
SmtpCode = $null
|
||||||
|
Reason = "OK"
|
||||||
|
PendingCount = $pendingCount
|
||||||
|
FreeSpaceMB = $freeSpaceMB
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function Save-SmtpMessage {
|
function Save-SmtpMessage {
|
||||||
param(
|
param(
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
@@ -743,23 +931,34 @@ function Save-SmtpMessage {
|
|||||||
[string[]]$Recipients,
|
[string[]]$Recipients,
|
||||||
|
|
||||||
[Parameter(Mandatory)]
|
[Parameter(Mandatory)]
|
||||||
[string]$RemoteAddress
|
[string]$RemoteAddress,
|
||||||
|
|
||||||
|
[string]$QueueId
|
||||||
)
|
)
|
||||||
|
|
||||||
$dirs = Get-QueueDirectories
|
$dirs = Get-QueueDirectories
|
||||||
$id = "{0}-{1}" -f (Get-Date -Format "yyyyMMdd-HHmmssfff"), ([guid]::NewGuid().ToString("N").Substring(0,8))
|
|
||||||
|
|
||||||
$incomingEmlTmp = Join-Path $dirs.Incoming "$id.eml.tmp"
|
if ([string]::IsNullOrWhiteSpace($QueueId)) {
|
||||||
$incomingMetaTmp = Join-Path $dirs.Incoming "$id.eml.json.tmp"
|
$QueueId = New-QueueId
|
||||||
|
}
|
||||||
|
|
||||||
$pendingEml = Join-Path $dirs.Pending "$id.eml"
|
$incomingEmlTmp = Join-Path $dirs.Incoming "$QueueId.eml.tmp"
|
||||||
|
$incomingMetaTmp = Join-Path $dirs.Incoming "$QueueId.eml.json.tmp"
|
||||||
|
|
||||||
|
$pendingEml = Join-Path $dirs.Pending "$QueueId.eml"
|
||||||
$pendingMeta = "$pendingEml.json"
|
$pendingMeta = "$pendingEml.json"
|
||||||
|
|
||||||
|
$messageLines = Add-RelayMessageHeaders `
|
||||||
|
-Lines $Lines `
|
||||||
|
-QueueId $QueueId `
|
||||||
|
-RemoteAddress $RemoteAddress
|
||||||
|
|
||||||
# SMTP DATA wird in CRLF normalisiert.
|
# SMTP DATA wird in CRLF normalisiert.
|
||||||
$raw = ($Lines -join "`r`n") + "`r`n"
|
$raw = ($messageLines -join "`r`n") + "`r`n"
|
||||||
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
$utf8NoBom = New-Object System.Text.UTF8Encoding($false)
|
||||||
|
|
||||||
$meta = [ordered]@{
|
$meta = [ordered]@{
|
||||||
|
QueueId = $QueueId
|
||||||
ReceivedUtc = [DateTime]::UtcNow.ToString("o")
|
ReceivedUtc = [DateTime]::UtcNow.ToString("o")
|
||||||
RemoteAddress = $RemoteAddress
|
RemoteAddress = $RemoteAddress
|
||||||
EnvelopeFrom = $MailFrom
|
EnvelopeFrom = $MailFrom
|
||||||
@@ -786,8 +985,11 @@ function Save-SmtpMessage {
|
|||||||
throw
|
throw
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Log "Mail angenommen: $id | Von=$MailFrom | An=$($Recipients -join ', ') | Client=$RemoteAddress"
|
Write-Log "[$QueueId] Mail angenommen | Von=$MailFrom | An=$($Recipients -join ', ') | Client=$RemoteAddress"
|
||||||
return $pendingEml
|
return [pscustomobject]@{
|
||||||
|
QueueId = $QueueId
|
||||||
|
Path = $pendingEml
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function Write-SmtpLine {
|
function Write-SmtpLine {
|
||||||
@@ -828,6 +1030,8 @@ function Handle-SmtpClient {
|
|||||||
|
|
||||||
$mailFrom = $null
|
$mailFrom = $null
|
||||||
$recipients = New-Object System.Collections.Generic.List[string]
|
$recipients = New-Object System.Collections.Generic.List[string]
|
||||||
|
$acceptedMessages = 0
|
||||||
|
$limits = Get-SmtpLimits
|
||||||
|
|
||||||
Write-SmtpLine $writer ("220 {0} SMTPGraphRelay ready" -f $script:Config.Smtp.Hostname)
|
Write-SmtpLine $writer ("220 {0} SMTPGraphRelay ready" -f $script:Config.Smtp.Hostname)
|
||||||
|
|
||||||
@@ -842,6 +1046,12 @@ function Handle-SmtpClient {
|
|||||||
Write-SmtpLine $writer "250 8BITMIME"
|
Write-SmtpLine $writer "250 8BITMIME"
|
||||||
}
|
}
|
||||||
elseif ($line -match '^(?i)MAIL FROM:\s*<([^>]*)>') {
|
elseif ($line -match '^(?i)MAIL FROM:\s*<([^>]*)>') {
|
||||||
|
if ($acceptedMessages -ge $limits.MaxMessagesPerConnection) {
|
||||||
|
Write-SmtpLine $writer "452 4.5.3 Too many messages in this session"
|
||||||
|
Write-Log ("SMTP-Session von {0}: Nachrichtenlimit {1} erreicht." -f $remoteIp, $limits.MaxMessagesPerConnection) "WARN"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
$mailFrom = $matches[1]
|
$mailFrom = $matches[1]
|
||||||
$recipients.Clear()
|
$recipients.Clear()
|
||||||
Write-SmtpLine $writer "250 2.1.0 OK"
|
Write-SmtpLine $writer "250 2.1.0 OK"
|
||||||
@@ -851,6 +1061,13 @@ function Handle-SmtpClient {
|
|||||||
Write-SmtpLine $writer "503 5.5.1 Need MAIL FROM first"
|
Write-SmtpLine $writer "503 5.5.1 Need MAIL FROM first"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($recipients.Count -ge $limits.MaxRecipients) {
|
||||||
|
Write-SmtpLine $writer "452 4.5.3 Too many recipients"
|
||||||
|
Write-Log ("SMTP-Session von {0}: Empfängerlimit {1} erreicht." -f $remoteIp, $limits.MaxRecipients) "WARN"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
$recipients.Add($matches[1])
|
$recipients.Add($matches[1])
|
||||||
Write-SmtpLine $writer "250 2.1.5 OK"
|
Write-SmtpLine $writer "250 2.1.5 OK"
|
||||||
}
|
}
|
||||||
@@ -860,6 +1077,20 @@ function Handle-SmtpClient {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($acceptedMessages -ge $limits.MaxMessagesPerConnection) {
|
||||||
|
Write-SmtpLine $writer "452 4.5.3 Too many messages in this session"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
# Backpressure VOR 354/DATA: Der Client soll große Nachrichtendaten
|
||||||
|
# gar nicht erst übertragen, wenn wir sie nicht sicher puffern können.
|
||||||
|
$pressure = Get-QueuePressure
|
||||||
|
if (-not $pressure.Accept) {
|
||||||
|
Write-SmtpLine $writer $pressure.SmtpCode
|
||||||
|
Write-Log ("SMTP-Backpressure für {0}: {1}" -f $remoteIp, $pressure.Reason) "WARN"
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
Write-SmtpLine $writer "354 End data with <CR><LF>.<CR><LF>"
|
Write-SmtpLine $writer "354 End data with <CR><LF>.<CR><LF>"
|
||||||
$data = New-Object System.Collections.Generic.List[string]
|
$data = New-Object System.Collections.Generic.List[string]
|
||||||
$size = 0
|
$size = 0
|
||||||
@@ -877,7 +1108,8 @@ function Handle-SmtpClient {
|
|||||||
$size += [System.Text.Encoding]::UTF8.GetByteCount($dataLine) + 2
|
$size += [System.Text.Encoding]::UTF8.GetByteCount($dataLine) + 2
|
||||||
if ($size -gt $maxBytes) {
|
if ($size -gt $maxBytes) {
|
||||||
$tooLarge = $true
|
$tooLarge = $true
|
||||||
} elseif (-not $tooLarge) {
|
}
|
||||||
|
elseif (-not $tooLarge) {
|
||||||
$data.Add($dataLine)
|
$data.Add($dataLine)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -885,9 +1117,28 @@ function Handle-SmtpClient {
|
|||||||
if ($tooLarge) {
|
if ($tooLarge) {
|
||||||
Write-SmtpLine $writer "552 5.3.4 Message size exceeds fixed maximum message size"
|
Write-SmtpLine $writer "552 5.3.4 Message size exceeds fixed maximum message size"
|
||||||
Write-Log "Mail von $remoteIp wegen Größenlimit verworfen." "WARN"
|
Write-Log "Mail von $remoteIp wegen Größenlimit verworfen." "WARN"
|
||||||
} else {
|
}
|
||||||
[void](Save-SmtpMessage -Lines $data -MailFrom $mailFrom -Recipients $recipients.ToArray() -RemoteAddress $remoteIp.ToString())
|
else {
|
||||||
Write-SmtpLine $writer "250 2.0.0 Queued"
|
$queueId = New-QueueId
|
||||||
|
|
||||||
|
try {
|
||||||
|
$saved = Save-SmtpMessage `
|
||||||
|
-Lines $data `
|
||||||
|
-MailFrom $mailFrom `
|
||||||
|
-Recipients $recipients.ToArray() `
|
||||||
|
-RemoteAddress $remoteIp.ToString() `
|
||||||
|
-QueueId $queueId
|
||||||
|
|
||||||
|
$acceptedMessages++
|
||||||
|
Write-SmtpLine $writer ("250 2.0.0 Message accepted for delivery; queue-id={0}" -f $saved.QueueId)
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
# Nach DATA darf niemals 250 gesendet werden, wenn die Queue-Datei
|
||||||
|
# nicht vollständig und atomar gesichert werden konnte.
|
||||||
|
Write-SmtpLine $writer "451 4.3.0 SMTPGraphRelay queue temporarily unavailable"
|
||||||
|
Write-Log ("[{0}] Queue-Speicherung fehlgeschlagen | Client={1} | Fehler={2}" -f `
|
||||||
|
$queueId, $remoteIp, $_.Exception.Message) "ERROR"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$mailFrom = $null
|
$mailFrom = $null
|
||||||
@@ -924,7 +1175,6 @@ function Handle-SmtpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function Get-RelayCertificateStatus {
|
function Get-RelayCertificateStatus {
|
||||||
$thumbprint = [string]$script:Config.Graph.CertificateThumbprint
|
$thumbprint = [string]$script:Config.Graph.CertificateThumbprint
|
||||||
|
|
||||||
@@ -1172,6 +1422,10 @@ $smtpFunctionNames = @(
|
|||||||
"Test-IPv4InCidr",
|
"Test-IPv4InCidr",
|
||||||
"Test-ClientAllowed",
|
"Test-ClientAllowed",
|
||||||
"Get-QueueDirectories",
|
"Get-QueueDirectories",
|
||||||
|
"New-QueueId",
|
||||||
|
"Add-RelayMessageHeaders",
|
||||||
|
"Get-SmtpLimits",
|
||||||
|
"Get-QueuePressure",
|
||||||
"Save-SmtpMessage",
|
"Save-SmtpMessage",
|
||||||
"Write-SmtpLine",
|
"Write-SmtpLine",
|
||||||
"Handle-SmtpClient"
|
"Handle-SmtpClient"
|
||||||
@@ -1251,11 +1505,17 @@ $listenIp = [System.Net.IPAddress]::Parse($script:Config.Smtp.ListenAddress)
|
|||||||
$listener = [System.Net.Sockets.TcpListener]::new($listenIp, [int]$script:Config.Smtp.Port)
|
$listener = [System.Net.Sockets.TcpListener]::new($listenIp, [int]$script:Config.Smtp.Port)
|
||||||
$listener.Start()
|
$listener.Start()
|
||||||
|
|
||||||
Write-Log "SMTPGraphRelay V1.5 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
Write-Log "SMTPGraphRelay V1.6 gestartet auf $($script:Config.Smtp.ListenAddress):$($script:Config.Smtp.Port)"
|
||||||
Write-Log "Graph-Absender: $($script:Config.Graph.SenderMailbox)"
|
Write-Log "Graph-Absender: $($script:Config.Graph.SenderMailbox)"
|
||||||
Write-Log "Maximale parallele SMTP-Verbindungen: $script:MaxConcurrentClients"
|
Write-Log "Maximale parallele SMTP-Verbindungen: $script:MaxConcurrentClients"
|
||||||
Write-Log "Queue-/Graph-Worker läuft separat vom SMTP-Listener."
|
Write-Log "Queue-/Graph-Worker läuft separat vom SMTP-Listener."
|
||||||
|
|
||||||
|
$limits = Get-SmtpLimits
|
||||||
|
Write-Log ("SMTP-Limits: max. {0} Empfänger/Mail, {1} Mails/Verbindung." -f `
|
||||||
|
$limits.MaxRecipients, $limits.MaxMessagesPerConnection)
|
||||||
|
Write-Log ("Backpressure: max. {0} Pending-Mails, mindestens {1} MB freier Speicher." -f `
|
||||||
|
$limits.MaxPendingMessages, $limits.MinFreeDiskSpaceMB)
|
||||||
|
|
||||||
$logSettings = Get-LogSettings
|
$logSettings = Get-LogSettings
|
||||||
Write-Log ("Log-Rotation: max. {0} MB pro Datei, Aufbewahrung {1} Tage." -f `
|
Write-Log ("Log-Rotation: max. {0} MB pro Datei, Aufbewahrung {1} Tage." -f `
|
||||||
$logSettings.MaxFileSizeMB, $logSettings.RetentionDays)
|
$logSettings.MaxFileSizeMB, $logSettings.RetentionDays)
|
||||||
|
|||||||
Reference in New Issue
Block a user