Dateien nach "/" hochladen

This commit is contained in:
2026-08-14 00:14:20 +02:00
parent 124c4f9ca4
commit 07db702a7a
+416
View File
@@ -873,6 +873,12 @@ function Install-New {
MaxMessageSizeMB = 25
ClientTimeoutSeconds = 120
MaxConcurrentClients = 20
MaxRecipients = 50
MaxMessagesPerConnection = 25
RequireAuth = $false
AuthMaxFailures = 5
AllowUnauthenticatedNetworks = @()
AuthUsers = @()
}
Graph = [ordered]@{
TenantId = $tenantId
@@ -888,6 +894,8 @@ function Install-New {
PollSeconds = 10
MaxRetries = 8
RetryMinutes = @(1,5,15,30,60,120,240,480)
MaxPendingMessages = 5000
MinFreeDiskSpaceMB = 1024
}
Paths = [ordered]@{
Queue = "queue"
@@ -1320,6 +1328,406 @@ function Invoke-HealthCheck {
& $health -ConfigPath (Join-Path $InstallPath $ConfigFileName)
}
function ConvertTo-PlainText {
param([Parameter(Mandatory)][Security.SecureString]$SecureString)
$ptr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($SecureString)
try {
return [Runtime.InteropServices.Marshal]::PtrToStringBSTR($ptr)
}
finally {
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($ptr)
}
}
function Invoke-Pbkdf2Sha256 {
param(
[Parameter(Mandatory)][string]$Password,
[Parameter(Mandatory)][byte[]]$Salt,
[Parameter(Mandatory)][int]$Iterations,
[int]$Length = 32
)
if ($Iterations -lt 1) {
throw "Iterations must be greater than zero."
}
# Auf unterstützten .NET-Framework-Versionen verwenden wir die native,
# schnelle PBKDF2-SHA256-Implementierung.
try {
$derive = New-Object System.Security.Cryptography.Rfc2898DeriveBytes(
$Password,
$Salt,
$Iterations,
[System.Security.Cryptography.HashAlgorithmName]::SHA256
)
try {
return $derive.GetBytes($Length)
}
finally {
$derive.Dispose()
}
}
catch {
# Kompatibilitäts-Fallback für ältere .NET-Framework-Stände.
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [Text.Encoding]::UTF8.GetBytes($Password)
try {
$hashLength = 32
$blocks = [Math]::Ceiling($Length / [double]$hashLength)
$output = New-Object byte[] ($blocks * $hashLength)
$offset = 0
for ($block = 1; $block -le $blocks; $block++) {
$blockBytes = [BitConverter]::GetBytes([int]$block)
if ([BitConverter]::IsLittleEndian) {
[Array]::Reverse($blockBytes)
}
$input = New-Object byte[] ($Salt.Length + 4)
[Array]::Copy($Salt, 0, $input, 0, $Salt.Length)
[Array]::Copy($blockBytes, 0, $input, $Salt.Length, 4)
$u = $hmac.ComputeHash($input)
$t = New-Object byte[] $u.Length
[Array]::Copy($u, $t, $u.Length)
for ($i = 2; $i -le $Iterations; $i++) {
$u = $hmac.ComputeHash($u)
for ($j = 0; $j -lt $t.Length; $j++) {
$t[$j] = $t[$j] -bxor $u[$j]
}
}
[Array]::Copy($t, 0, $output, $offset, $t.Length)
$offset += $t.Length
}
$result = New-Object byte[] $Length
[Array]::Copy($output, 0, $result, 0, $Length)
return $result
}
finally {
$hmac.Dispose()
}
}
}
function New-SmtpPasswordRecord {
param([Parameter(Mandatory)][Security.SecureString]$Password)
$plain = ConvertTo-PlainText -SecureString $Password
try {
$salt = New-Object byte[] 16
$rng = [Security.Cryptography.RandomNumberGenerator]::Create()
try {
$rng.GetBytes($salt)
}
finally {
$rng.Dispose()
}
$iterations = 150000
$hash = Invoke-Pbkdf2Sha256 `
-Password $plain `
-Salt $salt `
-Iterations $iterations `
-Length 32
return [pscustomobject]@{
Salt = [Convert]::ToBase64String($salt)
PasswordHash = [Convert]::ToBase64String($hash)
Iterations = $iterations
}
}
finally {
$plain = $null
}
}
function Ensure-SmtpAuthConfig {
param([Parameter(Mandatory)]$Config)
if (-not ($Config.Smtp.PSObject.Properties.Name -contains "RequireAuth")) {
$Config.Smtp | Add-Member -NotePropertyName RequireAuth -NotePropertyValue $false
}
if (-not ($Config.Smtp.PSObject.Properties.Name -contains "AuthMaxFailures")) {
$Config.Smtp | Add-Member -NotePropertyName AuthMaxFailures -NotePropertyValue 5
}
if (-not ($Config.Smtp.PSObject.Properties.Name -contains "AllowUnauthenticatedNetworks")) {
$Config.Smtp | Add-Member -NotePropertyName AllowUnauthenticatedNetworks -NotePropertyValue @()
}
if (-not ($Config.Smtp.PSObject.Properties.Name -contains "AuthUsers")) {
$Config.Smtp | Add-Member -NotePropertyName AuthUsers -NotePropertyValue @()
}
return $Config
}
function Save-SmtpAuthConfigAndRestart {
param([Parameter(Mandatory)]$Config)
Write-RelayConfig -Config $Config -TargetPath $InstallPath
Ensure-ScheduledTask -TargetPath $InstallPath
Stop-RelayTask
Start-RelayTask
}
function Manage-SmtpAuth {
$config = Get-RelayConfig -TargetPath $InstallPath
if (-not $config) {
Write-Fail "config.json nicht gefunden."
return
}
$config = Ensure-SmtpAuthConfig -Config $config
while ($true) {
Clear-Host
Write-Title "SMTPGraphRelay - SMTP-AUTH"
$users = @($config.Smtp.AuthUsers)
$authState = if ([bool]$config.Smtp.RequireAuth) { "ERFORDERLICH" } else { "optional / nicht erforderlich" }
Write-Host "Status: $authState"
Write-Host "Benutzer: $($users.Count)"
Write-Host "Max. Fehlversuche: $($config.Smtp.AuthMaxFailures)"
Write-Host "Ohne Auth erlaubte Netze: $(@($config.Smtp.AllowUnauthenticatedNetworks) -join ', ')"
Write-Host ""
Write-Host " [1] SMTP-AUTH erforderlich EIN/AUS"
Write-Host " [2] Benutzer hinzufügen"
Write-Host " [3] Benutzer anzeigen"
Write-Host " [4] Passwort ändern"
Write-Host " [5] Benutzer löschen"
Write-Host " [6] Netze ohne Auth verwalten"
Write-Host " [7] Max. Fehlversuche ändern"
Write-Host " [0] Zurück"
Write-Host ""
$choice = Read-Host "Auswahl"
switch ($choice) {
"1" {
$config.Smtp.RequireAuth = -not [bool]$config.Smtp.RequireAuth
if ($config.Smtp.RequireAuth -and @($config.Smtp.AuthUsers).Count -eq 0) {
Write-Warn "AUTH wurde aktiviert, aber es existiert noch kein SMTP-Benutzer."
}
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "SMTP-AUTH Status geändert."
Read-Host "Enter"
}
"2" {
$username = Read-Host "Benutzername"
if ([string]::IsNullOrWhiteSpace($username) -or $username -notmatch '^[A-Za-z0-9._@-]{1,128}$') {
Write-Fail "Ungültiger Benutzername."
Read-Host "Enter"
continue
}
$existing = @($config.Smtp.AuthUsers) |
Where-Object { ([string]$_.Username).Equals($username, [StringComparison]::OrdinalIgnoreCase) } |
Select-Object -First 1
if ($existing) {
Write-Fail "Benutzer '$username' existiert bereits."
Read-Host "Enter"
continue
}
$p1 = Read-Host "Passwort" -AsSecureString
$p2 = Read-Host "Passwort wiederholen" -AsSecureString
$plain1 = ConvertTo-PlainText -SecureString $p1
$plain2 = ConvertTo-PlainText -SecureString $p2
try {
if ($plain1.Length -lt 8) {
Write-Fail "Passwort muss mindestens 8 Zeichen lang sein."
Read-Host "Enter"
continue
}
if ($plain1 -cne $plain2) {
Write-Fail "Passwörter stimmen nicht überein."
Read-Host "Enter"
continue
}
}
finally {
$plain1 = $null
$plain2 = $null
}
$record = New-SmtpPasswordRecord -Password $p1
$newUser = [pscustomobject]@{
Username = $username
Salt = $record.Salt
PasswordHash = $record.PasswordHash
Iterations = $record.Iterations
}
$config.Smtp.AuthUsers = @($config.Smtp.AuthUsers) + @($newUser)
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "SMTP-Benutzer '$username' angelegt."
Read-Host "Enter"
}
"3" {
Write-Host ""
if (@($config.Smtp.AuthUsers).Count -eq 0) {
Write-Warn "Keine SMTP-Benutzer vorhanden."
}
else {
@($config.Smtp.AuthUsers) |
Select-Object Username,Iterations |
Format-Table -AutoSize
}
Read-Host "Enter"
}
"4" {
$username = Read-Host "Benutzername"
$user = @($config.Smtp.AuthUsers) |
Where-Object { ([string]$_.Username).Equals($username, [StringComparison]::OrdinalIgnoreCase) } |
Select-Object -First 1
if (-not $user) {
Write-Fail "Benutzer '$username' nicht gefunden."
Read-Host "Enter"
continue
}
$p1 = Read-Host "Neues Passwort" -AsSecureString
$p2 = Read-Host "Passwort wiederholen" -AsSecureString
$plain1 = ConvertTo-PlainText -SecureString $p1
$plain2 = ConvertTo-PlainText -SecureString $p2
try {
if ($plain1.Length -lt 8) {
Write-Fail "Passwort muss mindestens 8 Zeichen lang sein."
Read-Host "Enter"
continue
}
if ($plain1 -cne $plain2) {
Write-Fail "Passwörter stimmen nicht überein."
Read-Host "Enter"
continue
}
}
finally {
$plain1 = $null
$plain2 = $null
}
$record = New-SmtpPasswordRecord -Password $p1
$user.Salt = $record.Salt
$user.PasswordHash = $record.PasswordHash
$user.Iterations = $record.Iterations
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "Passwort für '$username' geändert."
Read-Host "Enter"
}
"5" {
$username = Read-Host "Benutzername"
$found = @($config.Smtp.AuthUsers) |
Where-Object { ([string]$_.Username).Equals($username, [StringComparison]::OrdinalIgnoreCase) }
if (-not $found) {
Write-Fail "Benutzer '$username' nicht gefunden."
Read-Host "Enter"
continue
}
if (Confirm-Yes "Benutzer '$username' wirklich löschen?") {
$config.Smtp.AuthUsers = @(
$config.Smtp.AuthUsers |
Where-Object { -not ([string]$_.Username).Equals($username, [StringComparison]::OrdinalIgnoreCase) }
)
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "Benutzer '$username' gelöscht."
}
Read-Host "Enter"
}
"6" {
$current = @($config.Smtp.AllowUnauthenticatedNetworks) -join ","
$input = Read-Default "Netze/IPs ohne AUTH, Komma getrennt; '-' für keine" $(if ($current) { $current } else { "-" })
if ($input -eq "-") {
$config.Smtp.AllowUnauthenticatedNetworks = @()
}
else {
$config.Smtp.AllowUnauthenticatedNetworks = @(
$input -split "," |
ForEach-Object { $_.Trim() } |
Where-Object { $_ }
)
}
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "Ausnahmen für SMTP-AUTH gespeichert."
Read-Host "Enter"
}
"7" {
$value = Read-Host "Maximale Fehlversuche pro Verbindung [aktuell: $($config.Smtp.AuthMaxFailures)]"
if ($value -match '^\d+$' -and [int]$value -ge 1 -and [int]$value -le 100) {
$config.Smtp.AuthMaxFailures = [int]$value
Save-SmtpAuthConfigAndRestart -Config $config
Write-Ok "Maximale Fehlversuche geändert."
}
else {
Write-Fail "Bitte einen Wert zwischen 1 und 100 eingeben."
}
Read-Host "Enter"
}
"0" {
return
}
default {
Write-Warn "Ungültige Auswahl."
Start-Sleep -Seconds 1
}
}
# Config nach jeder Änderung neu laden, damit Serialisierung/Arrays exakt
# dem installierten Zustand entsprechen.
$config = Get-RelayConfig -TargetPath $InstallPath
$config = Ensure-SmtpAuthConfig -Config $config
}
}
function Uninstall-Relay {
Write-Title "SMTPGraphRelay - Deinstallation"
@@ -1436,6 +1844,12 @@ function Show-Status {
Write-Host " Sender: $($config.Graph.SenderMailbox)"
Write-Host " SMTP: $($config.Smtp.ListenAddress):$($config.Smtp.Port)"
Write-Host " Client: $($config.Graph.ClientId)"
if ($config.Smtp.PSObject.Properties.Name -contains "RequireAuth") {
$authText = if ([bool]$config.Smtp.RequireAuth) { "erforderlich" } else { "optional / aus" }
$authUsers = if ($config.Smtp.PSObject.Properties.Name -contains "AuthUsers") { @($config.Smtp.AuthUsers).Count } else { 0 }
Write-Host " AUTH: $authText ($authUsers Benutzer)"
}
} catch {}
}
else {
@@ -1472,6 +1886,7 @@ function Show-Menu {
Write-Host " [6] Health Check ausführen"
Write-Host " [7] Deinstallieren"
Write-Host " [8] Status anzeigen"
Write-Host " [9] SMTP-AUTH verwalten"
Write-Host " [0] Beenden"
Write-Host ""
}
@@ -1492,6 +1907,7 @@ while ($true) {
"6" { Invoke-HealthCheck }
"7" { Uninstall-Relay }
"8" { Show-Status }
"9" { Manage-SmtpAuth }
"0" { break }
default { Write-Warn "Ungültige Auswahl." }
}