Search
Write a publication
Pull to refresh

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

Reading time6 min
Views9.4K


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


Чтобы не передавать наш пароль в открытом виде, нужно включить SSL шифрование. Для этого мы укажем в настройках, для каких доменов следует использовать ssl сертификат. Следует заметить, что не следует включать опцию sslcontext=, которую вы можете увидеть в дефолтном конфиге, так как в этом случае Yate будет ожидать от клиента изначально шифрованное соединение, тогда как jabber клиенты обычно используют двух-этапную процедуру StartTLS.

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.
  • Плохая: YATE не умеет MUC (Multi User Chat).


Если что-то непонятно, или не работает, задавайте вопросы, постараюсь ответить.
Tags:
Hubs:
Total votes 27: ↑26 and ↓1+25
Comments3

Articles