Почтовый фильтр, или Milter = Mail + Filter::Журнал СА 2.2003
www.samag.ru
     
Поиск   
              
 www.samag.ru    Web  0 товаров , сумма 0 руб.
E-mail
Пароль  
 Запомнить меня
Регистрация | Забыли пароль?
Журнал "Системный администратор"
Журнал «БИТ»
Наука и технологии
Подписка
Где купить
Авторам
Рекламодателям
Архив номеров
Контакты
   

  Опросы
  Статьи

Электронный документооборот  

5 способов повысить безопасность электронной подписи

Область применения технологий электронной подписи с каждым годом расширяется. Все больше задач

 Читать далее...

Рынок труда  

Системные администраторы по-прежнему востребованы и незаменимы

Системные администраторы, практически, есть везде. Порой их не видно и не слышно,

 Читать далее...

Учебные центры  

Карьерные мечты нужно воплощать! А мы поможем

Школа Bell Integrator открывает свои двери для всех, кто хочет освоить перспективную

 Читать далее...

Гость номера  

Дмитрий Галов: «Нельзя сказать, что люди становятся доверчивее, скорее эволюционирует ландшафт киберугроз»

Использование мобильных устройств растет. А вместе с ними быстро растет количество мобильных

 Читать далее...

Прошу слова  

Твердая рука в бархатной перчатке: принципы soft skills

Лауреат Нобелевской премии, специалист по рынку труда, профессор Лондонской школы экономики Кристофер

 Читать далее...

1001 и 1 книга  
19.03.2018г.
Просмотров: 9926
Комментарии: 0
Потоковая обработка данных

 Читать далее...

19.03.2018г.
Просмотров: 8136
Комментарии: 0
Релевантный поиск с использованием Elasticsearch и Solr

 Читать далее...

19.03.2018г.
Просмотров: 8241
Комментарии: 0
Конкурентное программирование на SCALA

 Читать далее...

19.03.2018г.
Просмотров: 5220
Комментарии: 0
Машинное обучение с использованием библиотеки Н2О

 Читать далее...

12.03.2018г.
Просмотров: 5903
Комментарии: 0
Особенности киберпреступлений в России: инструменты нападения и защита информации

 Читать далее...

Друзья сайта  

 Почтовый фильтр, или Milter = Mail + Filter

Архив номеров / 2003 / Выпуск №2 (3) / Почтовый фильтр, или Milter = Mail + Filter

Рубрика: Администрирование /  Электронная почта

РОМАН СУЗИ 

Почтовый фильтр или Milter = Mail + Filter

В версии Sendmail 8.11.6 появилось нововведение, которое приоткрывает дверцу над таинственными процессами по  обработке почтовых сообщений. И эта дверца – программный интерфейс фильтров по содержанию (content filter API, или, коротко, Milter)  – очень существенный элемент для организации обработки почты в масштабах всего  почтового сервера.

Если для индивидуальной настройки доставки почты в UNIX-системах обычно применяется procmail, то для обработки всей проходящей через сервер почты Milter подходит как нельзя кстати. Для большей эффективности рекомендуется применять версию Sendmail 8.12.

Главными применениями Milter можно считать:

  • отклонение писем со спамом;
  • организация проверки на вирусы;
  • обезвреживание опасных вложений;
  • фильтрация по содержимому;
  • организация нестандартной обработки и сортировки почты.

Еще раз отметим, что Milter дает доступ не только ко входящим письмам, но и к исходящим. Это позволяет предотвращать злоупотребления почтовым сервером со стороны местных пользователей.

