Category: it

PHP-5.6.10+: PHP Warning: mail(): Multiple or malformed newlines found in additional_header

Исправление ошибки #68776> (mail() does not have mail header injection prevention for additional headers) в версиях 5.6.10, 5.5.26, 5.4.42 сломало наиболее распространённый вариант отправки средствами php составных писем (что как правило используется для вложения файлов).
Интернеты более чем полны _неправильных_ примеров решения данной задачи посредством подстановки пустого значения в качестве третьего, обязательного аргумента функции, с передачей отправляемого письма в качестве четвёртого, первого опционального, аргумента, что ломается исправлением данной ошибки.
В журналах сервера ошибка отражается примерно следующим образом:
Jul 15 13:20:13 web httpd: PHP Warning: mail(): Multiple or malformed newlines found in additional_header in /var/www/project/inc/mail.inc on line 47

Широко распространены рекомендации разного рода «продвинутых» граждан НЕ использовать прямого вызова функции mail().
По этому поводу отмечу лишь, что одна из рекомендуемых альтернатив (пакет dev-php/PEAR-Mail с зависимостями) в лучшем случае не описывает (а скорее вообще не предоставляет возможности) полностью корректного решения задачи отправки почты, либо делегируя корректную подстановку bounce-адреса на уровень сервера, либо, что куда вероятнее, просто предполагая забивание на анализ ошибок рассылки.

Просто процитирую правильный вариант рыбы функции отправки составных (multipart) писем для простейшего случая: ansi plain text с приложением одного файла.
<?php

// Заголовок письма:
$header = "From: ".$from_mail."\r\n";
$header .= "MIME-Version: 1.0\r\n";
$header .= "Content-Type: multipart/mixed; boundary=\"".$uid."\"\r\n\r\n";
// Тело письма:
// Собственно письмо:
$message = "--".$uid."\r\n";
/* Для отправки в другом формате, например html надо прописать соответствующий тип,
см. список в /etc/mime.types, аналогично для прилагаемого файла. */
$message .= "Content-type:text/plain; charset=iso-8859-1\r\n";
$message .= "Content-Transfer-Encoding: 7bit\r\n\r\n";
$message .= $email_text."\r\n\r\n";
// Прилагаемый файл:
$message .= "--".$uid."\r\n";
$message .= "Content-Type: application/octet-stream; name=\"".$filename."\"\r\n";
$message .= "Content-Transfer-Encoding: base64\r\n";
$message .= "Content-Disposition: attachment; filename=\"".$filename."\"\r\n\r\n";
$message .= $file_data."\r\n\r\n";
// Флаг, обозначающий конец письма:
$message .= "--".$uid."--";
// Дополнительный параметр, проставляющий адрес отправителя в качестве bounce:
$envelope = "-f" . $from_mail;
// И собственно отправка письма:
mail($mailto, $subject, $message, $header, $envelope);

?>

Напоследок отмечу прекрасное: согласно комментариям в багзилле php согласно стандарту строки в письме _должны_ разделяться парой символов 'CRLF'.
Согласно комментариям в интернетах, qmail этого не любит. Но я этого зверя вообще не видел ☺ только читал об альтернативности его логики.
Но самое смищное здесь то, что _вопреки_ (!) сказкам типо разработчиков похапе, вышецированный скрипт замечательно работает и без 'CR', с использованием стандартного и достаточного разделителя строк 'LF'.

Издержки пользователецентричности

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

Collapse )

