Связываем Active Directory, Asterisk и OpenFire

asterisk openfire AD

Alexcr

Дружим между собой Active Directory, сервер IP телефонии Asterisk и Jabber-сервер OpenFire.



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

Диспозиция:


Есть контроллер домена dc.steepler.local (10.10.8.200). Домен, соответственно — steepler.local. На сервере заведены пользователи, побиты на отделы и т.д. Что важно – у каждого пользователя в графе «телефон» прописан его номер на сервере Asterisk.

Есть установленный VoIP сервер Asterisk (10.10.8.98). Все пользователи подключены по SIP, соответственно устройства, в понимании Asterisk, у них вида SIP/XXX (где XXX – добавочный номер). На момент написания сервер был давно установлен и уже подвергнут серьезным настройкам. Именно поэтому работа не была доведена до логического конца – закономерным финалом была бы автоматическая генерация SIP конфига оконечных устройств и плана набора. К сожалению, я побоялся, что придется серьезно переписывать существующую конфигурацию, да и в любом случае – автоматическая генерация того плана набора, который существует сейчас в нашей организации и имеет множество интерактивных меню и прочих кастомизаций, не будет интересна читателю, перед которым стоит задача, в первую очередь, связать между собой сервера. Куда двигаться дальше — будет понятно. Написанные скрипты не сложны и имеют хороший задел для дальнейшей работы. 

Итак, есть Asterisk с установленным фронтендом FreePBX, что сильно усложнило задачу. Ибо FreePBX имеет обыкновение и необходимость переписывать поверх все конфигурационные файлы после внесения любых изменений в web интерфейсе. То есть, поменять конфиги из командной строки мы можем, но, как только поменяем что-то потом через веб-морду, конфиги будут затерты FreePBX’ом, как порядочным фронтендом. Конечно, создатели оболочки не полагались на свою гениальность и оставили возможность для тонкой настройки. Возможность эта реализуется через подгрузку дополнительных контекстов с суффиксом «-custom» (чего, в итоге, оказалось достаточно), либо при помощи “override” конфигов, которые жестко фиксируют необходимые вам изменения. Но, надо понимать, что то, что прописано в custom или override конфиге будет работать по-вашему, не взирая на веб-интерфейс FreePBX. Либо шашечки, либо ехать. То есть, что бы вы ни крутили там во фронтенде, что бы ни настраивали – если оно коснется кастомизированных настроек – работать будут настройки из файлов, а не фронтенда. Именно поэтому было потрачено много времени на трассировку плана набора, сгенерированного FreePBX – хотелось найти точку входа в такой процедуре, жесткая привязка которой не затронула бы дальнейшую работу. 

Есть, вернее, на момент написания статьи – не было Jabber сервера. Выбор, по необъяснимым причинам пал на OpenFire. На самом деле, причины просты – OpenFire позволяет организовать сквозную (Kerberos/GSSAPI/SASL) авторизацию пользователей. То бишь – пользователю не надо вводить ни логина ни пароля. Если он прошел доменную авторизацию при входе в windows – он наш клиент. При запуске клиент сам подставит пользователя, пошлет запрос на jabber-сервер, а тот, используя Kerberos, подтвердит или опровергнет подлинность запроса клиента. Не буду вдаваться в детали, для нас важно, что авторизация проходит прозрачно для клиента даже в том случае, когда в домене существует политика периодической смены пароля. Не стоит генерировать истории про «тупых юзеров», которые пишут жалобы начальству о том, что у них что-то перестало работать по тому, что они забыли поменять пароль. Надо просто делать так, чтобы им было негде тупить. 
На OpenFire можно установить штатный плагин Asterisk-IM для связи с Asterisk. Он позволяет динамично отслеживать статусы пользователей, звонить на IP телефоны, отправлять уведомления. К сожалению, из коробки автоматизация оставляет желать лучшего — не смотря на то, что возможна сквозная аутентификация и авторизация пользователей через AD и то, что в AD изначально предоставлена информация о рабочем телефоне пользователя, приходится вручную сопоставлять пользователей AD/Asterisk.
Итак. Jabber серверу быть OpenFire, зваться jbrgseveren01.steepler.local и работать по адресу 10.10.8.226.
Дальше я исхожу из того, что Linux у меня в виде CentOS5, Asterisk 1.8.2, а домен-контроллер Win2008. Хотя, это совершенно не принципиально, заработает и при других раскладах. Критична только версия Asterisk – поддержка jabber появилась только с ветки 1.6, да и скомпилирована PBX должна быть с его поддержкой.
 
