Спам не дает забыть о себе большинству пользователей интернета. Многие программеры
озабочены решением проблемы спама. Среди них как известные производители ПО, так и индивидуальные
кодеры, однако до сих пор не существует
идеального технического решения, т. е.
такого фильтра, который бы убивал весь спам, но пропускал все хорошие письма. Приходится искать и выбирать лучший для
конкретного случая фильтр. Я тоже не предлагаю идеальное решение, но я подхожу к решению этой проблемы
творчески и хочу поделиться своими соображениями. Любые программы имеют ограниченные возможности настройки. Если же мы пишем прогу
сами, да еще на таком богатом высокоуровневыми функциями языке, как JavaScript, то возможности
по изменению ее алгоритма практически безграничны.
Изменить скрипт легко, и его не нужно компилировать, а можно сразу запускать. Работа со
строками, массивами и регулярными выражениями в JavaScript не вызывает такого геморроя, как в С++. Остается, пожалуй, главный
вопрос: как в JavaScript получать почту по протоколу POP3?

ANPOP Components

ANPOP - это набор из нескольких COM-компонентов для работы с РОР3, в т. ч. через
SSL/TLS. Их можно использовать в программах на С++, VB, VBScript и JavaScript и в любых средах, поддерживающих
COM и ActiveX. Одно неприятно: эти компоненты шароварные. 

Скачать их можно тут: http://www.emailarchitect.com/webapp/download/anpop.exe,
вместе с ними устанавливается и документация.

Разберем архитектуру ANPOP. Объект POPMAIN содержит методы для подключения к РОР3-серверу, отключения от него,
получение списка писем, их размеров и, наконец, скачивания самих писем. Все письмо возвращается в
виде одной строки, содержащей все заголовки и само письмо в том формате и той кодировке, в которой
оно пришло. Для разбора письма (извлечения из него каждого заголовка и каждого вложения) нужен
объект POPMSG. Объект MSGSTORE служит для управления почтовыми ящиками на локальной машине.
Объекты Pop3Request и Pop3Queue используются для передачи заданий на скачивание писем от одного
процесса или потока к другому. Вот, как все это работает.

// server, login, pass содержат адрес сервера, логин и пароль.
//
strFileSavePath - путь для сохранения писем. Слэш в конце обязательно.

var oPop3 = new ActiveXObject("ANPOP.POPMAIN");
var oMsg = new ActiveXObject("ANPOP.POPMSG");

nRet = oPop3.Connect(server, login, pass); // Возвращает 0, если все в порядке
if (nRet != 0)
{
WScript.Echo("Error connecting to "+server+"\n");
} else
{
nCount = oPop3.GetTotalOfMails(); //
Количество писем или -1 при ошибке.
if (nCount == -1)
{
WScript.Echo("Error in GetTotalOfMails() on " + server + "\n");
} else
{
WScript.Echo("Connected to " + server + "\n");
WScript.Echo("You have " + nCount + " messages\n");
for (i = 1; i <=nCount; i++) //
Обрабатываем письма поочередно
{
msgid = oPop3.GetMsgID(i);
//
Тут мы можем проверить, не
обработали ли мы уже это письмо

//
Мы могли уже обработать это письмо, но не удалять с сервера.
if (false) //
Впишите сюда условие
{
WScript.Echo("The message " + msgid + "has been already processed\n");
} else
{
oMsg.RawContent = strHeaders = oPop3.GetMsgHeader(i); //
Получаем заголовки письма
//
Обрабатываем заголовки
if (true) //
Некоторые письма можно
отсеять уже по заголовкам и не скачивать

{
oMsg.RawContent = strMessage = oPop3.Retrieve(i); //
Скачиваем письмо
if (strMessage) fl = ProcessMessage(); //
ProcessMessage() - это наша фильтрующая функция
if (fl) oMsg.ExportFile(strFileSavePath + msgid + ".eml"); //
Сохраняем
WScript.Echo("The message " + msgid + " from " + oMsg.GetHeaderItem("From") + " with subject " + oMsg.GetSubject());
if (fl) WScript.Echo(" has been saved\n"); else WScript.Echo(" is spam\n");
}
if (false) oPop3.Delete(i); //
Удалим письмо, если оно не нужно
//
обязательно сохранить инфу том, что письмо с идентификатором msgid обработано
}
}
}
oPop3.Close();
}

