Quando cheguei ao trabalho nesta empresa, eu já tinha base em dispositivos IP, vários servidores com asterisco e um splash na forma de FreeBPX. Além disso, a central telefônica analógica Samsung IDCS500 funcionava em paralelo e, em geral, era o principal sistema de comunicação da empresa; a telefonia IP funcionava apenas para o departamento de vendas. E tudo teria continuado assim, mas um belo dia foi dado um decreto para transferir todos para telefonia IP, datas foram acordadas, equipamentos foram adquiridos e um plano para transferir a empresa para o século XXI começou a ser implementado.
A primeira coisa que começa a incomodar nessa situação é o número crescente de telefones que precisam ser gerenciados de alguma forma, e a segunda coisa que foi muito preocupante foi a lista telefônica. Se o Endpoint Manager (que, a propósito, foi retirado das versões mais recentes do FreePBX) poderia nos ajudar com a primeira, então surgiram algumas perguntas com o livro:
- Em primeiro lugar, como garantir sua precisão com uma constante mudança de deslocamento / rotatividade de usuários?
- Em segundo lugar, como despersonalizar completamente os telefones. E não preencha o nome do contato todas as vezes?
A tarefa foi interessante, a solução não demorou a chegar. Agora vou dar uma lista completa, e depois analisaremos em ordem.
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')
O programa é executado no computador do usuário e funciona sob a condição de que o computador esteja conectado à rede pelo telefone, pois o Yealink T19 não pode funcionar como gateway.
Primeiro precisamos entender se está conectado? e qual mac e ip tem nosso telefone.
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
Aqui usamos a função sniff da estrutura scapy, e obtemos um pacote udp predefinido, esperamos 70 segundos e, se não capturarmos nada, saia.
count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060"
Em seguida, garantimos que o dispositivo seja realmente o Yealink e retornemos os valores necessários (ip e mac).
Usando uma solicitação especial, descobrimos a conta atual no telefone. Para fazer isso, a configuração atual é baixada do telefone e analisada.
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
Descobrimos a senha para esta conta. Para fazer isso, passamos para a tabela asterisk.sip e nela para o campo de dados.
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
Bem, no estágio final, nos conectamos ao ldap AD e usamos sAMAccountName obtido através da função
getpass.getuser () para obter o cn do usuário atual (que geralmente contém o nome do usuário).
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
Nós nos conectamos à tabela criada anteriormente no banco de dados (eu criei no mesmo local) e adicionamos tudo o que aprendemos, a saber: ip, mac, nome de usuário.
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()
Poderíamos parar com isso, porque já criamos um catálogo de endereços dinâmico, você pergunta, mas fui além e estraguei os dispositivos de autoprovisionamento aqui.
Para fazer isso, a configuração do modelo é baixada do servidor tftp pré-configurado, no qual fazemos nossas alterações e salvamos como mac.cfg. Ou seja, para o Yealink, existem dois tipos de configuração, um global e o segundo aplicado a um telefone específico, e deve ter o formato mac_phone.cfg
Depois de todas as alterações no arquivo e salvá-lo no servidor tftp, damos o comando ao telefone para provisionar e reiniciar o dispositivo.
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')
Depois de reiniciar o dispositivo, obtemos um nome completo na tela do telefone + sempre preenchemos corretamente o catálogo de endereços na frente do banco de dados; então, precisamos apenas fixar XML e um pouco de PHP para exibição dinâmica do conteúdo. Existem muitos exemplos, até o próprio YEALINK.
PS: Para maior escalabilidade, você pode mover as principais configurações (variáveis) para um arquivo separado.