$ErrorActionPreference = 'Stop' function Get-MailContext { param([string]$ConfigPath = (Join-Path (Split-Path -Parent $PSScriptRoot) 'config.ini')) $settings = @{} foreach ($line in Get-Content -LiteralPath $ConfigPath -Encoding UTF8) { $trimmed = $line.Trim() if (-not $trimmed -or $trimmed.StartsWith('#') -or $trimmed.StartsWith(';')) { continue } $parts = $trimmed.Split('=', 2) if ($parts.Count -ne 2) { throw "Invalid configuration line in $ConfigPath" } $name = $parts[0].Trim().ToUpperInvariant() if ($settings.ContainsKey($name)) { throw "Duplicate setting: $name" } $settings[$name] = $parts[1].Trim() } foreach ($name in @('DB_PATH', 'DB_PROVIDER', 'DB_BITNESS', 'SMTP_SECRET_ID', 'SMTP_USER')) { if (-not $settings.ContainsKey($name) -or -not $settings[$name]) { throw "Missing setting: $name" } } if ($settings['DB_PROVIDER'] -notin @('Microsoft.Jet.OLEDB.4.0', 'Microsoft.ACE.OLEDB.12.0', 'Microsoft.ACE.OLEDB.16.0')) { throw 'Unsupported DB_PROVIDER' } $actualBitness = if ([IntPtr]::Size -eq 4) { 'x86' } else { 'x64' } if ($settings['DB_BITNESS'] -ne $actualBitness) { throw "Run this setup under $($settings['DB_BITNESS']) PowerShell" } $databasePath = $settings['DB_PATH'] if ($databasePath.StartsWith('\\')) { throw 'DB_PATH must be on a local drive' } if (-not [System.IO.Path]::IsPathRooted($databasePath)) { $databasePath = Join-Path (Split-Path -Parent $PSScriptRoot) $databasePath } $databasePath = [System.IO.Path]::GetFullPath($databasePath) if ([System.IO.Path]::GetExtension($databasePath) -ne '.mdb') { throw 'DB_PATH must end in .mdb' } if (-not (Test-Path -LiteralPath $databasePath -PathType Leaf)) { throw "MDB not found: $databasePath" } return @{ Settings = $settings; DatabasePath = $databasePath } } function Open-MailDatabase { param($Context) $builder = New-Object System.Data.OleDb.OleDbConnectionStringBuilder $builder.Provider = $Context.Settings['DB_PROVIDER'] $builder['Data Source'] = $Context.DatabasePath $connection = New-Object System.Data.OleDb.OleDbConnection($builder.ConnectionString) $connection.Open() return $connection } function Save-SmtpPassword { param($Context, [System.Security.SecureString]$Password) $encrypted = ConvertFrom-SecureString -SecureString $Password $bytes = [System.Text.Encoding]::UTF8.GetBytes($encrypted) $connection = Open-MailDatabase $Context try { $check = $connection.CreateCommand() $check.CommandText = 'SELECT COUNT(*) FROM [ProtectedSecret] WHERE [Id]=?' $null = $check.Parameters.Add('id', [System.Data.OleDb.OleDbType]::VarWChar, 100) $check.Parameters[0].Value = $Context.Settings['SMTP_SECRET_ID'] $exists = [int]$check.ExecuteScalar() -gt 0 if ($exists) { $command = $connection.CreateCommand() $command.CommandText = 'UPDATE [ProtectedSecret] SET [DpapiCiphertext]=?, [RotatedAtUtc]=? WHERE [Id]=?' $null = $command.Parameters.Add('ciphertext', [System.Data.OleDb.OleDbType]::LongVarBinary, $bytes.Length) $command.Parameters[0].Value = $bytes $null = $command.Parameters.Add('rotated', [System.Data.OleDb.OleDbType]::Date) $command.Parameters[1].Value = [DateTime]::UtcNow $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::VarWChar, 100) $command.Parameters[2].Value = $Context.Settings['SMTP_SECRET_ID'] } else { $command = $connection.CreateCommand() $command.CommandText = 'INSERT INTO [ProtectedSecret] ([Id],[Purpose],[DpapiCiphertext],[CreatedAtUtc]) VALUES (?,?,?,?)' $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::VarWChar, 100) $command.Parameters[0].Value = $Context.Settings['SMTP_SECRET_ID'] $null = $command.Parameters.Add('purpose', [System.Data.OleDb.OleDbType]::VarWChar, 50) $command.Parameters[1].Value = 'smtp_password' $null = $command.Parameters.Add('ciphertext', [System.Data.OleDb.OleDbType]::LongVarBinary, $bytes.Length) $command.Parameters[2].Value = $bytes $null = $command.Parameters.Add('created', [System.Data.OleDb.OleDbType]::Date) $command.Parameters[3].Value = [DateTime]::UtcNow } $null = $command.ExecuteNonQuery() } finally { $connection.Dispose() [Array]::Clear($bytes, 0, $bytes.Length) } } function Load-SmtpPassword { param($Context) $connection = Open-MailDatabase $Context try { $command = $connection.CreateCommand() $command.CommandText = 'SELECT [DpapiCiphertext] FROM [ProtectedSecret] WHERE [Id]=? AND [Purpose]=?' $null = $command.Parameters.Add('id', [System.Data.OleDb.OleDbType]::VarWChar, 100) $command.Parameters[0].Value = $Context.Settings['SMTP_SECRET_ID'] $null = $command.Parameters.Add('purpose', [System.Data.OleDb.OleDbType]::VarWChar, 50) $command.Parameters[1].Value = 'smtp_password' $bytes = $command.ExecuteScalar() if ($null -eq $bytes -or $bytes -is [DBNull]) { throw 'SMTP password is not configured' } $encrypted = [System.Text.Encoding]::UTF8.GetString($bytes) return ConvertTo-SecureString -String $encrypted } finally { $connection.Dispose() } }