Dateien nach "/" hochladen
This commit is contained in:
@@ -873,6 +873,12 @@ function Install-New {
|
|||||||
MaxMessageSizeMB = 25
|
MaxMessageSizeMB = 25
|
||||||
ClientTimeoutSeconds = 120
|
ClientTimeoutSeconds = 120
|
||||||
MaxConcurrentClients = 20
|
MaxConcurrentClients = 20
|
||||||
|
MaxRecipients = 50
|
||||||
|
MaxMessagesPerConnection = 25
|
||||||
|
RequireAuth = $false
|
||||||
|
AuthMaxFailures = 5
|
||||||
|
AllowUnauthenticatedNetworks = @()
|
||||||
|
AuthUsers = @()
|
||||||
}
|
}
|
||||||
Graph = [ordered]@{
|
Graph = [ordered]@{
|
||||||
TenantId = $tenantId
|
TenantId = $tenantId
|
||||||
@@ -888,6 +894,8 @@ function Install-New {
|
|||||||
PollSeconds = 10
|
PollSeconds = 10
|
||||||
MaxRetries = 8
|
MaxRetries = 8
|
||||||
RetryMinutes = @(1,5,15,30,60,120,240,480)
|
RetryMinutes = @(1,5,15,30,60,120,240,480)
|
||||||
|
MaxPendingMessages = 5000
|
||||||
|
MinFreeDiskSpaceMB = 1024
|
||||||
}
|
}
|
||||||
Paths = [ordered]@{
|
Paths = [ordered]@{
|
||||||
Queue = "queue"
|
Queue = "queue"
|
||||||
@@ -1320,6 +1328,406 @@ function Invoke-HealthCheck {
|
|||||||
& $health -ConfigPath (Join-Path $InstallPath $ConfigFileName)
|
& $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 {
|
function Uninstall-Relay {
|
||||||
Write-Title "SMTPGraphRelay - Deinstallation"
|
Write-Title "SMTPGraphRelay - Deinstallation"
|
||||||
|
|
||||||
@@ -1436,6 +1844,12 @@ function Show-Status {
|
|||||||
Write-Host " Sender: $($config.Graph.SenderMailbox)"
|
Write-Host " Sender: $($config.Graph.SenderMailbox)"
|
||||||
Write-Host " SMTP: $($config.Smtp.ListenAddress):$($config.Smtp.Port)"
|
Write-Host " SMTP: $($config.Smtp.ListenAddress):$($config.Smtp.Port)"
|
||||||
Write-Host " Client: $($config.Graph.ClientId)"
|
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 {}
|
} catch {}
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
@@ -1472,6 +1886,7 @@ function Show-Menu {
|
|||||||
Write-Host " [6] Health Check ausführen"
|
Write-Host " [6] Health Check ausführen"
|
||||||
Write-Host " [7] Deinstallieren"
|
Write-Host " [7] Deinstallieren"
|
||||||
Write-Host " [8] Status anzeigen"
|
Write-Host " [8] Status anzeigen"
|
||||||
|
Write-Host " [9] SMTP-AUTH verwalten"
|
||||||
Write-Host " [0] Beenden"
|
Write-Host " [0] Beenden"
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
}
|
}
|
||||||
@@ -1492,6 +1907,7 @@ while ($true) {
|
|||||||
"6" { Invoke-HealthCheck }
|
"6" { Invoke-HealthCheck }
|
||||||
"7" { Uninstall-Relay }
|
"7" { Uninstall-Relay }
|
||||||
"8" { Show-Status }
|
"8" { Show-Status }
|
||||||
|
"9" { Manage-SmtpAuth }
|
||||||
"0" { break }
|
"0" { break }
|
||||||
default { Write-Warn "Ungültige Auswahl." }
|
default { Write-Warn "Ungültige Auswahl." }
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user