Страница 1 из 1

Обработка почтовых отчетов на Perl

Добавлено: 2011-07-27 7:52:22
serge
Есть задача распарсить отчет об ошибочной доставки письма на Perl и далее уже данные передать в БД. Сама загвоздка встала в разборке текста. Вариантов отчетов от почтовых серверов несколько, все выглядят по разному. Можно для упрощения начать с одного наиболее распространенного типа (пример его в низу).
Из отчета хотелось бы доставать email на который не дошло письмо и текст ошибки доставки.
Интересует алгоритм работы с текстами (доставание оттуда инфы, которая может размещаться в разных местах текста), реализация этого алгоритма на perl в частности. Можно просто ссылки на аналогичные скрипты, можно даже на других языках.

Пример самого распространенного типа отчета об ошибке:

Код: Выделить всё

This message was created automatically by mail delivery software.

A message that you sent could not be delivered to one or more of its
recipients. This is a permanent error. The following address(es) failed:

  morosowwa.tat@yandex.ru
    SMTP error from remote mail server after RCPT TO:<morosowwa.tat@yandex.ru>:
    host mx.yandex.ru [87.250.250.89]: 550 5.7.1 No such user!

Код: Выделить всё

This message was created automatically by mail delivery software.
A message that you sent has not yet been delivered to one or more of its
recipients after more than 24 hours on the queue on mailer.smartresponder.ru.

The message identifier is:     1QlZXa-000E6I-Dd
The date of the message is:    Tue, 26 Jul 2011 08:46:45 +0400
The subject of the message is: =?windows-1251?B?T3JpY28uIMLg+CDv8Ojh++v87fvpIEFMUEhBLeHr7uMhIDIuMA==?=

The address to which the message has not yet been delivered is:

  orico@km.ru
    Delay reason: SMTP error from remote mail server after end of data:
    host m2.mx.km.ru [217.197.114.195]: 450 4.4.2 Bad connection

No action is required on your part. Delivery attempts will continue for
some time, and this warning may be repeated at intervals if the message
remains undelivered. Eventually the mail delivery software will give up,
and when that happens, the message will be returned to you.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-07-27 10:43:58
serge
email достать получилось, с текстом ошибки вопрос открытый.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-07-27 12:31:21
serge
разобрался

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-07-28 21:23:20
Alex Keda
ну тк выложи результат.
от того что ты разобрался - никому не тепло ни холодно

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-07-31 20:34:35
serge
Для доставания email применил модуль Perl Email-Find.
По тексту тупо условиями if else проверка текстов писем на наличие в них нужных фраз и в зависимости от типа письма выдергивание нужных абзацев или строк.
Сам скрипт корявый, но при необходимости могу приложить))

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-07-31 21:15:37
Alex Keda
и тему закрыл...
наверное, чтобы не попросили приложить? =)

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-01 8:18:36
serge
Прикладываю свое корявое поделие :)

Код: Выделить всё

#!/usr/bin/perl -w

#use strict;
use Email::Find;
use DBI;

