Интеграция антиспама DSPAM в ZMailer
Аналогично интеграции DSPAM для CGP в режиме добавления заголовков, можно использовать эту технологию для интеграции DSPAM-a для ZMailer MTA.
Обязательным условием интеграции является модификация исходного кода dspam, для перевода его в режим добавления только заголовков. Для этого надо наложить модификацию: dspam_addheader.patch
План интеграции такой:
DSPAM для CGP v1.0.1

Обновил dspam-cgp.c до версии 1.0.1.
Добавлен традиционный для CGP заголовок вида: X-Junk-Score: 90 [XXXX]. Его удобно использовать для применения различных действий к письмам с разным уровнем «вероятности» спама. Этот заголовок автоматически используется в разделе «Упрощённые Правила по Обработке Спама». Так же это можно использовать и в своих правилах вида:
Header Field is X-Junk-Score:*[XXXX* Store in Junk Discard
Подробнее о том как использовать этот заголовок можно прочитать на сайте CGP в описании настроек для фильтра CGPSpamCatcher.
Для задания граничных точек уровня вероятности используется массив чисел:
/* Defines the bar score ranges. By default the following ratios are used:
* digital Bar score
* 0 []
* 1-49 [X]
* 50-70 [XX]
* 71-89 [XXX]
* 90-94 [XXXX]
* 95-99 [XXXXX]
* 100 [XXXXXX]
*/
int BARSCORERANGES[] = {0,49,70,89,94,99,100, -1};
Вероятность может быть от 0 до 100. Количество диапазонов может быть любым. Вероятность вычисляется исходя из результатов, которые сообщает dspam. Пока мне кажется оптимальным такое распределение вероятностей. Но вы можете сами изменить их, отредактировав BARSCORERANGES[]. Конечный «-1» всегда должен присутствовать последним элементом, он используется для определения конца массива.
О том как изменить dspam и использовать его для CGP читать в предыдущей статье: DSPAM для CGP в режиме добавления заголовков
DSPAM для CGP в режиме добавления заголовков
DSPAM — это свободное программное обеспечение, представляющее собой статистический спам фильтр.
Проект DSPAM, который некоторое время оказался заброшенным, вот уже больше полугода активно развивается dspam-сообществом. В 2007 году его бывший автор Jonathan Zdziarski передал свои права компании Sensory Networks. А в январе 2009 года компания Sensory Networks объявила, что перестаёт заниматься этим проектом и полностью передала все права dspam-сообществу.
Про настройку, обучение и работу с dspam-ом есть много статей, я хочу написать об изменениях, которыми пользуюсь я для связки dspam-а и CGP.
Белые списки для CGP
Наравне с технологией DNS blacklisting (DNSBL) существует технология DNS whitelisting (DNSWL). Это списки ip-адресов, хранимые с использованием системы архитектуры DNS. Но в отличии от «чёрных списков»(blacklist), которые хранят ip-адреса распространителей спама, «белые списки»(whitelist) хранят ip-адреса тех, кто в рассылках спама не замечен.
Основная идея «белых списков» — уменьшить количество ложных срабатываний остальных антиспам фильтров.
Мне показалось, что наиболее полную базу «белых адресов» имеет ресурс dnswl.org. Этот ресурс, помимо самого факта «чистоты» ip-адреса, хранит так же уровень этой чистоты(Trust Level).
Всего уровней четыре:
- High – никогда не рассылал спам;
- Medium – крайне редки случаи спама, быстро реагируют на проблемы;
- Low – иногда рассылают спам, активно реагируют на проблемы, но менее оперативно;
- None – легитимный почтовый сервер, но может рассылать спам.
Применение этого белого списка может быть таким:
- не применять технологию «серых списков» для всех ip-адресов с уровнями None-High;
- не применять технологию «черных списков» для всех ip-адресов с уровнями None-High;
- не проверять письма на спам (или проверять в меньшей степени) для ip-адресов с уровнями Medium-High.
Вся остальная почта с адресов, которых нет в DNSWL, пройдёт полную проверку.
Для CGP я написал dnswl-cgp.c фильтр (cgp helper), который делает запрос в DNSWL и добавляет в сообщение заголовок: «X-DNSWL-Status: <Trust Level>».
fgets () из socket-а с timeout-ом
Понадобилось реализовать построчное чтение из сокета с timeout-ом. Написал для fgets () обёртку — ifgets (). Для обеспечения timeout-a используется select (). В процессе написания столкнулся с проблемой: select() почему-то всегда выдавал timeout, хотя данные были прочтены еще не все.
Strace показал странное поведение: мною читалась первая строка, далее был выход из функции чтения, но далее кто-то(не я) читал всё остальное содержимое открытого сокета. Естественно, при моей попытке прочитать следующую строку из сокета, по мнению select () он был пуст и происходил timeout.
Причиной это проблемы стало буферизированное чтение из сокета. Сокет опустошался в буфер, а select-у ничего не оставалось. После выключения буферизированного чтение, всё заработало как надо.
Пользоваться почти так же как fgets ():
int fd;
FILE *fds;
/* Делаем TCP сокет */
fd = socket(PF_INET, SOCK_STREAM, 0);
/* Параметры для соединения опущены */
/* Соединяемся */
connect(fd, (struct sockaddr *) &srv_i, sizeof(srv_i));
/* Переоткрываем сокет как поток */
fds = fdopen(fd, "w+");
/* !!! ВНИМАНИЕ! Надо обязательно выключить буферизированное чтение.
Иначе всё сразу будет прочтено в буфер и
select будет возвращать только timeout
*/
setvbuf(fds, (char *) NULL, _IONBF, 0);
/* Ну а дальше вызываем ifges() и передаём timeout в сек.
и integer descriptor для select-а
*/
if (ifgets(buf, buflen, fds, timeout, fd))
return strlen(buf);
if (errno == ETIMEDOUT) {
print("Socket timeout after %d seconds", timeout);
return (-1);
}
Сама ifgets () реализована так:
char *
ifgets(char *s, int size, FILE *stream, int timeout, int fd)
{
int res;
if (timeout) {
do {
res = select_fd (fd, timeout, 0);
} while (res == -1 && errno == EINTR);
if (res < = 0) {
if (res == 0) {
errno = ETIMEDOUT;
return NULL;
}
return NULL;
}
}
if(fgets(s, size, stream)) {
return s;
}
return NULL;
}
/* ----------------------------------------------------------------- */
static int
select_fd (int fd, int maxtime, int writep)
{
fd_set fds, exceptfds;
struct timeval timeout;
FD_ZERO (&fds);
FD_SET (fd, &fds);
FD_ZERO (&exceptfds);
FD_SET (fd, &exceptfds);
timeout.tv_sec = maxtime;
timeout.tv_usec = 0;
/* HPUX reportedly warns here. What is the correct incantation? */
return select (fd + 1, writep ? NULL : &fds, writep ? &fds : NULL,
&exceptfds, &timeout);
}







