Калі я прыйшоў працаваць у гэтую кампанію, у мяне ўжо мелася некаторая база па ip апаратах, некалькім серверам з asterisk і нашлепкай у выглядзе FreeBPX. Акрамя таго раўналежна працавала аналагавая АТС Samsung IDCS500 і ўвогуле-то была асноўнай сістэмай сувязі ў кампаніі, ip тэлефанія працавала толькі для аддзела продажаў. І ўсё б варылася так і далей, але ў адзін цудоўны дзень быў дадзены ўказ пераводзіць усіх на IP тэлефанію, былі абумоўлены тэрміны, закуплена абсталяванне і план па перакладзе прадпрыемства ў 21 стагоддзе стаў ператворацца ў жыццё.
Першае што пачынае турбаваць у такой сітуацыі, гэта хутка нарастальнае кол-ць тэлефонных апаратаў, якімі трэба неяк кіраваць, другое, што моцна трывожыла была тэлефонная кніга. Калі з першым нам мог дапамагчы Endpoint Manager (які дарэчы выпілавалі з апошніх версій FreePBX), то вось з кнігай узнікалі некаторыя пытанні:
- Па-першае, як забяспечыць яе дакладнасць пры пастаяннай змене дыслакацыі/цякучасці карыстальнікаў?
- Па-другое, як цалкам абязлічыць тэлефоны. І не запаўняць кожны раз імя кантакту?
Задачка была цікавая, рашэнне не прымусіла сябе доўга чакаць. Цяпер я прывяду поўны лістынг, а потым разбяром па парадку.
from scapy.all import sniff
from scapy.layers.inet import IP
import mysql.connector
import ldap
import getpass
import tftpy
import requests
import os
import time
from string import replace
def conn_ldap(login):
ad = ldap.initialize('ldap://***.local')
ad.simple_bind_s('voip@***.local', 'password')
basedn = 'OU=IT,DC=***,DC=LOCAL'
basedn_user = 'OU=***,OU=***,DC=***,DC=LOCAL'
scope = ldap.SCOPE_SUBTREE
filterexp = "(&(sAMAccountName=" + login + ")(ObjectClass=person))"
filterexp2 = "(&(ObjectClass=organizationUnit))"
attrlist = ['cn']
attrlist2 = ['OU']
search = ad.search_s(basedn, scope, filterexp, attrlist)
adname = search[0][1]['cn'][0].decode('utf-8')
if adname == ' ':
search = ad.search_s(basedn_user, scope, filterexp2, attrlist2)
for i in range(1, len(search)+1):
group = search[i][1]['ou'][0]
basedn_user2 = 'OU='+group+','+basedn_user
search = ad.search_s(basedn_user2, scope, filterexp, attrlist)
adname = search[0][1]['cn'][0].decode('utf-8')
if adname != ' ':
return adname
adname = search[0][1]['cn'][0].decode('utf-8')
ad.unbind_s()
return adname
def tftp_file_change(config,place,adname,current_account,current_account_password):
client = tftpy.TftpClient("192.168.0.3", 69)
client.download('template.cfg', place)
fileread = open(place, 'r')
line = fileread.readlines()
fileread.close()
line[5] = (('account.1.label = ').encode('utf-8') + adname.encode('utf-8') + 'n')
line[2] = (('account.1.auth_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
line[3] = (('account.1.display_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
line[6] = (('account.1.password = ').encode('utf-8') + current_account_password[0][0] + 'n')
filewrite = open(place, 'w')
for i in line:
filewrite.write(i)
filewrite.close()
print place
print config
client.upload(config,place)
def get_phone_inform(ipaddr):
fileconf = requests.get('http://admin:admin@'+ipaddr+'/servlet?phonecfg=get[&accounts=1]')
conf = fileconf.text.split('|')
current_account = conf[2]
return current_account
def sniff_frame():
pcapf = sniff(count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060")
if len(pcapf) == 0:
exit()
frame = pcapf[0]
macaddr = frame.src
print macaddr[:8]
if macaddr[:8] != '80:5e:c0':
exit()
ipaddr = frame[0][IP].src
return macaddr, ipaddr
def conn_mysql(query,fquery,macaddr,qwery2):
connect = mysql.connector.connect(host='192.168.0.3', database='voip', user='voip_wr', password='***')
cursor = connect.cursor()
cursor.execute(fquery)
state = cursor.fetchall()
state = bool(state[0][0])
if state == True:
cursor.execute(qwery2)
connect.commit()
connect.close()
else:
cursor.execute(query)
connect.commit()
connect.close()
def check_account(current_account):
connect = mysql.connector.connect(host='192.168.0.3', database='asterisk', user='voip_wr', password='***')
cursor = connect.cursor()
qwery = 'select data from sip where id=' + current_account + ' and keyword="secret";'
cursor.execute(qwery)
password = cursor.fetchall()
if password == ' ':
exit()
else:
return password
if __name__ == '__main__':
macaddr, ipaddr = sniff_frame()
current_account = get_phone_inform(ipaddr)
current_account_password = check_account(current_account)
macaddr = macaddr.replace(':', '')
ipaddr = ipaddr.decode('utf-8')
adname = conn_ldap(getpass.getuser())
query = 'INSERT INTO station (mac, ip, name, number) VALUES (' + '"' + macaddr + '",' + '"' + ipaddr + '",' + '"' + adname + '",' + '"' + get_phone_inform(ipaddr) + '"' + ')'
qwery2 = 'UPDATE station SET ip=' + '"' + ipaddr + '"' + ', name=' + '"' + adname + '"' + ', number=' + '"' + get_phone_inform(ipaddr) + '"' + ' WHERE mac=' + '"' + macaddr + '"'
fquery = 'SELECT EXISTS(SELECT mac FROM voip.station WHERE mac=' + '"' + macaddr + '")'
query = query.encode('utf-8')
fquery = fquery.encode('utf-8')
config = macaddr + '.cfg'
place = os.path.expanduser("~") + "" + "AppDataLocal" + config
conn_mysql(query,fquery,macaddr,qwery2)
tftp_file_change(config,place,adname,current_account,current_account_password)
requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=AutoP')
requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=Reboot')
Праграма запускаецца на кампутары карыстальніка і працуе пры ўмове, што кампутар падлучаны да сеткі праз тэлефон, бо Yealink T19 не ўмее працаваць у якасці шлюза.
Для пачатку нам неабходна зразумець ці падключаны? і які mac і ip мае наш тэлефон.
def sniff_frame():
pcapf = sniff(count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060")
if len(pcapf) == 0:
exit()
frame = pcapf[0]
macaddr = frame.src
print macaddr[:8]
if macaddr[:8] != '80:5e:c0':
exit()
ipaddr = frame[0][IP].src
return macaddr, ipaddr
Тут мы выкарыстоўваем функцыю sniff з фраемворка scapy, з дапамогай яе мы атрымліваем загадзя вызначаны udp пакет, чакаем 70 секунд і калі нічога не злавілі выходзім.
count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060"
Далей пераконваемся, што апарат сапраўды Yealink і вяртаем неабходныя значэнні (ip і mac).
З дапамогай спецыяльнага запыту высвятляем бягучы акаўнт на тэлефоне. Для гэтага спампоўваецца бягучая канфігурацыя з тэлефона і распарсваецца.
def get_phone_inform(ipaddr):
fileconf = requests.get('http://admin:admin@'+ipaddr+'/servlet?phonecfg=get[&accounts=1]')
conf = fileconf.text.split('|')
current_account = conf[2]
return current_account
Высвятляем пароль для дадзенага акаўнта. Для гэтага звяртаемся да табліцы asterisk.sip і ў ёй да поля data.
def check_account(current_account):
connect = mysql.connector.connect(host='192.168.0.3', database='asterisk', user='voip_wr', password='***')
cursor = connect.cursor()
qwery = 'select data from sip where id=' + current_account + ' and keyword="secret";'
cursor.execute(qwery)
password = cursor.fetchall()
if password == ' ':
exit()
else:
return password
Ну і для фінальнага этапу падлучаемся да ldap AD і з дапамогай sAMAccountName атрымоўванага праз функцыю getpass.getuser() забіраем cn бягучага карыстальніка (у якім звычайна змяшчаецца ПІБ карыстальніка).
def conn_ldap(login):
ad = ldap.initialize('ldap://***.local')
ad.simple_bind_s('voip@***.local', 'password')
basedn = 'OU=***,DC=***,DC=LOCAL'
basedn_user = 'OU=***,OU=***,DC=***,DC=LOCAL'
scope = ldap.SCOPE_SUBTREE
filterexp = "(&(sAMAccountName=" + login + ")(ObjectClass=person))"
filterexp2 = "(&(ObjectClass=organizationUnit))"
attrlist = ['cn']
attrlist2 = ['OU']
search = ad.search_s(basedn, scope, filterexp, attrlist)
adname = search[0][1]['cn'][0].decode('utf-8')
if adname == ' ':
search = ad.search_s(basedn_user, scope, filterexp2, attrlist2)
for i in range(1, len(search)+1):
group = search[i][1]['ou'][0]
basedn_user2 = 'OU='+group+','+basedn_user
search = ad.search_s(basedn_user2, scope, filterexp, attrlist)
adname = search[0][1]['cn'][0].decode('utf-8')
if adname != ' ':
return adname
adname = search[0][1]['cn'][0].decode('utf-8')
ad.unbind_s()
return adname
Падлучаемся да загадзя створанай табліцы ў бд (у мяне была створана тамака жа) і ўносім усё тое, што мы пазналі, а менавіта: ip, mac, імя карыстача.
def conn_mysql(query,fquery,macaddr,qwery2):
connect = mysql.connector.connect(host='192.168.0.3', database='voip', user='voip_wr', password='***')
cursor = connect.cursor()
cursor.execute(fquery)
state = cursor.fetchall()
state = bool(state[0][0])
if state == True:
cursor.execute(qwery2)
connect.commit()
connect.close()
else:
cursor.execute(query)
connect.commit()
connect.close()
На гэтым можна было б спыніцца, бо мы ўжо стварылі дынамічную адрасную кнігу і спытаеце вы, але я пайшоў далей і прыкруціў сюды ж аўтаправізійнінг апаратаў.
Для гэтага з загадзя наладжанага tftp сервера спампоўваецца template канфігурацыя, у якую мы ўносім свае змены і захоўваем з як mac.cfg. Гэта значыць для Yealink існуюць два выгляду канфігурацыі, адна глабальная, а другая ўжываецца да пэўнага тэлефона і павінна быць выгляду mac_тэлефона.cfg
Пасля ўсіх змен у файле і захаванню яго зваротна на tftp сервер мы аддаём каманду тэлефону на правізіёнінг і перазагрузку апарата.
def tftp_file_change(config,place,adname,current_account,current_account_password):
client = tftpy.TftpClient("192.168.0.3", 69)
client.download('template.cfg', place)
fileread = open(place, 'r')
line = fileread.readlines()
fileread.close()
line[5] = (('account.1.label = ').encode('utf-8') + adname.encode('utf-8') + 'n')
line[2] = (('account.1.auth_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
line[3] = (('account.1.display_name = ').encode('utf-8') + current_account.encode('utf-8') + 'n')
line[6] = (('account.1.password = ').encode('utf-8') + current_account_password[0][0] + 'n')
filewrite = open(place, 'w')
for i in line:
filewrite.write(i)
filewrite.close()
print place
print config
client.upload(config,place)
requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=AutoP')
requests.get('http://admin:admin@'+ipaddr+'/cgi-bin/ConfigManApp.com?key=Reboot')
Пасля перазагрузкі апарата мы атрымліваем поўнае фіа на экране тэлефона + заўсёды карэктна запоўненую адрасную кнігу ў асобе БД, далей застаецца толькі прыкруціць XML і крыху PHP для дынамічнага адлюстравання кантэнту. Такіх прыкладаў маса, ёсць нават у самога YEALINK.
PS: Для мацнейшай маштабаванасці можна вынесці асноўныя налады (зменныя) у асобны файлік.
Крыніца: habr.com