Вступайте в группу Вконтакте, чтобы быть в курсе предстоящих путешествий: https://vk.com/club96800544

Корпоративный чат

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

1. Связка с Active Directory – для входа в чат должны использоваться учетные записи AD + список контактов из AD;

2. Возможность отправки сообщений всем пользователям с указанием имени отправителя;

2. Ведение логов переписки пользователей.

До этого все пользовались net send.

Решил все сделать на Ubuntu Server и Jabber (xmpp). Во-первых, это бесплатно, во-вторых, при желании, можно настроить как угодно. Из вариантов Jabber-серверов сейчас актуальны Openfire и Ejabberd.

Openfire и Spark

Вначале попробовал связку сервер Openfire + клиент Spark, оба программных продукта выпускаются одним разработчиком. Написаны на Java, как следствие, требовательность к ресурсам и на стороне сервера, и на стороне клиента. У Openfire удобная админка через веб-интерфейс, можно легко расширить функционал за счет установки плагинов. Я использовал базу данных MySQL и были небольшие проблемы с кодировкой, также были проблемы с привязкой к Active Directory. Openfire подгружал контакты из AD, но почему-то новые пользователи, добавленные в AD, не появлялись даже после перезагрузки сервера. Был ещё ряд моментов, которые мне не понравились, поэтому решил попробовать Ejabberd.

Настройка Ejabberd

Настройка Ejabberd сложнее Openfire, но на мой взгляд, более удобная. Веб-интерфейсом в случае с Ejabberd пользоваться нет смысла, самый удобный вариант – это править файл конфига ejabberd.cfg напрямую. Я потратил довольно много времени на изучение документации и различных вариантов конфигов, которые попадались в интернете. Ни один из них полностью не подошел.

Первая проблема при настройке Ejabberd была в том, что пользователи AD не могли зайти под собой. Все решилось вот так:

        {auth_method, ldap}.
        {ldap_servers, [“nameserver.yourdomain.local”]}.
        {ldap_uids, [{“userPrincipalName”, “%u@yourdomain.local”},{“uid”}]}.
        {ldap_port, 389}.
        {ldap_base, “dc=yourdomain, dc=local”}.
        {ldap_rootdn, “userdomain@yourdomain.local”}.
        {ldap_password, “passworduserdomain”}.
        {ldap_filter, “(objectClass=user)”}.

Нужно заменить на свои:

nameserver – DNS имя сервера,

yourdomain – имя домена,

userdomain – имя пользователя домена,

passworduserdomain – пароль от userdomain

Вторая проблема в том, что не подгружались контакты из Active Directory. Заработало вот с этой настройкой:

{mod_roster, []},
        {mod_shared_roster, []},
        {mod_shared_roster_ldap,
                [
                {ldap_rfilter, “(objectClass=user)”},
                {ldap_groupattr, “department”},
                {ldap_memberattr, “sAMAccountName”},
                {ldap_filter, “(objectClass=user)”},
                {ldap_userdesc, “displayName”}
                ]
        },

В данном случае заработала сортировка по отделам (нужно заполнить поле department в AD), но мне нужен был полный список пользователей без сортировки, поэтому я отсортировал по полю город (оно у всех одинаково).

{ldap_groupattr, “l”},

Третья проблема была в том, что часть пользователей не могла зайти в чат и их не было в общем списке. Решилось все добавлением нужных пользователей в созданную группу Jabber в AD.

Оставалось организовать запись логов служебной переписки пользователей, для этого есть модуль mod_log_chat, который не входит в набор стандартных. Его надо скачать (в гугле набрать mod_log_chat.erl) и установить:

erlc mod_log_chat.erl

Скорей всего будет ругаться, на то что не хватает нескольких файлов, их нужно скопировать к mod_log_chat.erl из /usr/lib/ejabberd/ebin/ и /usr/lib/ejabberd/include/.

После того, как из mod_log_chat.erl получится mod_log_chat.beam, этот файл надо переместить к остальным модулям в /usr/lib/ejabberd/ebin/.

В ejabberd.cfg в раздел модулей добавляем:

{mod_log_chat, [{path, “/var/log/ejabberd/chat”}, {format, html}]}, 

Для удобства просмотра можно настроить apache и разбить логи по дням. Про настройку apache писать не буду, там все просто. Для того, чтобы раскидать файлы по дате использую скрипт (нашел где-то в интернете), который запускается с помощью cron.

#/bin/bash

SRC_DIR=”/var/log/ejabberd/chat”

DST_DIR=”/var/www/chat”

