Антивирусная проверка хранилища сообщений 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. - Честный учёт ошибок. Файлы, которые сканер не смог прочитать, считаются отдельно и никогда не отмечаются как чистые.
- Предохранители. Пути вне каталога
DatahMailServer не удаляются никогда; файлы не с расширением.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 выполняется рано намеренно. Полная проверка хранилища может занять часы, и обнаружить неверный пароль после её завершения — значит потерять всё окно обслуживания.
Требования
| Компонент | Версия | Примечания |
|---|---|---|
| Windows | 10 / Server 2016+ | Нужен доступ к реестру и COM |
| PowerShell | 7.2 или новее | Проверено на 7.6.3 |
| ClamAV | 1.x | Проверено на 1.5.3 |
| hMailServer | 5.x | COM-объект 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 в этом репозитории настроены под почтовое хранилище. Самые важные параметры:
| Параметр | Значение | Обоснование |
|---|---|---|
TCPSocket | 3310 | Демон под Windows поддерживает только TCP-сокеты |
TCPAddr | 127.0.0.1 | localhost может разрешаться в ::1, тогда как клиент подключается по IPv4 |
ScanMail | yes | Без этого вложения внутри .eml вообще не распаковываются |
ScanArchive | yes | Обход вложенных архивов |
AlertOLE2Macros | no | Иначе каждый документ с макросами становится обнаружением |
AlertEncryptedArchive | no | Архивы с паролем — обычное дело в почте |
AlertExceedsMax | yes | Файлы, упёршиеся в лимит, помечаются, а не проходят как чистые |
MaxFileSize | 100M | Держите согласованным с -MaxFileSizeMB |
ExcludePath | карантин | Значение — регулярное выражение; разделители нужно экранировать |
NotifyClamd | clamd.conf | freshclam перезагружает демон после обновления, перезапуск не нужен |
Добавьте в исключения резидентного антивируса:
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
Справочник параметров
Расположение компонентов
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
-ClamRoot | string | C:\Program Files\ClamAV | Каталог установки ClamAV |
-ClamdScanPath | string | авто | Явный путь к clamdscan.exe |
-ClamScanPath | string | авто | Явный путь к clamscan.exe |
-FreshclamPath | string | авто | Явный путь к freshclam.exe |
-DataDirectory | string | реестр | Каталог Data hMailServer |
Учётные данные
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
-AdminPassword | object | автопоиск | Принимает string, securestring или PSCredential, поэтому результат Import-Clixml можно передавать напрямую |
-AdminUser | string | Administrator | Учётная запись для аутентификации через COM; игнорируется, если PSCredential несёт имя пользователя |
-CredentialPath | string | автопоиск | Явный путь к файлу кредов, созданному Export-Clixml |
-NonInteractive | switch | выкл. | Никогда не запрашивать пароль; вместо этого завершаться ошибкой, если креды не разрешились |
-AdminPassword намеренно объявлен как object, а не string. Привязка SecureString к параметру строкового типа молча превращает его в буквальный текст System.Security.SecureString, который ни к чему не аутентифицируется, и скрипт возвращается к интерактивному запросу пароля.
Поведение сканирования
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
-Engine | Auto | Clamd | Clamscan | Auto | Выбор движка |
-SinceDays | 0–3650 | 0 | Только письма новее N дней; 0 — сканировать всё |
-IncludeQueue | switch | выкл. | Включить файлы в корне Data (очередь доставки) |
-Stream | switch | выкл. | Передавать демону содержимое файлов вместо путей |
-MaxFileSizeMB | 1–4096 | 100 | Лимиты размера для движка Clamscan |
-TimeoutMinutes | 0–1440 | 240 | Таймаут одного вызова; 0 — ждать бесконечно |
-UpdateDatabase | switch | выкл. | Запустить freshclam перед сканированием |
Отчёты и удаление
| Параметр | Тип | По умолчанию | Описание |
|---|---|---|---|
-ReportDirectory | string | C:\mail\reports\clamav | Корень для артефактов запуска |
-QuarantineDirectory | string | C:\mail\quarantine | Корень для карантинных копий |
-SkipQuarantine | switch | выкл. | Удалять без сохранения копий |
-Delete | switch | выкл. | Удалять найденные письма через COM |
-DeleteOrphan | switch | выкл. | Удалять также обнаружения без записи в базе |
-DiagnoseOnly | switch | выкл. | Только проверка окружения и пробное сканирование |
-MaxDebugLines | 10–5000 | 120 | Глубина дампа в трассировке |
Работают стандартные ключи CmdletBinding: -Debug, -Verbose, -WhatIf, -Confirm. Значение ConfirmImpact — High, поэтому автономным запускам нужен -Confirm:$false.
Работа без оператора
Интерактивные запуски спрашивают пароль hMailServer. Запуск по расписанию обязан передать его без консоли и обязан громко упасть, если не может, а не висеть вечно на невидимом запросе.
Порядок разрешения учётных данных
Resolve-HMailAdminCredential перебирает источники и останавливается на первом сработавшем:
- Значение, переданное в
-AdminPassword—string,securestringилиPSCredential. - Явный
-CredentialPath. - Переменная среды
HMAIL_CRED_PATH. - Автопоиск
hmail*.cred.xml,cred.xmlилиcredential.xmlв каталоге скрипта,.\creds\,%ProgramData%\HMailClamAvCleanup\и%LOCALAPPDATA%\HMailClamAvCleanup\. Побеждает самый свежий файл поLastWriteTimeUtc. - Интерактивный запрос — подавляется ключом
-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 files ≠ files to scan | Сломан разбор вывода | Убедитесь, что используется парсер, не зависящий от разделителей; clamdscan на Windows не всегда разделяет результаты через CR, LF или NUL |
Cannot create hMailServer.Application | COM не зарегистрирован или несовпадение разрядности | Проверьте регистрацию; 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.
- Найдите строку в
infected-messages.csvза этот запуск. - Скопируйте файл из карантина и уберите суффикс
.bad. - Вносите письмо обратно средствами 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.
Автор
Михаил Дейнекин
- Сайт: deynekin.com
- GitHub: @paulmann
- Email: git@deynekin.com
Репозиторий: https://github.com/paulmann/hmailserver-clamav-cleanup