Openfire+Miranda+Asterisk+Active Directory+щепотка php,bash,C# или как звонить из Miranda, используя обычные телефоны

    image
    Заставить Miranda осуществлять звонки — несложно, используя плагины для SIP-телефонии, превращающие Miranda в софтфон,
    Однако, что же делать когда у пользователей нет ни гарнитур, ни микрофонов?
    А уж обучить пользователей работать с софт-фоном, встроенным с Miranda — страшный сон.

    Вариант один — использовать существующие телефонные аппараты (IP, аналог, цифра) для осуществления звонков из Miranda.

    Исходные данные:
    1. Цифровая мини АТС Panasonic TDE200, к которой подключены все телефоны
    2. Куча абонентов с разношерстными типами телефонов: IP, аналог, цифра, системные телефоны
    3. Рабочий XMPP-сервер на базе OpenFire
    4. Клиент Miranda для общения по протоколу XMPP (Jabber) — установлен у всех пользователей.

    Хотелки:
    1. Возможность совершать звонок, используя клиент Miranda.
    2. Звонок и разговор должен совершаться по обычным рабочим телефонам (IP, аналог, цифра).
    3. Информация о номерах телефонов должна браться из Active Directory.
    4. Прозрачность/простота использования для пользователей.
    5. При совершении звонка добавить оповещение по Jabber о звонящем.

    Кого заинтересовало вступление — добро пожаловать под кат. Ниже будет интересно.


    Предыстория


    Несколько лет назад, в поисках решения для корпоративного IM-сервиса в Интернете наткнулся на статью ИТшников из города Киров, про корпоративный IM-Сервис на OpenFire с прозрачной аутентификацией пользоваталей, где в роли клиента выступала Miranda, да не простая, а с возможностью инициализации удаленного подключения по RDP и VNC к компьютеру, того пользователя которого вы выбрали в контакт-листе, при этом Вам нет необходимости вводить DNS-имя компьютера пользователя, оно автоматически подставляется в адресное поле, и устанавливает соединение.

    Я довольно долго пользовался их сборкой Miranda, но со временем стали появляться глюки в работе, нестабильность подключения, вылеты клиента.
    В итоге было принято решение — собрать свою Miranda с блэк-джеком и … с новым ядром и необходимыми функциями.

    Новый клиент



    Собрать свою сборку Miranda — дело не хитрое: установил последнюю версию ядра накопировал нужных плагинов и вуаля — своя сборка.
    Но статья не про это.

    Новый клиент Miranda полностью дублировал функционал подключения к RDP и VNC, однако стал намного стабильнее ввиду обновленного ядра (буду называть его — «админский клиент»). К слову сказать — для пользователей была собрана своя Miranda, но уже без «плюшек» подключения (пользовательский клиент).
    В течении нескольких месяцев клиент успешно работал у нашей группы технической поддержки — нареканий не было.
    За это время я успешно реализовал мониторинг за Active Diretory (статья тут и тут), за файловым сервером (тут) и VPN-серверами.
    В процессе создания мониторинга, несколько раз слышал от ребят из технической поддержки, что было бы не плохо еще добавить в клиент возможность подключения к пользователю, используя «Удаленный помощник», т.к. у нас он является стандартом дэ-факто для поддержки пользователей

    Собственно, это не доставило каких-то трудностей.
    Казалось бы — что еще нужно? Все необходимое для поддержки присутствует.

    Небольшое отступление

    В тот момент я увлекся программированием на C# — переписал весь мониторинг с Powershell на C#, прикрутил мониторинг к базе данных (раньше писал в текстовые файлы) и наваял простенькую web-страничку с php, которая подтягивала данные о мониторинге из базы данных — стало совсем хорошо.
    На страничке можно было сделать выборку:
    1. для изменений в AD: по имени пользователя или имени компьютера
    2. для файлового сервера: по имени пользователя или имени файла
    3. для VPN-серверов: по имени пользователя

    Обновленный новый клиент

    image
    Для еще большего удобства технической поддержки пользователей было решено добавить выборку по необходимому пользователю в клиент Miranda.
    Как это работает:
    Для подключения к примеру по RDP, Miranda при помощи определенного плагина может возвращать не только значение имени компьютера пользователя, но и логин данного пользователя.
    В клиент были добавлены функции открытия web-страниц с таблицами мониторинга, в URL которых передавались GET'ом логин необходимого пользователя.
    В итоге клиент приобрел совсем другую окраску — теперь он не только позволяет осуществлять поддержку в режиме реального времени, но и производить анализ по прошедшим событиям, касающегося данного пользователя (к примеру таким образом можно выяснить какие файлы данный пользователь открывал или удалял на сетевом ресурсе, к каким серверам он пытался подключаться, какие действие производились с его учетной записью в AD).

    Далее мной было написано простенькое WindowsForm приложение на C#, которое, используя WMI-провайдера, может получать список процессов удаленного компьютера, с возможностью их завершения.

    Не долго думая, данное приложение было «внедрено» в функционал админского клиента.
    Теперь поддержка пользователей получила новую окраску: нет необходимости вести таблицы с данными о компьютерах, IP-адресах, пользоваталей, которые работают на данных компьютерах, и уж тем более теперь нет необходимости запрашивать информацию об имени или IP-адресе компьютера у пользователя (при условии конечно, что сеть все же у него есть, иначе Miranda не сможет «взять» имя компьютера пользователя, находящегося в оффлайне, но логин пользователя — сможет).

    То была предыстория, теперь основная тема — возможность осуществлять звонки


    Как говорится: Лень — двигатель прогресса.
    Данный клиент до минимума свел ввод данных вручную для подключения к пользователю или поиску информации о нем в базе данных мониторинга — все делается 2мя кликами.
    В процессе использования Miranda с расширенным функционалом в течении 2 лет, меня периодически посещала мысль о том, что было бы неплохо сделать возможность осуществлять звонки используя клиент Miranda — без необходимости набора номера вручную.
    Одна проблема — у нас нет IP-телефонии, точнее она есть, но используется у 5% абонентов:)
    Т.е. хочется сделать так, чтобы при выборе пользователя в контакт-листе Miranda, можно было выбрать действие — «звонить», а разговаривать уже по своему привычному рабочему телефону.
    И вот однажды сам себе поставил задачу — реализовать это. Сказано — сделано!

    Дано:
    1. АТС Panasonic TDE200
    2. 300 абонентов (15 — SIP, 285 — аналоговые или цифровые телефоны)
    3. Miranda (куда же без нее)
    4. Сверлящая идея, не дающая спать ночами:)

    Пойдем от обратного, т.е. от телефонов до Miranda.

    Связываем Panasonic с LAN

    Первое, о чем я задумался, как Miranda достучится до АТС.
    Решение оказалось незамысловатое — Asterisk.
    Процедуру установки и конфигурирования Asterisk'а я затрагивать не буду, в сети и без того много статей на эту тему.
    Объединяем Asterisk и Panasonic TDE200 SIP-транком, по данной статье.

    Сделано. Теперь используя компьютер, можно совершать звонки на телефоны пользователей. Однако нам нужно совершать звонки с обычных телефонов, без набора номера. Копаем дальше.

    CallBack

    Asterisk — мощная программная IP-АТС, все что можно придумать с телефонией — Asterisk умеет делать.
    В том числе у него есть замечательная функция как CallBack, т.е. обратный вызов — от этого и будем плясать.
    Так же Asterisk умеет формировать звонок автоматически, используя специально сформированные файлы звонков (call-файлы). В них присутствует информация о номерах телефонов, на которые необходимо сделать вызов. Данные файлы при помещении в специальную папку на Asterisk'е и инициируют вызов обоих абонентов и соединяет их.
    Пример текста файла:
    Channel: SIP/utde/9311264
    CallerID: test <9311264>
    Context: from-internal
    Extension: 994120031
    

    Это значит, что астериск должен позвонить по номерам: 9311264 (наш номер) и 994120031 (куда звоним). Звонок по обоим номерам уходит с номера 9311264 (т.е. с нашего, указан в свойстве CallerID).

    Автоматизируем создание Call-файлов

    Для автоматизации воспользуемся обычным bash-скриптом, который при помощи команды echo будет писать в новый call-файл необходимые данные для совершения звонка: т.е. номера телефонов того, кто звонит и того, кому звонят. Оба номера телефона будем передавать параметрами при запуске bash-скрипта, например: call.sh 945954 945932

    Пример скрипта, для формирования Call-файла:
    #!/bin/bash
    #
    outcall="/var/spool/asterisk/$1-$2.call"
    echo Channel: SIP/utde/$1 > $outcall
    echo CallerID: 'call <'$1'>' >> $outcall
    echo Context: from-internal >> $outcall
    echo Extension: $2: >> $outcall
    chown asterisk:asterisk $outcall
    mv $outcall /var/spool/asterisk/outgoing
    


    Как запустить bash-скрипт «извне»

    Все это замечательно, мы уже можем с помощью запуска скрипта и передачей ему 2х параметров, заставить совершить звонок на телефоны двух сотрудников и объединить их в разговор.
    Но каким образом нам заставить это делать, не находясь в консоли на сервере, где находится Asterisk?
    Ответ довольно прост — web.
    Развернем apache, сформируем php-страницу, которая будет запускать bash-скрипт и передавать ему 2 номера телефона в ввиде параметров.
    Запустить bash_скрипт из php возможно следующей функцией:
    shell_exec("sudo /var/scripts/out.sh $from $to");
    

    Где имя скрипта: out.sh
    Ваш номер телефона в параметре: $from
    Номер, куда звоним: $to

    Передаем данные в php

    Так же как и bash-скрипт, php-страница должна получить 2 номера телефона. Самым простым решением данной проблемы является метод GET.
    Т.е. специально сформированная URL, несущая в себе 2 номера телефона, передает их в php, где они уже передаются в bash.

    Передать с помощью метода GET в переменную не сложно, достаточно в php-коде указать:
    $from=$_GET['from'];
    $to=$_GET['to'];
    

    Т.е. переменным $from и $to присваиваем значение которые были переданы в url в параметрах from и to,
    Например ссылка: pbx.domain.ru/index.php?from=943216&to=987632
    передает в переменную $from номер телефона 943216, а в переменную $to — 987632

    Формируем GET-запрос из Miranda

    Для решения данной проблемы я не нашел готового решения, поэтому было решено написать собственно приложение, которое бы само формировала нужный URL и делала запрос по нему, передавая номера телефонов. И конечно же реализовал это на C#. Назовем это приложение условно: Приложение C#.
    Что делает приложение:
    1. Принимает от Miranda входящим параметром — логин сотрудника, которому хотим совершить звонок.
    2. Забирает логин текущего пользователя на нашем компьютере (т.е. наш логин)
    3. Делает запрос в Active Directory, в котором ищет этих пользователей по их логинам и возвращает их номера телефонов (Телефоны должны быть указаны в атрибуте учетной записи telephoneNumber).
    4. Формирует URL в который вставляет 4 значения: номер «кто звонит», номер «кому звонят», логин «кто звонит», логин «кому звонят» (К примеру: pbx.domain.ru/index.php?from=924374&to=859345&fromLogin=ivanov&toLogon=petrov (Логины передаю для создания оповещения, об этом ниже).
    5. Делает запрос по данному URL.

    На этом «звонилка» готова.

    Вот так выглядит теперь контекстное меню админской Miranda:
    image

    А так у юзеров:
    image

    При выборе функции «Звонок» запускается Приложение C#:
    image

    Пользователь выбирает на какой номер хочет совершить звонок, и нажимает соответствующую кнопку звонка.
    Если мобильный номер не указан в Active Directory, то выводиться он не будет (как и кнопка звонка).
    Если номеров у данного пользователя нет, то приложение об этом сообщит.

    Создаем оповещение о входящем звонке

    Т.к. у большинства пользователей телефоны не имеют контакт листа, то и определить кто звонит достаточно сложно, даже если Вы видите, какой номер Вам звонит, то была добавлена функция оповещения в Jabber о том, кто звонит: ФИО, должность, отдел, номер телефона.
    Выше я написал, что приложение передает помимо 2х телефонов еще и два логина.
    Т.е. php может принять 4 параметра, не только номера телефонов но и логины.
    Дописываем php. Добавляем функцию запроса в Active Directory.
    Что в итоге делает php:
    1. получив логины, делает запрос в AD на получение ФИО, Должности, Отдела человека, который совершает вызов
    $srv = "10.10.10.1";  //ip контроллера домена
    $srv_domain = "domain.ru"; //имя домена
    $srv_login = "username@".$srv_domain;   //имя учетной записи под которой будем логиниться к LDAP
    $srv_password = "PASSW0RD";   //пароль от этой учетной записи
    $dn = "dc=domain,dc=ru";    //DN-Имя домена
    $filter = "(&(sAMAccountName=$fromLogin)(objectCategory=user)(memberOf=CN=Openfire,CN=Users,DC=domain,DC=ru)(!(userAccountControl:1.2.840.113556.1.4.803:=2)))";
    $attr = array("cn","telephoneNumber","title","department","company");
    $dc = ldap_connect($srv);
    ldap_set_option($dc, LDAP_OPT_PROTOCOL_VERSION, 3);
    ldap_set_option($dc, LDAP_OPT_REFERRALS, 0);
    if ($dc) {
    	ldap_bind($dc,$srv_login,$srv_password);
    	$result = ldap_search($dc,$dn,$filter,$attr);
    	$result_entries = ldap_get_entries($dc,$result);
    	ldap_unbind($dc);
    }
    $DisplayName = $result_entries[0]['cn'][0];   //Получаем ФИО пользователя
    $Title = $result_entries[0]['title'][0];        //Должность
    $Department = $result_entries[0]['department'][0];            //Отдел
    $DisplayName = iconv('utf-8', 'cp1251', $DisplayName);
    $Title = iconv('utf-8', 'cp1251', $Title);
    $Department = iconv('utf-8', 'cp1251', $Department);
    

    2. формирует сообщение, содержащее информацию о звонящем.
    3. Запускает bash-скрипт, в который передает тело сообщения и логин «кому звонят».
    #!/bin/bash
    #
    echo "$2" | sendxmpp $1@domain.ru
    

    bash-скрипт запускает sendxmpp (Perl-скрипт, для отправки сообщений по протоколу XMPP/Jabber, свободно доступен в репозиториях) и передает ему тело сообщения и логин «кому звонят». Sendxmpp отправляет сообщение пользователю от специально-созданного контакта (назовем его Оператор).

    Так же в теле сообщение передается ссылка, при открытии которой, создается обратный звонок, человеку, который Вам звонил — сделано для удобства. К примеру пропустили Вы звонок, увидели сообщение, хоть в сообщении и присутствует информация о номере звонящего, но «Лень — двигатель прогресса» и мы не хотим набирать вручную номер на телефоне, или искать данного сотрудника в списке контактов Miranda, чтобы совершить вызов — Мы просто кликаем по ссылке, и вся эта котовасия со скиптами начинает работать:
    идут запросы в AD из php
    формируются сообщение тому человеку, что вы ему звоните, так же со ссылкой на перезвон
    формируется call-файл
    звонят телефоны

    Так выглядит сообщение:
    image

    Заключение


    В итоге мы получили полноценный клиент, из которого можно осуществлять подключение к пользователям, просмотр информации по мониторингу, совершать звонки одним кликом.
    Плюс данного решения заключается в том, что все данные берутся из Active Directory, что упрощает хранение данных о номерах телефонах — нужно хранить только в одном месте.
    Приложение C#, запрашивает данные в Active Directory не только о рабочем номере телефона, но и о мобильном. И мы можем выбрать на какой номер хотим звонить: рабочий или мобильный.
    Звонки так же совершаются и на междугородние номера, это особенно актуально для нас, т.к. у нас несколько филиалов в разных городах — что несомненно упрощает жизнь, т.к. не нужно набирать длинные номера телефонов с кодами городов.

    Общая схема работы:
    image
    Выглядит конечно громоздко, но на практике, после нажатия кнопки «звонок», Ваш телефон начинает звонить через 1-2 секунды.

    Безопасность

    Все это замечательно работает, но есть нюансы в безопасности:
    1. Я кодирую номера телефонов и логинов в приложении на C#, чтобы они не передавались в открытом виде (т.к. ссылка присутствует в тексте оповещения). Php уже их декодирует и получает нормальные данные(номера и логины).
    2. Обязательно прочтите в сети о защите Asterisk. Как минимум настройте iptables и разрешите только вашей подсети ходить на Asterisk.

    Вопрос: Подскажите, как можно реализовать тайм аут, к примеру, чтобы пользователь не мог совершить звонок через Miranda, в течении 30 секунд, после начала предыдущего звонка? На ум приходит только записывать IP адреса и время откуда пришел запрос на звонок. МБ есть еще какие-то варианты?

    PS: Т.к. заносить номера телефонов в Active Directory достаточно утомительное занятие, я написал небольшое WindowsForm приложение на C#, которое единожды запускается на компьютерах пользователей, и просит их указать свои номера телефонов (рабочий и мобильный). Эти данные записываются в соответствующий атрибут учетной записи пользователя в Active Directory. Приложение это нельзя закрыть корме как введя свои данные. Закрыть можно через диспетчер задач, но пользователи обычно до этого не доходят.
    Данное приложение было так же добавлено в клиент Miranda, чтобы пользователи всегда могли указать свои номера телефонов, если они изменились. (Тут уже, конечно, приложение можно закрыть без обязательной записи своего номера).

    PSS: Спасибо за помощь с Астериском Lanched
    Поделиться публикацией

    Комментарии 4

      0
      Сожалею, что поздно увидел статью, сходил сказать спасибо в карму.

      Подивился вашему скину с иконками из квипа.

      Про программерские элементы проекта — было бы уместно опубликовать исходники на гитхабе (а может и описать подробнее сделанное).

      Вопрос лучше задать на Тостере — там люди направленно отвечают на ту тему, в которой разбираются.

      В целом, здорово, что вы сделали такую систему на базе открытого клиента. Жалко, что эта система получилась сложной — потребовалось собирать свою миранду в двух версиях, добавлять несколько самописных модулей для интеграции с сервером, да и сервер у вас Openfire, что ограничивает применение — для персональных нужд сейчас многие уже стали использовать prosody.
        +2
        А не желаете прийти с этим в сообщество форка миранды? Там наверняка помогут причесать немного и за одно может пойдёт в апстрим.
          0
          Спасибо за комментарий.
          Я думаю что на гитхаб не стоит с «этим» идти:) Исходные коды приложения, которое вызывает миранда довольно просты: стандартные методы поиска и работы с Active Directory + создание web-request.
          А в сообщество.., возможно было бы удобнее если бы вместо приложения это был бы плагин миранды, было бы удобнее конечно.
          prosody… интересно, хотя бегло пробежался по configuration — не увидел интеграции с АД (плохо если нет).
          А два клиента, так это только из-за функций описанных в начале статьи, для поддержки пользователей (чтобы они не пугались большому количеству функций в контекстном меню).
            0
            Хотя нет, есть интеграция с LDAP.
            Но в данном случае, серверная часть XMPP не имеет значения, все делает клиент, с сервера никаких данных не запрашивается. Звонок будет работать даже если сервер будет в «дауне»:)

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое