В почтовой службе Gmail by Google изменен алгоритм забора сообщений со сторонних почтовых серверов
18 декабря что-то ударило в голову разработчикам сборщика почты в Gmail (функция имеет оригинальное название POP3 mail fetching). Не знаю какою природу/градус имеет это что-то, но отныне почта от Google использует APOP в качестве первичного средства авторизации на стороннем почтовом сервере при заборе сообщений по протоколу POP3.
Подобные изменения породили неожиданные проблемы. Стали поступать жалобы о том, что письма более не забираются с ящиков aspu.ru, соответственно более не доставляются в Gmail. Сперва я просто не верил в существование проблемы. При этом недовольные пользователи долго сбивали с толку говоря что у них “не работает редирект”.
По неподтвержденным данным (со слов пользователей) подобным образом изменили алгоритм работы и другие крупные почтовики, такие как Yandex и Mail. Если у вас похожие симптомы – обязательно читаем дальше
Строго говоря, APOP является необязательным расширением, о чем явно говорится в RFC 1939 (APOP Command находится в секции Optional POP3 Commands). Так какого лешего Gmail вдруг стал пытаться аутентифицироваться именно по нему?? Непонятно. У нас plaintext авторизация – команда USER/PASS, тоже опциональные кстати.
С другой стороны, имеется определенная вина нашего POP3 сервера, который дает повод “думать”, что он имеет настроенную APOP авторизацию. В частности, признак поддержки APOP недвусмысленно определен в RFC 2449. Там написано: “Clients discover server support of APOP by the presence in the greeting banner of an initial challenge enclosed in angle brackets (“<>”). Смотрим что у нас:
neoadmin@422-linux:~$ telnet mail.aspu.ru 110
Trying 62.183.105.228...
Connected to mail.aspu.ru.
Escape character is '^]'.
+OK
Вот такая засада. Работа POP3 сервера обеспечивается демоном tpop3d, который очень неплохо работает уже не первый год. При попытке авторизации по APOP в лог попадает такая ошибка:
Dec 29 13:26:57 aspu tpop3d[84134]: auth_mysql_new_apop: mysql_query: Table 'postfix.popbox' doesn't exist
Dec 29 13:26:57 aspu tpop3d[84134]: connection_do: client `[6]172.16.5.77/aspu.ru': username `test3': 1 authentication failures
Откуда он берет таблицу popbox?? В данной ситуации очень сильно помогает изучение tpop3d.conf на предмет APOP либо man tpop3d.conf на предмет тогоже. Тем не менее, таблица popbox нигде не всплыла, самое интересное, возможности отключение APOP тоже нет (явно не RFC compliance продукт).
А строка APOP-запроса после изучения исходников продукта оказалась “вшитой” (hardcoded), что вообще меня поразило.
У меня в конфиге определено
## MySQL authentication options
# auth-mysql-enable: (yes | true)
# Enable MySQL authentication.
auth-mysql-enable: true
далее следуют реквизиты подключения к БД. Затем
# auth-mysql-pass-query: substitution string
# Query template to use for USER/PASS authentication.
# Return mailpath, password, userid, mailspool type
auth-mysql-pass-query: SELECT CONCAT('/var/mail/virtual/', maildir), CONCAT('{crypt_md5}', password), 1001, 'bsd' from mailbox where username='$(user)'
# auth-mysql-apop-query: substitution string
# Query template to use for APOP authentication.
# See auth-mysql-pass-query
То есть всё что имеет отношение к APOP оставлено по умолчанию за неимением возможности отключения. Ну и логично было обнаружить код в auth_mysql.c:
char *apop_query_template =
"SELECT concat(domain.path, '/', popbox.mbox_name), popbox.password_hash, "
"domain.unix_user, 'bsd' "
"FROM popbox, domain "
"WHERE popbox.local_part = '$(local_part)' "
"AND popbox.domain_name = '$(domain)' "
"AND popbox.domain_name = domain.domain_name";
В общем-то, на первый взгляд ничего страшного, ведь запрос можно уточнить. Подводный камень в том, что при APOP-аутентификации пароль передается в виде MD5 хеша. При этом в качестве исходной строки используется не просто пароль, а временная метка, конкатенированная с паролем пользователя. Временная метка – это то что сервер выдает в угловых скобках при соединении, на самом деле при рассмотрении она не совсем временная, но это другая тема. То есть, аутентификация через APOP выглядит как
APOP name digest, где
name = имя пользователя = username
digest = MD5(timestamp.password) = MD5 (d9af0351d82dd480983b95b03e474599.pa$$w0red) = 89882ca98ea9a062be93a0b8c5415057
Вроде всё прозрачно. А вот как сервер должен сравнивать полученный digest со значением в базе?? Единственный вариант, в базе пароль пользователя должен храниться в plaintext-виде. На мой взгляд это великое зло. С одной стороны, мы повышаем безопасность при передаче пароля по сети, с другой, храним пароли в открытом виде на сервере.
При этом существует POP3 over SSL, который работает по 995 порту. И по сети идет в зашифрованном виде, и в базе можно хранить только хеши, и APOP не нужен, и в Gmail на такой случай _отдельная_ галочка.
В моем случае у меня в базе хеши (Unix MD5, не путать с простым MD5). Для POP3 over SSL необходимо отдельно собирать OpenSSL из портов имеющейся древнющей FreeBSD 5.2.1, что видится мне особым приключением. Поднимать APOP в масштабах 4 тысяч пользователей вообще не серьезно, но делать что-то нужно.
Для начала переопределил шаблон запроса. Для этого я ничего не пересобирал, а уточнил запрос в конфиге:
auth-mysql-apop-query: SELECT CONCAT('/var/mail/virtual/', maildir), CONCAT('{plaintext}', password), 1001, 'bsd' from mailbox where username='$(user)'
К сожалению, я не имею возможности посмотреть лог соединения с Gmail (на самом деле имею, просто лень)) + в интерфейсе Gmail’а выводится абсолютно бредовое сообщение об ошибке. Заметку пишу постфактум, но там было что-то в духе “не удается связаться с сервером”, хотя связывается прекрасно (логи не обмануть). В любом случае, после проделанных действий картина изменилась в лучшую сторону. Gmail пробует по APOP, получает отлуп и пробует обычным plaintext. Почему для этого было необходимо поправить конфигурацию tpop3d мне не ясно.
Ну и случайно было обнаружено, что имеется возможность указать
auth-mysql-apop-query: none
Вывод один, надо внедрять POP3 over SSL, но это уже на новой почтовой системе. На этой оптимистичной ноте блог, с одной стороны, выходит из осенне-зимней спячки, а с другой, уходит на праздники =)) Всех с наступающим!!