И бонусом цитата из практически одноимённой статьи заочных аналитиков компьютерры:
«Поскольку трафик, передаваемый по радиоканалу, в принципе не защищен от прослушивания и изменения, в технологии Wi Fi изначально был встроен механизм шифрования канала***. Первым протоколом стал WEP (1999 год), который использовал 40 битные ключи и довольно распространенный алгоритм RC4. Уже тогда перебор всех ключей (их около 500 млрд) не представлял проблем для обычного персонального компьютера.
Производители оборудования перешли на 104 битные ключи, полный перебор оказался исключен, и тогда за дело взялись крипто-аналитики, которые обнаружили уязвимость в самом алгоритме RC4****. Результат — возможность взлома за часы и даже минуты. В 2003 году альянс Wi Fi принял новый протокол — WPA (затем, в 2004 году, WPA2), а прежний WEP признал непригодным с точки зрения безопасности. Но, как показывает опыт, от врожденной уязвимости избавиться очень трудно: требуется или замена/обновление программно-аппаратных средств, или ручные операции со стороны пользователей, к которым те совершенно не склонны. На волне интереса к WiFi было закуплено много оборудования, которое работает только с WEP, и просто так выбрасывать его мало кому хочется, так же, как и вчитываться в содержимое сайтов производителей, предлагающих инструкции по повышению безопасности.
Довольно интересны в этом плане результаты вардрайвинга, кото рый провела «Информзащита» в Москве в 2005 году: только 5% всех точек доступа использовали более защищенный протокол WPA; 12% применяли настройки «по умолчанию» (весьма небезопасные). То есть производители добились от потребителей того, чего хотели: це почки «купи, включи и ни о чем не думай».»
http://www.computerra.ru/cio/old/offline/2009/86/475530/

Как сейчас помню свой первый опыт по настройке wifi-подключения. На стороне клиента «внезапно» выступала «лучшая ОС майкрософта» (XP).
Которая конечно же совершенно «случайно» не подключалась к роутеру в режимах отличных от WEP.
Так что дело не только, и даже не столько, в преступной халатности пользователей, сколько в тяжком бремени технологической (и не только) инерции.

«Сетевая папка» Windows в Thunar

Предварительные замечания:
Протокол — smb.
Файловая система — cifs.

У меня сейчас:
xfce-base/thunar-1.6.3

Сказка:
В приказчике файлов XFce не собственной реализации функций монтирования. Для решения этой задачи используется gnome-base/gvfs (у меня здесь и сейчас gnome-base/gvfs-1.18.3-r1).

Проверка системы на поддержку необходимой функции/в случае необходимости — реконфигурирование в Gentoo GNU/Linux производится не просто, а очень просто.
Достаточно проверить состояние (при необходимости включить) флаг samba (внезапно?) для пакета gnome-base/gvfs (после реконфигурации также потребуется перезапустить пользовательского демона).

Собственно, на этом всё.
Единственное, вместо привычного вендопользователям формата адреса \\IIS_server\ надо писать smb://IIS_server/

