YATE в качестве jabber сервера

от автора

YATE является во многих отношениях уникальным телефонном сервером. Он понимает SIP-T, считается лучшим H323-SIP конвертером, а также поддерживает большую часть семейства протоколов ОКС-7 (SS7). И все это доступно под GPL. С другой стороны, проблемой является недостаточная документированность проекта.

Но я хочу рассказать о другом. Кролики — это не только ценный мех, но и Yate может служить также jabber сервером. Забавно, что Yate не указан в списке xmpp.org/xmpp-software/servers/, хотя поддержка jabber сервера появилась в нем еще в 2010 году.

Трудно сказать, зачем может понадобиться использовать Yate для джаббера, если есть ejabberd, Openfire, Prosody и Tigase. Этот вопрос — за рамками статьи. Я хочу лишь познакомить вас с еще одним вариантом.

Итак, добро пожаловать под cut. (Осторожно, много букв!) Заодно расскажу, как прикрутить авторизацию в Active Directory.

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

Указываем основные параметры jabber сервера: домен, слушающие сокеты, отменяем digest авторизацию (необходимо для передачи пароля в исходном виде для авторизации в AD).

Внимание! Опция c2s_plainauthonly доступна только в svn, в версии 4.3 ее не было.

jabberserver.conf

[general] domains=mydomain.org c2s_plainauthonly=yes; force text password for LDAP auth  [listener s2s] enable=yes type=s2s port=5269  [listener c2s] enable=yes type=c2s port=5222 

Yate умеет использовать использует StartTLS процедуру. Чтобы не передавать наш пароль в совсем уж открытом виде, нужно SSL шифрование. Для этого мы настраиваем openssl сертификат. Для перечисленных в опции «domains» доменов клиентам будет предложено перейти на SSL шифрование.
openssl.conf

[yate] certificate=yate.pem domains=mydomain.org 

Как генерировать сертификат, рассказывать не буду.

Настраиваем модуль авторизации и регистрации в базе данных
register.conf

[general] user.auth=yes user.register=yes user.unregister=yes engine.timer=yes  [default] account=yate  [user.auth] query=SELECT password FROM users WHERE username='${username}' AND password IS NOT NULL AND password<>'' result=password  [user.register] query=UPDATE users SET location='${data}', expires=CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second WHERE username='${username}'  [user.unregister] query=UPDATE users SET location=NULL,expires=NULL WHERE expires IS NOT NULL AND username='${username}'  [engine.timer] query=UPDATE users SET location=NULL,expires=NULL WHERE expires IS NOT NULL AND expires<=CURRENT_TIMESTAMP 

Структура базы данных для MySQL. Можно использовать и Postgres.

