Популярность сервисов callbackhunter и zingaya посеяла в мою голову идею, реализовать возможность обратного звонка с нашего сайта, используя уже работающую много лет в компании IP ATC Askozia. Ранее я писал про нее в отдельном посте. alt Ситуация также усложнялась тем, что сайт расположен в публичной сети, а 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

При настройке firewall я выбрал вариант установки Askozia в облаке и получил такие параметры блокировки по умолчанию

На всякий случай указал свой домашний IP адрес для доступа к web интерфейсу Askozia в случае неправильных настроек firewall, иначе можно больше не попасть в админку :)

Далее активируем fail2ban для блокировки подбора паролей. Это делается одной галочкой, остальные все настройки оставим по умолчанию.

Добавим кастомное правило для iptables, чтобы сайт мог достучаться до AMI интерфейса сквозь Firewall. Для этого в секции расширенные, добавим такую строчку: iptables -A INPUT -p tcp -s 93.188.40.98 --dport 5038 -j ACCEPT

Должно получиться как на картинке:

На этом настройку firewall можно завершить, сохранить изменения и перейти к настройке AMI интерфейса. Создадим нового пользователя webcall с минимальными правами и разрешим ему доступ только с одного IP адреса, нашего сайта.


Дадим ему сложный-сложный пароль:9[U.[2o{9$?H$$su

Теперь нам нужно понять, куда мы будем направлять звонок с Web сайта. Я решил не придумывать ничего хитрого и отправить его по тому же маршруту, по которому нам звонят клиенты в HOTLINE. Это очередь вызовов с внутренним номером 93

Ну и последнее что нам понадобится, это получить контекст провайдера, через которого будем осуществлять звонок клиенту. Для этого открываем карточку редактирования настроек провайдера и копируем из адресной строки его контекст.

Теперь у нас есть всё необходимое для настройки обратного звонка со стороны сайта:

  • IP Адрес Askozia: 93.188.40.99
  • Логин AMI пользователя: webcall
  • Пароль AMI пользователя: 9[U.[2o{9$?H$$su
  • Контекст провайдера: SIP-PROVIDER-1646711234f40d80266c2f
  • Внутренний номер маршрута вызова: 93
Настройка скриптов на стороне сайта

Для начала нам нужно нарисовать простую форму ввода номера телефона на сайте.

Код формы с сайта на Битриксе (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>
      Если вы находитесь в России, то можете заказать обратный звонок на свой телефон.<br><br>
     <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/ajaxcallhandler_inc.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").'.<br>
Подождите пока Ваш телефон зазвонит!<br>Если телефон не позвонил в течении минуты, попробуйте ещё раз.
</div>';

    }
    else
    {
        echo '<div class="ui negative message">Не верно указан номер телефона. Номер должен состоять из 11 цифр, например +7 (495) 229-30-42</div>';

        unset($_SESSION['call_send']);
    }
}
?>

Что получилось в итоге: