hMailServer ClamAV Cleanup

PowerShell
Platform
ClamAV
hMailServer
License
Version



Антивирусная проверка хранилища сообщений hMailServer по требованию. ClamAV работает исключительно как детектор; все удаления выполняются через COM API hMailServer, поэтому запись в базе и файл .eml удаляются вместе, и хранилище никогда не рассинхронизируется.

English version: README.md


Содержание


Решаемая боль

В почтовых хранилищах накапливаются письма, которые на момент доставки были чистыми и стали вредоносными только после обновления сигнатур. Резидентная защита не перепроверяет архив задним числом, поэтому нужна периодическая проверка по требованию.

Первая реализация управляла установленной локально консольной обёрткой Kaspersky — avp.com. На целевом хосте (потребительская сборка 21.26.4.406) интерфейс командной строки оказался полностью отключён: любая команда, включая HELP и STATUS, возвращала код 3 без вывода. Это подтверждено матрицей из двенадцати вариантов запуска — из каталога продукта, с паролем настроек и без него, через cmd.exe и напрямую.

ClamAV заменил его в роли детектирующего движка: он бесплатен, активно поддерживается на Windows, поставляется и как клиент демона, и как автономный сканер, и выдаёт стабильный машиночитаемый формат вердикта:

C:\path\to\message.eml: Trojan.Foo-1 FOUND

Резидентный Kaspersky продолжает работать; ClamAV только читает файлы, поэтому конфликта между ними нет.


Возможности

  • Сканирование только на обнаружение. Параметры --remove, --move и --copy никогда не передаются ClamAV. Антивирус не может изменить хранилище.
  • Транзакционное удаление через COM. DeleteByDBID удаляет запись в базе и файл письма одной операцией.
  • Автоматический выбор движка. Предпочитается clamdscan при запущенном clamd; при отсутствии демона используется clamscan.
  • Инкрементальная или полная проверка. -SinceDays ограничивает область по времени изменения; по умолчанию сканируется всё хранилище.
  • Карантин по умолчанию. Каждое удаляемое письмо сначала копируется в карантинный каталог с отметкой времени.
  • Готовность к работе без оператора. Сохранённый PSCredential или SecureString обнаруживается автоматически, а -NonInteractive превращает отсутствие секрета в немедленную ошибку вместо повисшего запроса пароля.
  • Ранняя проверка учётных данных. Креды разрешаются до сканирования, поэтому испорченный файл кредов стоит секунд, а не полного прохода.
  • Разбор вывода без опоры на разделители. Вердикты извлекаются глобальными регулярными выражениями, потому что clamdscan на Windows не гарантирует разделения результатов через CR, LF или NUL.
  • Честный учёт ошибок. Файлы, которые сканер не смог прочитать, считаются отдельно и никогда не отмечаются как чистые.
  • Предохранители. Пути вне каталога Data hMailServer не удаляются никогда; файлы не с расширением .eml отклоняются; для «осиротевших» файлов нужен отдельный явный ключ.
  • Поддержка ShouldProcess. -WhatIf и -Confirm работают именно так, как ожидается от команды с высоким уровнем воздействия.
  • Полный аудиторский след. Каждый запуск пишет отладочный лог, транскрипт, «сырой» вывод сканера, а также отчёты в CSV и JSON.
  • Чистота по линтеру. Invoke-ScriptAnalyzer проходит без ошибок и предупреждений, без подавлений, исходник в чистом ASCII.

Как это работает

  ┌─────────────────────────┐
  │ 1. Проверка окружения   │  clamdscan / clamscan, состояние clamd, возраст баз
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 2. Разрешение кредов    │  параметр → путь → переменная среды → автопоиск
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 3. Поиск каталога Data  │  реестр InstallLocation → hMailServer.INI → Data
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 4. Область сканирования │  *.eml по возрасту, UTF-8 список для --file-list
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 5. Сканирование         │  --no-summary --log, захват stdout в кодировке OEM
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 6. Разбор вердиктов     │  FOUND / ERROR / OK через глобальный regex
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 7. Сопоставление в COM  │  сначала полный путь, затем уникальное имя файла
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 8. Отчёты               │  CSV + JSON + отладочный лог
  └───────────┬─────────────┘
              │
  ┌───────────▼─────────────┐
  │ 9. Карантин и удаление  │  только с -Delete, под защитой ShouldProcess
  └─────────────────────────┘

Письма сопоставляются по абсолютному пути. Если по пути совпадение не найдено, используется имя файла — но только если оно уникально среди всех обнаружений; неоднозначные имена пропускаются с сообщением.

Шаг 2 выполняется рано намеренно. Полная проверка хранилища может занять часы, и обнаружить неверный пароль после её завершения — значит потерять всё окно обслуживания.


Требования

КомпонентВерсияПримечания
Windows10 / Server 2016+Нужен доступ к реестру и COM
PowerShell7.2 или новееПроверено на 7.6.3
ClamAV1.xПроверено на 1.5.3
hMailServer5.xCOM-объект hMailServer.Application зарегистрирован
ПраваАдминистраторНужны для хранилища, службы и COM

Установка

1. Установить ClamAV

winget install ClamAV.ClamAV

2. Настроить его под эту задачу

git clone https://github.com/paulmann/hmailserver-clamav-cleanup.git
cd hmailserver-clamav-cleanup
pwsh.exe -NoProfile -File .\Install-ClamAvForHMail.ps1 -ConfigSource .

Почему winget и Install-ClamAvForHMail.ps1?

Эти два шага решают разные задачи и намеренно не объединены:

ШагОтветственность
winget install ClamAV.ClamAVПолучает и устанавливает сам продукт: скачивает подписанный пакет вендора, распаковывает бинарники в C:\Program Files\ClamAV, регистрирует метаданные для удаления и остаётся идемпотентным и обновляемым через winget upgrade.
Install-ClamAvForHMail.ps1Выполняет постустановочную интеграцию, о которой не может знать ни один пакетный менеджер: создаёт C:\ProgramData\ClamAV\logs и ...\tmp, проверяет конфигурационные файлы и отказывается работать при активной строке Example, запускает freshclam для первой загрузки сигнатур, регистрирует и запускает службу clamd, ставит её на автозапуск и проверяет, что clamdscan реально достаёт демон.

Упаковать установщик вендора внутрь скрипта проекта означало бы тащить с собой логику скачивания, проверку хешей и обработку обновлений, которые winget уже делает корректно и безопасно. И наоборот: winget не умеет создавать каталоги, которых ждёт эта конфигурация, а это упущение фатально — freshclam и clamd инициализируют логгер раньше всего остального, поэтому отсутствующий каталог логов прерывает их с ошибкой Failed to open log file ... No such file or directory, и служба так и не регистрируется.

3. Сохранить учётные данные hMailServer

Экспортируйте креды один раз, чтобы пароль никогда не появлялся ни в командной строке, ни в определении задачи:

$dir = Join-Path $env:ProgramData 'HMailClamAvCleanup'
New-Item -ItemType Directory -Force -Path $dir | Out-Null

Get-Credential -UserName 'Administrator' -Message 'hMailServer administrator' |
    Export-Clixml -LiteralPath (Join-Path $dir 'hmail.cred.xml')

Скрипт находит этот файл автоматически, поэтому -AdminPassword становится необязательным. PSCredential заодно несёт в себе имя учётной записи, которое переопределяет -AdminUser.

Export-Clixml защищает секрет через DPAPI и привязывает его к одному пользователю на одной машине. Создавайте файл под той учётной записью, от имени которой будет идти проверка, — полный разбор настройки задачи см. в разделе Работа без оператора.


Конфигурация

Файлы clamd.conf и freshclam.conf в этом репозитории настроены под почтовое хранилище. Самые важные параметры:

ПараметрЗначениеОбоснование
TCPSocket3310Демон под Windows поддерживает только TCP-сокеты
TCPAddr127.0.0.1localhost может разрешаться в ::1, тогда как клиент подключается по IPv4
ScanMailyesБез этого вложения внутри .eml вообще не распаковываются
ScanArchiveyesОбход вложенных архивов
AlertOLE2MacrosnoИначе каждый документ с макросами становится обнаружением
AlertEncryptedArchivenoАрхивы с паролем — обычное дело в почте
AlertExceedsMaxyesФайлы, упёршиеся в лимит, помечаются, а не проходят как чистые
MaxFileSize100MДержите согласованным с -MaxFileSizeMB
ExcludePathкарантинЗначение — регулярное выражение; разделители нужно экранировать
NotifyClamdclamd.conffreshclam перезагружает демон после обновления, перезапуск не нужен

Добавьте в исключения резидентного антивируса:

  • C:\mail\quarantine — иначе карантинные копии будут уничтожены и откат станет невозможен.
  • C:\Program Files\ClamAV\database — ускоряет обновление сигнатур.

Проверка перед первым запуском

1. Демон доступен

Test-NetConnection -ComputerName 127.0.0.1 -Port 3310
Get-Service -Name clamd | Select-Object Name, Status, StartType

2. Диагностика окружения

.\Invoke-HMailClamAvCleanup.ps1 -DiagnoseOnly -Debug

Ожидается движок Clamd, clamd instances: 1, три файла сигнатур со свежей отметкой времени и probe exit code: 0. -DiagnoseOnly не обращается к COM API, поэтому учётные данные ему не нужны вовсе.

3. Проверка парсера — критически важный шаг

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Debug

Значение clean files обязано совпадать с files to scan. Расхождение означает, что разбор вывода сломан, и результат 0 detections ничего не доказывает.

4. Проверка обнаружения

$dir = 'C:\mail\scripts\av\eicar-test'
New-Item -ItemType Directory -Force -Path $dir | Out-Null
$body = -join (@(88,53,79,33,80,37,64,65,80,91,52,92,80,90,88,53,52,40,80,94,41,55,67,67,41,55,125,36,69,73,67,65,82,45,83,84,65,78,68,65,82,68,45,65,78,84,73,86,73,82,85,83,45,84,69,83,84,45,70,73,76,69,33,36,72,43,72,42) | ForEach-Object { [char] $_ })
[System.IO.File]::WriteAllText("$dir\test.eml", $body)
& 'C:\Program Files\ClamAV\clamdscan.exe' --no-summary "$dir\test.eml"
"exit=$LASTEXITCODE"
Remove-Item -LiteralPath "$dir\test.eml" -Force -ErrorAction Ignore

Ожидается Eicar-Test-Signature FOUND и exit=1. Сначала исключите тестовый каталог из резидентной защиты, иначе файл исчезнет раньше, чем его увидит ClamAV.


Использование

При сохранённых учётных данных аргумент с паролем не нужен:

Проверка последних суток, только отчёт

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Debug

Полная проверка хранилища со свежими сигнатурами

.\Invoke-HMailClamAvCleanup.ps1 -UpdateDatabase -TimeoutMinutes 480 -Debug

Явные учётные данные из произвольного места

$cred = Import-Clixml -LiteralPath 'C:\mail\scripts\av\hmail.cred.xml'
.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -AdminPassword $cred

Включить очередь доставки

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -IncludeQueue

Предпросмотр удалений без каких-либо изменений

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Delete -WhatIf

Реальное удаление

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Delete

Полностью автономный запуск, без запросов при любых условиях

.\Invoke-HMailClamAvCleanup.ps1 -NonInteractive -Delete -DeleteOrphan -Confirm:$false

Демон недоступен

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Engine Clamscan

Демон не может читать хранилище (работает под другой учётной записью)

.\Invoke-HMailClamAvCleanup.ps1 -SinceDays 1 -Engine Clamd -Stream

Справочник параметров

Расположение компонентов

ПараметрТипПо умолчаниюОписание
-ClamRootstringC:\Program Files\ClamAVКаталог установки ClamAV
-ClamdScanPathstringавтоЯвный путь к clamdscan.exe
-ClamScanPathstringавтоЯвный путь к clamscan.exe
-FreshclamPathstringавтоЯвный путь к freshclam.exe
-DataDirectorystringреестрКаталог Data hMailServer

Учётные данные

ПараметрТипПо умолчаниюОписание
-AdminPasswordobjectавтопоискПринимает string, securestring или PSCredential, поэтому результат Import-Clixml можно передавать напрямую
-AdminUserstringAdministratorУчётная запись для аутентификации через COM; игнорируется, если PSCredential несёт имя пользователя
-CredentialPathstringавтопоискЯвный путь к файлу кредов, созданному Export-Clixml
-NonInteractiveswitchвыкл.Никогда не запрашивать пароль; вместо этого завершаться ошибкой, если креды не разрешились

