Использование собственных методов авторизации в IMAP-сервере Dovecot

Интеграция со сторонними нестандартными сервисами

HELP-ME-24.COM (Freelance Team), Черноусов Антон
Почтовый сервер Dovecot

Dovecot - наверное самый популярный POP3/IMAP сервер среди OpenSource реализаций

POP3/IMAP-сервер Dovecot разрабатывается с 2002-го года, основными преимуществами по сравнению с другими реализациями является его модульность и высокое быстродействие.

При построение почтовых серверов мы в своей практике предпочитаем использовать именно его.

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

Dovecot, согласно документации поддерживает следующие хранилища данных авторизации:

  • Passwd-file - файл формата системного /etc/passwd с возможностью задать его расположение

  • LDAP - Взаимодействие с OpenLDAP но есть методв подружить и с Active Directory.

  • SQL - Использование баз данных PostgreSQL, MySQL, SQLite для хранения базы пользователей и это наверное самый распространенный вариант.

  • Dict - Использование баз данных типа ключ/значение Redis, memcached и т.п.

  • CheckPassword - Внешняя программа или скрипт.

  • Static - Статический файл passdb для простых конфигураций

Обратите внимание, что помимо хранилищ конфигурации имеется так же набор механизмов авторизации. Механизмы описывают скажем так процесс обмена данными авторизации между клиентом и сервером и соответственно при разных механизмах может передаваться например MD5-хэш пароля или Kerberos-тикет или даже используется Oauth2 обмен.

Естественно, что для разных хранилищ допустимы определенные методы авторизации и наиболее распространенными и поддерживающиеся всеми почтовыми клиентами являются механизмы plain, login и cram-md5, а прочие механизмы можно смело относить к узкоспециализированным.

Как я уже сказал выше, в простых и типовых случаях случаях обычно используются базы данных, PAM или LDAP, но когда дело доходит до экзотических решений в дело вступает внешняя авторизация где уже мы сами пишем приложение которое будет определять авторизовать пользователя на сервере POP3/IMAP или нет. Используя методы внешней авторизации можно построить любые даже самые экзотические методы авторизации клиентов!

Использование внешнего скрипта для авторизации клиентов Dovecot

За основу настройки IMAP-сервера Dovecot вы можете использовать наше подробное описание из статьи "Создание корпоративного почтового сервера (IMAP-сервер Dovecot)". В данной статье описано создание типового IMAP-сервера с использованием хранилища данных авторизации и алиасов в базе данных, но мы модифицируем это описание для использования внешних скриптов, но сначала немного теории.

Как мы уже говорили в прошлой статье посвященной настройке настройке Dovecot-сервера, для проверки работоспособности механизмов авторизации используется две команды.

Первая проверяет, что авторизация с использованием логин-пароль прошла успешно:

# doveadm auth test chernousov@help-me-24.com "xxxUSERPASSWORDxxx"
passdb: chernousov@help-me-24.com auth failed

Использует раздел passdb в конфигурационном файле /etc/dovecot/conf.d/auth-checkpassword.conf.ext.

Вторая команда:

# doveadm user -u "freelance@help-me-24.com"

Запрашивает сведения о пользователе и расположении его почтового каталога и использует раздел userdb этого же конфигурационного файла. Фактически, оба блока функционала обрабатывает один и тот же скрипт на Python. Создадим скрипт который будет обрабатывать авторизацию пользователей и предоставлять сведения о домашних каталогах почтовых пользователей в каталоге /etc/dovecot/:

# touch /etc/dovecot/external-auth.py
# chmod +x /etc/dovecot/external-auth.py

Внесем наш скрипт в файл конфигурации /etc/dovecot/conf.d/auth-checkpassword.conf.ext, который теперь имеет вид:

passdb {
driver = checkpassword
args = /etc/dovecot/external-auth.py
}

userdb {
driver = prefetch
}

userdb {
driver = checkpassword
args = /etc/dovecot/external-auth.py
}

При вызове скрипта параметры вызова передаются в качестве переменных окружения и аналогичный механизм мы уже рассматривали когда настраивали внешнюю авторизацию для OpenVPN-сервера.

В отличие от внешней авторизации OpenVPN-сервера есть несколько особенностей на которые необходимо обратить внимание. Во первых, включите режим отладки модуля авторизации, для чего в файл auth-checkpassword.conf.ext добавьте параметры:

auth_verbose = yes
auth_debug = yes
auth_debug_passwords = yes
auth_verbose_passwords = yes

Во вторых, скрипт в случае успеха запускает переданное ему в качестве параметров приложение (экзотично), в противном случае в логах вы получите ошибку:

Oct  9 14:56:00 mail-server dovecot: auth: Debug: checkpassword: execute: /etc/dovecot/external-auth.py /usr/lib/dovecot/checkpassword-reply
Oct 9 14:56:00 mail-server dovecot: auth: Debug: checkpassword: Received input:
Oct 9 14:56:00 mail-server dovecot: auth: Debug: checkpassword: exit_status=0
Oct 9 14:56:00 mail-server dovecot: auth: Error: checkpassword: Received no input

Для тестирования того какие параметры передаются в каждом из случаев, мы сделаем из скрипта авторизации external-auth.py фальшивый скрипт который будет записывать все переменные окружения в текстовый файл, но при этом мы учтем особенности передачи параметров:

#!/usr/bin/python3
import os
import sys

tmp_file=open('/tmp/test_out.txt','a')
tmp_file.write('TEST-OUT\n')
tmp_file.write(str(os.environ))
tmp_file.close()

Выполним оба представленные выше запроса (на проверку авторизации и получение сведений о каталоге) и проанализируем передаваемые параметры.

В первом случае (проверка авторизации) при запросе с именем пользователя anton@testdomain мы получили следующие результаты:

'AUTH_ORIG_USER': 'anton@testdomain'
'AUTH_DOMAIN_LAST': 'testdomain'
'AUTH_DOMAIN_FIRST': 'testdomain'
'AUTH_ORIG_USERNAME': 'anton'
'AUTH_MECH': 'PLAIN'
'AUTH_USER': 'anton@testdomain'
'AUTH_ORIG_DOMAIN': 'testdomain'
'AUTH_USERNAME': 'anton'
'AUTH_DOMAIN': 'testdomain

Естественно, что параметров окружения было больше, а я оставил только те, что представляют для нас интерес, а при выполнение запроса на получение сведений о пользователе переменные окружения (запрос производился для пользователя demouser@mydomain.com) были установлены следующими:

'AUTH_USERNAME': 'demouser'
'AUTHORIZED': '1'
'AUTH_ORIG_USER': 'demouser@mydomain.com'
'AUTH_ORIG_USERNAME': 'demouser'
'AUTH_USER': 'demouser@mydomain.com'
'AUTH_DOMAIN_FIRST': 'mydomain.com'
'AUTH_DOMAIN_LAST': 'mydomain.com'
'AUTH_DOMAIN': 'mydomain.com'
'AUTH_ORIG_DOMAIN': 'mydomain.com'

Единственным отличием двух вызовов является наличие переменной окружения AUTHORIZED, а пароль пользователя передается другим механизмом. Согласно документации пароль передается через файловый дескриптор номер три и отловить передаваемое значение можно при помощи модифицированного скрипта:

#!/usr/bin/python3
import os
import sys

tmp_file=open('/tmp/test_out.txt','w')
password_dsk=os.fdopen(3)
tmp_file.write('>'+password_dsk.read()+'<')
password_dsk.close()
tmp_file.close()

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

>anton@testdomainxxxUSERPASSWORDxxx<

Из полученного ответа в дескрипторе мы можем получить нормальную пару логин/пароль и конечно по наличию в переменных окружения переменной AUTHORIZED определить с каким типом запроса мы имеем дело.

Пример скрипта внешней авторизации для Dovecot (на python3)

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

#!/usr/bin/python3
import os
import sys

def debug(debug_string,init=False):
if init:
tmp_file=open('/tmp/test_out.txt','w')
tmp_file.write(debug_string+'\n')
tmp_file.close()
else:
tmp_file=open('/tmp/test_out.txt','a')
tmp_file.write(debug_string+'\n')
tmp_file.close()


debug('Login debug started',init=True)

# Check file descriptor for password
password_dsk=os.fdopen(3)
password=password_dsk.read()
password_dsk.close()

# Collect information
if 'AUTHORIZED' in os.environ:
try_auth=False
else:
try_auth=True

username=os.environ['AUTH_ORIG_USER']

if try_auth:
password=password.split(chr(0))[1]
else:
password=''

debug('Try auth: '+str(try_auth))
debug('Login: '+username)
debug('Password: '+password)

auth_ok = True

if not try_auth:
if auth_ok:
os.environ['AUTHORIZED'] = '2'
os.environ['USER'] = username
os.environ['HOME'] = '/home/mailboxes/'+username
os.environ['userdb_uid'] = '107'
os.environ['userdb_gid'] = '112'
# Execute auth helper
application=sys.argv[1]
os.system(application)
sys.exit(2)
if try_auth:
if auth_ok:
os.environ['USER'] = username
# Execute auth helper
application=sys.argv[1]
os.system(application)
sys.exit(0)

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

В результате запроса сведений о пользователе вы получите сообщение о успехе и пути к почтовому каталогу заданные относительно /home/mailboxes/. При запросе авторизации вы получите сообщение о успехе авторизации при любых учетных данных. Вам необходимо написать свой обработчик для взаимодействия с нестандартным сервисом, что теперь труда не составит.

Я надеюсь, что это небольшое исследование поведения внешних скриптов авторизации Dovecot сохранит вам время и нервы.

Оставьте комментарий

Вы должны быть вошедший в чтобы отправить комментарий