Дружим между собой 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.
Проверяем работу сервиса…
Надеюсь, чем-то помог. Если есть вопросы – пишите.