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

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

Событие  

В банке рассола ждет сисадмина с полей фрактал-кукумбер

Читайте впечатления о слете ДСА 2024, рассказанные волонтером и участником слета

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

Организация бесперебойной работы  

Бесперебойная работа ИТ-инфраструктуры в режиме 24/7 Как обеспечить ее в нынешних условиях?

Год назад ИТ-компания «Крок» провела исследование «Ключевые тренды сервисного рынка 2023». Результаты

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

Книжная полка  

Читайте и познавайте мир технологий!

Издательство «БХВ» продолжает радовать выпуском интересных и полезных, к тому же прекрасно

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

СУБД PostgreSQL  

СУБД Postgres Pro

Сертификация по новым требованиям ФСТЭК и роль администратора без доступа к данным

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

Критическая инфраструктура  

КИИ для оператора связи. Готовы ли компании к повышению уровня кибербезопасности?

Похоже, что провайдеры и операторы связи начали забывать о требованиях законодательства

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

Архитектура ПО  

Архитектурные метрики. Качество архитектуры и способность системы к эволюционированию

Обычно соответствие программного продукта требованиям мы проверяем через скоуп вполне себе понятных

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

Как хорошо вы это знаете  

Что вам известно о разработках компании ARinteg?

Компания ARinteg (ООО «АРинтег») – системный интегратор на российском рынке ИБ –

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

Графические редакторы  

Рисование абстрактных гор в стиле Paper Cut

Векторный графический редактор Inkscape – яркий представитель той прослойки open source, с

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

День сисадмина  

Учите матчасть! Или как стать системным администратором

Лето – время не только отпусков, но и хорошая возможность определиться с профессией

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

День сисадмина  

Живой айтишник – это всегда движение. Остановка смерти подобна

Наши авторы рассказывают о своем опыте и дают советы начинающим системным администраторам.

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

Виртуализация  

Рынок решений для виртуализации

По данным «Обзора российского рынка инфраструктурного ПО и перспектив его развития», сделанного

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

Книжная полка  

Как стать креативным и востребованным

Издательский дом «Питер» предлагает новинки компьютерной литературы, а также книги по бизнесу

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

Книжная полка  

От создания сайтов до разработки и реализации API

В издательстве «БХВ» недавно вышли книги, которые будут интересны системным администраторам, создателям

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

Разбор полетов  

Ошибок опыт трудный

Как часто мы легко повторяем, что не надо бояться совершать ошибки, мол,

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

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

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

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

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

12.03.2018г.
Просмотров: 4196
Комментарии: 0
Глубокое обучение с точки зрения практика

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

12.03.2018г.
Просмотров: 2997
Комментарии: 0
Изучаем pandas

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

12.03.2018г.
Просмотров: 3800
Комментарии: 0
Программирование на языке Rust (Цветное издание)

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

19.12.2017г.
Просмотров: 3810
Комментарии: 0
Глубокое обучение

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

19.12.2017г.
Просмотров: 6305
Комментарии: 0
Анализ социальных медиа на Python

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

19.12.2017г.
Просмотров: 3157
Комментарии: 0
Основы блокчейна

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

19.12.2017г.
Просмотров: 3450
Комментарии: 0
Java 9. Полный обзор нововведений

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

16.02.2017г.
Просмотров: 7270
Комментарии: 0
Опоздавших не бывает, или книга о стеке

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

17.05.2016г.
Просмотров: 10634
Комментарии: 0
Теория вычислений для программистов

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

30.03.2015г.
Просмотров: 12357
Комментарии: 0
От математики к обобщенному программированию

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

18.02.2014г.
Просмотров: 13991
Комментарии: 0
Рецензия на книгу «Читаем Тьюринга»

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

13.02.2014г.
Просмотров: 9116
Комментарии: 0
Читайте, размышляйте, действуйте

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

12.02.2014г.
Просмотров: 7071
Комментарии: 0
Рисуем наши мысли

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

10.02.2014г.
Просмотров: 5381
Комментарии: 3
Страна в цифрах

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

18.12.2013г.
Просмотров: 4610
Комментарии: 0
Большие данные меняют нашу жизнь

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

18.12.2013г.
Просмотров: 3420
Комментарии: 0
Компьютерные технологии – корень зла для точки роста

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

04.12.2013г.
Просмотров: 3149
Комментарии: 0
Паутина в облаках

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

03.12.2013г.
Просмотров: 3397
Комментарии: 0
Рецензия на книгу «MongoDB в действии»

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

02.12.2013г.
Просмотров: 3019
Комментарии: 0
Не думай о минутах свысока

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

Друзья сайта  

 Практикум Python: обрабатываем входящую электронную почту

Архив номеров / 2006 / Выпуск №2 (39) / Практикум Python: обрабатываем входящую электронную почту

Рубрика: Программирование /  Автоматизация   | Дополнительные материалы

Сергей Супрунов

Практикум Python: обрабатываем входящую электронную почту

«Хороший сисадмин – ленивый сисадмин». Если что-то можно сделать вручную, то почти наверняка это же можно и автоматизировать. Вопрос лишь в соотношении цена/качество, которым определяется целесообразность разработки очередного скрипта.

Если говорить об автоматизации процессов администрирования, то более точно будет говорить о соотношении «время на разработку/экономия времени в дальнейшем». Лично для меня наилучшее значение достигается при использовании языка Python.

В данной статье мы рассмотрим решение одной достаточно узконаправленной задачи. Тем не менее методы и приёмы, которые будут здесь продемонстрированы, я надеюсь, помогут вам решать проблемы гораздо более широкого спектра.

Постановка задачи

Итак, есть почтовый сервер: FreeBSD + Sendmail + ClamAV, остальное в данном случае не важно.

ClamAV выполняет проверку всей входящей корреспонденции на вирусы и, помимо всего прочего, отсылает уведомления об обнаруженных вирусах на адрес postmaster. Проблема заключается в том, что администратору (для определённости будем считать, что он получает почту на адрес admin, что достигается соответствующей настройкой в /etc/mail/aliases) приходится обрабатывать достаточно большое число таких уведомлений. В то же время, они позволяют следить за вирусной активностью на вверенном узле, так что отказываться от такой информации нежелательно.

Таким образом, требуется снизить нагрузку на администратора без потери накапливаемой в процессе работы статистики.

Анализ проблемы

Попробуем решить данную задачу путём формирования ежедневных «дайджестов», отражающих вирусную активность за прошедшие сутки. Интересовать нас будет количество обнаруженных вирусов с разбивкой по их названиям.

Очевидно, что наиболее простым и в принципе более правильным решением было бы отключить уведомления на postmaster и просто обрабатывать log-файлы, формируемые в процессе работы.

Однако мы пойдём другим путём, чтобы рассмотреть на практике один из достаточно полезных приёмов: попробуем обрабатывать всю поступающую на postmaster почту, что называется, «на лету». Поскольку на данный адрес идёт почта не только от ClamAV, нам нужно перехватывать только интересующие нас уведомления, а всё остальное без изменений пересылать администратору (в нашем примере, на адрес admin).

Идеи по реализации

Наиболее удобным выглядит перенаправление почты на вход скрипта-обработчика путём создания канала (pipe), что можно реализовать с помощью псевдонимов (aliases). Например, если в файле /etc/mail/aliases добавить такую строку:

postmaster: “| /usr/local/scripts/maildigest/maildigest.py”

то вся почта, поступающая на указанный адрес, будет передаваться на стандартный вход (STDIN) сценария maildigest.py. На время тестирования можно будет не переправлять, а дублировать почту на скрипт, оставив в качестве получателя и прежнего пользователя (чтобы избежать потери почты):