Вот вам простейший шаблон почтового фильтра.
Нужно еще каким-то образом получать письма почтовым клиентом, а не просто сохранять в папку.
Тут есть два способа. Первый - нужен POP3-сервер, который будет брать EML-файлы из нашей папки.
Второй - не удалять нужные письма с сервера, а спам удалять, а затем почтовым клиентом получить
то, что не удалено. Сохранять в этом случае наш скрипт ничего не должен, хотя можно сохранять
подозрительные письма, удаленные с сервера.

О методах фильтрации спама

Самым простым и в то же время эффективным методом является метод Байеса. Суть его в том,
что есть слова, которые встречаются в основном, в спаме, а есть слова, которых спамеру не знают.
Нужен словарь, в котором будут записаны частоты вхождения различных слов в спаме и в хороших
письмах. При получении письма нужно получить данные из словаря для всех слов, содержащихся в письме
и на основании этих данных сделать вывод о том, как классифицировать это письмо. Словарь для каждого
пользователя индивидуальный, поэтому его неоткуда взять в готовом виде. Словарь вначале пуст, но
в процессе работы программы он должен пополняться. Говорят, что программа самообучается. Делается
это так: если программа классифицировала письмо не тек, как вам того хотелось, вы выбираете команду
"переклассифицировать", и новые данные заносятся в словарь. У этого метода есть рад недостатков, и самый очевидный из них в том, что учитываются только
вхождения слов, а не их порядок. Одними и теми же словами можно сказать о разных вещах. Более сложный метод - метод шинглов. Из текста выбираются не отдельные слова, а более
длинные подстроки (шинглы) по каким-то сложным правилам. Шинглы могут охватывать не весь текст или,
наоборот, перекрываться. В словарь заносятся данные не по словам, а по шинглам. Поскольку шинглы
длинные, обычно хранят не сами шинглы, а их хэшы. Наконец, самые навороченные методы разбирают текст на основе знания языка и понимают,
о чем там говориться, а не высчитывают какие-то
статистические характеристики.

Подробнее читать тут:

http://www.spamtest.ru/varticles.html?id=0036
- "Статистические (вероятностные) методы фильтрации спама"
http://www.spamtest.ru/varticles.html?id=0032
- "Детектирование массовых рассылок на Яндекс.Почте"

Работаем с реестром

Теперь о таких немаловажных деталях, как сохранение инфы, с которой работает наш скрипт.
Сохранять нам нужно, главным образом, ту инфу, по которой мы фильтруем письма, например, частоты
вхождения слов в спамерских и неспамерских письмах. Еще мы будем сохранять идентификаторы
обработанных писем, чтобы не обрабатывать одни и те же письма несколько раз в том случае, если мы
их не удаляем с сервера. Самым простым вариантом будет использование системного реестра. Специально
для использования в скриптах в Windows существует объект Wscript.Shell, в котором есть функции для
работы с реестром, ярлыками, специальными папками Windows и
т.д. Допустим, что частоты вхождения слова в спамерских и неспамерских письмах лежат в одном
DWORD - значении, имя которого соответствует этому слову. Тогда процедура оценки текста может
выглядеть так:

oShell = new ActiveXObject("Wscript.Shell"); // Создаем объект

function ProcessText(strText)
{
nGood = nAPlus; //
nAPlus - некое положительное число
nBad = nAPlus;
arr = strText.split("[\\W\\S]"); //
Разделим письмо на отдельные слова
for (i = 0; i < arr.length; i++)
{
if (arr[i] != "")
{
try //
RegWrite выбрасывает исключение, если значение не найдено
{ //
strRegWordList - ключ реестра, где лежит наш словарь
dwValue = oShell.RegRead(strRegWordList + escape(arr[i])); //
Читаем значение
} catch (ex) {dwValue = 0;} //
Если не найдено, значит, 0
nGood += dwValue & 0xFFFF; //
Нижнее слово - количество вхождений в хороших письмах
nBad += dwValue >> 16; //
Верхнее слово - количество вхождений в плохих письмах
}
}
return (nBad / (nBad + nGood)); //
Возвращаем отношение
}

Число, которое возвращает эта функция, можно рассматривать как вероятность того, что данное письмо - спам.

Теперь напишем процедуру переклассификации
письма

function Reclassify(strText, t)
{
arr = strText.split("[\\W\\S]"); //
Разделим письмо на отдельные слова
for (i = 0; i < arr.length; i++)
{
if (arr[i] != "")
{
try
{
dwValue = oShell.RegRead(strRegWordList + escape(arr[i]));
} catch (ex) {dwValue = 0;}
if (t) dwValue += 1; else dwValue += 0x10000; //
Изменим значение
oShell.RegWrite(strRegWordList + escape(arr[i]), dwValue, "REG_DWORD"); //
и запишем его
}
}
}

Работаем с файлами

Поскольку ни один метод фильтрации не совершенен, антиспамерский фильтр должен поддерживать
черные и белые списки адресов, тем и фрагментов текста. Еще лучше, если в списках будут не просто подстроки,
а регулярные выражения, тем более, что для этого в скрипте нужно изменить всего одну строчку. А храниться эти
списки должны в файлах, потому что юзер должен их редактировать вручную. Для работы с файлами из
скриптов в системе тоже есть объекты. Называется Scripting.FileSystemObject. Пусть в каждой строке файла
будет по одному регулярному выражению. Напишем функцию, которая будет возвращать true, если заданная
строка соответствует хотя бы одному выражению из списка.

var oFSO = new ActiveXObject("Scripting.FileSystemObject"); //
Создаем объект

function LookUpList(strListFile, strString)
{
try
{
file = oFSO.OpenTextFile(strListFile, 1); //
Открываем файл
} catch (ex) {return (false); } //
Если не открылся, то false
while (!file.AtEndOfStream) //
Читаем каждую строку
{
strRegExp = file.ReadLine();
if (strRegExp) //
Если не пустая строка
{
re = new RegExp(strRegExp); //
Создаем объект регулярного выражения
if (re.test(strString)) {file.Close(); return (true);} //
Если строка соответствует этому выражению, то true
}
}
file.Close();
return (false);
}

И вот как может выглядеть обработка письма:

// Определяет по заголовкам, нужно ли
скачивать данное письмо

//
и нужно ли его удалять
//
Возвращает 0, если спам, 1, если хорошее письмо, 2, если нужно скачать,
//
чтобы классифицировать
function SelectiveDl()
{
subj = oMsg.GetSubject();
rcpt = oMsg.GetHeaderItem("To");
from = oMsg.GetHeaderItem("From");

// Некоторым спамерам лень даже написать мой адрес в поле To 
re = new RegExp("Mr*.Black");
if (!re.test(rcpt)) return 0; //
Если письмо адресовано не мне

// Проверим по черным и белым спискам
if (LookUpList(strAddressBlackList, from)) return 0;
if (LookUpList(strAddressWhiteList, from)) return 1;
if (LookUpList(strSpamSubjectList, subj)) return 0;
if (LookUpList(strGoodSubjectList, subj)) return 1;

// И, наконец, проверка по словарю
if (ProcessText(subj + " " + from) > nThreshold)
{
//
spam
return 0;
} else
{
//
not spam
return 2;
}
}

// Определяет по целому письму, является ли оно спамом
//
и нужно ли его удалять с сервера (это может быть не связано)
//
Возвращает 0, если спам, 1, если не спам
function ProcessMessage()
{
body = oMsg.GetBodyText();

// Проверим по черному и белому списку фрагментов текста
if (LookUpList(strGoodTextList, body)) return 1;
if (LookUpList(strSpamTextList, body)) return 0;

// и по словарю
if (ProcessText(body) > nThreshold)
{
//
spam
return 0;
} else
{
//
not spam
return 1;
}
}

Заключение

Мы рассмотрели только простейший метод фильтрации, поэтому скрипты, представленные в этой статье
в исходном виде бесполезны, и это только шаблон, на основе которого
вы, сможете написать почтовый фильтр, который будет использовать
ваш метод. Кроме того, файлы и реестр не годятся для хранения больших баз
данных. Данные можно хранить в MDB-файле, и работать с ними через объекты ADODB. Кроме анализа самого
письма можно использовать данные RBL и WHOIS о хостах, через которые прошло письмо, если найдутся
какие-нибудь ActiveX-оъекты для работы с этими службами.

Исходники

  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    0 комментариев
    Межтекстовые Отзывы
    Посмотреть все комментарии