Быстрое развертывание телефонной сети на Asterisk+Cisco

в 18:20, , рубрики: asterisk, Cisco, perl, provisioning, shell, sqlite3, метки: , , , , ,

Так случилось, что в короткие сроки появилась необходимость перевезти 70 человек с аналоговыми телефонами из одного бизнес центра в другой. Ситуация усугублялась тем, что в новом офисе у владельца не было аналоговых портов в АТС, а АТС в старом принадлежала телефонной компании. Пришлось в короткие сроки внедрять IP телефонию с переносом всех аналоговых городских линий на Asterisk. Поставка оборудования была назначена на день предшествующий дню переезда, что означало — времени на развертывание телефонии будет очень мало.

Что из этого вышло под катом.
Материала много, так что не пугайтесь.

Итак имеющаяся конфигурация:

  • 60 пользователей со своими компьютерами.
  • 60 телефонов Cisco SPA 502G
  • 4 панели Cisco SPA500S
  • 5 баз Siemens C610A с парой трубок к каждой.
  • Один сервер на FreeBSD.
  • Пара свичей Cisco SF300-24P

Поискав и почитав в сети множество документов связанных с autoprovision мы решили воспользоваться возможностью asterisk выполнять автонастройку устройств самостоятельно. Всё бы ничего, но как оказалось автонастройка через sql базу в asterisk не позволяет мониторить состояние экстеншена на доппанелях. Т.е. нельзя настроить hint(по крайней мере в августе 2012 оно не поддерживалось). Имея в наличии пару телефонов мы решились на написание своего autoprovision.

Начали с настройки сервера и свичей. Создали 2 VLAN с номерами 204 и 214. Первый для локальной сети, второй для IP телефонии. Т.к. телефоны имеют встроенный конфигурируемый коммутатор, это было лучшим решением с нашей точки зрения.
Выставили порты на коммутаторах к которым подключаются пользователи в транк и изменили native vlan.

interface fastethernet1
switchport trunk allowed vlan add 204
switchport trunk native vlan 214
exit

Подключили сервер к коммутатору в транковый порт и поменяли настройки dhcpd.conf

# Phones Subnet Vlan 214
subnet 172.16.214.0 netmask 255.255.255.0 {
        range 172.16.214.10 172.16.214.250;
        option routers 172.16.214.1;
        option tftp-server-name "http://172.16.214.1/XMLDefault.cnf.xml";
        option domain-name "phones.mydomain.local";
        option domain-name-servers 172.16.214.1;
        option broadcast-address 172.16.214.255;
        ddns-updates on;
        ddns-domainname "phones.mydomain.local";
        ddns-rev-domainname "in-addr.arpa";
}

# Computers Subnet Vlan 204
subnet 172.16.6.0 netmask 255.255.255.0 {
  range 172.16.6.12 172.16.6.240;
  option broadcast-address 172.16.6.255;
  option domain-name-servers 172.16.6.1;
  option domain-name "mydomain.local";
  option routers 172.16.6.1;
  if option host-name = "" {
        option host-name = concat ("dev-", binary-to-ascii( 10, 8, "", substring( reverse( 1, leased-address), 0, 1)));
        ddns-hostname = concat ("dev-", binary-to-ascii( 10, 8, "", substring( reverse( 1, leased-address), 0, 1)));
  }
}

Отлично. Адресочки раздаются, телефоны бутятся, пришло время запилить базовый конфиг.
Поднимаем thttpd и кладём ему в корень XMLDefault.cnf.xml

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<flat-profile xmlns="http://www.sipura.net/xsd/SPA50x-30x-SIP" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sipura.net/xsd/SPA50x-30x-SIP http://www.sipura.net/xsd/SPA50x-30x-SIP/SPA50x-30x-SIP-7-5-2.xsd">