Одной из очень полезных особенностей Milter является его большая эффективность по сравнению с другими средствами обработки почты. Во-первых, Milter дает приложению-фильтру доступ к информации о сообщении на очень ранних стадиях его получения Sendmail: с помощью Milter приложение-фильтр получает атрибуты SMTP-команд вместе с Sendmail, что позволяет отклонять сообщения, еще не получив их в полном объеме. Во-вторых, Milter поддерживает многопоточность, что позволяет иметь всего один экземпляр приложения-фильтра, а не запускать приложение для каждого сообщения (как это делает procmail). В-третьих, подключение Milter-приложения к Sendmail можно сделать достаточно безопасным, определив требуемую реакцию Sendmail на тот случай, если приложение-фильтр не отвечает. Наконец, приложение-фильтр может находиться на другой машине, общаясь с почтовым сервером по TCP/IP. Кстати, фильтр может быть не один. Крючки (hooks) к Milter API имеются для многих известных антивирусных и антиспамовых программ.

Есть, конечно, у Milter и минусы. Во-первых, он работает только с Sendmail. Во-вторых, в нем не очень удобно (хотя и возможно) реализовать фильтрацию на основе индивидуальных настроек пользователей, как это имеет место с procmail.

Sendmail и приложение-фильтр общаются через сокет. Это может быть сокет в файловой системе, IP-сокет (или IPv6-сокет). Первый более эффективен, если обработка почты происходит на локальной машине, а второй позволяет вынести фильтрацию почты на другую машину (или машины). Например, один хост может быть целиком задействован для фильтрации спама, другой – проверять сообщения на вирусы, а третий – анализировать соответствие содержимого почтовых сообщений принятой политике (скажем, образовательное учреждение может фильтровать письма сомнительного содержания). На любом этапе Milter может пропустить (ACCEPT), отклонить (REJECT) или выбросить (DROP) обрабатываемое сообщение. Milter также может менять список получателей письма, изменять заголовочную часть и тело. При отклонении письма можно указать его детальную причину.

Sendmail написан на C, и поэтому Milter API ориентирован на C-приложения. Однако пример, который мы рассмотрим ниже, использует Python Milter – обертку Milter API для языка Python. Эта обертка – полноценный объектный интерфейс к обрабатываемому SMTP-соединению. Отметим, что использование интерпретатора скриптового языка существенно не влияет на производительность приложения, так как приложение работает в режиме демона. Зато мы получаем большой выигрыш в выражении логики приложения-фильтра и доступ к богатым стандартным библиотекам Python.

Чтобы использовать приложения-фильтры, нужно скомпилировать Sendmail с поддержкой Milter API. Как это сделать, описано в документации к Sendmail. В частности, необходимо указать в файле devtools/Site/site.config.m4 опцию:

APPENDDEF(“conf_sendmail_ENVDEF”, “-DMILTER”)

Для нашего примера понадобится Python 2.x и Python Milter. Первый можно взять на www.python.org, а второй – на http://www.bmsi.com/python/milter.html. Его необходимо скомпилировать, использовав библиотеки соответствующей версии Sendmail. Подробнее об этом написано в документации к Python Milter. Возможно, потребуется дополнительно установить заголовочные файлы и библиотеки libmilter и smutil – конкретнее об этом сказано на указанной выше www-странице. [Мне даже приходилось переименовывать libsmutil в libsm – прим. автора]. В результате компиляции Python Milter должен получиться файл milter.so, который вместе с mime.py и Milter.py нужно поместить в каталог с другими модулями Python.

Python Milter имеет в своем составе два законченных примера, однако подходящий именно вам фильтр лучше составить самому. Приведенный ниже пример (листинг 1) Milter поможет вам разобраться в Milter API. Конечно, это не законченный продукт, а скорее конструктор, который нужно доработать, добавив требуемую вам логику.

Листинг 1

#!/usr/bin/python2

"""

  Пример Milter, на основе которого можно написать собственный фильтр для почты.

"""

import sys, os, Milter, tempfile, re, traceback

from time import strftime, time, localtime

def dbg_except():

  """Функция, которую можно ставить в части except: оператора try-except для отладки"""

  sys.stderr.write(strftime(“%Y%m%d%H%M%S “) +

    "".join(apply(traceback.format_exception, sys.exc_info())))

