суббота, 15 мая 2010 г.

Прицел на eJabberd

Поговорим о выборе системы сообщений для пользователей некоего проекта или компании. Притом исходим из следующих требований:

  • пользовательский интерфейс системы сообщений должен встраиваться в интернет/интранет портал, работающий на строго фиксированном ip с доступом исключительно по https. Почему такое ограничение, обсуждать особенно ни к чему, поскольку это бизнес-требование, добавлю лишь, что для некоторых пользователей есть и техническое ограничение - например, скайлинк на тарифных планах для корпоративщиков (по крайней мере, на некоторых) предоставляет лишь доступ по https.
  • собственная авторизация (любым способом, без ограничений), так что зарегистрированные в основной БД пользователи имеют доступ к системе сообщений (притом из соображений безопасности в основной БД хранится лишь хэш пароля)
  • всеобъемлющий API для интеграции с системой сообщений - в частности, для подключения AJAX-интерфейса пользователя (никаких web sockets, см. первое требование) и логирования всех сообщений (логирование может быть реализовано от варианта с ведением простого лог-файла до записи в БД с онлайн-индексированием, рассылкой уведомлений по email и созданием отчетов и проч.)

В настоящий момент, все перечисленные условия (и не только) могут быть реализованы с помощью XMPP-протокола. HTTP доступ предоставляют все современные jabber-сервера с помощью технологии HTTP binding (примечание: нужно пробросить директорию /http-bind/ портала на порт jabber-сервера). Среди некоторого многообразия jabber-серверов есть и один весьма неплохой - ejabberd. Почему именно он, история долгая, но на этом варианте я остановился два года назад и с тех пор не было причин изменить свое мнение (тестировал его, мелкие баги были, но сейчас они уже поправлены). В силу того, что в дебиане сейчас присутствует работоспособная версия ejabberd, пришло время запускать его в продакшен (мне хватает забот со своим деб-репозиторием и мантейнить лишний пакет без необходимости не стану).

Как всегда, быстрее всего ознакомиться с возможностями выбранного продукта можно, посмотрев существующие расширения к нему: Contributions
И, конечно, смотрим Official ejabberd documentation А также страницу вики Делаем Jabber-сервер

Что касается логирования, меня больше интересует отдельная система, нежели встроенный модуль. Например, это Bandersnatch:
Guide to Install Bandersnatch
Install Bandersnatch to log Messages
или фронт-энд Jorge (мне плевать, что оно на пхп, поскольку рассматриваю перечисленные варианты как образцы для написания своего решения).
Впрочем, есть и встраиваемые модули для логирования:
mod_logdb - Log User Messages to DB
Сохранение логов muc-конференций в виде html

Теперь поговорим об AJAX-интерфейсе. как один из вариантов интерфейса (есть в дебиане): A web based Jabber/XMPP client Умеет многое, смотрим здесь в подробностях: Features
Немного об установке интерфейса с джаббер-сервером:
EJabberd + JWChat + Apache2
А тут рассказано, как можно потыкать пальцем протокол для разной мелкой автоматизации: Джаббер чат на веб-странице

И немного о публичных сервисах, для ознакомления:
http://www.olark.com/portal/?rid=hab.la
http://www.meebo.com/
Create a Google Talk chatback badge

Upd.
За пример работающего конфига и некоторые подсказки спасибо Anton Kovalenko.

Пользователь admin существует сразу после установки, надо лишь назначить ему нужный пароль. Например, вот так:
sudo ejabberdctl change_password admin хост пароль
Теперь от имени пользователя admin с указанным паролем заходим в веб-интерфейс http://хост:5280/admin/ Хм, почему же здесь доступ по открытому HTTP? Непорядок, правим в конфиге
-web_admin
+web_admin, tls, {certfile, "/etc/ejabberd/ejabberd.pem"}
Далее создаем свой сертификат - установленный по умолчанию мне не интересен, лучше свой выписать, или получить здесь ICA XMPP Foundation (бесплатно). После перезапуска еджаббера входим уже по защищенному соединению https://хост:5280/admin/

В веб-интерфейсе выбираем наш виртуальный хост, и для него можно создавать shared rosters ("группы общих контактов"), управлять пользователями и прочее. Впрочем, вот именно "управление" вызывает разумные сомнения, так что оставим только возможность просмотра настроек - пусть значения из конфиг-файла перезаписывают все изменения, вносимые через веб-интерфейс:
override_global.
override_local.
override_acls.

Что касается авторизации, то все достаточно просто, см. 4 Authentication
Примеры скриптов вполне наглядны, вот один из них: Postgresql external auth script (Perl)

Вот такой тиклевый скрипт умеет авторизовывать пользователей:
ejabberd_auth
#!/usr/bin/tclsh8.5
# https://git.process-one.net/ejabberd/mainline/blobs/raw/2.1.x/doc/dev.html#htoc8

fconfigure stdin -translation binary -buffering none
fconfigure stdout -translation binary -buffering none

# the result code (coded as a short), should be 1 for success/valid, or 0 for failure/invalid
proc auth_ok {} {
    puts -nonewline [binary format Su2 {2 1}]
}
proc auth_no {} {
    puts -nonewline [binary format Su2 {2 0}]
}

# check user or user/password
proc auth {server user {password {}}} {
    auth_ok
}

while {1} {
    if {[binary scan [read stdin 2] Su len] == 1} {
        lassign [split [read stdin $len] :] type user server password
        exec logger "ejabberd external auth script: $type $user $server $password"
        switch $type {
            auth {
                # check if a username/password pair is correct
                auth $server $user $password
            }
            isuser {
                # check if it’s a valid user
                auth $server $user
            }
            default {
                auth_no
            }
        }
    }
    if {[eof stdin]} {
 break
    }
}
На самом деле скрипт у меня обращается к БД для проверки авторизации, здесь же я упростил для наглядности.
Для основного домена "домен" я желаю встроенную авторизацию, а для всех прочих - внешним скриптом. В конфиге это будет записано так:
%%%   ==============
%%%   AUTHENTICATION

%%
%% Authentication using external script
%% Make sure the script is executable by ejabberd.
%%
{auth_method, external}.
{host_config, "домен", [{auth_method, internal}]}.

{extauth_program, "/usr/local/bin/ejabberd_auth"}.
Проверил, указанная конфигурация успешно работает. С той оговоркой, что скрипты в цикле поедают ресурсы и при перезапуске ejabberd продолжают работать (при каждом старте ejabberd запускается еще 4 скрипта). Проблемы решаемые, но ничего хорошего в этом нет.

Раз у нас есть авторизация, самое время поиграться с ростерами. Пробуем:
sudo ejabberdctl srg_create groupadm хост1 "Администраторы" "Группа администраторов" groupadm\\ngroupall
sudo ejabberdctl srg_create groupall хост1 "МР ЮГ" "Все пользователи МР ЮГ" "groupadm"

sudo ejabberdctl srg_user_add admin хост groupadm хост1
sudo ejabberdctl srg_user_add veter хост1 groupadm хост1
sudo ejabberdctl srg_user_add @all@ хост groupall хост1

sudo ejabberdctl srg_user_del test хост groupall хост1

sudo ejabberdctl srg_delete groupadm хост1
Здесь в команде srg_create последний аргумент содержит список всех групп, которые будут видеть участники создаваемой группы; обычно туда включают и саму создаваемую группу, чтобы ее участники видели друг друга. В документации этот момент разъясняется как-то странно, сразу и не понять, что саму группу туда тоже нужно включить:
Displayed groups
A list of groups that will be in the rosters of this group’s members.

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

Рассматриваемый подход имеет как свои плюсы, так и минусы. Хорошо то, что информация о пользователях может забираться из внешнего источника, а плохо то, что нужен постоянный доступ к этому источнику, фиксированный его формат, высокая скорость отклика и минимальные накладные расходы даже при множестве обращений. Еще вспомним про невозможность поиска по стандартным атрибутам - конечно, свой коннектор позволит искать в ldap и других внешних хранилищах, но создаст заведомо узкое место в системе. Так что теперь посмотрим, как может быть выполнена репликация нашей БД с пользователями и пользовательской информации в ejabberd. Что ж, я исхожу из принципа "мухи отдельно, котлеты отдельно", а вариант с доступом к внешнему хранилищу уже подробно рассмотрен выше. Вот что касается вариантов онлайн-синхронизации пользовательской информации:
ejabberdctl that may be useful for you, like push_roster_all or srg_user_add

This way you can implement the logic of adding/deleting contacts in your
prefered language, and then call the script ejabberdctl to perform the changes.

If you later notice that calling a shell script for each change is too
slow for you,
you can install also ejabberd_xmlrpc, which provides an XML-RPC server where
you can send your command queries (instead of the shell script ejabberdctl).

Есть и сторонние сервисы/утилиты для переноса контактов, в том числе между разными серверами. Например, Jabber roster utility или ее клон Утилита для Jabber Ростера на PHP

Продолжаем установкой пакета jwchat (копируем или симлинкаем к себе файлы *.ru, создаем конфиг, правим симлинк на XMPP javascript-библиотеку). Выясняется, что при включенном SSL для веб-интерфейса не работает http-bind, так что пока отключаем SSL (возвращаем к дефолтовым настройкам секцию конфига). Плюс в разделе модулей вписываем в конфиг модуль {mod_http_bind, []} без которого http binding не работает (понятно, почему не работает, но непонятно, с чего бы это в дефолтовом конфиге его нет, раз уж предлагается http binding). Теперь
links http://localhost:5280/http-bind/
работает и показывает страничку с информацией. Идем в jwchart -все верно, получаем желанный AJAX-интерфейс. Для корпоративного портала стоит предусмотреть подстановку имени и пароля пользователя, дабы не напрягать последнего лишними деталями.

Довольно интересной для пользователей возможностью является icq-гейт. Что ж, попробуем:
sudo aptitude install -R pyicq
sudo mcedit /etc/pyicqt.conf.xml
sudo mkdir /var/lib/pyicqt/JabberID/
sudo chown pyicqt:pyicqt /var/lib/pyicqt/JabberID/
sudo /etc/init.d/pyicqt restart
Здесь JabberID это icq.что-то-там согласно определенному в конфиге.
Гейт прикручивается к указанному виртуальному домену, хотя при желании его можно использовать для любого домена, если знать адрес гейта или адрес его домена. В общем, работает. Настроить клиента Миранда можно вот так: Подключение ICQ в Miranda - только учтем, что в новых версиях это меню располагается слева внизу главного окна Миранды (впрочем, там вариантов немного, так что я быстро нашел).

Теперь на AJAX-программирование подробнее посмотрим. jwchat работает "из коробки" (см. выше, как его ставить). Отображаемые поля vCard меня не устроили, например, нет номера телефона, но это решается добавлением нужной строчки в соответствующий html-файл (яваскрипт видит все значения в карточке, а заполняет, что логично, только существующие на странице поля, так что настройка отображения vCard заключается в добавлении строчек в html-таблицу).

Расширение Strophe: Programming with XMPP and JavaScript
jQuery and Strophe: Made for Each Other
jquery-bosh
XMPP LIBRARY
Professional XMPP Programming with JavaScript and jQuery Jan 2010 eBook-BBL

Upd.
А вот и способ автоматизировать отправку сообщений из консоли:
sudo aptitude install sendxmpp
echo "admin@myhost password" > ~/.sendxmpprc
chmod a-rw,u+rw ~/.sendxmpprc
# сообщение пользователю
echo "автоматическая рассылка"|sendxmpp veter@nnov
# рассылка всем онлайн-пользователям
echo "автоматическая рассылка"|sendxmpp nnov/announce/online
# сообщение в чат от несуществующего пользователя robot
echo -e "сообщение\n"|sendxmpp  -r robot --chatroom talks@conference.nnov
# просто строка с форматированием
echo "строка"|sendxmpp conference.nnov
Отправка сообщения занимает порядка 1 с времени, этого во многих случаях вполне достаточно; заметим, что отправка через ejabberdctl ничуть не быстрее.

Комментариев нет:


(C) Alexey Pechnikov aka MBG, mobigroup.ru