for F in ${SRC_DIR}/*; do SUB=$(date -d”$(stat -c%y “${F}”)” +%Y%m%d)

mkdir -p “${DST_DIR}/${SUB}”

cp “${F}” “${DST_DIR}/${SUB}/”

done

В качестве джаббер-клиента для корпоративного чата использую Psi+, остановился на нем, только после того, как перепробовал все остальные. Большая часть клиентов не могла подключится к серверу Ejabberd из-за того, что созданный сертификат не подписан, ну и небольшое количество оставшихся клиентов, которые все-таки подключались, имели один важный недостаток – они хранили пароли в открытом виде, а ведь это, по сути, пароли от учетки в Active Directory. С Psi+ все в порядке, админ написал скрипт, который разворачивал готовые настройки на компьютере пользователя и оставалось только ввести пароль.

Особая проблема была в отправке сообщений всем, так, чтобы был виден отправитель. Я не нашел такой возможности. В jabber можно отправлять сообщения всем, для этого у Ejabberd есть пара модулей.

        {mod_adhoc,    []},
        {mod_announce, [{access, announce}]},
        {mod_caps,     []},

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

После того, как корпоративный чат заработал, обнаружилась ещё одна проблема. После того, как количество пользователей становится больше 30 человек, стали происходить периодические перезагрузки ejabberd. И дальнейшие попытки пользователей подключиться были безуспешны, пока их количество не уменьшится. Решил эту проблему внесением в конфиг shaper, которые режут траффик пользователей по скорости. И это помогло.

{shaper, normal, {maxrate, 1000}}.
{shaper, fast, {maxrate, 50000}}.

%% For C2S connections, all users except admins use “normal” shaper
{access, c2s_shaper, [{none, admin},
{normal, all}]}.

%% All S2S connections use “fast” shaper
{access, s2s_shaper, [{fast, all}]}.

Из оставшихся проблем:

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

Не работает поиск, хотя все поля для поиска отображаются нормально, результат всегда 0. Но для Psi+ это не критично, так как достаточно в набрать имя или фамилию пользователя и он фильтрует весь список пользователей.

Ещё один неприятный момент – необходимость перезагрузки сервера каждый раз, как в AD добавляется новый пользователь, но мне кажется это уже никак не исправишь.

Но в целом я и все остальные довольны корпоративным чатом.

Рабочий ejabberd.cfg:

override_global.
override_local.
override_acls.

{loglevel, 4}.

{listen, [

{5222, ejabberd_c2s, [starttls, {certfile, “/etc/ejabberd/server.pem”}]},

{5269, ejabberd_s2s_in, []},

{5280, ejabberd_http, [web_admin]}

]}.

{s2s_use_starttls, true}.
{s2s_certfile, “/etc/ejabberd/server.pem”}.

{outgoing_s2s_port, 5269}.

{hosts, [“nameserver”]}.

{acl, admin, {user, “userdomain”, “nameserver”}}.
{acl, admin, {user, “userdomain2”, “nameserver”}}.

{acl, local, {user_regexp, “”}}.

{shaper, normal, {maxrate, 1000}}.
{shaper, fast, {maxrate, 50000}}.

{access, max_user_sessions, [{10, all}]}.

{access, max_user_offline_messages, [{25, admin}, {10, all}]}.

{access, local, [{allow, local}]}.

{access, c2s, [{deny, blocked},
{allow, all}]}.

{access, c2s_shaper, [{none, admin},
{normal, all}]}.

{access, s2s_shaper, [{fast, all}]}.

{access, announce, [{allow, admin}, {allow, local}]}.

{access, configure, [{allow, admin}]}.

{access, muc_admin, [{allow, admin}]}.

{access, muc_create, [{allow, local}]}.

{access, muc, [{allow, all}]}.

{access, pubsub_createnode, [{allow, local}]}.

{access, register, [{allow, all}]}.

{auth_method, ldap}.
{ldap_servers, [“nameserver.yourdomain.local”]}.
{ldap_uids, [{“userPrincipalName”, “%u@yourdomain.local”},{“uid”}]}.
{ldap_port, 389}.
{ldap_base, “dc=yourdomain, dc=local”}.
{ldap_rootdn, “userdomain@yourdomain.local”}.
{ldap_password, “passworduserdomain”}.
{ldap_filter, “(memberOf=*)”}.

{language, “ru”}.

{modules,
[
{mod_adhoc,    []},
{mod_announce, [{access, announce}]},
{mod_caps,     []},
{mod_configure,[]}, % requires mod_adhoc

{mod_log_chat, [{path, “/var/log/ejabberd/chat”}, {format, html}]},

{mod_offline, [{access_max_user_messages, max_user_offline_messages}]},

{mod_roster, []},

{mod_shared_roster, []},

{mod_shared_roster_ldap,
[
{ldap_rfilter, “(objectClass=user)”},
{ldap_groupattr, “department”},
{ldap_memberattr, “sAMAccountName”},
{ldap_filter, “(objectClass=user)”},
{ldap_userdesc, “displayName”}
]
},

{mod_stats,    []},
{mod_time,     []},
{mod_last, []},
{mod_disco, []},
{mod_vcard_ldap, [

{matches, 1000},
{search, true},
{allow_return_all, true},
{search_all_hosts, true},

{ldap_vcard_map,
[
{“NICKNAME”, “%s”, [“displayName”]},
{“GIVEN”, “%s”, [“givenName”]},
{“MIDDLE”, “%s”, [“initials”]},
{“FAMILY”, “%s”, [“sn”]},
{“FN”, “%s”, [“displayName”]},
{“EMAIL”, “%s”, [“mail”]},
{“ORGNAME”, “%s”, [“company”]},
{“ORGUNIT”, “%s”, [“department”]},
{“CTRY”, “%s”, [“c”]},
{“LOCALITY”, “%s”, [“l”]},
{“STREET”, “%s”, [“streetAddress”]},
{“REGION”, “%s”, [“st”]},
{“PCODE”, “%s”, [“postalCode”]},
{“TITLE”, “%s”, [“title”]},
{“URL”, “%s”, [“wWWHomePage”]},
{“DESC”, “%s”, [“description”]},
{“TEL”, “%s %s”, [“telephoneNumber”,”telephoneNumber”]}
]},

{ldap_search_fields,
[
{“User”, “%u@yourdomain.local”},
{“Full Name”, “displayName”},
{“Given Name”, “givenName”},
{“Middle Name”, “initials”},
{“Family Name”, “sn”},
{“Nickname”, “%u”},
{“Birthday”, “birthDay”},
{“Country”, “c”},
{“City”, “l”},
{“Email”, “mail”},
{“Organization Name”, “o”},
{“Organization Unit”, “ou”}
]},

{ldap_search_reported,
[
{“Full Name”, “FN”},
{“Given Name”, “FIRST”},
{“Middle Name”, “MIDDLE”},
{“Family Name”, “LAST”},
{“Nickname”, “NICKNAME”},
{“Birthday”, “BDAY”},
{“Country”, “CTRY”},
{“City”, “LOCALITY”},
{“Email”, “EMAIL”},
{“Organization Name”, “ORGNAME”},
{“Organization Unit”, “ORGUNIT”}
]}
]},

{mod_version,  []}

]}.

31 комментарий

Оставить комментарий
  1. В Уфе на работе как раз ejabberd + Spark и стояли.

    1. Я как всегда узнал, только после того как решил проблему 🙂
      Хотя интересно их конфиг посмотреть, это реально?

      1. Вряд ли, хотя могу попробовать написать админу.

        1. Буду благодарен )

  2. На будущее, в Openfire новые пользователи и не появляются при добавлении их в АД, потому что он связывается с АД в момент аутентификации (или вы думали, что Openfire кэширует пароли у себя?) пользователя. Схема проста, как две копейки:
    1. Создаем пользователя в АД
    2. Подключаемся им к Openfire
    3.PROFIT!
    В момент аутентификации Openfire кидает запрос к АД, если все удачно он смотрит свой кэш ростера и добавляет нового пользователя в ростер, чтобы он отобразился у всех нужно переподключиться к серверу, потому что ростер инициализируется только в момент подключения к серверу. Можно поиграться с размером кэша и временем его жизни.

    1. Спасибо за информацию, все так и есть, просто я понял это позже поработав с Ejabberd. 🙂

  3. Можно воспользаваться jabber хостингом например http://corpchat.ru/
    Цены весьма справедливые

    1. Есть бесплатные и надежные джаббер хостинги, только смысл внутреннюю переписку компании выносить наружу?

  4. а можно вопрос? у меня миранда не авторизуется с галкой “доменный логин”, если ее убираю и вбиваю вручную доменные логин и пароль – то пускает. все ок…

    Вот пытался отследить, как авторизует – первый вкл галкой “доменный логин” – судя по записям не передает пароль почему-то

    =INFO REPORT==== 2015-06-04 07:55:01 ===
    D(:ejabberd_receiver:320) : Received XML on stream = "bdmPC-BDM64"
    =INFO REPORT==== 2015-06-04 07:55:01 ===
    I(:ejabberd_c2s:559) : ({socket_state,gen_tcp,#Port,}) Failed legacy authentication for bdm@jabber.kuis.local/PC-BDM64

    =INFO REPORT==== 2015-06-04 07:55:01 ===
    D(:ejabberd_c2s:1553) : Send XML on stream = <<"bdmPC-BDM64">>

    а тут все удачно – вводил вручную доменные логин\пароль – проппустил

    =INFO REPORT==== 2015-06-04 07:55:03 ===
    D(:ejabberd_receiver:320) : Received XML on stream = "test111111PC-BDM64"
    =INFO REPORT==== 2015-06-04 07:55:03 ===
    I(:ejabberd_c2s:517) : ({socket_state,gen_tcp,#Port,}) Accepted legacy authentication for test@jabber.kuis.local/PC-BDM64 by ejabberd_auth_ldap

    1. А какой именно домен? Такой jabber.kuis.local или kuis.local?

      1. сам jabber настроен и запущен на jabber.kuis.local, а имя домена kuis.local

        1. Я думаю, причина в том, что когда стоит галочка Use Domain Login применяется учетная запись user@kuis.local, без неё user@jabber. Проверка пользователя идет через сервер Jabber, поэтому второе правильно.

          1. не понял. как вылечить? что исправить надо?
            если я укажу в настройках Миранды домен\сервер, не jabber, а kuis.local, то стучаться будет уже к др серваку, где на 5222 порту никто ничего не ждет)

  5. эх… теги все побились… хотя код вставлял сюда с

  6. и,кстати, когда ставлю галку “use domail login” – в поле “домен\сервер” – jabber, то выдает ошибку –
    Аутентификация не пройдена для mylogin@jabber

    1. У меня, с этой галочкой, по-другому ошибка выглядит, чтобы я не вводил в имя домена у меня появляется ошибка моего доменного пользователя. Что-то вроде: “Аутентификация не пройдена для mylogin”.
      Скорей всего у Миранды не правильный алгоритм работы с доменом и тут только разработчики могут помочь.
      Кстати если в место jabber написать имя контроллера домена, то ошибка не выдается. Какое-то время идет попытка подключиться и все.

      1. ну, так,если “вместо jabber написать имя контроллера домена” – то это физически др устройство, на котором нет джаббера. или не прав? так что даже джаббер не будет знать что и куда вы хотите подключить

        1. Конечно контроллер домена это другая машина. Я к тому, что в случае с контроллером домена ошибка авторизации не выходит.
          А в случае с Openfire в строке домен, что введено?

          1. ejabberd не поддерживает NTLM-аутентификацию.
            “use domail login” не работает
            есть какие-то внешние скрипты-костыли добавляющие данный функционал.

            1. Ejabberd поддерживает доменную аутентификацию, проблема с клиентом.

  7. и это не проблема миранды. я взял ту же миранду,что у меня прозрачно коннектится к Openfire на др сервере. там все ок.

  8. Привет.
    как в jabber можно отправлять сообщения всем? можно ли завести пользователя в общем ростере и обозвать его Рассылка? как? не нашел в интернете..

    можно где-нибудь почитать про твоего бота для жаббер сервера?

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

    2. ANT ответил вам? Завел все то же, но вот бота для подмены имени рассылающего пока не придумал, как реализовать 🙁

    3. В Psi+ есть раздел в меню “Обзор сервисов”, там отображаются доступные сервисы и если зайти туда, то можно увидеть, например “Разослать объявление всем пользователям” и т.п.

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

      Бот не мой, писал программист, который уже не работает. Попробую спросить у него, но сомневаюсь, что исходники остались.

      1. Да как отправлять-то уже разобрались, спасибо 🙂
        Смущает лишь то, что вместо имени разославшего это сообщение подставляется virtual_host сервера. С одной стороны, удобно (обезличенная рассылка от кого-то, кто обладает такой полнотой власти), с другой – иногда полезно бывает не от абстрактного “громовержца” оповещения слать, а от имени конкретного человека. Так что исходники этого бота были бы чрезвычайно полезно. Или контакты самого программиста, возможно, небольшое материальное вознаграждение поможет поискам 😉

        1. Лучше напрямую пообщайтесь: Semenovnv@asonycs.ru

  9. Было бы тоже интересно узнать про бота для рассылки. Сейчас отображается имя хоста, что не совсем удобно. Заранее благодарен за помощь.

    1. Написал ответ выше, постараюсь найти исходники или более подробное описание.

  10. ждем исходников)))

  11. Тарас сказал что весь код устаревший, не стоит его использовать

Добавить комментарий

Войти с помощью: 

Ваш e-mail не будет опубликован.

*