Задачи:


Нужно установить Jabber сервер, настроить его так, чтобы необходимых пользователей он брал из доменной группы IM. Нужно тем или иным способом автоматизировать сопоставление информации о пользователях домена, пользователях jabber, абонентах Asterisk. 
Как все работает из коробки? (Или подводные камни)
OpenFire обращается по ldap протоколу к домен-контроллеру и получает от него информацию о пользователях, которым разрешено пользоваться jabber. 
Дальше нам нужно поставить плагин Asterisk-IM (два клика мышкой в веб-морде OpenFire). В плагине нам нужно прописать сервер Asterisk. И, oh-shit, руками, заново прописать всех пользователей в плане – логин — номер телефона — его абонентское устройство в понимании Asterisk. После этого заработает функционал плагина – при помощи родного клиента OpenFire под названием Spark пользователи смогут звонить друг — другу используя существующие телефоны просто кликая по списку контактов. То есть – нахожу контакт, щелкаю правой мышкой «позвонить», у меня на столе начинает звонить телефон, я снимаю трубку и, тотчас же, телефон начинает звонить у контакта. Более того, когда кто-то говорит по телефону, его статус в контакт-листе меняется на соответствующий. Удобно. Но, работу по прописыванию пользователей надо автоматизировать. 
Едем дальше – есть желание присылать в jabber уведомления о пропущенных звонках. Одно дело, когда на телефоне моргает лампа и надо лезть в меню, смотреть, кто звонил и — совсем другое дело, когда тебя дожидается сообщение с точным временем и координатами звонивших. Из коробки не реализуется никак. То есть Asterisk, конечно легко цепляется к OpenFire серверу в режиме клиента или компонента, но вся дальнейшая задача по обработке и отправке сообщений ложится на ваши плечи. Конечно не сама работа по отправке, а объяснительная работа с Asterisk ;-) Самым тупым решением тут является обработка каждого номера по отдельности. Но, если номеров больше пяти, то оно нам не подходит. Плюс существует вероятность миграции пользователей между телефонами, добавление новых, удаление старых. К тому же, не забываем про FreePBX. Если мы жестко определим правила набора, то лишимся возможности пользоваться замечательным веб-интерфейсом. В общем, отказать в грубой форме. Не катит. Нам нужно найти точку входа в плане набора, написать свою процедуру, которая будет по номеру адресата искать соответствующего доменного пользователя и, в случае «недозвона» отправлять тому jabber сообщение – мол, звонил вам такого-то числа во столько-то такой-то абонент. 
 
Приступаем


Первое, что нам понадобится – пользователи. Создаем в домене двух пользователей. Один нам будет нужен для ldap аутентификации, другой для Kerberos. Первого я назвал openfire, второго xmpp-openfire. Дальше – сразу создаем группу для пользователей jabber (у меня она называется IM) и добавляем в нее необходимых пользователей. Проверяем, чтобы у всех пользователей, имеющих внутренний телефон и входящих в группу IM в поле «номер телефона», стоял именно внутренний номер абонента. 
Второе – прописываем в DNS наш будущий jabber сервер. Нам нужна и прямая и обратная зона. На самом jabber сервере настраиваем имя хоста – прописываем в /etc/hosts:
 
127.0.0.1 localhost.localdomain localhost
10.10.8.226 jbrgseveren01.steepler.local  jbrgseveren01


Проверяем со всем сторон nslookup’ом – все должно правильно ресолвиться. Да, имя хоста в маленьком регистре. Это важно. 
Третье — ставим OpenFire по инструкции — http://www.igniterealtime.org/builds/openfire/docs/latest/documentation/ldap-guide.html
Там все просто, подводных камней нет. Максимум сложностей – формирование грамотных фильтров в ldap запросе. Подключаемся через первого пользователя. На выходе получите функционирующий сервер со сквозной авторизацией через AD. Можно подключать клиентов и работать. Но, наша задача – SSO: Single Sign On. Нам надо, чтобы пользователю не надо было думать о своем логине и пароле для клиента. 
Приступаем к настройке Kerberos. Тут используем второго созданного нами юзера. Все описано тут —http://community.igniterealtime.org/docs/DOC-1060
Все чуть сложнее, есть подводные камни. Главное – пОмНиТе пРО РегиСтры – все имеет значение. Пишите как в мануале – где заглавными, там заглавными, где строчными, там строчными. Не забудьте ввести сервер в домен и проверить факт введения. Это важно!!!
Да, keytab я создавал на контроллере домена – у меня все заработало. Средствами java я не пользовался. 
Ставьте Spark – родной клиент OpenFire, проверяйте – если SSO работает – хорошо. Если нет, надо разбираться – ищите, пишите, посмотрим. 
Идем на сервер Asterisk (не забывайте – у меня стоит FreePbx, поэтому даю названия файлов относительно его схемы; в случае голого Asterisk все будет чуть проще) и прописываем в manager_custom.conf пользователя OpenFire:
[openfire]
secret = XXXX
deny=0.0.0.0/0.0.0.0
permit=10.10.8.226/255.255.255.0
read = all
write = all

Теперь ставим плагин Asterisk-IM. Он есть в веб-интерфейсе OpenFire, в закладке с доступными плагинами. Прописываем на появившейся вкладке Asterisk-IM наш VoIP сервер:
Server Name: AsteriskGSeveren01
ServerAddress: 10.10.8.98
Port: 5038 
Username: openfire
Password: XXXX

Настала очередь прописывать пользователей руками… Надо идти на вкладку Phone Mappings и писать, писать, писать. Ограничимся парой пользователей, проверим работу. В контакт листе Spark, при щелчке правой кнопкой по имени пользователя, должна появиться опция call. Выбираем – должен зазвонить наш аппарат, при подъеме трубки – аппарат абонента. 
Если все работает – хорошо. Если нет, надо разбираться – ищите, пишите, посмотрим. 
Теперь начинается то, на что было потрачено основное время.
Надо объяснить Asterisk-IM, что бывают доменные пользователи и что в Active Directory есть вся необходимая информация. 
Напрямую – никак. Плагин старый, поддержка его прекращена – жрите, что есть. А есть у нас MySQL база данных, в которой плагин хранит свою информацию. Самым простым способом было бы вынимать из базы информацию по пользователям OpenFire и подсовывать ее Asterisk-IM. Но, так как аутентификация у нас сквозная, то в своей базе OpenFire ничего не хранит – тащит напрямую с домен-контроллера. 
Хорошо. Пишем скрипт, который будет цепляться к AD по ldap протоколу (пользователь у нас уже есть), тащить информацию по пользователям домена, входящим в группу IM и вынимать поля, содержащие полное имя, логин и номер телефона. Потом формируем SQL инъекцию и запихиваем в прямо базу Asterisk-IM. Тупой костыль, но работает.
Скриптов вышло два – один я нашел готовый тут. Написан на perl – он тянет инфу из домена и, кстати, в состоянии выводить готовый sip.conf после минимальной правки. Второй, на баше – вызывает первый, препарирует его вывод (да я знаю, что я извращенец, но раз задача кем-то уже решена, не надо городить), формирует SQL инъекции и пихает все в БД.
Несного измененный users-from-AD.pl
#!/usr/bin/perl
# users.pl v1.1
#
# Script to generate asterisk 'users.conf' file from Active Directory (LADP) on users which contains 'phone' attribute
#
# Using:
# 1. Print users to STDOUT:
# users.pl
#
# 2. Print users to file:
# users.pl users_custom.conf

use strict;
use warnings;
use Net::LDAP;
use Lingua::Translit;

######################
### BEGIN SETTINGS ###
######################
my $debug = 0;
my $warning = 0;

# name of Domain
my $AD="steepler.local";

# Domain name in format AD
# for example  mydomain.ru
my $ADDC="DC=steepler,DC=local";

# user in Active directory
# example: "CN=asterisk,CN=Users,$ADDC"
my $ADUserBind="cn=openfire, cn=users, dc=steepler, dc=local";
my $ADpass="XXXXXXX";

# base search tree
# example "OU=Users,$ADDC"
my $ADUsersSearchBase="$ADDC";

# Field in active directory where telephone number, display name, phone stored
# "telephonenumber", "displayname", "mail"
my $ADfieldTelephone="telephonenumber";
my $ADfieldFullName="displayname";
my $ADfieldMail="mail";
my $ADfieldUser="samaccountname";

my $ADfieldGroup="memberOf";
my $ADSearchGroup="CN=IM,CN=Users,DC=steepler,DC=local";


# You need to create a dialplan in your asterisk server;
my $dialplan="office";

# default settings
my $user_static =
"context = $dialplan
call-limit = 100
type = friend
registersip = no
host = dynamic
callgroup = 1
threewaycalling = no
hasdirectory = no
callwaiting = no
hasmanager = no
hasagent = no
hassip = yes
hasiax = yes
nat=yes
qualify=yes
dtmfmode = rfc2833
insecure = no
pickupgroup = 1
autoprov = no
label =
macaddress =
linenumber = 1
LINEKEYS = 1
callcounter = yes
disallow = all
allow = ulaw,alaw,iLBC,h263,h263p
";
#######################
### END OF SETTINGS ###
#######################

my $ldap;

# get array DNS names of AD controllers
my $dig = "dig -t srv _ldap._tcp.$AD" . '| grep -v "^;\|^$" | grep SRV | awk "{print \$8}"';
my @adControllers = `$dig`;
# try connect to AD controllers
foreach my $controller (@adControllers){
    $controller =~ s/\n//;
    #INITIALIZING
    $ldap = Net::LDAP->new ( $controller ) or next;
    print STDERR "Connected to AD controller: $controller\n" if $debug > 0;
    last;
}
die "$@" unless $ldap;

my $mesg = $ldap->bind ( dn=>$ADUserBind, password =>$ADpass);

#PROCESSING - Displaying SEARCH Results
# Accessing the data as if in a structure
#  i.e. Using the "as_struct"  method
my $ldapUsers = LDAPsearch (
    $ADUsersSearchBase,
    "$ADfieldGroup=$ADSearchGroup",
    [ $ADfieldFullName, $ADfieldTelephone, $ADfieldMail, $ADfieldUser ]
)->as_struct;

# translit RUS module.
# GOST 7.79 RUS, reversible, GOST 7.79:2000 (table B), Cyrillic to Latin, Russian
my $tr = new Lingua::Translit("GOST 7.79 RUS");

my %hashPhones = ();
my $phones = \%hashPhones;

my @out;

while ( my ($distinguishedName, $attrs) = each(%$ldapUsers) ) {
    # if not exist phone or name - skipping
    my $attrPhone = $attrs->{ "$ADfieldTelephone" } || next;
    my $attrUser = $attrs->{ "$ADfieldUser" } || next;
    my $attrName = $attrs->{ "$ADfieldFullName" } || next;
    my $encName = $tr->translit("@$attrName");
    my $attrMail = $attrs->{ "$ADfieldMail" } || [""];


    # check for duplicates phone number
    if ( $phones -> {"@$attrPhone"} ){
        my $currUser = "@$attrName";
        my $existUser = $phones -> {"@$attrPhone"};
        print STDERR "@$attrPhone alredy exist! Exist:'$existUser' Current:'$currUser'... skipping - '[@$attrPhone] $currUser'\n" if $warning;
        next;
    } else {
        $phones -> {"@$attrPhone"} = "@$attrName";
    }

    # password for SID = (telephonenumber without first digit) + 1
    # example: phone=6232 pass=233
    #$phsecret =sprintf("%03d",( substr("@$attrVal",1,100)+1));
    my $phsecret = "@$attrPhone";
    my $lcuser = "@$attrUser";
    $lcuser = lc($lcuser);
    push (@out,
        "@$attrPhone "
        . "$lcuser "
        . "$encName\n"
    );
}       # End of that DN