При подключении (курсор в поле ввода помещается мышью или комбинацией Ctrl-L) Thunar попросит ввести имя пользователя (по умолчанию подставляется значение переменной окружения USER, домен (куда вероятно подпадает и рабочая группа и пароль).
И также при каждом переподключении, что неудобно.
Поэтому разделы, которые предполагается монтировать если не постоянно, то регулярно, следует прописать в /etc/fstab. Этот аспект пока подробно не рассматриваю, но полагаю к нему ещё вернуться.
Строго говоря, та же проблема (с сохранением/подстановкой учётных данных) наблюдается и для прочих протоколов (ftp, sftp…).

Замечание об адресации:
По умолчанию, самая распространённая ОС в качестве сетевого имени компьютера исторически использовала локально задаваемую строку (с сетевым представлением в формате NETBIOS, но вроде бы в последнее время наблюдается тенденция к уходу от этой технологии).
В фрюниксах (и соответственно — samba) традиционно используются «взрослые» технологии, в данном случае — DNS.
То есть в строке адреса:
smb://IIS_server/
Предполагается разворачивание имени на основании /etc/hosts до
smb://IIS_server.domain.tld/
И что базовое имя (IIS_server) известно используемому DNS-серверу или на худой конец прописано в hosts.

ЗЫ: В процессе экспериментов ни один Window$-сервер не пострадал, за сервера всегда работала samba.

OpenLDAP OLC (slapd.d aka cn=config), изменение списка прав доступа

Предварительные замечания:
Уровни доступа
LevelPrivilegesDescription
none =0no access
disclose =dneeded for information disclosure on error
auth =dxneeded to authenticate (bind)
compare =cdxneeded to compare
search =scdxneeded to apply search filters
read =rscdxneeded to read search results
write =wrscdxneeded to modify/rename
manage =mwrscdxneeded to manage

Идентификаторы объектов
SpecifierEntities
*All, including anonymous and authenticated users
anonymousAnonymous (non-authenticated) users
usersAuthenticated users
selfUser associated with target entry
dn[.<basic-style>]=<regex>Users matching a regular expression
dn.<scope-style>=<DN>Users within scope of a Distinguished Name


Пример правил в legacy (slapd.conf) формате:
access to attrs=userPassword
by dn="cn=ldapadmin,dc=mydomain,dc=ru" write
by self read
by anonymous auth
by * none

access to dn.subtree="dc=mydomain,dc=ru"
by dn="cn=ldapadmin,dc=mydomain,dc=ru" write
by users read
by anonymous auth
by * none

#
## First Global ACL
access to *
by * none


Комментирование выглядит стандартным образом, разделитель правил — пустая строка, символы новой строки и табуляции интерпретируются как обычный разделитель (по умолчанию пробел).
Применение инструкций в рамках правила идёт от начала к концу, причём последующие обладают меньшим весом.
Применение правил идёт от конца к началу. При этом в случае пересечения последнее правило перекрывает все обработанные ранее.
Поэтому во избежание неприятных сюрпризов писать правила следует от общего к конкретному следя за тем, чтобы один и тот же объект на одном уровне описывался только один раз.
Подробности стоит поискать на страницах:
slapd.access (5)
slapacl (5)

Первоисточник описаний контейнеров/атрибутов в используемых схемах.
В интернетах можно посмотреть например здесь.
Для используемого примера dc → domainComponent, cn → commonName.

В новом формате (OLC) приведённый блок правил выглядит следующим образом:

olcAccess: {0}to attrs=userPassword by dn.base="cn=ldapadmin,dc=mydomain,dc=ru" write by self read by anonymous auth by * none
olcAccess: {1}to dn.subtree="dc=mydomain,dc=ru" by dn.base="cn=ldapadmin,dc=mydomain,dc=ru" write by users read by anonymous auth by * none
olcAccess: {2}to * by * none

Разрыва последовательности нумерации не допускается.
С синтаксисом замены и отношением к дублям (опыты провожу на 2.4.35) разбираться было честно в лом.

Допустим, внезапно выяснилось, что необходимо разрешить доступ к вложенным схемам.
Т.е. добавить правило в формате slapd.conf (на вторую позицию, т.е. сразу после применяемого последним правила доступа к атрибутам пользовательских паролей) выглядящее следующим образом:
access to dn="cn=subschema"
by users read
by * none


Задача решается посредством индекса (ldif) следующего вида:
modify_acl.ldif:
dn: olcDatabase={-1}frontend,cn=config
changetype: modify
delete: olcAccess
olcAccess: {1}
olcAccess: {2}

dn: olcDatabase={-1}frontend,cn=config
changetype: modify
add: olcAccess
olcAccess: {1} to dn.base="cn=subschema" by users read by * none
olcAccess: {2}to dn.subtree="dc=mydomain,dc=ru" by dn.base="cn=ldapadmin,dc=mydomain,dc=ru" write by users read by anonymous auth by * none
olcAccess: {3}to * by * none


В случае настроенного согласно предыдущей заметке сервера обновление правил доступа производится командой:
# ldapmodify -Y EXTERNAL -H ldapi:/// -f /tmp/modify_acl.ldif

Перечитать и убедиться что всё правильно — командой:
# ldapsearch -Y EXTERNAL -H ldapi:/// -b "olcDatabase={-1}frontend,cn=config"

Update:
Пример несколько переусложнён.
Для простой вставки в середину блока достаточно добавления одной строки с указанием нужной позиции.
Остальные правила (начиная с занимающего целевую строку) будут сдвинуты вверх (+1 к номеру).
Но иллюстрирует добавление/удаление отдельных правил.
Изменение (замена, replace) тоже работает, но для всего блока (а не как в рассмотренном примере — подмножества).

СПО и ППО — результат наглядного сравнения на примере web

Повторю очевидное:

Свободное Программное Обеспечение (СПО) доныне (что не вполне соответствует оптимальному использованию ресурсов, но не о том речь) пишется во-первых, в целях разработки самого себя, и во-вторых, в целях решения автором (а по совместительству — разработчиком) некоторой своей задачи. И обычно хорошо подходит для решения его коллегами-единомышленниками тех же или схожих задач.
Иначе говоря: оно используется теми, кому оно нужно, кто знает зачем оно ему нужно и кто готов приложить некоторое количество труда к освоению инструмента.

Проприетарное же Программное Обеспечение (ППО) пишется с целью извлечения прибыли. Что практически обуславливает тенденцию к расширению пользовательской базы на тех, кто даже не знает зачем оно ему нужно (но тут на помощь приходят подсказки с каналов коммерческой пропаганды), и тем долее не жаждет утруждать себя освоением того, потребности в чём он не ощущает.
Ситуация только усугубляется профессионализмом разработчиков (Которые именно что _разрабатывают_, но как правило даже в страшном сне не представляют себе ситуации, когда им придётся использовать свои разработки. Лично.).

Ну и в результате наблюдаем прекрасное:
Расширение функциональности web за счёт выполняемых на стороне клиента скриптов (JavaScript) в условиях рыночных технологий слишком часто используется не только не в интересах конечного пользователя, но совсем наоборот.
Что породило вполне закономерную традицию даже в клиентах, данную технологию поддерживающих, по умолчанию блокировать выполнение JavaScript.

http://stackoverflow.com/ и прочие ориентированные на Сообщество разработчиков СПО ресурсы, достаточно хорошо работающие и без JavaScript'ов, доступность функции проверяют и, если она не поддерживается, красной строкой в заголовке страницы предупреждают:
Stack Overflow works best with JavaScript enabled

Вконтактик (и подавляющее большинство, исключений навскидку вообще не припоминается, прочих разработанных профессионалами по заказу профессионалов ресурсов, требующих JavaScript) доступность функции проверяет весьма условно (только при заходе на главную страницу, причём весьма альтернативным образом в виде постоянного перенаправления на vk.com/badbrowser.php, т.е. после разрешения необходимо вернуться на главную, причём скрипт проверки удовлетворяется результатом, недостаточным для работы ресурса), не смотря на то, что без JavaScript'а он практически неработоспособен.
То же можно наблюдать и для прочих ресурсов с браузерными играми.

История программных революций от Microsoft, вкратце

По мотивам встреченного в последней подшивке от Альва упоминания старой, если не древней традиции программистов («сделать всё также, только иначе» ☺)

Сначала были Windows API и DLL Hell.

Революцией №1 было DDE - помните, как ссылки позволили нам создавать статусные строки, отражающие текущую цену акций Microsoft? Примерно тогда же Microsoft создала ресурс VERSION INFO, исключающий DLL Hell.
Но другая группа в Microsoft нашла в DDE фатальный недостаток — его писали не они! Для решения этой проблемы они создали OLE (похожее на DDE, но другое), и я наивно вспоминаю докладчика на Microsoft-овской конференции, говорящего, что скоро Windows API перепишут как OLE API, и каждый элемент на экране будет ОСХ-ом. В OLE появились интерфейсы, исключающие DLL Hell. Помните болезнь с названием "по месту", при которой мы мечтали встроить все свои приложения в один (возможно, очень большой) документ Word? Где-то в то же время Microsoft уверовала в религию С++, возникла MFC решившая все наши проблемы еще раз.
Но OLE не собиралась, сложа руки смотреть на это, поэтому оно заново родилось под именем COM, и мы внезапно поняли, что OLE (или это было DDE?) будет всегда - и даже включает тщательно разработанную систему версий компонентов, исключающую DLL Hell.
В это время группа отступников внутри Microsoft обнаружила в MFC фатальный недостаток — его писали не они! Они немедленно исправили этот недочет, создав ATL, который как MFC, но другой, и попытались спрятать все замечательные вещи, которым так упорно старалась обучить нас группа COM.
Это заставило группу COM (или это было OLE?) переименоваться в ActiveX и выпустить около тонны новых интерфейсов (включая интерфейсы контроля версий, исключающие DLL Hell), а заодно возможность сделать весь код загружаемым через броузеры, прямо вместе с определяемыми пользователем вирусами (назло этим гадам из ATL!).
Группа операционных систем громким криком, как забытый средний ребенок, потребовала внимания, сказав, что нам следует готовиться к Cairo, некой таинственной хреновине, которую никогда не могли даже толком описать, не то, что выпустить. К их чести, следует сказать, что они не представляли концепции "System File Protection", исключающей DLL Hell.
Но тут некая группа в Microsoft нашла фатальный недостаток в Java — её писали не они! Это было исправлено созданием то ли J, то ли Jole, а может, и ActiveJ (если честно, я просто не помню), точно такого же как Java, но другого.
Это было круто, но Sun засудило Microsoft по какому-то дряхлому закону. Это была явная попытка задушить право Microsoft выпускать такие же продукты, как у других, но другие.
Помните менеджера по J/Jole/ActiveJ, стучащего по столу туфлей и говорящего, что Microsoft никогда не бросит этот продукт? Глупец! Все это означало только одно - недостаток внимания к группе ActiveX (или это был COM?). Эта невероятно жизнерадостная толпа вернулась с COM+ и MTS наперевес (может, это стоило назвать ActiveX+?). Непонятно почему к MTS не приставили "COM" или "Active" или "X" или "+" — они меня просто потрясли этим! Они также грозились добавить + ко всем модным тогда выражениям. Примерно тогда же кое-кто начал вопить про "Windows DNA" (почему не DINA) и "Windows Washboard", и вопил некоторое время, но все это почило раньше, чем все поняли, что это было.
К этому моменту Microsoft уже несколько лет с нарастающей тревогой наблюдала за интернет. Недавно они пришли к пониманию, что у Интернет есть фатальный недостаток: ну, вы поняли. И это приводит нас к текущему моменту и технологии .NET (произносится как "doughnut (пончик по-нашему)", но по-другому), похожей на Интернет, но с большим количеством пресс-релизов. Главное, что нужно очень четко понимать — .NET исключает DLL Hell.
В .NET входит новый язык, C#, (выясняется, что в Active++ Jspresso был фатальный недостаток, от которого он и помер). .NET включает виртуальную машину, которую будут использовать все языки (видимо, из-за фатальных недостатков в процессорах Интел). .NET включает единую систему защиты (есть все-таки фатальный недостаток в хранении паролей не на серверах Microsoft). Реально проще перечислить вещи, которых .NET не включает. .NET наверняка революционно изменит Windows-программирование… примерно на год.


Автор мне не известен, и упираться в достоверное установление авторства мне сейчас лень.

Не самом деле, отсмеявшись, стоит отложить смехуёчки в сторону и подумать над тем фактом, что, в сколько–нибудь сложном проекте анализ готового продукта (как на предмет полноты/качества реализации необходимых функций, так и на предмет наличия отсутствия разного рода закладок) по ресурсоёмкости в лучшем случае равен (в реальности же скорее превосходит, обычно — значительно; пример приснопамятного патча SSL в OpenBSD тому примером) самостоятельной разработке с чистого листа.
Слава бизнесменам–аутсорсерам!!!

Кодировка клиента СУБД Oracle

На стороне клиента кодировка сервера определяется переменной NLS_LANG.
Формат переменной: <Язык>_<Территория>.<CHARACTERSET>

Список значений CHARACTERSET для русского языка (скорее всего неполный, но рыть проприетарную документацию для поиска исторических значений… скучно и грустно):
AL16UTF16, AL32UTF8, CL8ISO8859P5, CL8KOI8R, CL8MSWIN1251, UTF8

Oracle uses the following naming convention for character set names:
<language or region><number of bits representing a character><standard character set name>[S | C]
UTF8 and UTFE are exceptions to the naming convention.

AL32 = All Languages, 32 bits maximum character width
AL16 = All Languages, 16 bits maximum character width
CL — не нашёл, по смыслу должно означать кириллицу.

Итого, в предположении использования на клиенте UTF8 и Oracle 11g в качестве сервера (возможно работает и на 10g) получаем следующее значение:
NLS_LANG=RUSSIAN_CIS.AL32UTF8
CIS в данном случае является обозначением территории СНГ (если кто-то ещё помнит что это такое).

P.S. Obsolete since 11g: docs.oracle.com/cd/E11882_01/server.112/e10729/applocaledata.htm
Правильное значение:
NLS_LANG=RUSSIAN_RUSSIA.AL32UTF8

Корректировка рефлексов

Не все знают, что помимо стандартной, привычной комбинации клавиш команды закрытия окна (Alt-F4) есть более мнемоничная и удобная комбинация (Ctrl-q).
Правда, она поддерживается не всеми (особо «кроссплатформенными») приложениями.

Заставь… профессионала дело делать…

«Нормальный» (© умственно альтернативный лемминг, решающий задачу утверждения ЧСВ) пользователь (то есть идейный потребитель программных продуктов фирмы майкрософт, да и администратор тоже, если не «тем более») в ситуации, когда необходимо _корректное_ решение в общем-то простых задач, но от правильности решения которых зависит очень многое, представляет весьма жалкое зрелище.
Сертификаты (вкупе с инфраструктурой цифровых подписей) выглядят… весьма грустно.

Сего дня столкнулся с следующей проблемой:
Dec 9 09:15:56 smtp sm-mta[69980]: ruleset=tls_server, arg1=SOFTWARE, relay=problemhost.example.com, reject=403 4.7.0 TLS handshake failed.

Гугль в качестве основного варианта рекомендует настраивать сертификаты на сервере (помним, что электронная почта относится к сервисам первого поколения, в которых безопасность во многом была принесена в жертву работоспособности на имеющейся аппаратной базе):

Установить/настроить сертификаты:
http://www.sendmail.org/~ca/email/starttls.html
define(`confCACERT_PATH', `/etc/mail/certs')dnl
define(`confCACERT', `/etc/mail/certs/CAcert.pem')dnl
define(`confSERVER_CERT', `/etc/mail/certs/MYcert.pem')dnl
define(`confSERVER_KEY', `/etc/mail/certs/MYkey.pem')dnl
define(`confCLIENT_CERT', `/etc/mail/certs/MYcert.pem')dnl
define(`confCLIENT_KEY', `/etc/mail/certs/MYkey.pem')dnl

(вопрос формирования файлов сертификатов здесь не рассматриваю)

Что в общем случае не согласуется с моим чувством гармонии.
Дальнейший анализ показал правоту моего чувства гармонии.
Для решения проблемы было нужно принудительно запретить SSL для хоста, на котором он настроен… странным образом:
http://bsdpants.blogspot.ru/2010/01/403-470-tls-handshake-failed.html
/etc/mail/access:
Try_TLS:problemhost.example.com NO
Try_TLS:problem2.example.com NO


После чего обновить рабочую конфигурацию:
Для FreeBSD просто:
# cd /etc/mail
#make
#make restart

Переключение Timezone Database в PHP с встроенной на внешнюю

Наблюдая стандартные грабли бинарной компоновки, отягчённые тырпрайс-сущностью (системная версия пакета — tzdata-2013b-1.el6.noarch) в качестве исходных данных:

date


date/time support enabled
"Olson" Timezone Database Version 2012.3
Timezone Database internal
Default timezone Europe/Moscow

Вспоминаем подарочек, преподнесённый нашим родным правительством в прошлом году (изменение определения временной зоны) ☺
В конфиге (php.ini) рулится только один, последний параметр (date.timezone).
Можно конечно свалить на Гринвич, но это неспортивно… Надо обновлять базу.
Обновление пакета (собственно основного сервера, в который на этапе компилляции зашита цитированная версия базы) несколько затруднительно.
Очевидным образом напрашивается workaround в виде переключения на внешнюю (относительно пакета) базу временных зон.

# rpm -ivh ~rpmbuilder/rpmbuild/RPMS/x86_64/php-pecl-timezonedb-2013.7-zend.el6.x86_64.rpm
SPEC не цитирую потому что во-первых стыдно (наличных знаний способов компоновки rpm-пакетов), а во-вторых совершенно нет гарантий совместимости данного наколенного поделия с конкретным сервером (оно и сочинялось потому что тынутый в интернетах пакет оказался несовместим с используемым сервером).

В результате после перезапуска сервера можно наблюдать переключение на внешнюю базу временных зон и испытать удовлетворение от актуальной версии оной:

date


date/time support enabled
"Olson" Timezone Database Version 2013.7
Timezone Database external
Default timezone Europe/Moscow