CREATE TABLE `offlinechat` (   `username` varchar(100) DEFAULT NULL,   `xml` text,   `time` int(11) NOT NULL,   KEY `username` (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;  CREATE TABLE `roster` (   `username` varchar(100) DEFAULT NULL,   `contact` varchar(100) DEFAULT NULL,   `name` varchar(100) DEFAULT NULL,   `groups` varchar(100) DEFAULT NULL,   `subscription` varchar(100) DEFAULT NULL,   UNIQUE KEY `uc` (`username`,`contact`),   KEY `username` (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8;  CREATE TABLE `users` (   `username` varchar(100) NOT NULL DEFAULT '',   `password` varchar(100) DEFAULT NULL,   `vcard` text,   `location` varchar(100) DEFAULT NULL,   `expires` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,   PRIMARY KEY (`username`) ) ENGINE=MyISAM DEFAULT CHARSET=utf8; 

Настраиваем коннектор в базе данных
mysqldb.conf

[yate] database=yate user=yate password=yatepass 

Настраиваем функцию vcard и оффлайн сообщений
jbfeatures.conf

[general] account=yate  [vcard] get=SELECT vcard FROM users WHERE username='${username}' set=UPDATE users SET vcard='${vcard}' WHERE username='${username}'  [offline_chat] get=SELECT * FROM offlinechat WHERE username='${username}' ORDER BY time add=INSERT INTO offlinechat (username,xml,time) VALUES ('${username}', '${xml}', ${time}) clear_user=DELETE FROM offlinechat WHERE username='${username}' 

Настраиваем ростер
subscription.conf

[general] account=yate  user_roster_load=SELECT users.username, roster.* FROM users LEFT OUTER JOIN roster ON users.username=roster.username WHERE users.username='${username}' user_roster_delete=DELETE FROM roster WHERE username='${username}' contact_load=SELECT * FROM roster WHERE username='${username}' AND contact='${contact}' contact_subscription_set=INSERT roster (username,contact,subscription) VALUES ('${username}','${contact}','${subscription}') ON DUPLICATE KEY UPDATE subscription='${subscription}' contact_set=INSERT roster (username,contact,name,groups) VALUES ('${username}','${contact}','${name}','${groups}') ON DUPLICATE KEY UPDATE name='${name}',groups='${groups}' contact_set_full=INSERT roster (username,contact,name,groups,subscription) VALUES ('${username}','${contact}','${name}','${groups}','${subscription}') ON DUPLICATE KEY UPDATE name='${name}',groups='${groups}',subscription='${subscription}' contact_delete=DELETE FROM roster WHERE username='${username}' AND contact='${contact}' 

Как видите, большая часть настройки сводится к прописыванию SQL запросов. Может показаться неудобным, но зато дает гибкость.

Теперь об авторизации через Active Directory
Во-первых, подправим register.conf, чтобы пользователи автоматически добавлялись в базу данных после успешной авторизации (а вот и гибкость пригодилась!).

[user.register] query=INSERT users (username,location,expires) VALUES ('${username}','${data}',CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second) ON DUPLICATE KEY UPDATE  location='${data}', expires=CURRENT_TIMESTAMP + INTERVAL ${expires}+300 second 

Во-вторых, добавим немного Yate магии. Ядро сервера представляет из себя диспетчер системных сообщений, которыми Engine обменивается с со стандартыми и внешними модулями (нет принципальных различий). Чтобы запустить внешний модуль на PHP, достаточно прописать его в конфиге. Наш скрипт jabber.php будет перехватывать системное сообщение user.auth (запрос на авторизацию), и обрабатывать его, обращаясь к AD. Сообщение перехватывается за счет приоритета обработчика = 40, тогда как остальные модули имеют приоритет 50 и выше, что является менее приоритетным.
extmodule.conf

[general] scripts_dir=/etc/yate/  [scripts] jabber.php= 

Сам скрипт авторизации. Обратите внимание, он использует библиотеку «libyate.php», входящую в дистрибутив Yate, она должна быть доступна для скрипта, лучше всего скопировать ее в один с ним каталог. Если домен контроллер использует самоподписанный сертификат, то следует добавить в /etc/openldap/ldap.conf строку

TLS_REQCERT never  

jabber.php

#!/usr/bin/php -q <?php require_once("libyate.php"); $ad_host = 'ldaps://dc.mydomain.org'; $ad_domain = 'mydomain.org';  /* Always the first action to do */ Yate::Init();  /* Install a handler for the call routing message */ Yate::Install("user.auth",40);  function ad_auth($user, $password) {         global $ad_host, $ad_domain;         $con = ldap_connect($ad_host);         return ldap_bind($con, "$user@$ad_domain", $password) & true; } /* The main loop. We pick events and handle them */ for (;;) {     $ev=Yate::GetEvent();     /* If Yate disconnected us then exit cleanly */     if ($ev === false)         break;     /* Empty events are normal in non-blocking operation.        This is an opportunity to do idle tasks and check timers */     if ($ev === true) { //        Yate::Output("PHP event: empty");         continue;     }     /* If we reached here we should have a valid object */     switch ($ev->type) {         case "incoming":             switch ($ev->name) {                 case "user.auth":                     if (!isset($ev->params["digest-uri"])) {                         $username = $ev->params["username"];                         $username = substr($username,0,strpos($username,'@'));                         $password = isset($ev->params["response"]) ? $ev->params["response"] : $ev->params["password"];                         $auth = ad_auth($username, $password);                         if ($auth) {                             $ev->retval = $password;                             $ev->handled = true;                         }                     }                     break;             }             $ev->Acknowledge();             break;         case "installed":             Yate::Output("PHP Installed: " . $ev->name);             break;         case "uninstalled":             Yate::Output("PHP Uninstalled: " . $ev->name);             break;         default:             Yate::Output("PHP Event: " . $ev->type);     } }  Yate::Output("PHP: bye!"); /* vi: set ts=8 sw=4 sts=4 noet: */ ?> 

Как говорит Paul Chitescu, главный разработчик Yate, — must be ready.

И напоследок у меня две новости, хорошая и плохая:

  • Хорошая: YATE умеет jingle (голос), то есть клиенты вашего jabber сервера могут звонить друг другу голосом (например, используя Psi). Не могу сказать, что с этим все гладко, но при желании можно добиться работоспособности. Возможно, напишу об этом позже. (Речь идет именно о совмещении функций jabber и jingle сервера. По отдельности все работает замечательно).
  • Плохая: YATE не умеет MUC (Multi User Chat).

Если что-то непонятно, или не работает, задавайте вопросы, постараюсь ответить.

ссылка на оригинал статьи http://habrahabr.ru/post/166235/


Комментарии

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

Ваш адрес email не будет опубликован. Обязательные поля помечены *