Одной из малоизвестных возможностей современных MTA является организация множественных раздельных входных почтовых потоков для каждого пользователя без какого-либо участия рута по заведению каждого такого входного потока; участие рута может требоваться для однократного включения данной возможности, потом оно уже не требуется. Это называется, в зависимости от стиля и происхождения источника, терминами: "тегированные форварды", "тегированные локальные имена", "тегированные входные потоки", "плюсованные форварды", "плюсованные локальные имена" или как-то иначе. Как минимум, это поддерживают sendmail, exim, cyrus-imapd, qmail, причем у qmail это свойство, никак не называясь, принципиально для построения ряда стандартных приложений и реализовано наиболее продвинуто (см. рассказ далее).

В отсутствие такой возможности ("plain old" вариант;))), вся поступающая на некоторого пользователя почта складывается в его ящик или же поступает на заданный входной фильтр, где она подвергается разбору в зависимости от самых разных параметров. Во многих случаях это более чем достаточно. Например, можно определять рассылки по полям заголовка `Sender:' и `Delivered-To:'; можно копировать в отдельные ящики в случаях определенных `From:'; можно фильтровать спам по `Subject:'. Но иногда возникают ситуации, когда никакой критерий по заголовку или телу письма не может однозначно задать, что делать с письмом. В этом случае тот, кто не знает про множественные входные потоки, решает, что единственный возможный вариант - завести еще один входной ящик методом заведения пользователя, неважно, на том же хосте, на отдельном домене или на каком-нибудь hotmail'е. Вместо этого, имя тегированного входного потока может использоваться в качестве отдельного ящика; конечно, по нему будет однозначно (или почти однозначно) видно, что это не независимый почтовый ящик, но во многих случаях это и не требуется, а возможность гибкого управления на месте (или невыход за пределы сети) оказывается значительно важнее.

Реализация в sendmail

Для случая форвардов, находящихся в домашнем каталоге пользователя (наверно, 99.99% установок на sendmail'е), и неизменении управляющих разбором локального адреса частей конфига от дефолтного, достаточно наличия в sendmail.cf опции ForwardPath со следующими компонентами:


$z/.forward+$h
$z/.forward

Пример такой опции (из дефолтной установки FreeBSD):
O ForwardPath=$z/.forward.$w+$h:$z/.forward+$h:$z/.forward.$w:$z/.forward

(Заметим, что man forward ни слова не говорит про такую возможность...)

Локальная часть адреса - то есть то, что в стандартной форме адреса (user@domain) находится слева от знака `@' - разбирается на формат "u+t", где u - существующий пользователь, t - некоторая строка, которая становится тегом. Так как `+' не может быть в имени пользователя, такое разбиение однозначно. Случай простого имени пользователя трактуется как случай пустого тега (тега нулевой длины), то есть `vasya' аналогично `vasya+'. Вычисленный тег (даже в случае пустого тега) пишется во внутреннюю переменную sendmail'а $h и применяется при разборе ForwardPath для поиска форварда. При ForwardPath приведенного вида, сначала будет попробован $z/.forward+$h (случай с участием $w я не считаю - кому надо, тот его разберет), затем, если $z/.forward+$h не найден, будет применен $z/.forward - дефолтный вариант. Например, для vasya@localhost будет сначала искаться ~vasya/.forward+, затем ~vasya/.forward; для vasya+from-masha@localhost - ~vasya/.forward+from-masha, затем ~vasya/.forward. (Здесь везде ~vasya означает домашний каталог пользователя vasya - замечание для тех редких читателей, кто этого не знает;)) Дальнейшее поведение уже однозначно определяется содержанием соответствующего форварда.

Описанная схема на самом деле более сложна - за счет излишне вычурного внутреннего поведения sendmail'а в отработке aliases и рулесетов 4 и 5. Так, для описанного примера, для vasya+from-masha будет сначала в алиасах искаться vasya+from-masha, затем - в алиасах vasya; только при отсутствии обоих, начнется поиск в форвардах. Алиас без тега, таким образом, имеет преимущество над форвардом с тегом. Изменением рулесетов можно добиться еще большего изменения столь изначально простой схемы; здесь мы этим заниматься не будем.

Преимущество над алиасами при вызове программы

Если в форварде, алиасе или включаемом файле указана отсылка письма программе, она запускается от владельца соотв. файла (форварда, алиаса или включенного файла), но если владелец - root, то он заменяется на первый найденный из набора фиксированных пользователей - mailnull, daemon, nobody, или же согласно параметру DefUserID. Почти всегда это неудобно. Например, пусть крупный news-сервер имеет адрес gup@ для управления подпиской. Написав в aliases


gup: "|/news/gup/bin/gup"

мы получим, что /news/gup/bin/gup вызван от mailnull; чтобы он получил права пользователя news, надо gup делать suid:news, или же делать какой-либо переходник. Вместо этого, лучше сделать так:


gup: news+gup

а в ~news/.forward+gup написать "|/news/gup/bin/gup". В результате, получим сразу запуск от пользователя news, что и требуется для нормальной настройки.

Объединение форвардов

Можно объединить несколько форвардов с разными тегами в один, задав тег каким-либо иным образом. Реальный пример: ~/.forward+ содержит


"|IFS=' '&& exec /usr/local/bin/procmail -f- || exit 75 netch+"

а ~/.forward+admin содержит


"|IFS=' '&& exec /usr/local/bin/procmail -f- TAG=admin || exit 75 netch+admin"

В ~/.procmailrc, блок отделения явного спама предшествует отделению почты на admin@, которая проверяется по тегу из командной строки:


:0H
* TAG ?? ^admin$
in.admin

Без возможности такого объединения, пришлось бы строить разные procmailrc и дублировать код фильтров в них.

Реализация в qmail

В qmail реализация тегированных локальных имен значительно более продвинута. Тег может быть параметризованным - состоять из постоянной части и переменной части; ищется тег с постоянной частью максимальной длины из возможных, переменная часть (параметр тега) запоминается и может быть использована для рефорварда, а в случае вызова программы - передается в окружении, откуда может быть легко извлечена. Например, если письмо направлено на vasya-locals-55, пользователь vasya существует и в ~vasya/.qmail-locals-default записан вызов программы, она будет вызвана с письмом на входе, а в ENV{DEFAULT} будет записано "55". Если ~vasya/.qmail-locals-default отсутствует, но есть ~vasya/.qmail-default, то указанная в нем программа будет вызвана с установкой ENV{DEFAULT} в значение "locals-55". За счет парсинга строки вызова, можно переадресовывать на другой адрес с передачей параметра тега, или делать еще что-то более сложное.

В ezmlm - менеджере списков рассылки от DJB - это свойство используется для получения отлупов на письма. На каждого подписчика отправляется отдельное письмо, обратный адрес которого содержит закодированный адрес получателя; например, для списка friends@pupkin.com и получателя vasilisa@lohankina.org обратный адрес будет friends-return-vasilisa=lohankina.org@pupkin.com. (Это одна из реализаций технологии VERP.) Установка ~alias/.qmail-friends-return-default приведет к вызову указанного в нем форварда с заданием ENV{DEFAULT} равного "vasilisa=lohankina.com".

В отличие от sendmail и postfix, где по умолчанию пользователь и тег разделены символом `+', и exim, где по умолчанию используется `.', в qmail символ для этого - `-'. Это дает возможность конфликта имен: например, если есть пользовать a и есть a-b, то a-b@localhost - это входной поток по умолчанию для пользователя a-b, или же входной поток для пользователя a с тегом b? Естественно, реализация старается найти самое длинное подходящее имя пользователя - потеря возможности для пользователя a иметь форвард вида a-b сочтена существенно меньшим злом, чем возможность полного и неуправляемого лишения пользователя a-b почты установкой форварда у пользователя a.

Недостатки тегированных локальных имен

До сих пор упоминались только преимущества использования тегированных локальных имен. Но вряд ли можно найти что-то без недостатков;)

1. Удобно использовать тегированное локальное имя для складывания какой-либо рассылки в ящик без разбора procmail'ом или аналогом (и опасностью того, что, например, письмо коллеги, который сделал bounce или forward Вам для привлечения внимания к письму, попадет в тот же ящик для рассылки, который Вы просматриваете раз в месяц). Но что если потребуется ответить в рассылку? Ставить во from то же тегированное локальное имя, вместо стандартного варианта? Это неудобно - если MUA не имеет средств для автоматизации подобных замен - можно легко забыть заменить From: и получить молчаливое пропадание письма (которое уйдет держателю списка с жалобой на письмо с адреса, не входящего в список, и скорее всего будет им молча удалено). Часть рассылок имеет варианты list-writers или list-post, которые не являются самостоятельными рассылками, а служат списками имен, с которых можно постить в основную рассылку; но такое делают далеко не все ведущие рассылок.

Некоторые менеджеры списков используют для определения подписчика не адрес из заголовка (From:, Reply-To: и т.д.), а адрес из конверта (или из Return-Path, что для типичного MTA то же самое). В этом случае, может оказаться слишком тяжело подставить необходимое тегированное локальное имя: MTA может принудительно формировать envelope-from как "$user@$hostname", игнорируя попытки переопределить его.

2. Представим себе ситуацию с некоторым количеством локальных рассылок и пользователем, который желает получать потенциально на разные потоки. Если он подписан на sales и staff, то в них может фигурировать vasya+sales и vasya+staff. Письмо, посланное на sales и staff, он получит в двух экземплярах. Если он послал письмо на какую-то из рассылок, то он всегда получит его копию: адрес отправителя (vasya) не совпадает с адресом получателя (vasya+sales). Если ему отвечают, нажав group reply, он может получить три(!) письма - на vasya, vasya+sales и vasya+staff... Причина этого - sendmail не считает vasya+sales и vasya+staff при отсутствии соответствующих forward'ов форвардами на vasya+, а считает их финальными получателями (соответственно, _разными_ финальными получателями).

Защита против этого: в ~vasya/.forward написать "vasya+", а в ~vasya/.forward+ записать нужный финальный раут (например, на procmail). Или же написать в ~vasya/.forward+ просто "\vasya". (Можно сделать еще проще - не вписывать в алиасы тегированное локальное имя, если нет соответствующего форварда; впрочем, это не всегда доступно. В одном из моих мест обитания, файл aliases строился автоматизированно, включение/выключение тегов задавалось для каждого пользователя отдельно, но по всем рассылкам.)

Такая защита не сработает, если у рассылок есть owner'ы: письмо будет разделено на несколько параллельно доставляемых писем, и все равно будет получено несколько экземпляров, независимо от используемости тегов. В этом случае может защитить только фильтрация дублирующихся писем procmail'ом (что имеет свои недостатки...)


$Id: plussed.html,v 1.2 2002/11/16 12:19:48 netch Exp $

(C) 2002 Valentin Nechayev. All rights reserved.