Популярность сервисов callbackhunter и zingaya посеяла в мою голову идею, реализовать возможность обратного звонка с нашего сайта, используя уже работающую много лет в компании IP ATC Askozia. Ранее я писал про нее в отдельном посте.
![](https://habrastorage.org/files/88e/455/40e/88e45540ec0a437386aa263d303fc048.png)
Ситуация также усложнялась тем, что сайт расположен в публичной сети, а Askozia внутри локальной, и публиковать ее в открытый доступ было страшновато.
Алгоритмов реализации механизма обратного звонка с сайта для Asterisk написано достаточно много. Самый распространенный — это генерация AMI команды Originate после того, как клиент введет свой номер телефона на сайте. И так приступим.
В последней версии Askozia наконец появился механизм активной защиты от атак на SIP телефонные системы в виде широко известной в Linux кругах пары iptables_fail2ban. А это в связке с тем, что Askozia имеет read-only linux ядро, сильно повышает защиту от взлома даже относительно такого распространенного дистрибутива как FreePBX. Хотя конечно вы можете со мной поспорить :)
Итак, исходные данные:
Сайт www.telefon1c.ru имеет ip адрес 93.188.40.98
Askozia развернута в виде виртуальной машины с публичным адресом 93.188.40.99
Первым делом активируем firewall и разрешаем доступ к системе из своей локальной сети, указывая ее подсеть. В данном случае настройка 172.16.32.0/24 означает все адреса в диапазоне 172.16.32.0 ~ 172.16.32.255
![](https://habrastorage.org/files/ee2/c82/f5b/ee2c82f5b96d4430bb53c60c219640ee.png)
При настройке firewall я выбрал вариант установки Askozia в облаке и получил такие параметры блокировки по умолчанию
![](https://habrastorage.org/files/0a9/1c8/2f0/0a91c82f05784104bbad90d223713bf1.png)
На всякий случай указал свой домашний IP адрес для доступа к web интерфейсу Askozia в случае неправильных настроек firewall, иначе можно больше не попасть в админку :)
Далее активируем fail2ban для блокировки подбора паролей. Это делается одной галочкой, остальные все настройки оставим по умолчанию.
![](https://habrastorage.org/files/7b1/eeb/78a/7b1eeb78a7a749b4a4d151b19bc62828.png)
Добавим кастомное правило для iptables, чтобы сайт мог достучаться до AMI интерфейса сквозь Firewall.
Для этого в секции расширенные, добавим такую строчку:
iptables -A INPUT -p tcp -s 93.188.40.98 --dport 5038 -j ACCEPT
Должно получиться как на картинке:
![](https://habrastorage.org/files/38a/4f6/a82/38a4f6a821564c0caab797145dd06512.png)
На этом настройку firewall можно завершить, сохранить изменения и перейти к настройке AMI интерфейса.
Создадим нового пользователя webcall с минимальными правами и разрешим ему доступ только с одного IP адреса, нашего сайта.
![](https://habrastorage.org/files/709/b11/39f/709b1139f13a48a9a9142bb76d91c674.png)
Дадим ему сложный-сложный пароль:9[U.[2o{9$?H$$su
Теперь нам нужно понять, куда мы будем направлять звонок с Web сайта. Я решил не придумывать ничего хитрого и отправить его по тому же маршруту, по которому нам звонят клиенты в HOTLINE. Это очередь вызовов с внутренним номером 93
![](https://habrastorage.org/files/48b/708/043/48b7080435fc423d920fa50f94c15d8a.png)
Ну и последнее что нам понадобится, это получить контекст провайдера, через которого будем осуществлять звонок клиенту. Для этого открываем карточку редактирования настроек провайдера и копируем из адресной строки его контекст.
![](https://habrastorage.org/files/91a/338/bc7/91a338bc722349fc8db1ad132c58232b.png)
Теперь у нас есть всё необходимое для настройки обратного звонка со стороны сайта:
Для начала нам нужно нарисовать простую форму ввода номера телефона на сайте.
![](https://habrastorage.org/files/aee/23d/b30/aee23db30f644975bc097b253d053b6d.png)
Что получилось в итоге:
![](https://habrastorage.org/files/88e/455/40e/88e45540ec0a437386aa263d303fc048.png)
Ситуация также усложнялась тем, что сайт расположен в публичной сети, а Askozia внутри локальной, и публиковать ее в открытый доступ было страшновато.
Алгоритмов реализации механизма обратного звонка с сайта для Asterisk написано достаточно много. Самый распространенный — это генерация AMI команды Originate после того, как клиент введет свой номер телефона на сайте. И так приступим.
Настройка на стороне Askozia
В последней версии Askozia наконец появился механизм активной защиты от атак на SIP телефонные системы в виде широко известной в Linux кругах пары iptables_fail2ban. А это в связке с тем, что Askozia имеет read-only linux ядро, сильно повышает защиту от взлома даже относительно такого распространенного дистрибутива как FreePBX. Хотя конечно вы можете со мной поспорить :)
Итак, исходные данные:
Сайт www.telefon1c.ru имеет ip адрес 93.188.40.98
Askozia развернута в виде виртуальной машины с публичным адресом 93.188.40.99
Настойка файрвола и fail2ban
Первым делом активируем firewall и разрешаем доступ к системе из своей локальной сети, указывая ее подсеть. В данном случае настройка 172.16.32.0/24 означает все адреса в диапазоне 172.16.32.0 ~ 172.16.32.255
![](https://habrastorage.org/files/ee2/c82/f5b/ee2c82f5b96d4430bb53c60c219640ee.png)
При настройке firewall я выбрал вариант установки Askozia в облаке и получил такие параметры блокировки по умолчанию
![](https://habrastorage.org/files/0a9/1c8/2f0/0a91c82f05784104bbad90d223713bf1.png)
На всякий случай указал свой домашний IP адрес для доступа к web интерфейсу Askozia в случае неправильных настроек firewall, иначе можно больше не попасть в админку :)
Далее активируем fail2ban для блокировки подбора паролей. Это делается одной галочкой, остальные все настройки оставим по умолчанию.
![](https://habrastorage.org/files/7b1/eeb/78a/7b1eeb78a7a749b4a4d151b19bc62828.png)
Добавим кастомное правило для iptables, чтобы сайт мог достучаться до AMI интерфейса сквозь Firewall.
Для этого в секции расширенные, добавим такую строчку:
iptables -A INPUT -p tcp -s 93.188.40.98 --dport 5038 -j ACCEPT
Должно получиться как на картинке:
![](https://habrastorage.org/files/38a/4f6/a82/38a4f6a821564c0caab797145dd06512.png)
На этом настройку firewall можно завершить, сохранить изменения и перейти к настройке AMI интерфейса.
Создадим нового пользователя webcall с минимальными правами и разрешим ему доступ только с одного IP адреса, нашего сайта.
![](https://habrastorage.org/files/709/b11/39f/709b1139f13a48a9a9142bb76d91c674.png)
Дадим ему сложный-сложный пароль:9[U.[2o{9$?H$$su
Теперь нам нужно понять, куда мы будем направлять звонок с Web сайта. Я решил не придумывать ничего хитрого и отправить его по тому же маршруту, по которому нам звонят клиенты в HOTLINE. Это очередь вызовов с внутренним номером 93
![](https://habrastorage.org/files/48b/708/043/48b7080435fc423d920fa50f94c15d8a.png)
Ну и последнее что нам понадобится, это получить контекст провайдера, через которого будем осуществлять звонок клиенту. Для этого открываем карточку редактирования настроек провайдера и копируем из адресной строки его контекст.
![](https://habrastorage.org/files/91a/338/bc7/91a338bc722349fc8db1ad132c58232b.png)
Теперь у нас есть всё необходимое для настройки обратного звонка со стороны сайта:
- IP Адрес Askozia: 93.188.40.99
- Логин AMI пользователя: webcall
- Пароль AMI пользователя: 9[U.[2o{9$?H$$su
- Контекст провайдера: SIP-PROVIDER-1646711234f40d80266c2f
- Внутренний номер маршрута вызова: 93
Настройка скриптов на стороне сайта
Для начала нам нужно нарисовать простую форму ввода номера телефона на сайте.
![](https://habrastorage.org/files/aee/23d/b30/aee23db30f644975bc097b253d053b6d.png)
Код формы с сайта на Битриксе call.php
<?
//Подключаем битриксовский хидер страницы
require($_SERVER["DOCUMENT_ROOT"]."/bitrix/header.php");
$APPLICATION->SetTitle("Звонок в компанию МИКО");
?>
<div class="ui page grid">
<div class="column">
<div class="ui segment">
//Подключаем обработчик вызовов через Askozia
<? // текст
$APPLICATION->IncludeFile("/ajax/ajax_call_handler_inc.php", Array(), Array(
"MODE" => "html",
"NAME" => "Редактирование включаемой области раздела"
));
?>
//Рисуем форму для указания номера клиента для обратного звонка
<h1 class="ui header block">Бесплатный звонок</h1>
<div class="ui segment">
<h3 class="ui header">Мгновенный callback</h3>
Если вы находитесь в России, то можете заказать обратный звонок на свой телефон.
<div class="ui form" id="CallOverAsterisk">
<form class="vertical" action="/call.php" method="post">
<div class="ui action large left icon input">
<input type="text" size="20" maxlength="10" name="txtphonenumber" placeholder="Введите российский номер, например: +7 (495) 123-45-67" id="phone">
<div class="ui button orange" id="calloverasterbtn">Перезвонить</div>
</div>
<button type="submit" style="display:none" id="calloverasterbtnsbmnt" name="call_send"/></button>
</form>
</div>
</div>
</div>
</div>
</div>
//Наводим красоту при вводе номера, рисуем красивую маску
<script type="text/javascript">
$("#phone").mask("+7 (999) 999-99-99");
$("#phone").on("blur", function() {
var last = $(this).val().substr( $(this).val().indexOf("-") + 1 );
if( last.length == 3 ) {
var move = $(this).val().substr( $(this).val().indexOf("-") - 1, 1 );
var lastfour = move + last;
var first = $(this).val().substr( 0, 9 );
$(this).val( first + '-' + lastfour );
}
});
//Помогаем форме сделать POST запрос при нажатии на красивую кнопку "Перезвонить"
$("#calloverasterbtn").click(function() {
$("#calloverasterbtnsbmnt").click();
});
</script>
//Подключаем битриксовский футер страницы
<?require($_SERVER["DOCUMENT_ROOT"]."/bitrix/footer.php");?>
Код php обработчика для генерации AMI команды /ajax/ajax_call_handler_inc.php
<?php
if (isset($_REQUEST["call_send"])){
#--------------------------------------------------------------------------------------------
#Ниже указываются основные настройки соединения с Askozia
#--------------------------------------------------------------------------------------------
$strHost = "93.188.40.99"; //IP адрес Askozia
$strUser = "webcall"; //Имя пользователя AMI Manager с правами звонков
$strSecret = "9[U.[2o{9$?H$$su"; //Его пароль
$strContext = "internal"; //Контекст для вызова. В Askozia используем internal
$strExten = "93"; //Внутренний номер, куда отправится звонок после набора клиенту
$strwebnum = preg_replace('~\D+~','',$_REQUEST['txtphonenumber']); //В этой переменной будет номер телефона очищенный от знаков
$strChannel = "Local/".$strwebnum."@SIP-PROVIDER-1646711234f40d80266c2f"; //Звонок клиенту пойдет как если бы сотрудник отдела набрал его номер на своем IP телефоне. Здесь важно правильно указать контекст провайдера: SIP-PROVIDER-1646711234f40d80266c2f
$strWaitTime = "30"; //Сколько секунд пользователь сайта будет слушать длинные гудки перед тем как произойдет отбой
$strPriority = "1"; //Приоритет вызова
#--------------------------------------------------------------------------------------------
#Все что ниже не нужно трогать при адаптации
#--------------------------------------------------------------------------------------------
#specify the caller id for the call
$strCallerId = "Web Call <$strwebnum>";
$length = strlen($strwebnum);
if ($length == 11 && is_numeric($strwebnum))
{
$oSocket = fsockopen($strHost, 5038, $errnum, $errdesc,30) or die("Connection to host failed");
stream_set_timeout($oSocket, 0, 500000);
fputs($oSocket, "Action: login\r\n");
fputs($oSocket, "Events: off\r\n");
fputs($oSocket, "Username: $strUser\r\n");
fputs($oSocket, "Secret: $strSecret\r\n\r\n");
while ($line = fgets($oSocket))
$result .= $line;
fputs($oSocket, "Action: originate\r\n");
fputs($oSocket, "Channel: $strChannel\r\n");
fputs($oSocket, "WaitTime: $strWaitTime\r\n");
fputs($oSocket, "CallerId: $strCallerId\r\n");
fputs($oSocket, "Exten: $strExten\r\n");
fputs($oSocket, "Context: $strContext\r\n");
fputs($oSocket, "Priority: $strPriority\r\n\r\n");
while ($line = fgets($oSocket))
$result .= $line;
fputs($oSocket, "Action: Logoff\r\n\r\n");
while ($line = fgets($oSocket))
$result .= $line;
fclose($oSocket);
echo '<div class="ui success message">Производится вызов на номер '.htmlentities($_REQUEST["txtphonenumber"], ENT_QUOTES, "UTF-8").'.
Подождите пока Ваш телефон зазвонит!
Если телефон не позвонил в течении минуты, попробуйте ещё раз.
</div>';
}
else
{
echo '<div class="ui negative message">Не верно указан номер телефона. Номер должен состоять из 11 цифр, например +7 (495) 229-30-42</div>';
unset($_SESSION['call_send']);
}
}
?>
Что получилось в итоге:
Комментарии (7)
Angelmoon
09.07.2015 14:24Отличная статья! Автору респект!
В сервисах обратного звонка принципиально ничего сложного нет. Дьявол, как обычно, в деталях.jorikfon Автор
09.07.2015 14:32Я 2 года пытался это сделать :) Оказалось все не так сложно :)
Angelmoon
09.07.2015 14:49+1А вы не пробовали посмотреть на voximplant.com?
jorikfon Автор
09.07.2015 16:26Периодически смотрю. Собственно на нем построена кнопка обратного звонка zingaya.com/ru, там есть минус, что клиент звоня через веб браузер не оставляет мне своего номера телефона, и мне нечего в CRM записать :)
Angelmoon
09.07.2015 16:42+2А мы на нем сделали свой сервис обратного звонка (если интересно — chaser.ru). И номер прилетает тот, какой нужно.
romy4
Под Askozia вы выделили отдельное железо? Какой канал нужен под это дело?
jorikfon Автор
У меня 20 сотрудников, виртуалка на сервере с Vmware ESXi, по характеристиками на машину выделено:
По сути это с большим запасом все. За год мы 5 гигов записей генерируем. Процессор на 20% максимум грузит.
Канал надо считать, по-моему 150 кб на 1 канал.