<Admin_Passwd ua="na">7654321</Admin_Passwd>
<SPCP_Auto-detect ua="na">No</SPCP_Auto-detect>
<Domain ua="rw">phones.mydomain.local</Domain>
<Primary_DNS ua="rw">172.16.214.1</Primary_DNS>
<Syslog_Server ua="na">172.16.214.1</Syslog_Server>
<Debug_Server ua="na">172.16.214.1</Debug_Server>
<Debug_Level ua="na">0</Debug_Level>
<Primary_NTP_Server ua="na">172.16.214.1</Primary_NTP_Server>
<Enable_VLAN ua="rw">Yes</Enable_VLAN>
<Enable_PC_Port_VLAN_Tagging ua="na">Yes</Enable_PC_Port_VLAN_Tagging>
<Enable_CDP ua="na">No</Enable_CDP>
<Enable_LLDP-MED ua="na">No</Enable_LLDP-MED>
<PC_Port_VLAN_ID ua="na">204</PC_Port_VLAN_ID>
<Profile_Rule ua="na">http://172.16.214.1/XMLDefault.cnf.xml</Profile_Rule>
<Profile_Rule_B ua="na">http://172.16.214.1/cfg/cfg.cgi?SN=$SN&MAC=$MA</Profile_Rule_B>
<Key_System_Auto_Discovery ua="na">No</Key_System_Auto_Discovery>
<G722_Enable_1_ ua="na">Yes</G722_Enable_1_>
<L16_Enable_1_ ua="na">No</L16_Enable_1_>
<G726-16_Enable_1_ ua="na">No</G726-16_Enable_1_>
<G726-24_Enable_1_ ua="na">No</G726-24_Enable_1_>
<G726-32_Enable_1_ ua="na">No</G726-32_Enable_1_>
<G726-40_Enable_1_ ua="na">No</G726-40_Enable_1_>
<Enable_IP_Dialing_1_ ua="na">No</Enable_IP_Dialing_1_>
<Use_Remote_Pref_Codec_1_ ua="na">Yes</Use_Remote_Pref_Codec_1_>
<Time_Format ua="rw">24hr</Time_Format>
<Date_Format ua="rw">day/month</Date_Format>
<Text_Logo ua="na">Company</Text_Logo>
<Time_Zone ua="na">GMT+04:00</Time_Zone>
<Upgrade_Rule ua="na">( $SWVER ne 7.5.2b )? http://172.16.214.1/sw/spa50x-30x-7-5-2b.bin</Upgrade_Rule>
</flat-profile>

Таким образом, после того как телефон возьмёт из dhcp параметры автоконфигурации и заберет с сервера базовый конфиг, он сделает вторую итерацию за конфигурационным файлом привязанному к серийному номеру устройства и его мак адресу. После полной инициализации конфига, телефон переведёт порт компьютера в 204 VLAN, обеспечив ему работу с локальной сетью.
Перейдём к тому как телефоны получают свой конфигурационный файл.
Создадим вспомогательный файл с настройками /usr/local/etc/astprov.conf

sqlite="/usr/local/bin/sqlite3"
ast_provisiondb="/var/db/asterisk/asterisk_provision.sqlite3"
ast_ext_dialplan="/var/db/asterisk/asterisk_ext_dialplan.conf"
ast_ext_accounts="/var/db/asterisk/asterisk_ext_accounts.conf"
logger_tag="astprov"
include="/etc/rc.conf"

Создадим по указанному в конфигурационном файле базу sqlite3 следующей структуры:

PRAGMA foreign_keys=OFF;
BEGIN TRANSACTION;
CREATE TABLE `provision` (
`macaddress` varchar(12) NOT NULL,
`serial` varchar(12) NOT NULL,
`secret` varchar(32) NOT NULL,
`ext` int(11) NOT NULL,
`fullname` varchar(64) NOT NULL,
`callerid` varchar(64) NOT NULL,
`callgroup` varchar(32) NOT NULL default '1',
`pickupgroup` varchar(32) NOT NULL default '1',
`context` varchar(32) NOT NULL,
`subscribecontext` varchar(32) NOT NULL default '1',
`ip` varchar(15) NOT NULL);
COMMIT;

Для последующей быстрой настройки рабочих мест создаём файл с телефонами

3027|Buhgalter
3097|Igor
3018|Sergey
3016|Oleg
3091|Vladimir
3014|Ekaterina
3012|Andrey
3015|Maxim

и скармливаем его скрипту в качестве параметра

#!/bin/sh
set -x
. /usr/local/etc/astprov.subr

context='local_pool'
callgroup=1
pickupgroup=1
subscribecontext=1
ip="none"
cat $1 | while IFS= read -r line; do
        randomstr=`< /dev/urandom tr -dc A-Za-z0-9 | head -c10`
        randompas=`< /dev/urandom tr -dc A-Za-z0-9 | head -c10`
        ext=$(echo $line | cut -d '|' -f1 )
        fullname=$(echo $line | cut -d '|' -f2 )
        # fullname <<< $(IFS=";"; echo $line)
        insertline="insert into provision values ('$randomstr','$randompas','$randompas',$ext,'$fullname','$fullname <$ext>',$callgroup,$pickupgroup,'$context',$subscribecontext,'$ip')"