# print to file
if (@ARGV){
    open FILE, "> $ARGV[0]" or die "Error create file '$ARGV[0]': $!";
    print STDOUT "Printing to file '$ARGV[0]'";
    print FILE @out;
    close FILE;
    print STDOUT " ...done!\n";
}
# print to STDOUT
else{
    print @out;
}

exit 0;

#OPERATION - Generating a SEARCH
#$base, $searchString, $attrsArray
sub LDAPsearch
{
    my ($base, $searchString, $attrs) = @_;
    my $ret = $ldap->search ( base    => $base,
                              scope   => "sub",
                                  filter  => $searchString,
                              attrs   => $attrs
                                );
    LDAPerror("LDAPsearch", $ret) && die if( $ret->code );
    return $ret;
}

sub LDAPerror
{
    my ($from, $mesg) = @_;
    my $err = "[$from] - error"
        ."\nCode: " . $mesg->code
        ."\nError: " . $mesg->error . " (" . $mesg->error_name . ")"
        ."\nDescripton: " . $mesg->error_desc . ". " . $mesg->error_text;
    print STDERR $err if $warning;
}

А вот второй на баше:
phone-bindings-update-from-AD.sh:
 
#!/bin/bash
TIMESTAMP=`/bin/date +%d%m%y%k%M%S`
BACKUPDIR=/opt/openfire/bin/phone-mappings/backup
BINDIR=/opt/openfire/bin
WORKDIR=$BINDIR/phone-mappings
SCRIPTNAMEDEVICE=$WORKDIR/phone-bindings-from-AD-device.sql
SCRIPTNAMEUSER=$WORKDIR/phone-bindings-from-AD-user.sql
SCRIPT=$WORKDIR/$SCRIPTNAME
PERLSCRIPT=$BINDIR/users-from-AD.pl
DEVICETPLHEAD=$WORKDIR/phoneDevice.tplhead
DEVICEINJ=$WORKDIR/phoneDevice.inj
DEVICETPLFOOT=$WORKDIR/phoneDevice.tplfoot
USERTPLHEAD=$WORKDIR/phoneUser.tplhead
USERINJ=$WORKDIR/phoneUser.inj
USERTPLFOOT=$WORKDIR/phoneUser.tplfoot

#backuping tables
mysqldump -uXXXXXXX -pXXXXXXX openfire phoneDevice > $BACKUPDIR/phoneDevice-$TIMESTAMP.sql
mysqldump -uXXXXXXX -XXXXXXX openfire phoneUser > $BACKUPDIR/phoneUser-$TIMESTAMP.sql


# Clearing injections
cat /dev/null > $DEVICEINJ
cat /dev/null > $USERINJ


# finding current Asterisk server ID in openfire DB
serverID=`mysql -Bse "SELECT serverID  FROM openfire.phoneServer;" -uXXXX -pXXXX`
# resetting counters
counter=0
counter2=0

#executing perl script to retrieve current phone numbers from AD
for i in `$PERLSCRIPT`; do

    counter=`expr $counter + 1`
    binder[$counter]=$i

done

maxcount=$counter
counter=1

while [ "$counter" -lt "$maxcount" ]
do

#  deviding array into two with extensions and jids

    counter2=`expr $counter2 + 1`

    extension=${binder[$counter]}
    counter=`expr $counter + 1`
    username=${binder[$counter]}
    counter=`expr $counter + 1`
    callerID=${binder[$counter]}
    counter=`expr $counter + 1`
    callerID=$callerID\ ${binder[$counter]}
    counter=`expr $counter + 1`
    deviceID=$counter2
    userID=$counter2