sub addTrashInfo {
my ($arg) = @_;

my $exim_body = Exim::expand_string('$message_body');

if ($exim_body =~ m/This\ message\ was\ created\ automatically\ by\ mail\ delivery\ software/i)
{
	my @emails = ();
	my $finder = Email::Find->new(sub {
								my($email, $orig_email) = @_;
								push (@emails, $email->format);
								return $orig_email;
							});
	$finder->find(\$exim_body);
	
	if ($exim_body =~ m/A\ message\ that\ you\ sent\ has\ not\ yet/i)
	{
		if ($exim_body =~ m/Delay\ reason/i)
		{
			($part1, $part2, $part3, $part4) = split /\n{2}/, $exim_body, 5;
			($part31, $part32) = split /\n/, $part4, 2;
				
			for ($part32) {
				s/^\s+Delay.*\n//;
			}
		}
		else
		{
			($part1, $part2) = split /\n{2}/, $exim_body, 3;
			($part31, $part32) = split /\n/, $part1, 2;
		}
	}
	else
	{
		($part1, $part2, $part3) = split /\n{2}/, $exim_body, 4;
		($part31, $part32) = split /\n/, $part3, 2;
			
		for ($part32) {
			s/^\s+SMTP.*\n//;
		}
	}
	
	for ($part32)
	{
		s/^\s+//;
		s/\n+//;
		s/\s+/ /g;
	}
	
	open(EXILOG,">> /var/log/exim_perl.log");
	
	my $dbh = DBI->connect("DBI:mysql:database=h11036_exim;host=schtep.ru","h11036_exim", "secret") || die $DBI::errstr;
	my $result = $dbh->prepare("INSERT INTO emails (email, error, body) VALUES (\'@emails['0']\', \'$part32\', \'$exim_body\');");
	$result->execute();
	
	print (EXILOG @emails['0']."\n");
	print (EXILOG $part32."\n");
	print (EXILOG "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
	print (EXILOG "\n");
	close(EXILOG);
}
else
{
	my $err_pattern = "BAD!";
	open(EXILOG,">> /var/log/exim_perl.log");
	print (EXILOG "$err_pattern\n");
	#print (EXILOG "$exim_body");
	print (EXILOG "\n");
	print (EXILOG "=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=");
	print (EXILOG "\n");
	close(EXILOG);
	
}
#Exim::log_write("Perl test. Body is $exim_body");

return "Perl: trash info has been added.";
}

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-01 8:21:49
serge
Есть дальнейшая задумка вынести все это в отдельный сервис (daemon) и передавать на него письма через LMTP. Как чето в этом направлении прояснится, то покажу результат.
Если у кого есть наработки в этой области (создание демона на perl и работа с lmtp) буду рад посмотреть на них. В инете достаточно мало примеров. Тему соответственно открываю.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-01 21:25:08
thefree
https://metacpan.org/module/Net::Server::Mail::LMTP решение всех ваших проблем

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-01 22:27:24
serge
thefree писал(а):https://metacpan.org/module/Net::Server::Mail::LMTP решение всех ваших проблем
Да, про него уже читал и тоже думаю что он мне подойдет. Тут я пока только не понимаю смогу ли использовать unix сокет? Ну и пока не вкуриваю как в итоге мне достать тело письма пришедшее по lmtp? :oops:

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-02 11:34:55
thefree
так там же callback есть, по ним и разбирайте письмо

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-02 14:39:17
serge
thefree писал(а):так там же callback есть, по ним и разбирайте письмо
да, чувствую нужно просто брать и пробовать... дойду до истины опытным путем. :roll:
===
а про unix сокет вопрос открытый... можно его задействовать в этом модуле?

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-02 18:39:42
serge
В общем пока имею вот что:
1. lmtp сервер (perl скрипт), слушающий unix сокет
2. роутер и транспорт в exim для редиректа писем ошибок на него
3. в скрипте достаем тело (data) письма и сохраняем в лог.
4. смотрим на бегущие строки в логе ;-)

