Собственный домен

Самое первое, что следует сделать - определить имя для хоста, на котором запущен sendmail, чтобы состояло не из одной части, а из нескольких, "как у больших дядей". При отсутствии такого имени (называемого fully qualified domain name, FQDN) sendmail будет разнообразно и неприятно сходить с ума. Имя может быть получено разными методами. Если есть uucp-подключение или подключение к провайдеру с доменом (даже если вся почта домена падает в POP3 ящик) - имя хоста можно брать равным этому домену или поддомену этого домена; например, имея zuka.com, можно назвать zuka.com, а можно unix.zuka.com, а можно hqnoc50div01.center.zuka.com... (просторы для фантазии безграничны). Имея фидошный адрес 2:98765/43210.77, можно назвать хост p77.f43210.n98765.z2.fidonet.org. И так далее... Ни в коем случае не следует использовать чужие имена!! Если нет никакого "мирового" имени (есть, например, ящик на mail.ru и больше ничего) - имя следует придумать: например, gandalf.arda, center.net5... Если имя придуманное, следует ожидать проблем с отправкой почты наружу - письма будут не пропускаться с диагностикой "sender's domain must exist"; о решении этой проблемы см. ниже. Это имя не должно быть localhost, или localhost.* (с любыми словами на месте звёздочки), иначе о связи с миром или даже с другими машинами в локалке придётся забыть.

Что sendmail думает о имени хоста - следует посмотреть, сказав команду `hostname' (получение из общесистемных настроек, чаще всего - из ядра) и команду `sendmail -d0.10 </dev/null', посмотрев значение для $j в секции "System identity". Если это не то, чего хотелось - следует разбираться в причинах. Возможно, придётся использовать прямое задание $j в sendmail.cf, но это крайний метод; лучше задать общесистемный hostname (например, его используют программы чтения почты для создания адреса отправителя).

Для переопределения $j в sendmail.cf надо написать строку

Dj<имя>

в sendmail.cf или
define(`confDOMAIN_NAME',`<имя>')dnl

в mc-файле. Не надо ставить пробел после Dj в sendmail.cf! С пробелом было бы удобнее смотреть, но, увы, тогда этот пробел передаётся в сгенерированные этим sendmail-ом message ids:(

В ряде случаев можно ставить имя из одной части в системный hostname, не задавать $j для sendmail явным образом и надеяться на то, что он вычислит его. Например, если хост назвать zuka, то написав в /etc/hosts:

127.0.0.1		zuka.locals zuka localhost

получим, что в результате внутреннего gethostbyname() sendmail получит zuka.locals в качестве основного имени и определит $j равным ему. Это можно рассматривать как вероятный стиль настройки, но я не рекомендую использовать методы столь зависимые от порядка хостов в /etc/hosts и прочих неожиданных факторов;)

Резолвинг внутренних имен и адресов


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

Есть ли тут проблема - можно определить, запустив команду

echo '$=w' | sendmail -bt

Задержки в выполнении команды на 30 секунд и более означают проблемы с таким резолвингом.

Адреса на поднятых интерфейсах можно получить командой `ifconfig -au', а для систем, не понимающих такое - `ifconfig -a' или аналог и вручную отделив поднятые интерфейсы (со флагом up). Для каждого такого адреса должны быть определены в доступных источниках имена, в которое резолвится этот адрес, и адреса, в которые резолвятся такие имена. Если резолвинг дал вернуться к исходному адресу, то в список доменов, собственных для данного sendmail'а (класс {w}, отсюда проверочная команда $=w для sendmail -bt), sendmail вписывает и это имя, и адрес в стандартной форме (A.B.C.D) квадратных скобках; если не смогли получить тот же адрес - то вписывает только этот адрес. Значение системного параметра hostname и значение переменной $j вписывается sendmail'ом в класс {w} всегда.

Все хосты, через которые может быть отправлена почта на данный хост на SMTP-сервер, желательно описать в DNS или /etc/hosts, задав имена для их адресов. Так как в стандартном конфиге делается неустранимая канонизация имени хоста отправителя (см. ниже про канонизацию), следует избежать задержек и проблем от этого.

Канонизация


В понимании sendmail канонизацией называется:

  1. Выполнение соответствия RFC1123, говорящего, что домен в доменной части адреса не может быть алиасом (CNAME-записью в DNS), путём замены таких CNAME-записей на имя, содержащееся в них в значении записи;
  2. Приведение домена в FQDN форму, если он был неполным (пропущена часть справа от некоторой точки, в расчёте на то, что FQDN форма будет восстановлена в соответствии с локальными настройками.
Понятно, что эта операция требует доступа к данным DNS, в нашем случае - к Internet. Задача сети без доступа к Internet - убрать требование канонизации, лучше всего - для чужих адресов, хуже - для всех, или же сделать так, чтобы канонизация происходила в локальных рамках и безболезненно;))

Вариант 1 (нехороший) - ломовое устранение канонизации средствами конфига независимо от версии и потребностей. Что для этого нужно:

Понятно, что этот вариант неудобен ручным насилием над конфигом после его генерации из mc-файла. В некоторых системах (например, FreeBSD) штатным методом апгрейда конфига при апгрейде sendmail'а является его сборка из mc-файла; при этом ручные правки будут потеряны.

Вариант 2 - для 8.12. Требуется в .mc вписать следующее (пояснения по адаптации к своим условиям - ниже):

FEATURE(`nocanonify',`canonify_hosts')dnl
FEATURE(`no_default_msa')dnl
DAEMON_OPTIONS(`Port=smtp, Name=MTA, M=C')dnl
DAEMON_OPTIONS(`Port=587, Name=MSA, M=CE')dnl
define(`confDIRECT_SUBMISSION_MODIFIERS',`CC u')dnl

В этом примере,

Данный пример может быть произвольно подстроен под свои нужды с сохранением флага C для демонов и компонента CC в confDIRECT_SUBMISSION_MODIFIERS по умолчанию. Флаг C даёт устранение канонизации для писем, пришедших по сети; определение confDIRECT_SUBMISSION_MODIFIERS - для писем, отправленных локально.

Следует при этом помнить, что через CANONIFY_DOMAIN или CANONIFY_DOMAIN_FILE может быть задан список исключений - доменов, которым требуется канонизация. Подробности - в документации (cf/README).

Ещё замечание: стандартный конфиг содержит неустранимую часть - канонизацию содержимого ${client_name} - полученного по DNS имени хоста отправителя (SMTP клиента). Выше было описано, что для устранения проблем с этим необходимо описать имена для хостов так, чтобы они успешно проверялись.

Проверка домена на существование

В стандартной конфигурации, при приёме письма по SMTP производится проверка адресов отправителя (в команде mail from) на существование домена этого адреса. Несуществование такого домена вызывает отказ в приёме; невозможность проверки - временный отказ в приёме. Лечение: добавить FEATURE(`accept_unresolvable_domains') в конфиг.

Попытки немедленной доставки

Приняв письмо, sendmail начинает немедленно, если хватает резерва загрузки системы, доставлять его получателю. В случае подключения по uucp это заканчивается укладкой письма в спул. При отсутствии uucp и желании отдать письмо в мир по SMTP картина усложняется.
Sendmail имеет параметр под названием delivery mode (режим доставки), который может принимать одно из следующих значений:

immediate
Письмо кладётся в очередь и первая попытка доставки производится до отдачи отправителю (по SMTP или в локальной команде) ответа о успешном принятии письма. Этот режим используется в весьма специфических случаях, речь о которых не идёт здесь.
background
Письмо кладётся в очередь и тут же отправителю сообщается об успешном принятии письма. При наличии возможности (штатная проверка на это содержит сравнение QueueLA и параметров письма по сложной формуле), запускается фоновый процесс доставки письма. Этот режим выбран по умолчанию.
queue
Письмо кладётся в очередь и тут же отправителю сообщается об успешном принятии письма. Фоновый процесс (как в случае background) не запускается, задача доставки письма возлагается на демоны-разгребальщики почтовой системы.
deferred
Режим аналогичен queue, но, кроме того, все проверки по map'ам с флагом -D отменяются. Этот флаг содержится в том числе в проверках на канонизацию и в вызовах DNS.
Названия режимов доставки могут быть сокращены до их первой буквы (i, b, q, d).

Режим immediate вряд ли нужен для системы в рассматриваемых условиях. Выбор между background, queue и deferred определяется другими факторами - в первую очередь, насколько удалось победить почтовую систему в других местах;) Вот замечания по сравнению режимов:

По показанному, игры с delivery mode могут частично дать необходимый результат, но он оказывается без применения других средств ущербным. Необходим выбор между возможностью немедленной доставки и лишней работой. Если оставить дефолтное background, то могут вылезти хвосты с канонизацией, проверкой домена отправителя на существование и т.д. (см. другие разделы данного описания), и будет лишняя работа по беспрерывным попыткам доставки в мир (которая может неслабо нагрузить сервер). Если включить deferred, то устраняются проблемы с канонизацией и существованием доменов, но будет опять или нагрузка от безнадёжных попыток доставки в мир, или необходимость тщательно выбирать список внутренних доменов для доставки независимо от наличия выхода в мир. Если задать queue, это будет примерно как background, с пропуском немедленной доставки, но опять же необходимостью подбора списка внутренних доменов.

Фактически, единственный полностью устраняющий проблему с попытками доставки метод появился в 8.12 с его queuegroups. Через рулесет queuegroup следует разделить потоки на внутренние домены и все остальные, остальные описать в очередь, для которой должно быть 0 разгребальщиков. Из /etc/ppp/ip-up или аналогичного по функциональности места вызывается разгребальщик на эту очередь. Имеет также смысл периодически запускать такой разгребальщик при наличии связи с Internet (то есть, на самом деле, логика наоборот - по крону запускать скрипт, который проверяет наличие связи и, найдя её, запускает загребальщик).

Есть ещё метод (описан в расчёте на настройку 8.12). Записав в mc-файле:

MODIFY_MAILER_FLAGS(`SMTP',`+ae')dnl
define(`confCON_EXPENSIVE',`True')dnl

и расписав в mailertable доставку всех внутренних доменов мэйлером esmtp, а в мир - мэйлером smtp - например, так:
.locals				esmtp:%1%0
.				smtp:%1%0

получим отказ от первых попыток доставки для внешних, направленных на мэйлер smtp, доменов. Функциональность мэйлера smtp здесь искажена, но не думаю, что сейчас она кому-то нужна в исходном виде; если нужна - то админ такого места сам достаточно грамотен, чтобы устранить конфликт.
В этом методе не устраняются попытки доставки, следующие за первой, это следует решать иными методами (например, описанный выше метод с queuegroup).

Поиск пункта назначения письма

Если обработка письма дошла, несмотря на все расставленные ей помехи, до вычисления того, куда следует направить письмо - в дело вступает задание раутинга письма. Переопределённый раутинг письма определяется в основном таблицей mailertable - которую следовало бы более правильно назвать routes - и параметром "smart host", который является эквивалентом default route из TCP/IP - то есть задаёт назначение, которое применяется при отсутствии иных явных назначений. После такого переопределения, sendmail вычисляет MX'ы адреса, если он не оказался в квадратных скобках.

Эти параметры (smarthost, записи в mailertable) могут и в ряде случаев должны изменяться в зависимости от того, каким именно образом соединились с Internet;)) - то есть, от того, через какого провайдера соединились, а порой и от других факторов. На сейчас, прямая отправка с диалапа в большом количестве случаев заканчивается отказом получателя принимать письмо. Принцип тут прост - пользователь без фиксированного адреса должен использовать SMTP сервер провайдера. Не будем сейчас обсуждать этот принцип, это тема для мегабайтов рассуждений и флеймов; примем его как факт: выходя в Internet с диалапа, надо все письма отдавать на SMTP сервер провайдера. Значит, запуск sendmail'а при наличии связи с Internet должен получать конфигурацию, содержащую SMTP сервер провайдера как default route. Назначения для других локальных доменов, если они есть, пишутся в mailertable - тут специфики нет.

При фиксированном провайдере достаточно его записать в smart host - define(`SMART_HOST') в .mc, DS в sendmail.cf. При меняющемся, надо запускать sendmail с конфигом с нужной записью. Это в принципе не так сложно, реализуется скриптом примерно такого содержания:

#!/bin/sh
## $1 is provider name
CF=/var/tmp/sendmail.cf.$$
trap 'rm -f $CF; exit 1' 1 2 3 15
rm -f $CF || { echo плохо мне; exit 1; }
cat /etc/mail/sendmail.cf >$CF
case "$1" in
  CoolInternet) SMTP=smtp.coolinternet.zz ;;
  VillageLine) SMTP=smtp.villageline.net.ru ;;
  *) echo я не знаю такого провайдера; exit 1 ;;
esac
echo DS"$SMTP" >>$CF
sendmail -C$CF -qf <другие опции, например, нужная очередь>
rm -f $CF

Впрочем, надеюсь, что большинству читающих это такие премудрости не понадобятся, и статический smart host в конфиге окажется достаточен.

Переписка адреса отправителя

Письма, отправляемые через запуск бинарника sendmail, содержат envelope-from (то есть адрес отправителя в конверте), собранный из имени запустившего его пользователя и выбранного имени почтовой системы хоста (см. первый раздел данного документа). Если, как описано в первом разделе, для почты данного хоста (или другого хоста в пределах сети) был выбран не известный миру домен, а неизвестный миру локальный домен, то письмо не сможет выйти в мир, а будет вместо этого срезано первым же мировым SMTP-сервером (ответ будет скорее всего 'sender domain must exist'). Чтобы этого не случилось, требуется на выходе преобразовать адрес к такому, который понимается в мире. Крайне желательно (например, чтобы не оказаться в один далеко не прекрасный день отключенным за подделку адресов), чтобы такой адрес принадлежал тому же, чей был соответствующий внутренний адрес. Два метода такого преобразования адреса - маскарадинг (masquerading) и genericstable ("таблица происхождений").

Нужности в применении этих мер можно избежать. Например, использование почтовых клиентов, которые отправляют по SMTP и разрешают задавать любой адрес в настройках - это Mozilla, KMail, sylpheed (со всеми клонами), и так далее. Например, если есть адрес pupkin@hotmail.com, то локальному SMTP серверу уже будет отдано таким клиентом письмо с envelope-from pupkin@hotmail.com и таким же адресом во From:. И даже работающие через локальный sendmail агенты можно иногда заставить задавать нужный envelope-from; например, в mutt команда `set envelope_from' даёт такой результат. Однако, если требуется отправлять в мир отчёты роботов, запускаемых из крона, работать из агентов, которые не задают envelope_from, и т.д., то потребуется переписка обратного адреса средствами sendmail.

Сразу, чтобы не забыть и подчеркнуть. Написание конфигурации с использованием маскарадинга и genericstable начинайте с FEATURE(`masquerade_envelope'). Без этого переписке будут подвергаться только адреса в полях заголовка From:, Sender:, Reply-To: и прочих с обратными адресами, но не в конверте; а нас здесь интересуют именно адреса в конверте. При дальнейшем описании буду считать, что эта команда вставлена в mc-файл.

Выбор из двух описываемых здесь подходов - маскарадинг и genericstable - или применение их вместе - основывается на том, вводится ли какая-то одинаковая политика (включая целевой домен) для всех адресов в домене, или для каждого требуется своё персональное назначение. Смена локальной части адреса также означает необходимость использования именно genericstable для этих адресов, а не маскарадинга. Маскарадинг работает для доменов целиком, без исключения, но genericstable тоже может быть применена для доменов целиком. Эти два подхода могут сочетаться; в этом случае genericstable срабатывает первым, а маскарадинг - вторым.

Применение genericstable достаточно подробно описано в cf/README, не буду на нём здесь останавливаться детальнее. Один из средних примеров применения маскарадинга выглядит так:

FEATURE(limited_masquerade)dnl
FEATURE(masquerade_envelope)dnl
MASQUERADE_AS(external.domain)dnl
MASQUERADE_DOMAIN(local.domain)dnl
MASQUERADE_DOMAIN(ns.local.domain)dnl
MASQUERADE_DOMAIN(appserver.local.domain)dnl

Тем не менее, не следует забывать значительно более простые методы - например, случай маскарадинга одного домена полностью покрывается определением $z в конфиге (см. выше).

$Id: sendmail-at-home.html,v 1.2 2003/04/03 07:37:24 netch Exp $

(c) 2003 Valentin Nechayev. All rights reserved.