class ExampleMilter(Milter.Milter):

  """Класс, каждый объект которого отвечает за одно соединение."""

  def log(self, *msg):

    print "%s [%d] %s" % (strftime(“%Y%m%d%H%M%S”), self.id, " ".join(map(str, msg)))

  def __init__(self):

    self.tempname=self.mailfrom=self.connfrom=self.fp=None

    self.id = Milter.uniqueID()

  def connect(self, hostname, unused, hostaddr):

    """Вызывается при установке SMTP-соединения"""

    self.log("connect: %s, %s" % (hostname, hostaddr))

    # Здесь можно сразу проверить и отклонить:

    # return Milter.REJECT

    return Milter.CONTINUE

  def hello(self,hostname):

    """Вызывается после команды HELO"""

    self.log("hello from %s" % hostname)

    return Milter.CONTINUE

  def envfrom(self, f, *s):

    """Вызывается после команды MAIL FROM (их может быть несколько в рамках одного соединения). Отмечает начало сообщения."""

    self.log("mail from", f, s)

    self.tempname = None           

    self.mailfrom = f

    self.headers = []

    self.bodysize = 0

    return Milter.CONTINUE

  def envrcpt(self, to, *s):

    """Вызывается после команды RCPT TO (их может быть несколько для каждого сообщения)."""

    self.log("rcpt to", to, s)

    return Milter.CONTINUE

  def header(self, name, val):

    """Вызывается для каждого поля заголовка сообщения"""

    # Записать поле в список

    self.headers.append("%s: %s" % (name, val))

    lname = name.lower()

    if lname in (“subject”, “from”, “to”):

      self.log(“%s: %s” % (lname, val))

    return Milter.CONTINUE

  def eoh(self):

    """Вызывается по окончании обработки заголовка"""

    # Начинаем записывать сообщение во временный файл. Сначала заголовки

    self.tempname = tempfile.mktemp(".milter.tmp")

    self.fp = open(self.tempname, "w+b")

    self.fp.write("\n".join(self.headers) + "\n\n")

    return Milter.CONTINUE

  def body(self, chunk):

    """Вызывается для каждого фрагмента тела сообщения"""

    # Тело сообщения по кусочкам пишется в тот же файл

    if self.fp:

      self.fp.write(chunk)

      self.bodysize += len(chunk)

    return Milter.CONTINUE

  def eom(self):

    """Вызывается в конце каждого сообщения"""

    # Закрываем временный файл (если он есть)

    if self.fp:

      self.fp.close()

    else:

      return Milter.TEMPFAIL

    # Здесь можно делать всевозможные проверки и вызывать следующие методы:

    # Удалить получателя:

    # self.delrcpt("<user1@host.ru>")

    # Добавить получателя:

    # self.addrcpt("<user2@host.ru>")

    # Добавить поле в заголовок:

    # self.addheader("X-Processed-By", "Milter")

    # ...и другое (см. документацию к Milter)

    # Заменить тело сообщения (покусочно):   

    # for c in chunks:

    #   self.replacebody(c)

    # Отклонить сообщение (с указанием причины):

    # self.setreply(“550”, “5.1.1”, “SPAM”)

    # return Milter.REJECT

    # Отбросить сообщение:

    # return Milter.DROP

    # Принять сообщение:

    self.log(“msg accepted: size=%s” % self.bodysize)

    return Milter.ACCEPT

  def abort(self):

    """Вызывается в случае ненормального завершения соединения"""

    self.log("abort. Size=%d" % self.bodysize)

    return Milter.CONTINUE

  def close(self):

    """Вызывается в конце соединения, даже если оно было прервано"""

    self.log("connection closed.")

    sys.stdout.flush()

    if self.fp:

      self.fp.close()

      self.fp = None

    if self.tempname:

      os.remove(self.tempname)

      self.tempname = None

    return Milter.CONTINUE