-AdminPassword намеренно объявлен как object, а не string. Привязка SecureString к параметру строкового типа молча превращает его в буквальный текст System.Security.SecureString, который ни к чему не аутентифицируется, и скрипт возвращается к интерактивному запросу пароля.

Поведение сканирования

ПараметрТипПо умолчаниюОписание
-EngineAuto | Clamd | ClamscanAutoВыбор движка
-SinceDays036500Только письма новее N дней; 0 — сканировать всё
-IncludeQueueswitchвыкл.Включить файлы в корне Data (очередь доставки)
-Streamswitchвыкл.Передавать демону содержимое файлов вместо путей
-MaxFileSizeMB14096100Лимиты размера для движка Clamscan
-TimeoutMinutes01440240Таймаут одного вызова; 0 — ждать бесконечно
-UpdateDatabaseswitchвыкл.Запустить freshclam перед сканированием

Отчёты и удаление

ПараметрТипПо умолчаниюОписание
-ReportDirectorystringC:\mail\reports\clamavКорень для артефактов запуска
-QuarantineDirectorystringC:\mail\quarantineКорень для карантинных копий
-SkipQuarantineswitchвыкл.Удалять без сохранения копий
-Deleteswitchвыкл.Удалять найденные письма через COM
-DeleteOrphanswitchвыкл.Удалять также обнаружения без записи в базе
-DiagnoseOnlyswitchвыкл.Только проверка окружения и пробное сканирование
-MaxDebugLines105000120Глубина дампа в трассировке

Работают стандартные ключи CmdletBinding: -Debug, -Verbose, -WhatIf, -Confirm. Значение ConfirmImpactHigh, поэтому автономным запускам нужен -Confirm:$false.


Работа без оператора

Интерактивные запуски спрашивают пароль hMailServer. Запуск по расписанию обязан передать его без консоли и обязан громко упасть, если не может, а не висеть вечно на невидимом запросе.

Порядок разрешения учётных данных

Resolve-HMailAdminCredential перебирает источники и останавливается на первом сработавшем:

  1. Значение, переданное в -AdminPasswordstring, securestring или PSCredential.
  2. Явный -CredentialPath.
  3. Переменная среды HMAIL_CRED_PATH.
  4. Автопоиск hmail*.cred.xml, cred.xml или credential.xml в каталоге скрипта, .\creds\, %ProgramData%\HMailClamAvCleanup\ и %LOCALAPPDATA%\HMailClamAvCleanup\. Побеждает самый свежий файл по LastWriteTimeUtc.
  5. Интерактивный запрос — подавляется ключом -NonInteractive, который вместо запроса выбрасывает исключение.

Каждое решение трассируется в debug.log под категорией [auth], поэтому неожиданный выбор легко разобрать по факту.

Шаг 1 — Создать выделенную сервисную учётную запись

Отдельная локальная учётка сужает область действия DPAPI и делает аудиторский след читаемым:

$password = Read-Host -AsSecureString -Prompt 'Password for svc_clamav'

New-LocalUser -Name 'svc_clamav' -Password $password `
    -FullName 'ClamAV cleanup service' `
    -Description 'Runs Invoke-HMailClamAvCleanup.ps1' `
    -PasswordNeverExpires -AccountNeverExpires

Add-LocalGroupMember -Group 'Administrators' -Member 'svc_clamav'

Права локального администратора здесь не опциональны: скрипт читает хранилище сообщений и создаёт COM-объект hMailServer.Application.

Шаг 2 — Экспортировать креды от имени этой учётной записи

DPAPI привязывает файл к одному пользователю на одной машине, поэтому записывать его нужно из сессии, работающей от svc_clamav:

# Сначала запустите оболочку от имени сервисной учётной записи:
#   runas /user:MAILSERVER\svc_clamav "pwsh.exe -NoExit"

$dir = Join-Path $env:ProgramData 'HMailClamAvCleanup'
New-Item -ItemType Directory -Force -Path $dir | Out-Null

Get-Credential -UserName 'Administrator' -Message 'hMailServer administrator' |
    Export-Clixml -LiteralPath (Join-Path $dir 'hmail.cred.xml')

Шаг 3 — Ограничить ACL

Скрипт предупреждает, если файл читаем группами Everyone или BUILTIN\Users, сравнивая общеизвестные SID, — поэтому проверка работает и на локализованной Windows. Отключите наследование и выдайте только необходимое:

$dir = Join-Path $env:ProgramData 'HMailClamAvCleanup'
$acl = Get-Acl -LiteralPath $dir
$acl.SetAccessRuleProtection($true, $false)

'SYSTEM', 'Administrators', 'MAILSERVER\svc_clamav' | ForEach-Object {
    $acl.AddAccessRule([System.Security.AccessControl.FileSystemAccessRule]::new(
        $_, 'FullControl', 'ContainerInherit,ObjectInherit', 'None', 'Allow'))
}

Set-Acl -LiteralPath $dir -AclObject $acl

Шаг 4 — Зарегистрировать задачу в планировщике

$script = 'C:\mail\scripts\av\Invoke-HMailClamAvCleanup.ps1'
$pwsh   = 'C:\Program Files\PowerShell\7\pwsh.exe'

$argument = @(
    '-NoProfile'
    '-NonInteractive'
    '-ExecutionPolicy', 'Bypass'
    '-File', "`"$script`""
    '-NonInteractive'
    '-SinceDays', '2'
    '-UpdateDatabase'
    '-Delete'
    '-TimeoutMinutes', '600'
    '-Confirm:$false'
) -join ' '