Если кому интересно, могу показать реализацию. Но она очень примитивна и сделана ради проверки работы связки. Основная логика обработки ошибок в ней отсутствует.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-02 20:59:42
thefree
показывай (:

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 7:39:12
serge
lmtp.pl

Код: Выделить всё

 use IO::Socket::UNIX;
 use Net::Server::Mail::LMTP;
 use Data::Dumper;

my @local_domains = qw(example.com example.org);
my $server = new IO::Socket::UNIX Listen =>1, Type => SOCK_STREAM, Local => '/tmp/lmtp.sock';

my $conn;
while($conn = $server->accept)
{
	my $esmtp = new Net::Server::Mail::LMTP socket => $conn;
	# adding some handlers
	$esmtp->set_callback(RCPT => \&validate_recipient);
	$esmtp->set_callback(DATA => \&queue_message);
	$esmtp->process();
	$conn->close()
}

sub validate_recipient
{
	my($session, $recipient) = @_;

	my $domain;
	if($recipient =~ /@(.*)>\s*$/)
	{
		$domain = $1;
	}

	if(not defined $domain)
	{
		return(0, 513, 'Syntax error.');
	}
	elsif(not(grep $domain eq $_, @local_domains))
	{
		return(0, 554, "$recipient: Recipient address rejected: Relay access denied");
	}

	return(1);
}

sub queue_message
{
	my($session, $data) = @_;

	my $sender = $session->get_sender();
	my @recipients = $session->get_recipients();

	return(0, 554, 'Error: no valid recipients')
		unless(@recipients);

	#my $msgid = add_queue($sender, \@recipients, $data)
	#  or return(0);

	#return(1, 250, "message queued $msgid");
	
	my $msg = Dumper($data);
	
	open(EXILOG,">> /var/log/exim_perl.log");
	print (EXILOG $msg);
	print (EXILOG "=-=-=-=-=-=-=");
	print (EXILOG "\n");
	close(EXILOG);
	
	return(1, 250, "message queued");
}
exim/configure

Код: Выделить всё

trash:
           driver          = accept
           condition       = "${if  match{$h_To:}{trash@example.com} {1}{0}}"
           transport       = lmtp
...
lmtp:
            driver = lmtp
            socket = /tmp/lmtp.sock
            batch_max = 2
            user = mailnull

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 7:44:20
serge
Тут возникла некоторая проблемка... данные в переменной $data в lmtp.pl хранятся в виде ссылки что ли. Т.е. при попытке вывести их непосредственно через print получается что-то типа этого SCALAR(8x007895). Поэтому пока извращаюсь через dumper. Может подскажет кто как их оттуда достать по человечески.
Ну и после создания сокета приходится менять его владельца на mailnull иначе exim на него не может ничего передать. Как понимаю при создании сокета этот параметр не задается? Менять его нужно после создания уже самостоятельно?

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 13:28:06
thefree
1. Покажи вывод Data::Dumper полностью.
2. сверху бы стоило добавить

Код: Выделить всё

use Proc::PID::File;
die "Already running!" if Proc::PID::File->running();
У вас же это демон.
3. В лес идти не будем

Код: Выделить всё

chown('mailnull', 'wheel', '/tmp/lmtp.sock') if -f "/tmp/lmtp.sock";
p.s. use strick & use warnings или use common::sense; Лучше сразу правильно кодить.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 13:33:35
thefree
вот еще я очень плохо знаком с рфс но не понимаю зачем Вам sub validate_recipient и обработка RCPT

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 14:11:11
serge
1. Ок, сделаю
2. Спасибо, добавил.
3. Спасибо, добавил. Только ключ -S при проверке сокета. Добавил так же проверку и удаление старого файла сокета. Остается если ранее запускался скрипт и падал с ошибкой. А при наличии уже созданного файла новый запуск скрипта вываливается в ошибку.
4. strict и warnings тоже добавил

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 14:29:47
serge
chown принимает цифровые значения uid и gid. Обыграл вот так:

Код: Выделить всё

my $sock = '/tmp/lmtp.sock';
my $eximuser = 'mailnull';

my ($login,$pass,$uid,$gid) = getpwnam($eximuser);
chown($uid, 0, $sock) if -S $sock;

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 15:16:53
serge
thefree писал(а):вот еще я очень плохо знаком с рфс но не понимаю зачем Вам sub validate_recipient и обработка RCPT
Интересно то, что если эти моменты исключить из скрипта, то exim ругается что соединение было закрыто сразу после открытия и сообщение соответственно на скрипт не передается. Пока оставил скрипт так как было, буду разбираться.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 19:58:06
thefree
покажите полностью ошибку и попробуйте print @{$data}

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-03 23:08:27
serge
Получилось через $$data.
Также исключил из скрипта функцию validate_recipient
===
Пока перешел к парсингу самого письма. Добиваю функцию выдергивания хедеров из отчета об ошибки и из оригинального письма, которое частично или полностью прикладывается к этому отчету.

Re: Обработка почтовых отчетов на Perl

Добавлено: 2011-08-04 8:08:18
thefree
будет нужна помощь пишите.