Первое что начинает беспокоить в такой ситуации, это быстро нарастающее кол-во телефонных аппаратов, которыми надо как-то управлять, второе, что сильно тревожило была телефонная книга. Если с первым нам мог помочь 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("~") + "\\" + "AppData\\Local\\" + 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.
P.S.: Для пущей масштабируемости можно вынести основные настройки (переменные) в отдельный файлик.
ссылка на оригинал статьи https://habr.com/ru/post/465779/
Добавить комментарий