# Creating phoneDevice injection
    echo INSERT INTO \`phoneDevice\` VALUES\($deviceID,\'SIP/$extension\',\'$extension\',\'$callerID\',1,$userID,$serverID\)\; >> $DEVICEINJ
# Creating phoneUser injection
    echo INSERT INTO \`phoneUser\` VALUES\($userID,\'$username\'\)\; >> $USERINJ
done

# Compile complete injections

cat $DEVICETPLHEAD > $SCRIPTNAMEDEVICE
cat $DEVICEINJ >> $SCRIPTNAMEDEVICE
cat $DEVICETPLFOOT >> $SCRIPTNAMEDEVICE


# Compile complete injections

cat $USERTPLHEAD > $SCRIPTNAMEUSER
cat $USERINJ >> $SCRIPTNAMEUSER
cat $USERTPLFOOT >> $SCRIPTNAMEUSER

# Injecting into tables

cat $SCRIPTNAMEDEVICE | mysql -uXXXXXXX -pXXXXXXX
cat $SCRIPTNAMEUSER | mysql -uXXXXXXX –pXXXXXXX

Как можно заметить, второй скрипт использует шаблоны для заголовка и футера инъекции. Шаблоны получены путем выполнения mysqldump к существующим таблицам и последующей обрезки результата. Собственно, вот шаблоны:

phoneUser.tplhead:
 
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

USE openfire;

DROP TABLE IF EXISTS `phoneUser`;
SET @saved_cs_client     = @@character_set_client;
SET character_set_client = utf8;
CREATE TABLE phoneUser (
userID bigint not null,
username varchar(255) not null unique,
primary key (userID)
);

SET character_set_client = @saved_cs_client;

LOCK TABLES `phoneUser` WRITE;
/*!40000 ALTER TABLE `phoneUser` DISABLE KEYS */;


phoneUser.tplfoot:
/*!40000 ALTER TABLE `phoneUser` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


phoneDevice.tplhead:
 
/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8 */;
/*!40103 SET @OLD_TIME_ZONE=@@TIME_ZONE */;
/*!40103 SET TIME_ZONE='+00:00' */;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;

USE openfire;

DROP TABLE IF EXISTS `phoneDevice`;
SET @saved_cs_client     = @@character_set_client;
SET character_set_client = utf8;
CREATE TABLE `phoneDevice` (
  `deviceID` bigint(20) NOT NULL,
  `device` varchar(255) NOT NULL,
  `extension` varchar(255) NOT NULL,
  `callerId` varchar(255) default NULL,
  `isPrimary` int(11) NOT NULL,
  `userID` bigint(20) default NULL,
  `serverID` bigint(20) NOT NULL,
  PRIMARY KEY  (`deviceID`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
SET character_set_client = @saved_cs_client;

LOCK TABLES `phoneDevice` WRITE;
/*!40000 ALTER TABLE `phoneDevice` DISABLE KEYS */;


phoneDevice.tplfoot:
 
/*!40000 ALTER TABLE `phoneDevice` ENABLE KEYS */;
UNLOCK TABLES;
/*!40103 SET TIME_ZONE=@OLD_TIME_ZONE */;

/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;


Скрипт запихиваем в крон, выполняем и обнаруживаем появившуюся привязку пользователей к телефонам в закладке Phone Bindings плагина Asterisk-IM. Полдела сделано. 

Следующая задача — отправка сообщений о пропущенных вызовах. Если у вас стоит TrixBox или FreePBX – делайте, как я – все скорее всего заработает. Если голый Asterisk – все в ваших руках, импровизируйте, вам доступно многое. Я, даже, отчасти завидую ))) 

Для начала необходимо выполнить авторизацию через ssh по ключам – мы будем использовать scp и дистанционное выполнение процедуры. ssh-keygen вам поможет, мануалов в сети достаточно, повторяться не буду. Скрипт на jabber сервере будет лезть в базу данных, которую мы правили предыдущим скриптом (да, их можно объединить в один, но я решал задачи не одновременно, да и в целом – unix way говорит о правильности разбиения задач на составляющие), вынимать логин пользователя, номер телефона. Формировать jID. Дальше мы готовим исполняемый скрипт, который будет вносить информацию во встроенную базу Asterisk, переносим его на сервер Asterisk и запускаем.

Вот что получилось:
phone-mapping-request.sh:
#!/bin/bash
WORKDIR=/opt/openfire/bin/phone-mappings
SCRIPTNAME=phone-mappings-script.sh
SCRIPT=$WORKDIR/$SCRIPTNAME
SERVER=jbrgseveren01.steepler.local
#asterisk USER@HOST:/PathToFile
ASTERISK=root@10.10.8.98
#asterisk /PathToFile
RPATH=/etc/asterisk/scripts

counter=0
counter2=0

#clearing script file
cat /dev/null > $SCRIPT

#perform MYSQL request for mappings
for i in `mysql -Bse "SELECT extension,username  FROM openfire.phoneDevice JOIN openfire.phoneUser ON openfire.phoneUser.UserID=openfire.phoneDevice.UserID;" -uXXXX -pXXXX`; do
    counter=`expr $counter + 1`
    mapper[$counter]=$i
done

maxcount=$counter
counter=1

while [ "$counter" -lt "$maxcount" ]
do

#  deviding array into two with extensions and jids

    counter2=`expr $counter2 + 1`
    extension[$counter2]=${mapper[$counter]}
    counter=`expr $counter + 1`
    jid[$counter2]=${mapper[$counter]}
    counter=`expr $counter + 1`

# forming asterisk script

    outstringdel="asterisk -rvx \"database del AMPUSER "${extension[$counter2]}"/jid\""
    outstringadd="asterisk -rvx \"database put AMPUSER "${extension[$counter2]}"/jid "${jid[$counter2]}"@"$SERVER"\""
    echo $outstringdel >> $SCRIPT
    echo $outstringadd >> $SCRIPT

done

# moving scrip to asterisk host

chmod 755 $SCRIPT
scp $SCRIPT $ASTERISK:$RPATH

# run script
ssh $ASTERISK $RPATH/$SCRIPTNAME


Дело за малым – объяснить Asterisk, что с этим делать. Тут было потрачено огромное количество времени на нахождение точки входа. Если заработает как у меня – прекрасно. Если нет – даю наводку. Астериск, в случае включения разных инструкций на одинаковое условие (то есть в диалплане написано одно действие на условие, во включении из подгружаемого контекста — другое) берет за инструкцию то, которое было получено первым. Последующие тупо игнорируются. Я к тому, что если вы написали какую-то функцию, вставили ее, а результата ноль – делайте dialplan show и смотрите, где это условие в этой ветке контекста со всеми include встречается раньше вашего. 
В моем случае оказалось достаточным добавить в extensions_custom.conf:
 
[from-internal-noxfer-custom]
; Missed calls Jabber notification
exten => h,1,Macro(XMPPSend,)
exten => h,n,Macro(hangupcall)


[macro-XMPPSend]
; Missed calls Jabber notification
exten => s,1,GotoIf($["foo${DB(AMPUSER/${THISDIAL:4}/jid)}" = "foo"]?5:2)
exten => s,n,Set(JID=${DB(AMPUSER/${THISDIAL:4}/jid)})
exten => s,n,Jabbersend(asterisk-jabber,${JID},${STRFTIME(${EPOCH},,%d/%m/%Y-%H:%M:%S)} - Пропущенный вызов на номер ${THISDIAL:4} от ${CALLERID(name)}, номер ${CALLERID(num)})
exten => s,n,MacroExit()
exten => s,n,Noop(No Jabber ID provided for target extension - ${THISDIAL:4})
exten => s,n,MacroExit()


И, прописать Asterisk как компонент OpenFire:
На стороне Asterisk:
Jabber.conf:
[general]
debug=no                              ;;Turn on debugging by default.
;autoprune=yes                          ;;Auto remove users from buddy list.
;autoregister=yes                       ;;Auto register users from buddy list.

[asterisk-jabber]                          ;;label
type=component                             ;;Client or Component connection
serverhost=jbrgseveren01.steepler.local                     ;;Route to server
username=asterisk                        ;;Username with optional roster.
secret=XXXX                       ;;Password
port=5275                               ;;Port to use defaults to 5222



На стороне OpenFire идем в Server -> Server Settings -> External Components Settings
Включаем Service Enabled. При желании добавляем asterisk в whitelist.

Проверяем работу сервиса…

Надеюсь, чем-то помог. Если есть вопросы – пишите.