Информация
На главную Главная

Мой t-cards.ru
Войти Войти
Зарегистрироваться Регистрация

Разное
Форум Форум
Вернуться Форумы на t-cards.ru> Hard"n"Soft
Логин
Пароль
Регистрация Участники Поиск >> FAQ


Сообщения в теме: "Exim - сортировка писем "на лету" по папкам IMAP п..."
07.07.2006 10:49
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Итак, не смотря на гибкость SQL запросов почту разносить по папкам на основе пользовательских правил можно и другим способом. Здесь будет рассмотрен пример конфигурации MTA Exim с использованием функций perl для сортировки писем по папкам. Perl обладает гигантскими возможностями в области обработки строк, поэтому о гибкости этого варианта говорить не приходится.

Не смотря на то, что будет рассмотрен вполне рабочий вариант помните, что это всего лишь пример и каждый может соорудить на этой основе свои обработки, что называется, "под себя" ))

В конфиге exim изменение необходимо внести в параметры локальной доставки:
local_delivery:
driver = appendfile
directory = /var/mail/${perl{getmaildir}{$domain}{$local_part}{$rheader_from:}{$rheader_reply-to:}{$rheader_subject:}{$spam_score_int}}
maildir_format
maildir_tag = ,S=$message_size
delivery_date_add
envelope_to_add
return_path_add
group = mail
user = mailnull
quota = ${lookup mysql{SELECT quota FROM users WHERE login="${local_part}" AND domain="${domain}"}}
mode = 0660
no_mode_fail_narrower

не забываем использовать IMAP maildir формат, иначе ничего не получится.
Как видно мы вызываем некую перловую функцию, для этого необходимо в конфиге подгрузить файл с этой функцией:
perl_startup = do "/usr/local/etc/exim/exim.pl"
В функцию передаётся 6 параметров:
1 Домен получателя
2 Локальная часть получателя
3 Отправитель (из заголовка)
4 Значение reply-to (из заголовка)
5 Тема (1я строка после заголовка)
6 Спамрейтинг письма.

Если не используется spamd в последнее значение передавайте 0.
Также перед вызовом перл функции указана корневая папка почты (/var/mail). Остальную информацию вернёт перловая

функция. Если совпадения по правилам найти не удасться, то, по умолчанию, функция вернёт $domain/$local_part и полный путь пришедшего письма будет /var/mail/$domain/$local_part.

Все правила хранятся в mysql БД, вот табличка:
create table rules (id int not null,
login varchar(16),
domain varchar(32),
rule int not null,
field int not null,
str varchar(128),
target varchar(255) not null,
primary key (id,login,domain));

Можно былоб в качестве идентификатора пользователя использовать ключ из другой таблицы, но здесь выборка

предполагается на основе домена и пользователя почтового ящика.
Колчиство правил (rule) неограничено, в моем скрипте их пока 7:
1 Равно (строковое сравнение)
2 Начинается с ...
3 Заканчивается на ...
4 Содержит в себе ...
5 Больше чем ... (сравнение integer для спамрейтинга)
6 Меньше чем ... (сравнение integer для спамрейтинга)

Столбец field - сравниваемое поле, от 1 до 4 по порядку передаваемых в функцию параметров.
1 Отправитель (из заголовка)
2 Значение reply-to (из заголовка)
3 Тема (1я строка после заголовка)
4 Спамрейтинг письма.

str - строка, котороая задаётся пользователем и которая учавствует в сравнениях.
id - номер правила... правила при обработке скрипта выбираются в порядке возрастания по этому полю. если есть вероятность, что правило может совпасть с несколькими вариантами сортировки, то возвращается путь первого совпавшего значения.
targer - путь в нотации IMAP. Это строка, которая кодирована в IMAPUTF7 кодировку и разделена точками в соответсвии с иерархической системой IMAP maildir (RFC2060). Например, .&BB4EQg- &BCAEPgQ8BDAEPQQw-.&BB0EPgQyBD4ENQ-
Если понадобится создать папку в папке Входящие (корневая папка пользователя) то префиксом будет .INBOX. Все указанные в правилах пути будут созданы автоматически агентом Exim (а точнее драйвером локальной доставки) в случае их отсутствия (не забудьте только подписаться на новые папки).

07.07.2006 10:49
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Наконец сам скрипт perl:

#!/usr/bin/perl -w
use strict;
use DBI;
use DBD::mysql;
use MIME::Words qw(:all);
use Text::Iconv;

my $debug_enable=1;
my $debug_logfile="/var/log/exim/perl.log";

debug("enter");

sub header_decode {
my ($header) = @_;
return join("", map {xcode(${$_}[1], ${$_}[0])} decode_mimewords($header));
}

sub xcode {
my ($charset, $str) = @_;
return $str unless $charset;
my $conv = Text::Iconv->new($charset, "CP1251");
return $conv->convert($str);
}

sub getmaildir {
my ($domain,$user,$h_from,$h_replyto,$h_subject,$spam) = @_;

my $dbh;
$dbh = DBI->connect("DBI:mysqlatabase=mail;host=localhost","root","");
if (!dbh)
{
debug("no mysql connection");
return "/$domain/$user";
}

my ($name_from);
my @val;
if ($h_from =~ /(.*)\<(.*)\>.*/)
{
$h_from = $2;
$name_from = $1;
}

push(@val,header_decode(trim($h_from)));
push(@val,header_decode(trim($h_replyto)));
push(@val,header_decode(trim($name_from)));
push(@val,header_decode(trim($h_subject)));
push(@val,header_decode(trim($spam)));

debug("message received");

debug("1: ".$val[0]);
debug("2: ".$val[1]);
debug("3: ".$val[2]);
debug("4: ".$val[3]);
debug("5: ".$val[4]);

my $sth = $dbh->prepare("SELECT * FROM rules where login="$user" AND domain="$domain" ORDER BY id");
$sth->execute;

if ($sth->rows() > 0)
{
foreach ($sth->fetchrow_hashref())
{
if ($_->{"rule"} == 1)
{
if ($val[$_->{"field"}-1] eq $_->{"str"})
{
debug("rule 1, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 1, no eq");
}
07.07.2006 10:49
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

elsif ($_->{"rule"} == 2)
{
if ($val[$_->{"field"}-1] =~ /^$_->{"str"}.*/)
{
debug("rule 2, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 2, no regexp");
}
elsif ($_->{"rule"} == 3)
{
if ($val[$_->{"field"}-1] =~ /.*$_->{"str"}$/)
{
debug("rule 3, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 3, no regexp");
}
elsif ($_->{"rule"} == 4)
{
if ($val[$_->{"field"}-1] =~ /.*$_->{"str"}.*/)
{
debug("rule 4, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 4, no regexp");
}
elsif ($_->{"rule"} == 5)
{
if ($val[$_->{"field"}-1] > $_->{"str"})
{
debug("rule 5, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 5, no regexp");
}
elsif ($_->{"rule"} == 6)
{
if ($val[$_->{"field"}-1] < $_->{"str"})
{
debug("rule 6, path returned");
return "/$domain/$user/$_->{"target"}";
}
debug("rule 6, no regexp");
}

}
}
debug("no rules, default returned");
return "/$domain/$user/";
}

sub debug {
if ($debug_enable)
{
open (LOG,">>$debug_logfile");
print LOG scalar localtime (time),": ", $_[0],"\n";
close LOG;
}
}

sub trim {
my @out = @_;
for (@out)
{
s/^\s+//;
s/\s+$//;
s/\n$//;
s/\r$//;
}
return @out == 1
? $out[0]
: @out;
}


Следующий шаг - написать для этого всего веб интерфейс.
07.07.2006 14:36
Admin

Регистрация: 05.10.2005
Проживание: Москва
Сообщения: 272
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Можно еще в таблицу rules добавить поле priority, чтоб пользователь мог задавать порядок отработки правил.

SELECT * FROM rules where login="$user" AND domain="$domain" ORDER BY priority
07.07.2006 15:33
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Поле ID эту функцию и выполняет... оно не auto_increment и 2х значений идентичных у любого из пользователей здесь, следуя объявлению таблицы, быть не может.
07.07.2006 19:55
Admin

Регистрация: 05.10.2005
Проживание: Москва
Сообщения: 272
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

> quota = ${lookup mysql{SELECT quota FROM users WHERE login="${local_part}" AND domain="${domain}"}}

Кстати, навеяло на мысль... Как интересно exim считает занятое письмами место? Каждый раз при прибытии письма он перебирает все файлы?
И существенный недостаток, что проверка квот идет ПОСЛЕ приема письма, т.е. сразу после команды DATA не генерится ошибка 5xx, а письмо принимается, а только в локальной доставке при превышении квоты генерится письмо об ошибке, что не есть гуд.
Интересно, а вот если заюзать переменную $message_size в процессе приема письма, просканить папку пользователя и генерить ошибки при превышении квоты, как долго будет отрабатывать прием письма? Допустим для пользователей, у которых мало писем, это быстро будет, а для тех у кого много?
07.07.2006 20:00
Admin

Регистрация: 05.10.2005
Проживание: Москва
Сообщения: 272
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

А вот так кстати отработает?

sub trim {
my @out = @_;
for (@out)
{
s/^\s+|\s+$|\r\n//;
}
return @out == 1
? $out[0]
: @out;
}
09.07.2006 23:09
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

> А вот так кстати отработает?
sub trim {
my @out = @_;
for (@out)
{
s/^\s+|\s+$|\r\n//;
}
return @out == 1
? $out[0]
: @out;
}


Да как угодно ))) только \r и \n надо в оператор ИЛИ тоже... в винде \r\n в юниксе \n в маках \n\r
у тебя удалится один из элементов, у меня все (если они будут)
09.07.2006 23:11
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

По поводу квот есть вариант ещё юзать квоты файловых систем, но, опять же, с точки зрения экономии трафика такой подход преимуществ не даст.
Да и МТА будет в frozen класть очередь, а не болтить отправителю.
18.07.2006 13:58
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Обнаружил на досуге болтик, вместо этого кода:

if ($sth->rows() > 0)
{
foreach ($sth->fetchrow_hashref())
{


следует писать:
if ($sth->rows() > 0)
{
while (my $str = $sth->fetchrow_hashref())
{
$_ = $str;

и фсё будет жжжжж.
20.07.2006 09:08
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Во время написания web интерфейса к базе с правилами было выяснено, что модуль, входящий в состав портов для кодирования строки в imap utf7 мог ошибаться. Пришлось руками отучать его от плохих привычек, вот что получилось:

sub _imap_utf7_decode {
my ($s) = @_;

$s =~ s/\+/PLUSPLACEHOLDER/g;
$s =~ s/&([^,&\-]*),([^,\-&]*)\-/&$1\/$2\-/g;
$s =~ s/&(?!\-)/\+/g;
$s =~ s/&\-/&/g;
$s =~ s/w-\ /w\ /g;
$s =~ s/PLUSPLACEHOLDER/\+/g;

return $s;
}

sub _imap_utf7_encode {
my ($s) = @_;

$s =~ s/\+\-/PLUSPLACEHOLDER/g;
$s =~ s/\+([^\/&\-]*)\/([^\/\-&]*)\-/\+$1,$2\-/g;
$s =~ s/&/&\-/g;
$s =~ s/\+([^\-]+)?\-/&$1\-/g;
$s =~ s/PLUSPLACEHOLDER/+/g;

return $s;
}

Вот, собственно, сам интерфейс http://192.168.150.13/cgi-bin/mail.pl
У кого есть почта в наших доменах вводите свои данные и вперёд...
20.07.2006 11:58
Admin

Регистрация: 05.10.2005
Проживание: Москва
Сообщения: 272
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

> Вот, собственно, сам интерфейс http://192.168.150.13/cgi-bin/mail.pl
У кого есть почта в наших доменах вводите свои данные и вперёд...

Клева!!
Слушай, а сопри мой скрипт для отрисовки дерева папок, который я применял тут
http://www.t-cards.ru/?target=mail&action=view
зайти и глянь пхп-шки, красиво блин, мне нравица! )
И кстати в пунктике Folder можно сделать кнопку выбора папки из этого же дерева..


> Во время написания web интерфейса к базе с правилами было выяснено, что модуль, входящий в состав портов для кодирования строки в imap utf7 мог ошибаться

Кстати, я тоже с подобным столкнулся.. тока я так и не понял, у меня при раскодировке "глотает" русскую заглавную букву "Р", перл тоже так делает?
20.07.2006 12:03
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Нет, в перле баг был иной. Он не понимал конструкций +BDSA+VDAQQw-
по идее в имап утф7 должно получиться нечто &BDSA+VDAQQw-
он же делал +BDSA&VDAQQw-
Соответственно, назад он тож из правильной строки не мог получить родной утф7.
На сколько я пробовал мой вариант делает всё верно ) время покажет, если это не так.
20.07.2006 12:06
Admin

Регистрация: 05.10.2005
Проживание: Москва
Сообщения: 272
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

Кстати, у меня такая же хрень была с пхп, смотри:

function folder_encode($str){
$str_ar = explode(".",$str);
$res_arr = array();
foreach ($str_ar as $val){
$res_arr[] = str_replace("+","&",imap_utf7_encode(mb_convert_encoding($val,"UTF-7","windows-1251")));
}
return implode(".",$res_arr);
}
20.07.2006 12:25
Admin

Регистрация: 19.04.2006
Проживание: Калуга
Сообщения: 57
По умолчаниюExim - сортировка писем "на лету" по папкам IMAP по пользовательским правилам -- Часть2

В рфс по этому поводу:
1) UTF-7 uses the "+" character for shifting; this conflicts with
the common use of "+" in mailbox names, in particular USENET
newsgroup names.

и мне почему-то думалось, что "+" в модифицированной строке быть не должно, ан нет...