$action = New-ScheduledTaskAction -Execute $pwsh -Argument $argument `
    -WorkingDirectory (Split-Path -Parent $script)

$trigger = New-ScheduledTaskTrigger -Daily -At '03:30'

$settings = New-ScheduledTaskSettingsSet `
    -ExecutionTimeLimit (New-TimeSpan -Hours 12) `
    -MultipleInstances IgnoreNew `
    -StartWhenAvailable `
    -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries `
    -RestartCount 1 -RestartInterval (New-TimeSpan -Minutes 30)

$runAs = Get-Credential -UserName 'MAILSERVER\svc_clamav' -Message 'Task run-as account'

Register-ScheduledTask -TaskName 'hMailServer ClamAV cleanup' `
    -Description 'Detect-only ClamAV sweep of the hMailServer store, then delete infected messages through COM.' `
    -Action $action -Trigger $trigger -Settings $settings `
    -User $runAs.UserName `
    -Password $runAs.GetNetworkCredential().Password `
    -RunLevel Highest

Четыре детали определяют, выживет ли это в продакшене:

  • -User и -Password, а не -Principal. Они относятся к разным наборам параметров Register-ScheduledTask. Передача одного принципала регистрирует задачу, которая молча отказывается запускаться, пока никто не вошёл в систему.
  • -ExecutionTimeLimit должен превышать -TimeoutMinutes. Иначе планировщик убьёт процесс посреди проверки — возможно, между созданием карантинной копии и удалением через COM.
  • -MultipleInstances IgnoreNew. Полная проверка хранилища может пережить собственный суточный интервал; без этого второй экземпляр запустится поверх первого.
  • pwsh -NonInteractive не подавляет Read-Host. Отсутствие запроса гарантирует только собственный ключ скрипта -NonInteractive — поэтому выше присутствуют оба.

-SinceDays 2 перекрывается намеренно: один пропущенный запуск не оставляет разрыва в покрытии.

Шаг 5 — Проверить первый запуск

Start-ScheduledTask -TaskName 'hMailServer ClamAV cleanup'

Get-ScheduledTaskInfo -TaskName 'hMailServer ClamAV cleanup' |
    Select-Object LastRunTime, LastTaskResult, NumberOfMissedRuns

LastTaskResult равный 0 означает успех. Затем убедитесь, откуда взялись учётные данные:

$run = Get-ChildItem 'C:\mail\reports\clamav' -Directory |
    Sort-Object LastWriteTime -Descending | Select-Object -First 1

Select-String -LiteralPath (Join-Path $run.FullName 'debug.log') -Pattern '\[auth\]'

Ожидаемый вывод:

[auth] credential file discovered: C:\ProgramData\HMailClamAvCleanup\hmail.cred.xml
[auth] credential loaded from C:\ProgramData\HMailClamAvCleanup\hmail.cred.xml as System.Management.Automation.PSCredential
[auth] COM identity resolved before scanning: Administrator

Для безопасной репетиции уберите -Delete и сузьте область через -SinceDays 1.

Смена пароля

Перезапишите файл от имени svc_clamav; задачу менять не нужно, поскольку путь остался прежним:

Get-Credential -UserName 'Administrator' -Message 'hMailServer administrator' |
    Export-Clixml -LiteralPath "$env:ProgramData\HMailClamAvCleanup\hmail.cred.xml"

.\Invoke-HMailClamAvCleanup.ps1 -NonInteractive -SinceDays 1 -Debug

Альтернативные хранилища секретов

ХранилищеКогда подходитОграничение
Export-Clixml (DPAPI)Один хост с одной выделенной сервисной учётной записьюПривязано к одному пользователю на одной машине; непригодно после сброса профиля или переустановки ОС
Protect-CmsMessageЗадача работает от SYSTEM либо один секрет нужен нескольким учётным записямТребует сертификат Document Encryption и ACL на его закрытый ключ
SecretManagement + SecretStoreНесколько секретов с плановой ротациейХранилище тоже привязано к пользователю; регистрируйте его от учётной записи задачи с отключёнными аутентификацией и взаимодействием
Group Managed Service AccountДомен с несколькими почтовыми хостамиТолько Active Directory; пароль вообще не попадает в скрипт

Артефакты запуска

Каждый запуск создаёт <ReportDirectory>\run-<timestamp>\:

ФайлСодержимое
debug.logПолная трассировка, пишется всегда, независимо от -Debug
transcript.logТранскрипт сессии PowerShell
clamav-scan.logЛог сканера, созданный ключом --log
clamav-scan.stdoutЗахваченный вывод консоли
scan-scope.lstСписок просканированных путей в UTF-8
infected-messages.csvУчётная запись, папка, ID письма, дата, отправитель, тема, размер, вердикт, путь
infected-messages.jsonТе же данные в JSON

Три строки, за которыми стоит следить:

files to scan: 1 015
clean files: 1015; detections: 0; error lines: 0; elapsed 00:00:03
matched messages: 0; unmatched detections: 0

Ненулевое error lines означает, что эти файлы не признаны чистыми. Подозрительно быстрое повторное сканирование — норма: clamd кеширует результаты по хешу файла.


Коды возврата

Код ClamAVЗначениеПоведение скрипта
0Угроз не найденоПродолжать
1Угрозы найденыПродолжать и разбирать обнаружения
2Ошибка сканированияПредупредить, если разобрано хотя бы одно обнаружение, иначе прервать работу

Диагностика проблем

СимптомПричинаРешение
Could not connect to clamd ... Connection refusedДемон остановлен или упал при стартеПроверьте Get-Service clamd и C:\ProgramData\ClamAV\logs\clamd.log; временно используйте -Engine Clamscan
Failed to open log file ... No such file or directoryОтсутствует C:\ProgramData\ClamAV\logsСоздайте каталог; ClamAV его никогда не создаёт
net start clamdимя службы недопустимоclamd.exe --install не сработал, обычно после ошибки логгераИсправьте каталоги, повторите с повышением прав, проверьте через Get-Service -Name '*clam*'
clean filesfiles to scanСломан разбор выводаУбедитесь, что используется парсер, не зависящий от разделителей; clamdscan на Windows не всегда разделяет результаты через CR, LF или NUL
Cannot create hMailServer.ApplicationCOM не зарегистрирован или несовпадение разрядностиПроверьте регистрацию; 0x80040154 обычно означает неверную архитектуру процесса
Запрос пароля появляется, хотя -AdminPassword был переданВ старой сборке параметр объявлен как securestring или string, и значение было приведено к строкеОбновитесь до сборки, где -AdminPassword имеет тип object
Cannot read ...cred.xml ... bound to one user on one machineФайл экспортирован другой учётной записью или на другом хостеПерезапишите файл под учётной записью, от имени которой работает задача
No hMailServer password was supplied, no credential file was foundАвтопоиск ничего не нашёл, а запрос пароля отключёнПередайте -CredentialPath, задайте HMAIL_CRED_PATH или положите файл в один из просматриваемых каталогов
hMailServer authentication failed for the ... accountНеверный пароль или PSCredential с неожидаемым именем пользователяПерезапишите креды или передайте -AdminUser явно
Задача не запускается при выходе из системыЗарегистрирована с -Principal вместо -User и -PasswordПеререгистрируйте, как показано в разделе Работа без оператора
Проверка убита на середине-ExecutionTimeLimit меньше -TimeoutMinutesПоднимите лимит задачи выше таймаута скрипта
Слишком много ложных срабатыванийВключены агрессивные эвристикиОтключите AlertOLE2Macros, AlertEncryptedArchive и DetectPUA в clamd.conf
Сканирование заканчивается за секундыКеш результатов clamdЭто ожидаемо; для «холодного» замера временно поставьте DisableCache yes

Восстановление удалённого письма

Карантинные копии лежат в <QuarantineDirectory>\<timestamp>\ и называются account_at_domain_<id>_<original>.bad.

  1. Найдите строку в infected-messages.csv за этот запуск.
  2. Скопируйте файл из карантина и уберите суффикс .bad.
  3. Вносите письмо обратно средствами hMailServer, а не копированием в папку учётной записи.

Копирование .eml назад в каталог учётной записи не делает письмо видимым: hMailServer отдаёт клиентам содержимое из базы, а не из каталога.


Вопросы безопасности

  • Пароль hMailServer не появляется ни в скрипте, ни в определении задачи, ни в командной строке. Храните его через Export-Clixml (DPAPI) либо передавайте securestring или PSCredential явно.
  • Держите файл кредов в каталоге с отключённым наследованием, доступном на чтение только SYSTEM, Administrators и сервисной учётной записи. Скрипт предупреждает, если доступ сохраняют Everyone или BUILTIN\Users.
  • Пароль в открытом виде существует только на время вызова COM-метода Authenticate, после чего переменная немедленно обнуляется.
  • Предпочитайте выделенную сервисную учётную запись личной: область DPAPI, владение задачей и аудиторский след остаются узкими.
  • Используйте -NonInteractive для каждого запуска по расписанию, чтобы испорченные креды приводили к падению задачи, а не к её незаметному зависанию.
  • В карантине лежит живой вредоносный код. Ограничьте его ACL и исключайте из резидентной проверки осознанно, а не случайно.
  • Удаления вне каталога Data отклоняются, файлы не с расширением .eml не удаляются никогда.
  • «Осиротевшие» обнаружения требуют одновременно -Delete и -DeleteOrphan; поведение по умолчанию — сообщить и остановиться.
  • Всегда делайте резервную копию базы и каталога Data перед первым запуском с удалением.

Ограничения

  • ClamAV обнаруживает существенно меньше, чем коммерческие движки; этот инструмент дополняет резидентную защиту, а не заменяет её.
  • Сопоставление по имени файла отключается при дублирующихся именах; в этом случае используются только абсолютные пути.
  • Обнаружения вне каталога Data попадают в отчёт, но не удаляются.
  • Удаление необратимо за пределами карантина: DeleteByDBID убирает и запись, и файл.
  • Файл кредов DPAPI нельзя передать между учётными записями или хостами; каждой машине и каждой сервисной учётной записи нужен свой экспорт.
  • Клиентам IMAP может потребоваться сжатие папок после массового удаления.

Планы развития

  • [ ] Вспомогательный скрипт повторного внесения письма из карантина через COM API
  • [ ] Опциональные бэкенды кредов SecretManagement и DPAPI-NG для задач, работающих от SYSTEM
  • [ ] Опциональный бэкенд Microsoft Defender через MpCmdRun.exe для хостов, где Defender работает в обычном режиме
  • [ ] Набор тестов Pester, проверяющий парсер на записанном выводе сканера
  • [ ] Опциональное уведомление по почте или через webhook при обнаружении
  • [ ] Файл метрик в формате, пригодном для Prometheus, на каждый запуск

Участие в разработке

Issues и pull requests приветствуются. Пожалуйста, убедитесь, что:

  • Invoke-ScriptAnalyzer -Path .\<script>.ps1 не выдаёт ошибок и предупреждений, без подавлений;
  • исходные файлы остаются в чистом ASCII, а локализованные шаблоны выражены escape-последовательностями \uXXXX;
  • деструктивные ветки кода по-прежнему защищены ShouldProcess;
  • новое поведение задокументировано в этом README.

Лицензия

Распространяется по лицензии MIT. См. LICENSE.


Автор

Михаил Дейнекин

Репозиторий: https://github.com/paulmann/hmailserver-clamav-cleanup

Добавить комментарий

Разработка и продвижение сайтов webseed.ru
Прокрутить вверх