postmaster: admin, “| /usr/local/scripts/maildigest/maildigest.py”

Получая очередное сообщение на обработку, сценарий должен будет проверить, не является ли оно уведомлением от ClamAV (будем контролировать два параметра: тему сообщения и тег заголовка «Auto-Submitted»). Если является, то обрабатываем его и записываем результат в некоторый файл (о формате пока не думаем). Если же это какое-то другое письмо, то положим его сразу в почтовый ящик пользователя admin. При необходимости изменить имя получателя его можно будет подправить непосредственно в коде сценария (не совсем «академическое» решение, но оно позволяет упростить код, не разбрасываясь на обработку параметров).

Небольшое исследование

Сначала давайте посмотрим, в каком виде сообщения поступают на вход сценария, а заодно проверим, с какими правами наш сценарий исполняется (эта информация нам понадобится в дальнейшем для решения проблемы передачи «прочей» почты пользователю admin).

Для этого напишем небольшой сценарий (как определено в aliases, сохраним его под именем maildigest.py, не забыв установить права на исполнение):

Листинг 1. Первый эксперимент

#!/usr/local/bin/python

import os, sys

mail = sys.stdin.read()

fd = open('/var/scripts/maildigest/mail.txt', 'w')

fd.write(mail)

fd.write(os.popen('id').read())

fd.close()

Здесь мы всё, что поступает на стандартный вход (дескриптор определён в sys.stdin), записываем в файл mail.txt. Сюда же добавляем строчку, возвращающую идентификатор текущего пользователя, для чего воспользуемся функцией os.popen(), которая создаёт канал между сценарием и системной командой (в нашем случае это команда id). На первых порах нужно разрешить любому пользователю создавать файлы в каталоге /var/scripts/maildigest.

В итоге получим примерно следующее (часть полей заголовка за ненадобностью не показана):

From clamav@mydomain.ru Fri Feb 17 10:00:07 2006

[. . .]

Date: Fri, 17 Feb 2006 09:53:41 +0300 (MSK)

Message-Id: <200602170653.k1H6rfHX047203@mydomain.ru>

From: MAILER-DAEMON@mydomain.ru

To: postmaster@mydomain.ru

Auto-Submitted: auto-submitted (antivirus notify)

Subject: Virus intercepted

X-Virus-Scanned: ClamAV 0.88/1291/Thu Feb 16 23:15:09 2006 on mydomain.ru

X-Virus-Status: Clean

[. . .]

 

The message k1H6rBq7047195 sent from to

 

contained Worm.SomeFool.P and has not been delivered.

uid=26(mailnull) gid=26(mailnull) groups=26(mailnull)

Вопросы доставки

Как видите, сообщение мы получаем в том виде, в каком оно будет в дальнейшем помещено в почтовый ящик пользователя. Обработать его проблем не составит – задействуем модуль rfc822, содержащий методы для разбора заголовков. А вот о чём придётся подумать, так это о том, как положить «транзитное» письмо в ящик пользователю. Можно, конечно, воспользоваться протоколом SMTP, но при наличии уже сформированного сообщения формировать его заново выглядит не очень разумным. К тому же есть риск зациклить обработку письма. Попробуем воспользоваться услугами локального агента доставки (LDA).

Хорошо бы просто отдавать сообщение на вход mail.local (LDA, используемый во FreeBSD по умолчанию). Здесь мы упираемся в то, что для выполнения своей работы mail.local должен запускаться с правами пользователя root.

Обойти это можно, установив на mail.local бит suid, однако поскольку такие права нужны нам для решения частной задачи, то более правильно будет создать копию агента доставки с нужными правами, а оригинальный файл не трогать:

# cd /usr/local/scripts/maildigest/

# cp /usr/libexec/mail.local mail.local.suid

# chmod 4555 mail.local.suid

Проведём ещё один эксперимент:

Листинг 2. Второй эксперимент

#!/usr/local/bin/python

import os

prefix_bin = '/usr/local/scripts/maildigest/'

prefix_var = '/var/scripts/maildigest/'

lda_command = prefix_bin + 'mail.local.suid admin'

mail = open(prefix_var + 'mail.txt', 'r').read()

os.popen(lda_command).write(mail))

Напомню, что все тестовые сценарии мы сохраняем под именем maildigest.py, чтобы не вносить каждый раз изменения в /etc/mail/aliases.

Здесь с помощью той же функции popen() создается канал с утилитой mail.local.suid, на вход которой передается текст сообщения, сохранённого в файле в результате предыдущего эксперимента. Пользователя-получателя указываем явно (admin). В принципе этот сценарий можно выполнить и непосредственно из командной строки, но лучше использовать тот же способ запуска через aliases, чтобы лишний раз убедиться в отсутствии проблем с правами доступа и переменными окружения.

Отправив тестовое сообщение на postmaster, убеждаемся, что доставка выполняется нормально, естественно, при условии, что запускался предыдущий тест, в результате которого должен был сформироваться файл, который в данном случае и используется. Значит, так и будем поступать в дальнейшем.

Сбор статистики и формирование «дайджеста»

Информация об обнаруженном вирусе (его название) содержится в последней строчке уведомления (если быть точнее, то в предпоследней, а последняя – пустая). Если эту строку разбить по пробелам, то нужное нам имя получим во втором поле.

Учитывая, что на моём сервере больших нагрузок не предвидится, для хранения результата я выбрал формат DBM.

В стандартную поставку Python включён модуль anydbm, который самостоятельно определяет, какая именно реализация DBM используется в вашей системе, так что об этом нам заботиться не придётся. Данные в этом формате хранятся в виде пар «имя – значение». Единственный его недостаток в нашем случае – это то, что он позволяет хранить только текстовые данные, т.е. придётся в процессе работы выполнять преобразования сохранённого значения, отражающего количество обнаруженных вирусов данного типа, из строки в число и обратно.

Для удобства будем хранить информацию посуточно, для чего имя db-файла будет формироваться с учётом текущей даты (см. код сценария ниже).

Наконец, при смене даты нам нужно будет формировать и отправлять сводный отчёт за прошедшие сутки. Поскольку отчёт предназначается локальному пользователю, то проще всего будет сформировать «вручную» сообщение с нужными заголовками и воспользоваться тем же LDA для его доставки.

Реализация

Итак, приступим к разработке сценария. Чтобы сохранить целостность восприятия, полностью приведу прокомментированный текст скрипта, а ниже дам некоторые пояснения.

Листинг 3. Сценарий maildigest.py

#!/usr/local/bin/python

# -*- coding: koi8-r -*-

# Импортирование нужных модулей

import os, sys, rfc822, anydbm

from StringIO import StringIO

from time import ctime, strftime

# «Родительские» каталоги для размещения файлов

prefix_bin = '/usr/local/scripts/maildigest/'

prefix_var = '/var/scripts/maildigest/'

# Команда доставки сообщения --

lda_command = prefix_bin + 'mail.local.suid admin'

# Текущее имя db-файла (зависит от текущей даты)

mdfile = prefix_var + 'md' + strftime('%Y%m%d')

# Файл хранит имя следующей для обработанной базы

next = prefix_var + '.next'

# Функция формирования и отправки «дайджеста»

def send_digest(fn=mdfile):

    # Здесь ошибку не проверяем, поскольку всё равно работу сценария придётся прерывать

    lda = os.popen(lda_command, 'w').write

    # Формируем необходимые заголовки

    lda('From: maildigest\r\n')

    lda('To: admin@mydomain.ru\r\n')

    lda('Date: %s\r\n' % ctime())

    lda('Subject: Virus digest\r\n')

    lda('\r\n')

    # Фомируем отчёт

    lda('Вирусная активность за %s\r\n\r\n' % fn[-8:])

    lda('%-30s | %3s\r\n' % ('Имя вируса', 'К-во'))

    lda('-' * 38 + '\r\n')

    try:

        d = anydbm.open(fn)

        total = 0

        for i in d.keys():

            lda('%-30s | %3s\r\n' % (i, d[i]))

            total += int(d[i])

        lda('-' * 38 + '\r\n')

        lda('%-30s | %3d\r\n’ % ('ИТОГО', total))

        # Записываем в .next следующую базу

        open(next, 'w').write(mdfile)

    except:

        lda('Ошибка формирования дайджеста для %s\r\n' % fn)

# Считываем поступившее сообщение...

mail = sys.stdin.read()

# ...и разбираем его «по косточкам»

message = rfc822.Message(StringIO(mail))

# Если в заголовке есть указанные поля с указанными значениями, то считываем имя вируса

# и увеличиваем для него счётчик

if (message.getheader('Subject') == 'Virus intercepted' and

               message.getheader('Auto-Submitted') ==

               'auto-submitted (antivirus notify)'):

    try:

        # Первый «сплит» выделяет предпоследнюю строку (последняя - пустая),

        # а второй - второе поле строки

        virusname = mail.split('\n')[-2].split(' ')[1]

    except:

        virusname = 'Format error'

    stat = anydbm.open(mdfile, 'c')

    if stat.has_key(virusname):

        stat[virusname] = str(int(stat[virusname]) + 1)

    else:

        stat[virusname] = '1'

else:

    os.popen(lda_command, 'w').write(mail)

# Если в next-файле записано имя «прошлой» базы, то отсылаем дайджест

if os.path.isfile(next):

    mdf = open(next, 'r').read(len(prefix_var) + 10)

    if mdfile != mdf:

        send_digest(mdf)

else:

    open(next, 'w').write(mdfile)

На всякий случай напомню, что блоки кода в Python задаются с помощью отступов. В принципе всё должно быть понятно. Пояснения требует разве что использование переменной lda в функции send_digest(). В первой строке мы присваиваем этой переменной ссылку на метод write(), который применяется к каналу, созданному для команды, определённой в lda_command. В дальнейшем мы можем использовать функцию lda() как замену конструкции os.popen(lda_command, 'w').write().

Ещё следует указать, что скобки позволяют обойти жёсткие требования соблюдать отступ, чем мы и воспользовались в трехстрочной конструкции if, где мы проверяем наличие в письме нужных нам заголовков.

Теперь вместо десятков уведомлений администратор будет получать одно письмо в день, отражающее вирусную активность на почтовом сервере за истекшие сутки (см. рис. 1).

Рисунок 1. Примерно так выглядят формируемые отчёты

Рисунок 1. Примерно так выглядят формируемые отчёты

Нет предела фантазии

Насладившись работой нашего сценария, поразмышляем о том, что ещё полезного можно сделать подобным способом.

Во-первых, можно реализовать автоматический разбор входящей почты (когда сообщения со словом «Договор» в теме направляются в абонентский отдел, а со словом «Счёт» – в бухгалтерию). Заодно можно организовать функцию автоматического ответа, когда отправитель сообщения будет получать уведомление, что его письмо получено и передано на обработку Иванову Ивану Ивановичу (ещё одно последствие борьбы со спамом, когда приходится подтверждать доставку почти каждого важного письма, чтобы убедиться, что оно не попало под «жернов» одного из фильтров).

Во-вторых, обработка входящих сообщений позволяет при определённой доле осторожности и «разумности» использовать электронную почту для управления каким-либо сервисом путём отправки команд в теле или теме сообщения на определённый адрес (только не используйте этот механизм для создания учётных записей и перезагрузки сервера!).

Наконец, можно пропускать входящую корреспонденцию через собственные фильтры или собирать ту или иную статистику. В общем, полёт вашей фантазии ничем не ограничен.


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

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

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

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

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