#       echo $insertline
        $sqlitecmd "$insertline"
done

скрипт автоматически заполнит базу и даст возможность быстрого подключения новых телефонов.

Теперь немного колдовства.
Asterisk работает под учёткой asterisk:asterisk, thttpd работает под www:www. Поэтому создаём группу astprov куда добавляем asterisk и www.

# chown -R asterisk.astprov /var/db/asterisk
# chmod 0775 /var/db/asterisk
# chmod 0664 /var/db/asterisk/*

Теперь нужно персонализировать настройки телефонов.
Создаём скрипт /usr/local/www/data/cfg/cfg.cgi, я люблю perl посему конфигуратор на перле.

#!/usr/bin/perl

use strict;
#use Data::Dumper;
use DBI;
use vars qw/%sv %form %cookie %rq $sth $dbh $config/;
use FileHandle;
use Sys::Syslog qw(:standard);

my $configfilename="/usr/local/etc/astprov.conf";
$config=_read_config_file($configfilename);

&systeminit;
&printhead;
#$form{SN}='CBT1602095Z';
#$form{MAC}='649ef37761c2';
#$sv{ip}="172.16.214.10";

openlog("astprov", 'cons,pid');

exit(1) if (not defined $form{SN} or not defined $form{MAC});

&baseconnect;
&get_info;
&baseclose;
closelog();
exit(1);

sub get_info
{
$form{SN} =~ s/[^0-9A-Za-z]//g;
$form{MAC} =~ s/[^0-9A-Za-z]//g;
my $dbdata;
if($dbdata = &request_phone_info)
    {
        &print_xml($dbdata);
    }
else
{
    if(keys %{$dbdata} < 1)
    {
        &insert_new_phone;
    }
    $dbdata = &request_phone_info;
    &print_xml($dbdata);
}
#print Dumper $dbdata;
}

sub print_xml
{
my $dbdata=shift;
my $additional;
if(-f "/usr/local/www/data/cfg/additional".lc($form{MAC}).".xml")
{
    open IN,"</usr/local/www/data/cfg/additional".lc($form{MAC}).".xml";
    undef $/;
    $additional = <IN>;
    close IN;
}
my $hostname = "office-".$dbdata->{ext};
print << "[end]";
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<flat-profile xmlns="http://www.sipura.net/xsd/SPA50x-30x-SIP" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sipura.net/xsd/SPA50x-30x-SIP http://www.sipura.net/xsd/SPA50x-30x-SIP/SPA50x-30x-SIP-7-5-2.xsd">
        <HostName ua="rw">$hostname</HostName>
        <Phone-UI-readonly ua="na">Yes</Phone-UI-readonly>
        <Phone-UI-user-mode ua="na">Yes</Phone-UI-user-mode>
        <Proxy_1_ ua="na">172.16.214.1</Proxy_1_>
        <Display_Name_1_ ua="na">$dbdata->{fullname}</Display_Name_1_>
        <User_ID_1_ ua="na">$dbdata->{ext}</User_ID_1_>
        <Password_1_ ua="na">$dbdata->{secret}</Password_1_>
        <Dial_Plan_1_ ua="na">(7[0-9][0-9]xxxxS0|*8|*xx|xxxx|0xxxxxxxxxxxx.)</Dial_Plan_1_>
        <Station_Name ua="na">$dbdata->{ext}</Station_Name>
        <Station_Display_Name ua="na">$dbdata->{fullname}</Station_Display_Name>
        <Server_Type ua="na">Asterisk</Server_Type>
        <XML_Directory_Service_Name ua="na">My Company</XML_Directory_Service_Name>
        <XML_Directory_Service_URL ua="na">http://172.16.213.1/directory.cgi</XML_Directory_Service_URL>
$additional
</flat-profile>
[end]
}

sub request_phone_info
{
my $cmd = "select ext,callerid,fullname,secret from provision where macaddress='".lc($form{MAC})."' and serial='".lc($form{SN})."'";
my $dbdata = $dbh->selectrow_hashref($cmd);
if($dbh->err)
{
    print "cmd = ",$cmd,"n";
    print "err = ",$dbh->err,"n";
    print "errstr = ",$dbh->errstr,"n";
    print "state = ",$dbh->state,"n";
    return undef;
}
return $dbdata;
}

sub insert_new_phone
{
    my $maxnum = $dbh->selectrow_array("SELECT MAX(ext) FROM provision where ext < 2000") || "1000";
    $maxnum++;
    $dbh->do("INSERT INTO provision (ip,macaddress,serial,secret,ext,fullname,callerid,context) VALUES ('".join ("','",$sv{ip},lc($form{MAC}),lc($form{SN}),lc($form{SN}),$maxnum,'Unregistered','Unregistered <'.$maxnum.'>','unreg')."')");
    syslog('info|local7',"New host added MAC:$form{MAC} SN:$form{SN}");

    if($dbh->err)
    {
        print "err = ",$dbh->err,"n";
        print "errstr = ",$dbh->errstr,"n";
        print "state = ",$dbh->state,"n";
        exit(1);
    }
}


sub systeminit
{
$sv{"ip"} = $ENV{"REMOTE_ADDR"};
$sv{"userhost"} = $ENV{"REMOTE_HOST"};
$sv{"url"} = $ENV{"HTTP_HOST"};
$sv{"doc"} = $ENV{"DOCUMENT_ROOT"};
$sv{"ref"} = $ENV{"HTTP_REFERER"};

# ----------------------------------------------------------------------------------------

my $request_url = $ENV{"REQUEST_URI"};
$request_url =~ s/%(..)/pack("c",hex($1))/ge;
$request_url =~ s/[^A-Za-z0-9-_+=:.,/@]//g;
$request_url =~ s/([-_+=.:,/@s]){2,}/$1/g;
$request_url =~ s/+/&/g; $request_url =~ s/^/+//g;
my $count = 0;
while ($request_url =~ /^([w-=_&.,:@s]+)//)
{       $rq{$count} = $1; $request_url =~ s/$rq{$count}///; $count++;  }
$request_url =~ s/^s+$//g;
chomp $request_url;
$rq{$count} = $request_url if (length($request_url) > 0);

# ----------------------------------------------------------------------------------------

if ( defined($ENV{"HTTP_COOKIE"}) && length($ENV{"HTTP_COOKIE"}) > 0 )
{
    my @cookies = split(/;/,$ENV{"HTTP_COOKIE"});
    foreach (@cookies)
    {
        my ($name,$value) = split(/=/,$_);
        $cookie{$name} = $value;
    }
}

# ----------------------------------------------------------------------------------------

    if ((defined($ENV{"QUERY_STRING"}) && length($ENV{"QUERY_STRING"}) != 0) || (defined($ENV{"CONTENT_LENGTH"}) && $ENV{"CONTENT_LENGTH"} != 0))
    {
        my $data = undef;
        my @data = undef;
            if ($ENV{"REQUEST_METHOD"} eq "GET")
            {
                $data = $ENV{"QUERY_STRING"};
            }
            else
            {
                read(STDIN,$data,$ENV{"CONTENT_LENGTH"});
            }
        @data = split(/&/,$data);
            foreach (@data)
            {
                $_ =~ s/+/ /g;
                my ($name, $value) = split(/=/,$_,2);
                $name =~ s/%(..)/pack("c",hex($1))/ge;
                $name =~ tr/[^A-Za-z0-9-_$+=~.,]//;
                $value =~ s/%(..)/pack("c",hex($1))/ge;
                $form{$name} .= "" if (defined($form{$name}));
                $form{$name} .= $value;
            }
    }
}

sub baseconnect
{
my $dbname = eval $config->{ast_provisiondb};
$dbh = DBI->connect("dbi:SQLite:dbname=$dbname","","");
$sth = $dbh->table_info('%', '%', 'provision');
my $result = $sth->fetchall_hashref('TABLE_NAME');
if(!defined($result->{'provision'}))
{
$dbh->do("
CREATE TABLE `provision` (
`macaddress` varchar(12) NOT NULL,
`serial` varchar(12) NOT NULL,
`secret` varchar(32) NOT NULL,
`ext` int(11) NOT NULL,
`fullname` varchar(64) NOT NULL,
`callerid` varchar(64) NOT NULL,
`callgroup` varchar(32) NOT NULL default '1',
`pickupgroup` varchar(32) NOT NULL default '1',
`context` varchar(32) NOT NULL,
`subscribecontext` varchar(32) NOT NULL default 'internal_phones',
`ip` varchar(15) NOT NULL)
");

}
}

sub baseclose
{
if ($dbh->{Active} && defined($sth)) { $sth->finish };
$dbh->disconnect;
}

sub printhead
{
print "Content-Type: text/plain; charset=UTF-8;rnrn";
}


sub _read_config_file {
    my $file = shift or return;

    my $conf = {};
    my $FH = new FileHandle;
    $FH->open("$file") or (
                        warn(loc(q[Could not open config file '%1': %2],$file,$!)),
                        return {}
                    );

    while(<$FH>) {
        next if     /s*#/;
        next unless /S/;

        chomp; s/^s*//; s/s*$//;

        my ($param,$val) = split /s*=s*/;

        ### add these to the config hash ###
        $conf->{ lc $param } = $val;
    }
    close $FH;

    return $conf;
}

Скрипт получает из запроса MAC устройства и его серийный номер, если связка есть в базе, то телефону отдаётся конфиг с логином/паролем и параметрами подключения к серверу. Если связки нет, то телефон добавляется в базу, ему присваивается «сервисный номер», в данном случае от 1000 до 2000 и телефону отдаётся конфиг. Если в папку "cfg" положить файл с именем additional<MAC>.xml, то телефону в конфиг добавится данный файл. Мы используем данную возможность для загрузки в телефон настроек панелей. Например:

<Attendant_Console_Call_Pickup_Code ua="na">*8</Attendant_Console_Call_Pickup_Code>
<Unit_1_Key_1 ua="na">fnc=sd+cp+blf;sub=2010@172.16.213.1;nme=2010</Unit_1_Key_1>
<Unit_1_Key_2 ua="na">fnc=sd+cp+blf;sub=2012@172.16.213.1;nme=2012</Unit_1_Key_2>
<Unit_1_Key_3 ua="na">fnc=sd+cp+blf;sub=2014@172.16.213.1;nme=2014</Unit_1_Key_3>
<Unit_1_Key_4 ua="na">fnc=sd+cp+blf;sub=2100@172.16.213.1;nme=2100</Unit_1_Key_4>
<Unit_1_Key_5 ua="na">fnc=sd+cp+blf;sub=2110@172.16.213.1;nme=2110</Unit_1_Key_5>
<Unit_1_Key_6 ua="na">fnc=sd+cp+blf;sub=2111@172.16.213.1;nme=2111</Unit_1_Key_6>
<Unit_1_Key_7 ua="na">fnc=sd+cp+blf;sub=2112@172.16.213.1;nme=2112</Unit_1_Key_7>
<Unit_1_Key_8 ua="na">fnc=sd+cp+blf;sub=2120@172.16.213.1;nme=2120</Unit_1_Key_8>
<Unit_1_Key_9 ua="na">fnc=sd+cp+blf;sub=2121@172.16.213.1;nme=2121</Unit_1_Key_9>

или чтобы поменять настройки порта для перевода его в другой VLAN

<Enable_VLAN ua="rw">Yes</Enable_VLAN>
<Enable_PC_Port_VLAN_Tagging ua="na">Yes</Enable_PC_Port_VLAN_Tagging>
<Enable_CDP ua="na">No</Enable_CDP>
<Enable_LLDP-MED ua="na">No</Enable_LLDP-MED>
<PC_Port_VLAN_ID ua="na">300</PC_Port_VLAN_ID>

Проверяем корректно ли грузятся телефоны и корректно ли им отдаётся конфиг.
Можно сделать это руками, заглянув в thttpd.log и скопировав запрос в браузер.

Если у нас всё хорошо, то приступаем к наиболее интересной части, а именно автоматике и настройкам астериска. Каждый пишет на том, на чём ему удобно, но уж коли у нас есть возможность порулить sqlite3 через консоль я решил написать железобетонные скрипты на shell. С первого взгляда страшно, но в итоге получилось очень компактно. Основную часть занимают обработки ошибок для интерактива с asterisk. Все действия производимые в режиме интерактивного управления из asterisk логируются в системный журнал. Почти во всех скриптах включен режим останова при возникновении внутренней ошибки. Если ниже по тексту в имени скрипта отсутствует путь, значит он лежит в /usr/local/etc/asterisk/scripts/.

В конфиг extensions.conf добавляем
#include "/var/db/asterisk/asterisk_ext_dialplan.conf"

В конфиг sip.conf добавляем
#include "/var/db/asterisk/asterisk_ext_accounts.conf"

Для того чтобы выполнять команды напрямую из asterisk создадим группу скриптов:
/usr/local/etc/astprov.subr — основной скрипт содержащий все процедуры работы с базой данных.

#!/bin/sh

. /usr/local/etc/astprov.conf

#set -x

getvalue()
{
        result=
        _request=$1
        _ext=$2
        selectcmd='select '${_request}' from provision where ext='${_ext}';'
        result=`$sqlitecmd "$selectcmd"`
        if [ "$result" ]; then
                echo $result
                return 0
        else
                syslog "Error: SQL request cannot be made (request ${_request},extention ${_ext})"
                return 1
        fi
}

checkvalue()
{
        result=
        _request=$1
        _ext=$2
        selectcmd='select '${_request}' from provision where ext='${_ext}';'
        result=`$sqlitecmd "$selectcmd"`
        echo $result
        return 0
}

setvalue()
{
        result=
        _what=$1
        _new=$2
        _where=$3
        _old=$4
        updatecmd="update provision set ${_what}='${_new}' where ${_where}='${_old}';"
        $sqlitecmd "$updatecmd"
        if [ $? -ne 0 ]; then
                syslog "Error: SQL update cannot be made (set ${_what}=${_new} where ${_where}=${_old})"
                return 1
        else
                return 0
        fi
}

get_script_name()
{
         result=
         result=`echo $0 | rev | cut -d/ -f1 | rev`
         echo $result
}

syslog()
{
        result=
        _message=$@
        logger -t $logger_tag `get_script_name`": ${_message}"
}

sqlitecmdcoln="$sqlite -column $ast_provisiondb"
sqlitecmdline="$sqlite -line $ast_provisiondb"
sqlitecmd="$sqlite $ast_provisiondb"

showall.sh дамп базы для диагностики

#!/bin/sh
set -e
. /usr/local/etc/astprov.subr
$sqlitecmd 'select * from provision'

rebuildlist.sh скрипт обновления динамической конфигурации для asterisk.

#!/bin/sh

set -e

. /usr/local/etc/astprov.subr

if [ -f $ast_ext_accounts -a ! -w $ast_ext_accounts ]; then
        syslog 'Cannot write to astprov extension database'
        exit 1
fi
if [ -f $ast_ext_accounts ]; then
        mv $ast_ext_accounts $ast_ext_accounts.backup
fi
if [ -f $ast_ext_dialplan -a ! -w $ast_ext_dialplan ]; then
        syslog 'Cannot write to astprov extension dialplan'
        exit 1
fi
if [ -f $ast_ext_dialplan ]; then
        mv $ast_ext_dialplan $ast_ext_dialplan.backup
fi
echo '[dynamic_internal_numbers]' >> $ast_ext_dialplan
selectcmd='select ext from provision order by ext;'
for ext in `$sqlitecmdcoln "$selectcmd"`; do
        echo 'exten => '$ext',1,dumpchan()' >> $ast_ext_dialplan
        echo 'same => n,Dial(SIP/'$ext',60,Tt)' >> $ast_ext_dialplan
        echo 'same => hint,SIP/'$ext >> $ast_ext_dialplan
        echo 'same => n,Hangup()' >> $ast_ext_dialplan
        echo ''>> $ast_ext_dialplan
        selectcmd='select callerid,fullname,macaddress,secret,context,callgroup,pickupgroup,subscribecontext,ip from provision where ext='$ext';'
        echo '['$ext'](all)' >> $ast_ext_accounts
$sqlitecmdline "$selectcmd" | sed -E 's/ = /=/g' |
                while IFS= read -r line; do
                echo $line >> $ast_ext_accounts
        done
        echo >> $ast_ext_accounts
done
syslog 'Asterisk dynamic list succesfully updated'

changenumber.sh скрипт замены номера телефона на другой.

#!/bin/sh

#set -x

. /usr/local/etc/astprov.subr

oldname=`getvalue 'ext' $1`
if [ "$?" -ne "0" ]; then
        echo -n GETERR
        exit
fi
numberexist=`checkvalue 'ext' $2`
if [ -z "$numberexist" ]; then
        setvalue 'ext' $2 'ext' $1
        if [ "$?" -ne "0" ]; then
                echo -n SETERR
                exit
        fi
        setvalue 'callerid' "$oldname <$2>" 'ext' $2
        if [ "$?" -ne "0" ]; then
                echo -n SETERR
                exit
        fi
        syslog "Number $1 renamed to $2"
        echo -n OK
else
        syslog "Number $1 cannot be renamed to $2. Number $2 already exist"
        echo -n NUMEXIST
        exit
#       return 1
fi

togglenumbers.sh скрипт для смены местами телефонов. скажем нужно ext 1023 поменять местами с 2035. Очень удобно когда пересаживаются сотрудники. Телефоны остаются на месте, а номера меняются местами.

#!/bin/sh

#set -x

. /usr/local/etc/astprov.subr

macaddress_from=`getvalue 'macaddress' $1`
if [ "$?" -ne "0" ]; then echo -n GETERR ; exit ; fi

macaddress_to=`getvalue 'macaddress' $2`
if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi

secret_from=`getvalue 'secret' $1`
if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi

secret_to=`getvalue 'secret' $2`
if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi

serial_from=`getvalue 'serial' $1`
if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi

serial_to=`getvalue 'serial' $2`
if [ "$?" -ne "0" ]; then echo -n GETERR; exit; fi

setvalue macaddress $macaddress_from'_'$macaddress_to macaddress $macaddress_from
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue macaddress $macaddress_to'_'$macaddress_from macaddress $macaddress_to
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue secret $secret_to macaddress $macaddress_from'_'$macaddress_to
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue secret $secret_from macaddress $macaddress_to'_'$macaddress_from
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue serial $serial_to macaddress $macaddress_from'_'$macaddress_to
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue serial $serial_from macaddress $macaddress_to'_'$macaddress_from
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue macaddress $macaddress_to macaddress $macaddress_from'_'$macaddress_to
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

setvalue macaddress $macaddress_from macaddress $macaddress_to'_'$macaddress_from
if [ "$?" -ne "0" ]; then echo -n SETERR; exit; fi

syslog "Phones $1($serial_from:$macaddress_from) and $2($serial_to:$macaddress_to) are reversed"
echo -n OK

Скрипты выложены. В принципе можно использовать скрипты и руками через
su -m asterisk, но лучший вариант интегрировать данную «фичу» в asterisk.
Т.к. все новые незарегистрированные телефоны попадают в гостевой контекст "[unreg]", то нужно создать в asterisk описание этого контекста.

;goto service menu
exten => 3999,1,Goto(unreg,_X.,1)

[unreg]
exten => _X.,1,Answer()
;same => n,Authenticate(040478)
same => n,NoOp(Entering Service Menu)
same => n,Playback(ivr/prov-welcome-to-service-menu)
same => n(menu),Background(ivr/change-number&ivr/swap-numbers&ivr/rebuild-list&ivr/reboot-phone&ivr/reload-server)
same => n,Read(CHOICE,,1,,1,10)
; 1 - Change Number
; 2 - Swap Numbers
; 3 - Rebuild extensions list
; 4 - Reload phone
; 5 - Reload server

; Rename Number
exten => 1,1,Read(RENAMETO,ivr/enter-dest-number,4,,1,5)
same => n,GotoIf($["${RENAMETO}" = ""]?menu)
same => n,Playback(ivr/entered-number)
same => n,SayDigits(${RENAMETO})
same => n,Read(APPROVE,ivr/press-1-for-accept,1,,1,5)
same => n,GotoIf($["${APPROVE}" != "1"]?menu)
same => n,Set(CHGNM=${SHELL(/usr/local/etc/asterisk/scripts/changenumber.sh ${CALLERID(num)} ${RENAMETO})})
same => n,NoOp(${CHGNM})
same => n,GotoIf($["${CHGNM}" = "GETERR"]?geterr)
same => n,GotoIf($["${CHGNM}" = "SETERR"]?seterr)
same => n,GotoIf($["${CHGNM}" = "NUMEXIST"]?numexist)
same => n,GotoIf($["${CHGNM}" = "OK"]?ok)
same => n(ok),Playback(ivr/prov-saved&privacy-thankyou)
same => n,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/rebuild-ok)
same => n,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)}")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/send-phone-reboot-ok)
same => n,System(/usr/local/sbin/asterisk -rx "reload")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/server-reload-ok)
same => n,Hangup()
same => n(geterr),Playback(ivr/script-get-error)
same => n,Hangup()
same => n(seterr),Playback(ivr/script-set-error)
same => n,Hangup()
same => n(numexist),Playback(ivr/prov-exist)
same => n,Hangup()
same => n(menu),Goto(_X.,menu)
same => n,Hangup()
same => n(error),Playback(ivr/script-error)
same => n,Hangup()

; Reverse numbers
exten => 2,1,Read(SWAPTO,ivr/enter-dest-number,4,,1,5)
same => n,GotoIf($["${SWAPTO}" = ""]?menu)
same => n,Playback(ivr/entered-number)
same => n,SayDigits(${SWAPTO})
same => n,Read(APPROVE,ivr/press-1-for-accept,1,,1,5)
same => n,GotoIf($["${APPROVE}" != "1"]?menu)
same => n,Set(CHGNM=${SHELL(/usr/local/etc/asterisk/scripts/togglenumbers.sh ${CALLERID(num)} ${SWAPTO})})
same => n,NoOp(${CHGNM})
same => n,GotoIf($["${CHGNM}" = "GETERR"]?geterr)
same => n,GotoIf($["${CHGNM}" = "SETERR"]?seterr)
same => n,GotoIf($["${CHGNM}" = "OK"]?ok)
same => n(ok),Playback(ivr/prov-saved&privacy-thankyou)
same => n,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/rebuild-ok)
same => n,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)} ${SWAPTO}")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/send-phone-reboot-ok)
same => n,System(/usr/local/sbin/asterisk -rx "reload")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/server-reload-ok)
same => n,Hangup()
same => n(geterr),Playback(ivr/script-get-error)
same => n,Hangup()
same => n(seterr),Playback(ivr/script-set-error)
same => n,Hangup()
same => n(error),Playback(ivr/script-error)
same => n,Hangup()
same => n(menu),Goto(_X.,menu)
same => n,Hangup()

exten => 3,1,System(/usr/local/etc/asterisk/scripts/rebuildlist.sh)
same => n,NoOp(${SYSTEMSTATUS})
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/rebuild-ok)
same => n,System(/usr/local/sbin/asterisk -rx "reload")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/server-reload-ok)
same => n,Goto(_X.,menu)
same => n(error),Playback(ivr/script-error)
same => n,Goto(_X.,menu)

exten => 4,1,System(/usr/local/sbin/asterisk -rx "sip notify cisco-check-cfg ${CALLERID(num)}")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/send-phone-reboot-ok)
same => n,System(/usr/local/sbin/asterisk -rx "reload")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/server-reload-ok)
same => n,Hangup()
same => n(error),Playback(ivr/script-error)
same => n,Goto(_X.,menu)
same => n,Hangup()

exten => 5,1,System(/usr/local/sbin/asterisk -rx "reload")
same => n,GotoIF($["${SYSTEMSTATUS}" != "SUCCESS"]?error)
same => n,Playback(ivr/server-reload-ok)
same => n,Goto(_X.,menu)
same => n(error),Playback(ivr/script-error)
same => n,Goto(_X.,menu)
same => n,Hangup()

Собственно что делает этот набор букв и цифр.
1. Смена номера.
Можно поменять существующий зарегистрированный рабочий номер на другой. Запрашивается номер телефона на который нужно поменять. После смены номера пересоздаётся список динамической конфигурации, делается reload на сервере и телефон автоматически перезагружается.
2. Поменять телефоны местами
Вызов осуществляется с телефона на который нужно поменять с другим телефоном. При замене телефонов внутри базы меняется информация отдаваемая конфигурационным скриптом. Логин пароль телефона не трогается, меняется только информация о callerid и ext.
После смены номеров пересоздаётся список динамической конфигурации, делается reload на сервере и телефоны автоматически перезагружаются.
3. Пересоздать список телефонов.
Пересоздаются файлы динамической конфигурации. Удобно использовать когда много незарегистрированных телефонов в автоконфигурации стоят и ждут своей очереди в обслуживании, но не могут зарегистрироваться на сервере. Выполняется с любого телефона.Во время выполнения пересоздаётся список динамической конфигурации, делается reload на сервере.
4. Перезагрузка аппарата
Перезагружает аппарат с которого осуществлялся вызов. Автоматически делает reload на сервере.
5. Обновление конфига на сервере.
Автоматически делает reload на сервере.

В общем-то всё. При корректном переносе скриптов на сервер, поднятие телефонии становится лёгким занятием. Изначально можно просто создать dialplan и влить его в базу. Дальнейшие действия вполне прозрачны и понятны любому человеку, даже не знакомому с администрированием asterisk. Будут вопросы — пишите. Схема вполне себе переносима на любую другую базу данных которую Вы больше любите.

© Aborche 2013
Быстрое развертывание телефонной сети на Asterisk+Cisco

Автор: aborche

Источник

Поделиться

* - обязательные к заполнению поля