Так случилось, что в короткие сроки появилась необходимость перевезти 70 человек с аналоговыми телефонами из одного бизнес центра в другой. Ситуация усугублялась тем, что в новом офисе у владельца не было аналоговых портов в АТС, а АТС в старом принадлежала телефонной компании. Пришлось в короткие сроки внедрять IP телефонию с переносом всех аналоговых городских линий на Asterisk. Поставка оборудования была назначена на день предшествующий дню переезда, что означало — времени на развертывание телефонии будет очень мало.
Что из этого вышло под катом.
Материала много, так что не пугайтесь.
Итак имеющаяся конфигурация:
Поискав и почитав в сети множество документов связанных с autoprovision мы решили воспользоваться возможностью asterisk выполнять автонастройку устройств самостоятельно. Всё бы ничего, но как оказалось автонастройка через sql базу в asterisk не позволяет мониторить состояние экстеншена на доппанелях. Т.е. нельзя настроить hint(по крайней мере в августе 2012 оно не поддерживалось). Имея в наличии пару телефонов мы решились на написание своего autoprovision.
Начали с настройки сервера и свичей. Создали 2 VLAN с номерами 204 и 214. Первый для локальной сети, второй для IP телефонии. Т.к. телефоны имеют встроенный конфигурируемый коммутатор, это было лучшим решением с нашей точки зрения.
Выставили порты на коммутаторах к которым подключаются пользователи в транк и изменили native vlan.
Отлично. Адресочки раздаются, телефоны бутятся, пришло время запилить базовый конфиг.
Поднимаем thttpd и кладём ему в корень XMLDefault.cnf.xml
Таким образом, после того как телефон возьмёт из dhcp параметры автоконфигурации и заберет с сервера базовый конфиг, он сделает вторую итерацию за конфигурационным файлом привязанному к серийному номеру устройства и его мак адресу. После полной инициализации конфига, телефон переведёт порт компьютера в 204 VLAN, обеспечив ему работу с локальной сетью.
Перейдём к тому как телефоны получают свой конфигурационный файл.
Создадим вспомогательный файл с настройками /usr/local/etc/astprov.conf
Создадим по указанному в конфигурационном файле базу sqlite3 следующей структуры:
Для последующей быстрой настройки рабочих мест создаём файл с телефонами
и скармливаем его скрипту в качестве параметра
скрипт автоматически заполнит базу и даст возможность быстрого подключения новых телефонов.
Теперь немного колдовства.
Asterisk работает под учёткой asterisk:asterisk, thttpd работает под www:www. Поэтому создаём группу astprov куда добавляем asterisk и www.
Теперь нужно персонализировать настройки телефонов.
Создаём скрипт /usr/local/www/data/cfg/cfg.cgi, я люблю perl посему конфигуратор на перле.
Скрипт получает из запроса MAC устройства и его серийный номер, если связка есть в базе, то телефону отдаётся конфиг с логином/паролем и параметрами подключения к серверу. Если связки нет, то телефон добавляется в базу, ему присваивается «сервисный номер», в данном случае от 1000 до 2000 и телефону отдаётся конфиг. Если в папку "cfg" положить файл с именем additional<MAC>.xml, то телефону в конфиг добавится данный файл. Мы используем данную возможность для загрузки в телефон настроек панелей. Например:
или чтобы поменять настройки порта для перевода его в другой VLAN
Проверяем корректно ли грузятся телефоны и корректно ли им отдаётся конфиг.
Можно сделать это руками, заглянув в 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 — основной скрипт содержащий все процедуры работы с базой данных.
showall.sh дамп базы для диагностики
rebuildlist.sh скрипт обновления динамической конфигурации для asterisk.
changenumber.sh скрипт замены номера телефона на другой.
togglenumbers.sh скрипт для смены местами телефонов. скажем нужно ext 1023 поменять местами с 2035. Очень удобно когда пересаживаются сотрудники. Телефоны остаются на месте, а номера меняются местами.
Скрипты выложены. В принципе можно использовать скрипты и руками через
su -m asterisk, но лучший вариант интегрировать данную «фичу» в asterisk.
Т.к. все новые незарегистрированные телефоны попадают в гостевой контекст "[unreg]", то нужно создать в asterisk описание этого контекста.
Собственно что делает этот набор букв и цифр.
1. Смена номера.
Можно поменять существующий зарегистрированный рабочий номер на другой. Запрашивается номер телефона на который нужно поменять. После смены номера пересоздаётся список динамической конфигурации, делается reload на сервере и телефон автоматически перезагружается.
2. Поменять телефоны местами
Вызов осуществляется с телефона на который нужно поменять с другим телефоном. При замене телефонов внутри базы меняется информация отдаваемая конфигурационным скриптом. Логин пароль телефона не трогается, меняется только информация о callerid и ext.
После смены номеров пересоздаётся список динамической конфигурации, делается reload на сервере и телефоны автоматически перезагружаются.
3. Пересоздать список телефонов.
Пересоздаются файлы динамической конфигурации. Удобно использовать когда много незарегистрированных телефонов в автоконфигурации стоят и ждут своей очереди в обслуживании, но не могут зарегистрироваться на сервере. Выполняется с любого телефона.Во время выполнения пересоздаётся список динамической конфигурации, делается reload на сервере.
4. Перезагрузка аппарата
Перезагружает аппарат с которого осуществлялся вызов. Автоматически делает reload на сервере.
5. Обновление конфига на сервере.
Автоматически делает reload на сервере.
В общем-то всё. При корректном переносе скриптов на сервер, поднятие телефонии становится лёгким занятием. Изначально можно просто создать dialplan и влить его в базу. Дальнейшие действия вполне прозрачны и понятны любому человеку, даже не знакомому с администрированием asterisk. Будут вопросы — пишите. Схема вполне себе переносима на любую другую базу данных которую Вы больше любите.
Aborche 2013

Что из этого вышло под катом.
Материала много, так что не пугайтесь.
Итак имеющаяся конфигурация:
- 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} .= "\0" 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;\r\n\r\n";
}
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
