[exim-conf] доработки реализации SMTP аутентификации, в том числе в случае интеграции с LDAP серверами вообще и с Active Directory в частности

Victor Ustugov victor на corvax.kiev.ua
Пт Апр 24 22:57:40 EEST 2015


приветствую

сегодня будет больше букв, чем обычно.

речь пойдет о доработках реализации SMTP аутентификации в случае
интеграции с LDAP серверами вообще и с Active Directory в частности.


сначала предыстория (уже реализованные варианты аутентификации SMTP
пользователей с проверкой на LDAP сервере):

1. самый простой способ - просто использовать SMTP логин и пароль при
аутентификации на LDAP сервере (при этом LDAP логин строится на базе
SMTP логина):

define(`confSMTP_AUTH_SOURCE', `LDAPAUTH')dnl
define(`confSMTP_AUTH_LDAPAUTH_HOST', `1.2.3.4')dnl
define(`confSMTP_AUTH_LDAPAUTH_LOGIN',
`cn=LOGIN,cn=Users,dc=domain,dc=tld')dnl

в случае аутентификации в Active Directory можно использовать упрощенный
вариант LDAP логина (в этом случае он должен совпадать не со значением
атрибута distinguishedName, а со значением артибута userPrincipalName):

define(`confSMTP_AUTH_LDAPAUTH_LOGIN', `LOGIN на domain.tld')dnl

2. в случае, если LDAP сервер может вернуть в результатах LDAP запроса
пароль пользователя в шифрованном или нешифрованном виде, можно
использовать другой вариант SMTP аутентификации:

define(`confSMTP_AUTH_LDAP_HOST', `127.0.0.1')dnl
define(`confSMTP_AUTH_LDAP_LOGIN', `LDAP_USER')dnl
define(`confSMTP_AUTH_LDAP_PASS', `LDAP_PASSWORD')dnl
define(`confSMTP_AUTH_LDAP_EXTRAATTR', `REFERRALS=nofollow')dnl
define(`confSMTP_AUTH_LDAP_BASE', `dc=domain,dc=tld')dnl
define(`confSMTP_AUTH_LDAP_PASSWD_ATTR', `userPassword')dnl
define(`confSMTP_AUTH_LDAP_FILTER',
`(&(objectClass=posixAccount)(uid=LOGIN))')dnl

в этом случае LDAP_USER и LDAP_PASSWORD - это логин и пароль
пользователя, от имени которого выполняются LDAP запросы для получения
данных о почтовых пользователях.

если пароли хранятся в базе LDAP сервера в открытом виде, нужно
использовать LDAP_PLAIN в качестве значения для confSMTP_AUTH_SOURCE:

define(`confSMTP_AUTH_SOURCE', `LDAP_PLAIN')dnl

если пароли хранятся в базе LDAP сервера в шифрованном виде, нужно
использовать LDAP в качестве значения для confSMTP_AUTH_SOURCE:

define(`confSMTP_AUTH_SOURCE', `LDAP')dnl

3. существует смешанный тип LDAP аутентификации, когда при
confSMTP_AUTH_SOURCE, равном `LDAP', в качестве логина и пароля LDAP
сервера указываются SMTP логин и SMTP пароль (как при
confSMTP_AUTH_SOURCE, равном `LDAPAUTH'), но при этом еще выполняет
запрос к LDAP серверу. при этом confSMTP_AUTH_LDAP_PASSWD_ATTR должен
быть пустым.

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

в качестве примера можно рассматривать случай интеграции exim с Active
Directory, когда только члены определенной группы считаются валидными
почтовыми пользователями.

define(`confSMTP_AUTH_SOURCE', `LDAP')dnl
define(`confSMTP_AUTH_LDAP_HOST', `dc.domain.tld')dnl
define(`confSMTP_AUTH_LDAP_LOGIN', `LOGIN на domain.tld')dnl
define(`confSMTP_AUTH_LDAP_PASS', `PASSWORD')dnl
define(`confSMTP_AUTH_LDAP_BASE', `dc=domain,dc=tld')dnl
define(`confSMTP_AUTH_LDAP_PASSWD_ATTR', `')dnl
define(`confSMTP_AUTH_LDAP_FILTER',
`(&(objectClass=user)(sAMAccountName=LOGIN)(memberOf=CN=Mail
Users,OU=Mail,DC=domain,DC=tld))')dnl


и вот на днях у одного из клиентов возникла необходимость реализации
SMTP аутентификации при интеграции exim с Active Directory, при этом в
качестве логина SMTP клиент не может указать значения атрибутов
distinguishedName или userPrincipalName (у клиента какая-то тупая
железка, которая берет значение адреса отправителя алертов и SMTP логин
из одного и того же поля настроек).

просто выполнить LDAP запрос с фильтром по атрибуту mail с получением
атрибута пароля для сравнения с паролем, указанным SMTP клиентом, не
получится, т. к. Active Directory не возвращает пароли пользователей ни
в открытом, ни в шифрованном виде.

просто проверить SMTP логин и пароль путем аутентификации на LDAP
сервере не получится, т. к. LDAP логин заранее неизвестен.

т. о. нужно сначала одним LDAP запросом от имени предопределенного
пользователя получить LDAP логин пользователя, в атрибуте mail которого
указан адрес, который был получен от SMTP клиента в качестве SMTP логина.

потом нужно выполнить аутентификацию на LDAP сервере от имени найденного
пользователя с использованием пароля, указанного SMTP клиентом.

опционально можно во втором (внешнем) запросе указать фильтр, чтобы
реализовать ограничения на использование SMTP сервера (как пример -
запретить отсылать письма всем, кроме пользователей определенных групп).

простейшим вариантом мог бы послужить вот такой ($auth1 и $auth2 указаны
применительно к аутентификатору с driver = plaintext и public_name =
LOGIN, для public_name = PLAIN нужно использовать $auth2 и $auth3):

	server_condition = ${if ldapauth{\
			    user=\
			      ${quote_ldap_dn:\
				${lookup ldapm{\
				  user=ldapsearch на domain.tld \
				  pass=XXXXXXXXXX \
				  REFERRALS=nofollow \
				  ldap:///dc=domain,dc=tld\
                                    ?sAMAccountName?sub?\
				    (&(objectClass=user)(mail=$auth1))\
				}{$value}fail}\
			      @domain.tld} \
			    pass=${quote:$auth2} REFERRALS=nofollow \
			    ldap:///\
			  }{yes}{no}}

тут один LDAP запрос вложен в другой (ldapm в ldapauth, при этом
результат выполнения ldapm является частью LDAP логина для ldapauth).

вложенный запрос выполняется от имени предопределенного LDAP
пользователя ldapsearch на domain.tld с паролем XXXXXXXXXX.

проблема лишь в том, что exim еще до выполнения вложенного запроса ldapm
пытается парсить синтаксис запроса ldapauth.

обойти проблему можно лишь полностью исключив из синтаксиса вложенного
запроса ldapm все пробелы, знак доллара и фигурные скобки.

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

	server_condition = ${if ldapauth{\
			    user=\
			      ${quote_ldap_dn:\
			        ${expand:\
				  \x24\x7Blookup\x20ldapm\x7B\
				    user=ldapsearch на domain.tld\x20\
				    pass=XXXXXXXXXX\x20\
				    REFERRALS=nofollow\x20\
				    ldap:///dc=domain,dc=tld\
				      ?sAMAccountName?sub?\
				      (&\
					(objectClass=user)(mail=$auth1)\
				      )\
				  \x7D\x7B\x24value\x7Dfail\x7D\
			        }@domain.tld\
			      } \
			    pass=${quote:$auth2} REFERRALS=nofollow \
			    ldap:///\
			  }{yes}{no}}

другим вариантом данного составного запроса является нижеприведенный
вариант, в котором ldapauth заменен на запрос ldap.

в этом случае можно наложить дополнительный фильтр, как в варианте из
пункта 3, упомянутого в начале.
			
	server_condition = ${lookup ldap{ \
			    user=\
			      ${quote_ldap_dn:\
				${expand:\
				  \x24\x7Blookup\x20ldapm\x7B\
				    user=ldapsearch на domain.tld\x20\
				    pass=XXXXXXXXXX\x20\
				    REFERRALS=nofollow\x20\
				    ldap:///dc=domain,dc=tld\
				      ?sAMAccountName?sub?\
				      (&\
					(objectClass=user)(mail=\x24auth1)\
				      )\
				  \x7D\x7B\x24value\x7Dfail\x7D\
				}@domain.tld\
			      } \
			    pass=${quote:$auth2} REFERRALS=nofollow \
			    ldap:///dc=domain,dc=tld?dn,cn?sub?\
			    (&\
			      (objectClass=user)\
			      (mail=$auth1)\
			      (memberOf=CN=Mail Users,OU=Mail,DC=domain,DC=tld)\
			    )\
			  }{yes}{no}}

в принципе, этот фильтр можно было бы указать во вложенном запросе
ldapm, но тогда читабельность всей конструкции была еще хуже.

фильтр по атрибуту mail во внешнем запросе ограничивает количество
возвращенных запросом записей одной. в противном случае будут получены
записи всех пользователей из группы "Mail Users". а их там может быть
ощутимое количество.

во всех вышеприведенных примерах от LDAP сервера было получено значение
атрибута sAMAccountName для построения LDAP логина в виде, указанном в
атрибуте userPrincipalName.

но ничто не мешает получать значение атрибута distinguishedName вместо
значения sAMAccountName и использовать непосредственно это значение в
качестве LDAP логина.


в случае же с клиентом, из-за железяки которого всё и началось,
потребовалась комбинация привычной схемы SMTP аутентификации с
использованием логинов и паролей из Active Directory с новой схемой в
вложенными LDAP запросами.

в результате получился вариант, близкий к вот этому:

	server_condition = ${if match{$auth1}{@}{\
			    ${lookup ldap{ \
			      user=\
			        ${quote_ldap_dn:\
				  ${expand:\
				    \x24\x7Blookup\x20ldapm\x7B\
				      user=ldapsearch на domain.tld\x20\
				      pass=XXXXXXXXXX\x20\
				      REFERRALS=nofollow\x20\
				      ldap:///dc=domain,dc=tld\
				        ?sAMAccountName?sub?\
					(&\
					  (objectClass=user)\
					  (mail=\x24auth1)\
					)\
				    \x7D\x7B\x24value\x7Dfail\x7D\
				  }@domain.tld\
			        } \
			      pass=${quote:$auth2} REFERRALS=nofollow \
			      ldap:///dc=domain,dc=tld?dn,cn?sub?\
			      (&\
			        (objectClass=user)\
			        (mail=$auth1)\
			      )\
			    }{yes}{no}}\
			  }{\
			    ${lookup ldap{ \
			      user=${quote_ldap_dn:$auth1 на domain.tld} \
			      pass=${quote:$auth2} \
			      REFERRALS=nofollow \
			      ldap:///dc=domain,dc=tld?dn,cn?sub?\
			      (&\
				(objectClass=user)\
				(sAMAccountName=\
				  ${quote_ldap_dn:$auth1})\
			      )\
			    }{yes}{no}}\
			  }}

для логинов с символом "@" применяются два вложенных LDAP запроса (новая
схема).
для логинов без "@" применяется одиночный LDAP запрос (старая схема).

в обоих случаях предусмотрены дополнительные фильтры (как в случае
контроля принадлежности к определенной группе в примере выше).


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

поэтому для confSMTP_AUTH_SOURCE введено новое значение `CUSTOM'

при этом значение для server_condition нужно указывать в переменной
confSMTP_AUTH_CUSTOM_SERVER_CONDITION:

define(`confSMTP_AUTH_SOURCE', `CUSTOM')dnl
define(`confSMTP_AUTH_CUSTOM_SERVER_CONDITION', `${if match{LOGIN}{@}{\
			    ${lookup ldap{ \
			      user=\
			        ${quote_ldap_dn:\
				  ${expand:\
				    \x24\x7Blookup\x20ldapm\x7B\
				      user=ldapsearch на domain.tld\x20\
				      pass=XXXXXXXXXX\x20\
				      REFERRALS=nofollow\x20\
				      ldap:///dc=domain`,'dc=tld\
					?sAMAccountName?sub?\
					(&\
					  (objectClass=user)\
					  (mail=\x24LOGIN)\
					)\
				    \x7D\x7B\x24value\x7Dfail\x7D\
				  }@domain.tld\
				} \
			      pass=${quote:PASSWORD} \
			      REFERRALS=nofollow \
			      ldap:///dc=domain`,'dc=tld?dn`,'cn?sub?\
			      (&\
				(objectClass=user)\
				(mail=LOGIN)\
			      )\
			    }{yes}{no}}\
			  }{\
			    ${lookup ldap{ \
			      user=${quote_ldap_dn:LOGIN на domain.tld} \
			      pass=${quote:PASSWORD} \
			      REFERRALS=nofollow \
			      ldap:///dc=domain`,'dc=tld?dn`,'cn?sub?\
				(&\
				  (objectClass=user)\
				  (sAMAccountName=\
				    ${quote_ldap_dn:LOGIN})\
				)\
			    }{yes}{no}}\
			  }}')dnl

в местах, в которых должны быть указаны переданные SMTP клиентом логин и
пароль, нужно указать ключевые слова LOGIN и PASSWORD по аналогии с
заполнением confSMTP_AUTH_LDAP_LOGIN и confSMTP_AUTH_LDAP_PASS.

пока такой вариант используется только при аутентификации методами PLAIN
и LOGIN.

есть пара ньюансов:

- если LOGIN надо указать во вложенной части составного запроса, то
нужно писать не LOGIN, а \x24LOGIN (\x24 - 16-ричное представление
символа "$"). тогда при замене LOGIN на $auth1 или $auth2 (в зависимости
от типа аутентификатора LOGIN или PLAIN) реально LOGIN будет заменен на
\x24auth1 и \x24auth2 соответственно.

- все запятые нужно взять в кавычки `'


данный способ описания механизма проверки SMTP логинов и паролей с одной
стороны является сложным и громоздким, а с другой стороны - очень
универсальным.

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

тогда на данном транзитном почтовом сервере можно организовать отдельные
механизмы SMTP аутентификации для разных доменов, выполняя запросы к
разным хранилищам учетных данных почтовых пользователей или проксируя
запросы аутентификации на другие SMTP сервера (как в случае
использования SMTP и SMTPTLS в качестве значений для confSMTP_AUTH_SOURCE).


вот как-то так.....


-- 
Best wishes Victor Ustugov  mailto:victor на corvax.kiev.ua
public GnuPG/PGP key:       http://victor.corvax.kiev.ua/corvax.asc
ICQ UIN: 371808614          JID: corvax_at_nb на jabber.corvax.kiev.ua
nic-handle: CRV-UANIC



Подробная информация о списке рассылки exim-conf