Михаил Кондрин
Белые списки в DSPAM
Как позволить пользователям настраивать индивидуальные белые списки в DSPAM?
Белые списки и статистические спам-фильтры
Использование статистических спам-фильтров на почтовом сервере в последнее время завоевало большую популярность среди системных администраторов. Несмотря на то что такое решение оказывает серьёзную нагрузку на вычислительные ресурсы сервера и не даёт снижения паразитного (спамерского) трафика в отличие от решений основанных, к примеру, на серых списках, для конечного пользователя этот вариант оказывается вполне удобным. Использование статистических фильтров на стороне сервера в отличие от клиентских (например, встроенных в почтовую программу) позволяет повысить точность оценок, выставляемых фильтром, за счёт использования большего объёма данных в силу закона больших чисел. Кроме того, как правило, статистические фильтры обычно конфигурируются таким образом, что письма доставляются пользователю независимо от выставляемых оценок, что гарантирует сохранность писем даже в случае ошибок, допускаемых фильтром. По сравнению с greylisting, когда разные почтовые серверы достаточно гибко подходят к интерпретации ошибок 4xx, что зачастую (при ненастроенных белых списках) приводит к потере писем, этот вариант даже предпочтительнее для пользователей, которые параноидально относятся к вероятности пропажи своей корреспонденции. Безусловно, статистические фильтры не являются панацеей, но в комбинации с другими средствами они серьёзно снижают привлекательность вашей почтовой системы для спамеров.
На страницах журнала [1] подробно описывались функциональность и особенности настройки одного из популярных серверных спам-фильтров DSPAM [2], так что на этом вопросе можно не останавливаться. Вместе с тем многих пользователей и администраторов смущает отсутствие в DSPAM принудительных белых списков (whitelisting), причём это ограничение для некоторых является весомым аргументом, чтобы отказываться от DSPAM в пользу самодельных решений [3]. Хотя в качестве опции в DSPAM есть возможность вести автоматические белые списки, когда адресат заносится в белый список после получения от него определённого количества неспамных писем (обычно 10), при том что пользователи ни разу не заносили его корреспонденцию в спам. Но обычно под белыми списками подразумеваются списки, создаваемые заранее вручную пользователем, со списком корреспондентов по умолчанию «хороших». В этом смысле действительно DSPAM такую функциональность не предоставляет, и (более того) есть несколько вполне весомых аргументов, почему whitelisting, выполненный в таком стиле, вреден. Например, как разрешить конфликт с письмом, которое при разборе идентифицировано как спам, но его отправитель в белом списке (ситуация, которая в автоматическом whitelisting крайне маловероятна)? Может быть, специалисты по статистическому анализу и могут предложить хороший вариант решения этой проблемы, но, вообще говоря, искусственное вмешательство в процесс тренировки спам-фильтра может серьёзно сказаться на его точности. Так что, по мнению противников белых списков, к числу которых относится и автор DSPAM Jonathan Zdziarski, эта функциональность представляет собой tick-feature, т.е. нечто, что пользователи хотят видеть в системе, но чем они реально не пользуются (или пользуются, но без особой выгоды для себя).
Вместе с тем, белые списки легко могут быть реализованы в DSPAM, так почему бы не предоставить пользователям то, чего они добиваются, постаравшись при этом минимизировать вред, который они могут себе нанести.
Но сначала о грустном. К сожалению, перспективы дальнейшего развития DSPAM представляются достаточно туманными. В мае 2007 года автор продал все права на DSPAM (вместе с домашним сайтом проекта) компании Sensory Networks. Перед самой продажей был выпущен новый (последний официальный на сегодняшний день) релиз 3.8.0, но поскольку, по всей видимости, он делался в спешке, то результат получился не слишком удачным. Как впоследствии сам автор жаловался в списке рассылки, он был введён в заблуждение относительно намерений этой компании и рассчитывал, что она будет продолжать развитие проекта. Однако этого не случилось. Проект фактически заморожен. Сама компания разработкой не занимается, лишь в начале 2008 в CVS проекта были внесены патчи, созданные сообществом пользователей DSPAM, но с тех пор всякая активность прекратилась, несмотря на то, что в настоящее время снова образовалась разрозненная масса заплаток, которую стоило бы добавить в проект. На странице [2] работает пользовательский список рассылки (dspam-users), но достучаться через него до сотрудников Sensory Networks (если таковые существуют) бесполезно.
Вообще говоря, давно уже назрела необходимость форка проекта (тем более что юридических препятствий к этому нет, так как DSPAM по-прежнему остаётся под лицензией GPL), к чему, кстати, призывал и сам автор, оговорившись, однако, что сам он этого делать не хочет и не может, будучи связанным контрактом. Вопрос заключается только в отсутствии желающих, и хотя бурные дискуссии на эту тему время от времени возникают в списке рассылки, но пока нового лидера проекта найти не удалось. Это ещё не поздно сделать, пока у пользователей ещё не пропал интерес к проекту. Понятно также, что время тут играет против, и сроки уже, честно говоря, поджимают.
Так что дальнейший текст предназначен в основном для тех администраторов, у которых уже имеется установленный DSPAM и у которых даже после прочтения предыдущего абзаца не появилось желания срочно снести его и установить SpamAssassin (для свежих инсталляций в данный момент это наиболее предпочтительный вариант). Предполагается, что используется версия DSPAM из CVS, которая в разных дистрибутивах обозначается как 3.8.1-3.8.4. Анонимный доступ к CVS открыт, поэтому при отсутствии подходящей бинарной версии в дистрибутиве можно скачать исходный код:
cvs -z3 -d :pserver:cvs@cvs.nuclearelephant.com:/usr/local/cvsroot login
cvs -z3 -d :pserver:cvs@cvs.nuclearelephant.com:/usr/local/cvsroot co dspam
и скомпилировать dspam используя скрипт configure:
./configure --sysconfdir=/etc \
--prefix=/usr \
--with-dspam-home=/var/dspam \
--with-storage-driver=mysql_drv \
--with-mysql-includes=/usr/include/mysql \
--with-mysql-libraries=/usr/lib/mysql \
--enable-preferences-extension \
--enable-virtual-users \
--enable-debug \
--enable-verbose-debug \
--enable-daemon
DSPAM в качестве LMTP-фильтра
Реализация белых списков в DSPAM не является универсальным решением, а привязана к конкретной системе, так что мне придётся вначале описать способ установки DSPAM, тем более что он в моем случае существенно отличается от описанного, например, в статье [1]. «Общепринятый способ» состоит во встраивании DSPAM в виде фильтра в MailTransferAgent, а его тренировка пользователем заключается в пересылке писем на специальные адреса либо же использовании веб-интерфейса. Однако мне больше нравится способ, предложенный в [4], когда DSPAM устанавливается между MTA и IMAP4-сервером, что позволяет как бы «приблизить» DSPAM к конечному пользователю, делая, с одной стороны, тренировку фильтра более удобной, а с другой, не привязывая пользователя к конкретному почтовому клиенту. Фактически тренировка DSPAM сводится к перемещению писем между IMAP-каталогами, что может быть выполнено в любом мало-мальски продвинутом почтовом клиенте. К его недостатку можно отнести тот факт, что он работает только для протокола IMAP4.
В руководстве [4] разобран способ установки DSPAM в качестве LMTP-фильтра между Sendmail и Cyrus-IMAP, т.е. полученное письмо передаётся по LMTP-протоколу от MTA к DSPAM, который проверяет письмо и в зависимости от выставляемых оценок пересылает его по LMTP-протоколу в один из пользовательских почтовых ящиков Cyrus-IMAP (с помощью «+» адресации). Эта схема была несколько модифицирована мною, во-первых, Sendmail заменён на Postfix. При этом конфигурация, описанная в статье [5], применима в данном случае практически без изменений. Единственный параметр, который требуется изменить в конфигурационном файле /etc/postfix/main.cf, – это замена стандартного транспорта, по которому отсылаются письма:
mailbox_transport = lmtp:unix:/var/dspam/dspam.sock
Это, как вы понимаете, просто название именованного сокета, прослушиваемого демоном dspam.
Во-вторых, вместо использования «+» адресации при отсылке проверенных писем в моем варианте DSPAM просто вставлял оценки в письма в виде нескольких заголовков, в расчёте на то, что пользователь сможет либо отфильтровать спамные письма в почтовом клиенте, либо использовать sieve-shell в Cyrus-IMAP. Эта группа заголовков имеет такой вид:
X-DSPAM-Result: Spam
X-DSPAM-Processed: Sun Sep 7 17:56:35 2008
X-DSPAM-Confidence: 0.9158
X-DSPAM-Probability: 1.0000 |
Таким образом, для перехвата такого рода писем, перемещения их в специальный Junk-каталог и маркирования их как «удалённых» можно воспользоваться следующим sieve-скриптом [6]:
require ["regex", "relational", "fileinto", "envelope", "imapflags"];
if header :contains ["X-DSPAM-Result"] "Spam"
{addflag "\\Deleted"; fileinto "Junk"; stop;}
Так что, как видно из рисунка cо снимком почтового клиента Thunderbird, для очистки каталога Junk достаточно будет нажать кнопку Compact (которая выполняет IMAP-команду expunge в соответствующем каталоге на сервере). Очень удобно! Хотя пользователь может сам выбрать, каким образом ему помечать спам, например, не перемещая его и не удаляя, а просто используя стандартную пиктограмму Thunderbird «Junk» (addflag «Junk»;). Стоит ещё упомянуть в этой связи, что для Thunderbird есть хорошее расширение, позволяющее редактировать sieve-скрипты [7], правда, и в этом случае лучше всё же использовать версию из CVS.
Результат работы статистического фильтра в окне Thunderbird
Таким образом два звена цепочки – Cyrus-IMAP и Postfix уже подготовлены для фильтрации спама. Осталось только соединить их с помощью демона dspam. Его настройка приведена в листинге, который я комментировать не буду, отсылая читателя к статье [1]:
Home /var/dspam
Trust root
Trust mail
Trust cyrus
StorageDriver /usr/lib/libmysql_drv.so
# --- Cyrus-IMAP ---
DeliveryHost /var/imap/socket/lmtp
DeliveryProto LMTP
# --- MySQL ---
MySQLServer /var/run/mysql/mysql.sock
MySQLPort 3306
MySQLUser dspam
MySQLPass _dont_use_this_pass_
MySQLDb dspam
# --- DSPAM daemon/client ---
ServerMode auto
ServerParameters "--deliver=innocent,spam"
ServerDomainSocketPath "/var/dspam/dspam.sock"
ClientHost /var/dspam/dspam.sock
# --- Preferences ---
...
Preference "spamAction=deliver"
Preference "signatureLocation=message"
Feature whitelist
...
Ничего сложного, каждый из блоков отвечает за настройку именованных сокетов, с одного из которых dspam получает письма на проверку от Postfix, а на другой передаёт уже проверенные письма серверу Cyrus-IMAP. Свои данные DSPAM хранит в базе данных MySQL, что отличается от случая, рассмотренного в статье [1], где был использован сервер PostgresSQL, но различия в их настройке применительно к DSPAM минимальны и несущественны.
Также блок Preferences может быть дополнен в соответствии с вашими предпочтениями, я выделил только необходимые опции. Как уже говорилось, независимо от выставленных оценок письмо будет доставлено получателю.
Кроме того, разумеется, включена функция автоматического whitelisting, без которого нам не удастся реализовать принудительный whitelisting, и добавлена опция, указывающая, что метки/signature, которыми dspam помечает проверенные письма, располагаются в теле письма. Это связано с ошибкой в последних версиях DSPAM, которая не позволяет правильно читать сигнатуры, вставленные в заголовки писем. Сами же метки необходимы для того, чтобы DSPAM мог найти данные вычисления оценки для письма в базе данных, если потребуется пересмотреть выставленную оценку.
Теперь запуск DSPAM в виде демона с помощью команды:
/usr/bin/dspam --daemon &
позволяет сортировать корреспонденцию на спам и неспам. Но это ещё только полдела, поскольку нужно дать возможность пользователю обучать статистический фильтр.
Это достигается с помощью создания двух IMAP-каталогов spam и ham в почтовых ящиках каждого из пользователей и периодической их проверкой скриптом, который запускает тренировку dspam. Идея состоит в том, что если пользователь считает оценку, выставленную dspam письму неправильной, то он перемещает false-negatives (письмо, ошибочно классифицированное как неспам) в папку spam, каковым оно и является, а false-positives (легальное письмо, ошибочно классифицированное как спам) в папку ham. Для периодической проверки этих каталогов на стороне сервера я использую скрипт traindspam.tcl:
#!/usr/bin/tclsh
set imapuser "dspam"
set imappasswd "_dont_use_this_pass_"
set dspam "/usr/bin/dspam"
set imaphost "mail.myrealm.ru"
set domain "myrealm.ru"
set spambox "spam"
set hambox "ham"
set otherpref "Other"
#Пользовательские параметры оканчиваются здесь
source /usr/local/bin/imap4.tcl
proc processmail { message username spam } {
#Обучение dspam
global dspam
set f [open "| $dspam --client --user $username --source=error --class=$spam --process" WRONLY]
fconfigure $f -encoding binary -buffering none -blocking 0
puts $f $message
flush $f
close $f
}
proc get_message { stream num } {
#формируем письмо из заголовков и тела письма
::imap4::fetch $stream $num header
set h [::imap4::msginfo $stream $num header]
::imap4::fetch $stream $num text
set h2 [::imap4::msginfo $stream $num text]
return ${h}${h2}
}
set imap [::imap4::open $imaphost]
::imap4::login $imap $imapuser $imappasswd
#Собираем ящики, доступные $imapuser
::imap4::request $imap {list "" "*"}
while 1 {
if { [::imap4::processline $imap] == "*" } {
lappend userslist [lindex [split [lindex $::imap4::info($imap,lastline) end] "."] end-1]
} else {
break
}
}
foreach user [lsort -unique $userslist] {
#Обработка ящиков spam и ham
foreach action [list "spam" "innocent"] {
if { $action == "spam" } {
set d ${otherpref}.${user}.${spambox}
} else {
set d ${otherpref}.${user}.${hambox}
}
::imap4::select $imap \"$d\"
set mn [::imap4::mboxinfo $imap exists]
for {set i 1} {$i <= $mn} {incr i} {
#извлекаем отдельные письма
::imap4::fetch $imap $i flags
if { [lsearch [::imap4::msginfo $imap $i flags] Deleted ] >= 0 } {
continue
}
if { ([lsearch [::imap4::msginfo $imap $i flags] Flagged ] >= 0)&($action == "innocent") } {
whitelist_message $imap $i ${user}@${domain}
}
processmail [get_message $imap $i] ${user}@${domain} $action
::imap4::request $imap "store $i +flags \\deleted"
::imap4::getresponse $imap
}
if { $action == "spam" } {
#очистка ящика spam
::imap4::request $imap "expunge"
::imap4::getresponse $imap
}
}
}
close $imap
Скрипт читает каталоги spam и ham, используя протокол IMAP, для чего необходим файл imap4.tcl, который входит в состав tcllib [8]. Кроме того, чтобы скрипт мог прочитать эти каталоги, он должен каким-то образом зарегистрироваться на сервере Cyrus-IMAP (в данном скрипте – под именем пользователя dspam), и, вообще-то говоря, каталоги spam и ham должны быть, как минимум, доступны для чтения этому пользователю. В конфигурации, рассмотренной в статье [5], для этой цели достаточно создать принципала dspam в базе данных Kerberos. Необходимо также отредактировать начальную часть скрипта, указав там имя этого принципала и его пароль, и остальные параметры, чьё назначение понятно из названий переменных. Единственный неочевидный параметр – это otherpref, который соответствует параметру userprefix: Other из конфигурационного файла /etc/imapd.conf и означает префикс, добавляемый к названиям каталогов других пользователей, с точки зрения пользователя dspam (т.е. каталог ham пользователя johndoe для dspam будет виден как Other.johndoe.ham).
Принцип работы скрипта состоит в следующем. Подключившись к IMAP-серверу, он собирает список всех пользователей, чьи подкаталоги доступны пользователю dspam. Затем последовательно проверяется каждый из каталогов. Если в них обнаруживаются письма без флага \deleted, то такое письмо передается на вход программе dspam (см. функцию process_message), запущенной как клиент демона dspam (ключ --client), в режиме исправления ошибок (ключ --source=error), и тип исправляемой ошибки очевидным образом связан с каталогом, в котором это письмо было обнаружено (--class=spam или --class=innocent). Потом письмо удаляется, т.е. просто помечается флагом \deleted, а каталог spam в итоге ещё и очищается, поскольку содержащиеся там письма для пользователя уж точно никакой ценности не представляют. Заметьте, что в самом скрипте содержится вызов функции whitelist_message, которая как раз и заносит корреспондента в белый список, но обсуждение этой темы я пока отложу.
Массовое создание каталогов ham и spam можно выполнить посредством приведённого ниже скрипта, который использует диалоговый язык expect для вызова интерактивной программы cyradm и с её помощью создаёт эти каталоги во всех зарегистрированных почтовых ящиках, попутно выставляя подходящие права доступа к ним пользователю dspam.
package require Expect
spawn cyradm mail.myrealm.ru
set prompt "\.ru> $"
expect {
-re $prompt
}
exp_send "lm user.*\r"
while 1 {
expect {
-re {user\.([[:alnum:]]+)} {
lappend users $expect_out(1,string)
}
-re $prompt {
break
}
}
}
foreach v2 [lsort -unique $users] {
exp_send "cm user.${v2}.spam;cm user.${v2}.ham\r"
expect {
-re $prompt
}
exp_send "sam user.${v2}.spam dspam lrswd\r"
expect {
-re $prompt
}
exp_send "sam user.${v2}.ham dspam lrsw\r"
expect {
-re $prompt
}
}
Что касается скрипта traindspam.tcl, то организовать его периодический запуск можно, либо настроив crontab, либо же непосредственно из Cyrus-IMAP (при этом скрипт будет работать под тем же пользователем и с теми же правами, что и сам Cyrus-IMAP), отредактировав конфигурационный файл cyrus.conf файл и добавив в раздел EVENTS строчку, которая вызывает этот скрипт раз в час:
EVENTS {
...
trainspam cmd="/usr/local/bin/traindspam.tcl" period=60
...
}
Пользовательские группы в DSPAM
Пока что статистический фильтр, сконфигурированный таким образом как показано выше, не даёт никаких преимуществ перед десктопными фильтрами, встроенными, например, в такие почтовые клиенты, как Thunderbird. Разве что с помощью Sieve можно организовать фильтрацию на сервере, тем самым уменьшив нагрузку на линию между клиентом и сервером. Однако преимущества DSPAM начинают сказываться, когда пользователи получают возможность обмениваться накопленными данными и координировать свои усилия в борьбе со спамом. В DSPAM это достигается за счёт создания пользовательских групп (конфигурационный файл /var/dspam/group). При этом существуют два слабо пересекающихся способа.
Первый из них состоит в создании merged (смешанных)-групп, когда статистика одного виртуального пользователя (т.е. этот пользователь должен присутствовать в таблице dspam_virtual_uids), чьим именем названа группа, добавляется к статистикам каждого из пользователей, входящих в группу.
dummy:merged:user1@myrealm.ru,user2@myrealm@ru
Синтаксис для всех групп выглядит однотипно – название группы, разделитель-двоеточие, тип, снова разделитель и далее список пользователей, входящих в группу (можно использовать шаблон – «*», включающий всех пользователей, и префикс-минус для исключения определённых пользователей), всё – без пробелов. В данном примере статистика пользователя dummy даёт как бы базовый функционал, который позволяет любому новому пользователю, ещё не набравшему своей собственной статистики, после включения в группу достаточно эффективно фильтровать спам.
Системный администратор может взять на себя поддержание данных пользователя dummy в актуальном состоянии, используя свою собственную статистику, например с помощью утилиты dspam_merge:
dspam_merge mike@myrealm.ru -o dummy
Другой вариант – включить пользователя dummy в inoculation (вакцинационную)-группу, которая позволяет пользователям обмениваться результатами работы программы dspam, запущенной в режиме вакцинации:
cat <vaccine-mail> | dspam --user dummy --class=spam --source=inoculation
В таком режиме информация заносится в базу данных с несколько иным статистическим весом, с тем чтобы выработать у фильтра, если можно так сказать, «иммунитет» на письма, подобные тем, что используются в качестве «прививки». Понятно, что такой режим следует использовать, если вам попалcя в руки очень характерный образчик спамерской почты и вас не смущает, что данные, полученные в результате, будут продублированы в базе данных для каждого пользователя, входящего в группу. Пример настройки вакцинационной группы:
vaccine:inoculation:dummy,johndoe@myrealm.ru,mike@myrealm.ru
Обычно используется следующий популярный приём (так называемая внешняя вакцинация – external inoculation) для полуавтоматической тренировки фильтров DSPAM в своего рода двухтактном режиме. Фиктивный пользователь johndoe@myrealm.ru подписывается на всевозможные спам-рассылки, при этом весь приходящий на его имя спам передаётся (с помощью alias или transport_map) на вход скрипту с вызовом dspam в режиме вакцинации. Таким образом статистика, набранная в результате вакцинации, становится доступной пользователю dummy (через inoculation группу), а затем от пользователя dummy через одноимённую merged-группу и всем пользователям этой группы.
Дополнительного увеличения точности оценок, выставляемых dspam, можно добиться за счёт внедрения classification (классификационных) – групп. Они используются в том случае, если данные пользователя, на чьё имя пришло письмо, не позволяют сделать вывод: спам это или нет. В таком случае письмо будет проверено, используя данные всех пользователей в классификационной группе, и если окажется, что хотя бы в одном случае оно признано спамом, то таковым оно и будет признано в итоге.
Эти три типа групп можно комбинировать в любых вариантах (единственное требование, чтобы имена групп не совпадали), например, распределять пользователей по группам в соответствии с типом корреспонденции (условно говоря, для бухгалтеров одна merged группа, для программистов – другая), причём никаких ограничений для добавления пользователей в любую из этих групп нет. При этом за подобную гибкость настроек приходится платить – в данном случае дисковым пространством, поскольку для каждого из пользователей dspam ведёт отдельную статистику, что существенно сказывается на объёме баз данных. Поэтому для систем с небольшим объёмом дискового пространства более приемлемым окажется альтернативный вариант с shared (разделяемыми) группами:
shared-user:shared:user1@myrealm.ru,user2@myrealm.ru
Фактически в данном случае все пользователи собирают статистику для одного виртуального пользователя, чьим именем названа группа, и данные именно этого пользователя используются при анализе почты каждого из участников. Хотя этот тип групп можно использовать вместе с тремя видами групп, перечисленными ранее, но пользователь из shared группы не может входить ни в какую другую. Таким образом следует выбирать между предоставлением возможности пользователю вести собственную статистику или же экономией места на диске.
У shared-групп есть ещё один недостаток – это борьба с «друзьями, которые оказываются хуже врагов», т.е. с пользователями, которые безответсвенно подходят к тренировке фильтра и тем самым портят статистику для всей группы. Если для merged-групп эта проблема решается автоматически, поскольку они выступают только в роли потребителей, а «производителями» являются inoculation-группы и администраторы, имеющие доступ к dspam_merge, то любой пользователь из shared-группы имеет полную свободу обучать dspam тем трюкам, каким ему захочется. Для предотвращения этого в DSPAM имеются управляемые (managed) разделяемые группы, где к тренировке фильтра допускается только администратор группы, чьим именем она названа. Согласно документации настроить такую группу можно следующим образом:
group-admin:shared,managed:user1,user2
Кроме того, к ещё одному недостатку shared-групп можно отнести ещё и тот факт, что для них не будет работать рецепт создания белых списков, к рассмотрению которого (после такого долгого вступления) мы сейчас и перейдём.
Организация белых списков в DSPAM
Идея метода состоит в обмане автоматического фильтра и маскировки писем, чей отправитель вручную помечен пользователем как легальный, под письма занесённые фильтром в автоматический белый список. В общем-то, именно поэтому для метода необходим параметр Feature «whitelist» в конфигурационном файле, без него он работать не будет.
Рассмотрим поподробнее, как происходит занесение письма в белый список. При обработке письма его тело и заголовки разбиваются по словам (токенам), которые заносятся в базу данных DSPAM в таблицу dspam_token_data. Её структура достаточно очевидна, она содержит колонки uid, token, innocent_hits, spam_hits, last – которые обозначают идентификатор получателя письма, сам токен, сколько раз он встречался в легальных и спамных письмах, а также дату, когда он последний раз попадался. Идентификатор связан с именем получателя письма согласно таблице dspam_virtual_uids, которая как раз состоит из всего двух колонок – uid и username. Данные, касающиеся всего письма целиком, и результаты вычислений оценки для этого письма собраны в отдельной таблице – dspam_signature_data, которая обычно используется для пересчёта вероятностей и тренировки фильтра, но нам она не понадобится.
Когда включён автоматический whitelisting, то в таблицу dspam_token_data заносится ещё один токен – это заголовок письма с именем отправителя и приставкой From*. При этом следует учитывать, что используется заголовок полностью, а не только адрес отправителя, например, для моих писем он будет выглядеть как From*M.Kondrin <mkondrin@hppi.troitsk.ru>. Хотя это уменьшает вероятность подделки «подписи» спамерами, но при отправке разными почтовыми программами это поле может меняться в зависимости от настроек клиента. Так что следует учитывать, что один и тот же отправитель может фигурировать в списках под разными именами.
Отправитель автоматически добавляется в белый список, если от него получено 10 (зависит от настроек dspam) легальных писем и не одного спамного. Подсчёт этой статистики ведётся в полях innocent_hits и spam_hits. При этом получатель письма может заметить, что отправитель внесен в белый список, по изменившимся заголовкам, дописываемым к письму фильтром:
X-DSPAM-Result: Whitelisted
X-DSPAM-Processed: Wed Oct 22 16:08:08 2008
X-DSPAM-Confidence: 0.5203
X-DSPAM-Probability: 0.0000 |
Так что с помощью изменения sieve-фильтра письма такого сорта тоже можно каким-то образом «украсить», чтобы они выделялись во входящем ящике почтового клиента.
Теперь решение задачи очевидно – для занесения отправителя в пользовательский белый список достаточно просто с помощью SQL-запроса найти соответствующий имени отправителя токен в базе данных и исправить в найденной записи поля innocent_hits и spam_hits! Остальное – технические детали. Во-первых, токены хранятся в таблице не в виде строки, а в виде числа, представляющего собой её контрольную сумму, вычисленную по алгоритму CRC-64-ISO [9]. Это упрощает поиск в таблице (поле token является также первичным индексом), и кроме того даёт несколько большую приватность, поскольку обратное восстановление строки по контрольной сумме невозможно. Алгоритм CRC – это, конечно же, не хэш-функция, но для слов естественного языка вероятность коллизий невелика, зато его гораздо легче реализовать даже на скриптовом языке, вроде используемого нами tcl. Но даже этого нам делать не потребуется, поскольку в состав пакета DSPAM входит утилита dspam_crc, которая как раз и вычисляет требуемую контрольную сумму. Во-вторых, поскольку потребуется выполнять непосредственное редактирование баз данных (сервером которых, напомню, у нас служит MySQL), то нам потребуется какой-нибудь интерфейс доступа к этим базам данных. Можно было бы воспользоваться соответствующей библиотекой для tcl [10], но для наших целей, поскольку запрос будет не очень сложным, это слишком мощное средство, и можно будет обойтись вызовом консольной программы mysql из скрипта. Так что теперь можно привести упомянутую выше функцию whitelist_message, которую нужно будет вставить в скрипт traindspam.tcl:
proc whitelist_message { stream num user } {
#Добавление отправителя в белый список
::imap4::fetch $stream $num from:
set h From*[string trim [::imap4::msginfo $stream $num from:]]
set h1 [lindex [regexp -inline {CRC: ([[:digit:]]*)} [exec /usr/bin/dspam_crc $h]] 1]
exec mysql -u dspam -p privet_dont_use -h post dspam -e \\
'update dspam_token_data natural left join dspam_virtual_uids set innocent_hits=10000, spam_hits=0 where token=$h1 and username=$user'
}
Запрос заключается в склейке таблиц dspam_virtual_uids и dspam_token_data по общему индексу (uids), поиске в получившейся таблице нужной пары пользователь-токен и модификации колонок innocent_hits и spam_hits. Эта конструкция «update-join» работает в 5-й версии MySQL, относительно более ранних я не уверен. В качестве innocent_hits выбрано достаточно большое значение для того, чтобы потом при необходимости можно было бы легко найти в базе данных записи, составляющие пользовательский белый список. Поскольку поиск происходит одновременно по имени пользователя и токену, то именно из-за этого нельзя использовать этот трюк для пользователей, входящих в shared-группу. Действительно, имя пользователя скрипт получает из названия обрабатываемого почтового ящика и передает его в виде параметра user функции whitelist_message. Выяснить, принадлежит ли этот пользователь shared-группе и узнать имя этой группы, чтобы использовать его для поиска в таблице, можно только с помощью синтаксического разбора файла /var/dspam/groups, что сложно и не всегда реализуемо, хотя бы из-за несовместимых прав доступа. Хотя, конечно же, если отказаться от поддержки белых списков, индивидуальных для каждого из пользователей, то это ограничение на shared-группы нетрудно обойти.
Теперь остается придумать способ, как разрешить пользователю заносить адресатов в белый список. Можно было бы создать ещё один IMAP-каталог для этой цели, но в приведенном выше скрипте traindspam.tcl используется другой способ. Процедура занесения отправителя письма в белый список запускается после того, как получатель письма скопирует это письмо в ящик ham и пометит его флажком \Flagged, с тем чтобы отличать эти письма от false-negatives, не требующих помещения в белый список. Наличие этого флага требуется по стандарту IMAP4 [11] и, следовательно, должно поддерживаться всеми IMAP-серверами и почтовыми клиентами. Единственная сложность – определить, как он называется в почтовом клиенте. Например, в ранних версиях Thunderbird письма, помеченные таким флажком, бесхитростно назывались flagged и отображались с соответствующей пиктограммой. Однако в версиях начиная c 2.0 флаг прeвратился почему-то в звезду (star), добавить которую можно через пункт меню «Message -> Mark -> Add star» или щелкнув на соответствующей колонке в окне заголовков почтового ящика.
Поставленную нами задачу реализации индивидуальных белых списков можно уже считать решённой, но осталось наложить ещё один слой полировки и отредактировать пользовательский sieve-скрипт. Раз мы используем флаги для отметки писем, требующих занесения в белый список, то почему бы не применять ту же символику и в обратном направлении и не выставлять этот флаг для писем, полученных от корреспондентов, которые уже внесены в белый список. Тем более тут нет ничего сложного, просто добавляем ещё одну проверку значения заголовка. Более трудный – это вопрос, поставленный в начале статьи, а именно: что делать с письмами, помещёнными в белый список, но при этом классифицированных фильтром как спам? В качестве одного из вариантов – автоматически помещать в каталог ham копию приходящих писем с высоким коэффициентом спамности (заголовок X-DSPAM-Probability больше 0.5), но при этом с оценкой «Whitelisted», чтобы при следующей проверке скрипт traindspam.tcl улучшил их показатели. Поскольку DSPAM при выставлении оценок больше исходит от содержания письма, а не от того, кем оно было отправлено (в конце концов имя отправителя можно и подделать), то эта ситуация не является чем-то невероятным. К примеру, если ваш адресат из белого списка использовал несколько слов, которые значительно чаще используются в спамных письмах, чем в легальных, то это снизит оценку всего письма. То есть в данном случае стоило бы повысить оценку содержания письма и ещё раз с помощью этого письма натренировать фильтр, с учетом того факта, что автор – ваш хороший знакомый. Решается это таким кодом, который нужно добавить в конец приведённого выше sieve-скрипта:
if allof ( header :value "ge" ["X-DSPAM-Probability"] "0.5", header :contains ["X-DSPAM-Result"] "Whitelisted")
{fileinto "ham"; addflag "\\Flagged"; keep;}
if header :contains ["X-DSPAM-Result"] "Whitelisted"
{addflag "\\Flagged"; stop;}
Тут нужно только уточнить, что модуль, обеспечивающий операции сравнения (relational [12]), является не вполне стандартным, хотя он присутствует в Cyrus-IMAP, но он может не поддерживаться другими реализациями sieve. Кроме того, сравнение в данном случае – это не сравнение действительных чисел (sieve – слишком ограниченный язык, и понятия действительного числа там нет), а алфавитное упорядочение, но поскольку DSPAM выставляет оценки в фиксированном формате, то результат в обоих случаях получается одинаковым.
Таким образом, с помощью нескольких скриптов и одного SQL-запроса нам удалось добавить недостающую функциональность DSPAM, реализовав там индивидуальные белые списки, пополнение которых находится в ведении пользователей. Можно было бы ещё продолжать усовершенствование нашего фильтра и, например, отредактировать скрипт traindspam.tcl, так чтобы письма из каталога spam, помеченные флажком, использовались для вакцинации фильтра, или организацию запуска этого скрипта не периодически, а непосредственно в момент перемещения письма в соответствующий каталог (в Линуксе это можно было бы осуществить средствами inotify), но я оставляю эти задачи читателю. Кроме того, хотелось бы ещё напомнить о сложной ситуации, в которой сейчас оказался проект DSPAM, и при наличии желания и навыков принять участие в его спасении.
- Супрунов С. Настраиваем DSPAM – ваш личный спам-фильтр. //Системный Администратор, №8, 2005 г. – С. 24-34 (http://www.samag.ru/cgi-bin/go.pl?q=articles;n=08.2005;a=04).
- Официальный сайт DSPAM – http://dspam.nuclearelephant.com/index.shtml.
- Литвинов П. Фильтруем спам в Exim с помощью SpamProbe. //Системный Администратор, №5, 2008 г. – С. 50-52.
- Arnaldo Viegas de Lima. The Sendmail + DSPAM + Cyrus HOWTO. How to Configure DSPAM 3.6 with Sendmail and Cyrus IMAP or How to create a spam protected, highly scalable and high performance mail server. arnaldo@viegasdelima.com, 0.61 edition, Apr 2006.
- Кондрин М. Kerberos и электронная почта. //Системный Администратор, №11, 2006 г. – С. 62-67.
- T.Showalter. RFC3028 – Sieve: A Mail Filtering Language. Technical report, Mirapoint, Inc., January 2001.
- Интерфейс к Sieve для Thunderbird – http://sieve.mozdev.org/source.html.
- Домашняя страница пакета Tcllib – http://www.tcl.tk/software/tcllib.
- Статья в Wikipediа о CRC – http://en.wikipedia.org/wiki/Cyclic_redundancy_check.
- Интерфейс доступа к базам данных MySQL из tcl – http://www.xdobry.de/mysqltcl.
- M.Crispin. RFC3501 – Internet Message Access Protocol – Version 4rev1. Technical report, University of Washington, March 2003.
- W.Segmuller and B.Leiba. Request for Comments: 5231. Sieve Email Filtering: Relational Extension. Technical report, IBM T.J. Watson Research Center, January 2008.