if __name__ == "__main__":

  os.chdir("/home/milter")

  tempfile.tempdir = "/var/tmp"

  socketname = "inet:2525@milter.host.ru"

  timeout = 240  # секунд

  Milter.factory = ExampleMilter

  Milter.set_flags(Milter.CHGBODY + Milter.CHGHDRS + Milter.ADDHDRS + Milter.DELRCPT + Milter.ADDRCPT)

  print """Example Milter start"""

  sys.stdout.flush()

  Milter.runmilter("mainfilter", socketname, timeout)

  print """Example Milter shutdown"""

В этой небольшой программе описывается класс ExampleMilter, основанный на классе Milter из модуля Milter. Для каждого SMTP-соединения создается новый объект класса ExampleMilter. Отдельные методы этого объекта отвечают за обработку определенных событий (см. комментарии в тексте программы). Вся информация, касающаяся определенного соединения, должна храниться в атрибутах объекта. В нашем примере так накапливается и хранится bodysize (размер тела сообщения) и некоторые вспомогательные объекты. (В Python сам объект передается в метод в качестве первого аргумента и традиционно называется self, поэтому для работы с атрибутами внутри метода нужно использовать self.имя_атрибута. Кстати, в Python новые атрибуты могут появляться в объекте в любой удобный момент.) Здесь следует заметить, что методы вызываются в определенной последовательности, и нет гарантии, что для данного соединения будет вызван тот или иной метод. Например, следом за hello() может сразу последовать abort(). Это обстоятельство необходимо учитывать при написании своего фильтра, как и то, что в рамках одного соединения может быть обработано несколько сообщений. В силу чего требуется правильно инициализировать имена: те, что относятся ко всему соединению, нужно инициализировать в connect(), а относящиеся к конкретному сообщению – в envfrom(). Соответственно, конечной точкой использования таких имен должны быть методы close() и eom().

На любом этапе работы фильтра можно решить судьбу сообщения, возвратив Milter.REJECT (отклонить), Milter.ACCEPT (принять), Milter.DROP (выбросить) или продолжить обработку – Milter.CONTINUE. В методе eom() можно менять некоторые свойства обрабатываемого сообщения (см. комментарии в листинге 1). Напомним, что состав получателей не обязательно соответствует содержимому полей To, Cc, Bcc, так как эти данные передаются отдельными командами протокола SMTP.

Для включения фильтра необходимо добавить примерно следующие две строки к файлу sendmail.mc:

MAIL_FILTER(`mainfilter", `S=inet:2525@milter.host.ru, T=C:10m;S:30s;R:30s;E:10m")

define(`confINPUT_MAIL_FILTERS", `mainfilter")

Здесь milter.host.ru и 2525 – хост, на котором запущен Milter, и порт (номер выбран произвольно).

Если вы привыкли напрямую править sendmail.cf (что очень не рекомендуется), то в него нужно добавить следующее:

O InputMailFilters=mainfilter

#O Milter.LogLevel O Milter.macros.connect=j, _, {daemon_name},

{if_name}, {if_addr}

O Milter.macros.helo={tls_version}, {cipher}, {cipher_bits},

{cert_subject}, {cert_issuer}

O Milter.macros.envfrom=i, {auth_type}, {auth_authen},

{auth_ssf}, {auth_author}, {mail_mailer}, {mail_host},

{mail_addr}

O Milter.macros.envrcpt={rcpt_mailer}, {rcpt_host}, {rcpt_addr}

Xfilteronegoru, S=inet:2525@milter.host.ru,

T=C:10m;S:30s;R:30s;E:10m

Описание каждого фильтра в файле конфигурации Sendmail может сопровождаться тремя опциями: F, S и T. Если опция F не задана, то проблемы с фильтром безболезненны для доставляемого Sendmail сообщения. Если F=T, неработоспособность фильтра приводит к временной неудаче (temporary fail) доставки сообщения. Если F=R и фильтр недоступен, сообщение отвергается (reject). В опции S указывается адрес фильтра, имеющий один из приведенных ниже форматов:

S=local:путь

S=inet:порт@хост

S=inet6:порт@хост

Здесь путь – путь к UNIX-сокету в локальной файловой системе, остальные – IP-сокеты на некотором хосте.

Опции Т задают таймауты. C – таймаут соединения с фильтром; S – таймаут при передаче информации от Sendmail фильтру; R – таймаут при ожидании ответа фильтра; E – таймаут ожидания окончательного подтверждения от момента передачи конца сообщения фильтру.

В случае использования двух фильтров фрагмент исходного файла конфигурации Sendmail будет выглядеть так:

MAIL_FILTER(`mainfilter", `S=inet:2525@milter.host.ru, T=C:10m;S:30s;R:30s;E:10m")

MAIL_FILTER(`filter2", `S=inet:2626@milter.host.ru, T=C:10m;S:30s;R:30s;E:10m")

define(`confINPUT_MAIL_FILTERS", `mainfilter,filter2")

Информация на фильтры посылается в порядке их описания. Следующий алгоритм взаимодействия Sendmail и Milter приведен в документации к Sendmail:

Для каждого соединения:

  Для каждого фильтра:

    Вызвать connect()

    Вызвать hello()

  Для каждого сообщения (последовательно):

    Для каждого фильтра:

      Вызвать envfrom()

    Для каждого получателя:

      Для каждого фильтра:

        Вызвать envrcpt()

    Для каждого фильтра:

      Для каждого поля заголовка:

        Вызвать header()

      Вызвать eoh()

      Для каждого фрагмента тела:

        Вызвать body()

      Вызвать eom()

  Для каждого фильтра:

    Вызвать close()

Примечание: при обрыве соединения на любой стадии и по инициативе любого агента вызывается abort() и close().

В листинге 2 приведен вариант метода eom(), с помощью которого можно вызывать из Milter произвольные программы для проверки сообщения (например, на спам и вирусы).

Листинг 2

  def eom(self):

    """Обработка сообщения: проверка на вирусы с помощью антивируса ClamAV"""

    # Закрываем временный файл (если он есть)

    if self.fp:

      self.fp.close()

    else:

      return Milter.TEMPFAIL

try:

 clam=os.popen("clamscanm <%s"%self.tempname,"r").read()

      if clam.find("FOUND") != -1:

        self.log(“virus rejected: %s” % clam)

        self.setreply(“550”,’5.1.1',’VIRUS FOUND %s’ % clam)

        return Milter.REJECT

    except:

      dbg_except()

    self.log(“msg accepted: size=%s” % self.bodysize)

    return Milter.ACCEPT

В этом методе вызывается программа clamscanm, принимающая файл на стандартный ввод и выводящая результат на стандартный вывод. Результат читается методом read() целиком и анализируется на присутствие подстроки «FOUND». Если есть такая строка, Milter устанавливает причину отказа методом setreply, пишет в лог и отклоняет сообщение. Так как все это происходит при установленном SMTP-соединении, отправитель получит «отлуп» сразу. Аналогично можно проверять почту на спам.

Следует отметить, что в процессе эксплуатации неаккуратно запрограммированный Milter может оставлять за собой временные файлы. Следующая небольшая программа на Python стирает оставленные Milter устаревшие файлы из каталога /var/tmp:

#!/usr/bin/python

import os, glob, time, stat

recent = time.time() - 60*20  # 20 min

for fl in glob.glob("/var/tmp/*.tmp"):

  try:

    if os.stat(fl)[stat.ST_MTIME] < recent:

      os.unlink(fl)

  except:

    pass

Итак, Milter дает нам полный контроль над передачей сообщений на самом раннем этапе – этапе входящего SMTP-соединения. Фильтры можно писать на C/C++, Perl и других языках, тем не менее Python Milter отлично справляется с задачей.


Комментарии отсутствуют

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

Комментарии могут оставлять только зарегистрированные пользователи

               Copyright © Системный администратор

Яндекс.Метрика
Tel.: (499) 277-12-41
Fax: (499) 277-12-45
E-mail: sa@samag.ru