Предыстория - рассматриваемое приложение отправляет письмо пользователю (user@example.net), делает Bcc: bcccopy@example.com (для внутренних нужд) и ставит Return-Path: sales@example.com. Если не лезть в дебри ООП и наслоения классов, то нужный код на PHP выглядит примерно так:
$email_to = 'user@example.net'; $email_subject = 'subject'; $email_body = 'email body'; $email_additional_headers = 'Bcc: bcccopy@example.com'; $email_additional_params = '-f sales@example.com'; mail($email_to, $email_subject, $email_body, $email_additional_headers, $email_additional_params);
Но при отправке письма из приложения приходило 3 одинаковых письма на user@example.net, на bcccopy@example.com и на sales@example.com, но при этом
Return-Path:
не выставлялся и выглядел примерно так: Return-Path: <www-data@www.example.com>
Нужное значание
Return-Path
может не выставляться, если почтовый сервер не считает пользователя доверенным, либо в вызове /usr/sbin/sendmail
нет ключа -f
. Проверить просто# su - www-data $ cat > /tmp/email.txt <_EOF_ Bcc: <bcccopy@example.com> From: "Sales" <sales@example.com> To: "User Full Name" <user@example.net> Subject: subject email body _EOF_ $ cat /tmp/email.txt | /usr/sbin/sendmail -t -i -f sales@example.com
После выполнения пришли два письма - на user@example.net и на bcccopy@example.com, Return-Path: при этом выставлен корректно. Следовательно проблема лежит выше MTA и нужно дебажить PHP приложение.
Когда мне нужно проверить взаимодействие PHP скрипта и системы я пользуюсь sleep() и strace. Для этого в нужный PHP скрипт в самое начало вставляется sleep(60); после чего выполняется нужное действие. При этом у меня есть 60 секунд, чтобы посмотреть в http://www.example.com/server-status PID нужного мне запроса и подключиться к процессу через strace.
# strace -f -o /tmp/php-trace.log -s 8192 -p <PID>
По истечению 60 секунд приложение начнет выполнять вызовы системы, которые будут записаны в лог файле /tmp/php-trace.log. Поскольку я дебажу отправку почты через mail(), то меня интересует вызов execve()
$ grep execve /tmp/php-trace.log 3253 execve("/bin/sh", ["sh", "-c", "/usr/sbin/sendmail -t -i sales@example.com"], [/* 9 vars */] <unfinished ...> 3253 <... execve resumed> ) = 0 3254 execve("/usr/sbin/sendmail", ["/usr/sbin/sendmail", "-t", "-i", "sales@example.com"], [/* 9 vars */]) = 0 3255 execve("/usr/sbin/postdrop", ["/usr/sbin/postdrop", "-r"], [/* 2 vars */]) = 0
Вот теперь видно почему отправляется три письма вместо двух - где-то в приложении ошибка и перед sales@example.com не добавляется параметр
-f
. А раз sales@example.com указан без параметра, то он трактуется как еще один адрес назначения.Но остается еще один вопрос - почему на некоторых серверах отправляется три письма, а на некоторых только два (правильный Return-Path не выставляется на любом сервере). Во всех случаях код приложения одинаковый и содержит ошибку.
На сервере, где пытались воспроизвести ошибку с отправкой трех писем вместо двух установлен WHM/cPanel и MTA там exim4. Поскольку код одинаковый, значит и функция mail() вызывается с одинаковыми параметрами, а значит и вызов /usr/sbin/sendmail должен быть одинаковый. На всякий случай сравниваю значение переменной sendmail_path в выводе phpinfo() на разных серверах - везде стоит одинаковое значение "/usr/sbin/sendmail -t -i". Но почему же тогда отличается поведение!
Смотрю дальше - на сервере, где отправляется только два письма стоит Exim4, а на остальных, где отправляется три письма, - Postfix. Сравниваю описание ключей -t и -i в man sendmail от этих MTA (взято из CentOS 6.5).
Exim4
-i This option, which has the same effect as -oi, specifies that a dot on a line by itself should not terminate an incoming, non-SMTP message. I can find no documentation for this option in Solaris 2.4 Sendmail, but the mailx command in Solaris 2.4 uses it. See also -ti. -t When Exim is receiving a locally-generated, non-SMTP message on its standard input, the -t option causes the recipients of the message to be obtained from the To:, Cc:, and Bcc: header lines in the message instead of from the command arguments. The addresses are extracted before any rewriting takes place and the Bcc: header line, if present, is then removed. If the command has any arguments, they specify addresses to which the message is not to be delivered. That is, the argu- ment addresses are removed from the recipients list obtained from the headers. This is compatible with Smail 3 and in accordance with the documented behaviour of several versions of Sendmail, as described in man pages on a number of operat- ing systems (e.g. Solaris 8, IRIX 6.5, HP-UX 11). However, some versions of Sendmail add argument addresses to those obtained from the headers, and the O'Reilly Sendmail book documents it that way. Exim can be made to add argument addresses instead of subtracting them by setting the option extract_addresses_remove_arguments false. If there are any Resent- header lines in the message, Exim extracts recipients from all Resent-To:, Resent-Cc:, and Resent-Bcc: header lines instead of from To:, Cc:, and Bcc:. This is for compatibility with Sendmail and other MTAs. (Prior to release 4.20, Exim gave an error if -t was used in conjunction with Resent- header lines.) RFC 2822 talks about different sets of Resent- header lines (for when a message is resent several times). The RFC also specifies that they should be added at the front of the mes- sage, and separated by Received: lines. It is not at all clear how -t should operate in the present of multiple sets, nor indeed exactly what constitutes a "set". In practice, it seems that MUAs do not follow the RFC. The Resent- lines are often added at the end of the header, and if a message is resent more than once, it is common for the original set of Resent- headers to be renamed as X-Resent- when a new set is added. This removes any possible ambiguity.
Postfix
-i When reading a message from standard input, don.t treat a line with only a . character as the end of input. -t Extract recipients from message headers. These are added to any recipients specified on the command line. With Postfix versions prior to 2.1, this option requires that no recipient addresses are specified on the command line.
Sendmail
-i Ignore dots alone on lines by themselves in incoming messages. This should be set if you are reading data from a file. -t Read message for recipients. To:, Cc:, and Bcc: lines will be scanned for recipient addresses. The Bcc: line will be deleted before transmission.
Смысл опции -i примерно одинаков у всех MTA (хотя значение ignore у sendmail, еще нужно проверить - возможно он вовсе выкидывает строку, содержащую только символ точки). А вот после прочтения разделов про опцию -t все становится на свои места. На серверах где стоит exim4 используется отличное от sendmail и postfix поведение, когда получатели, указанные в командной строке, исключаются из итогового списка получателей письма.
Комментариев нет:
Отправить комментарий