Yealink T19 Auto-provisioning + Dynamic Address Book

When I came to work in this company, I already had some base on ip devices, several servers with asterisk and a splash in the form of FreeBPX. In addition, the Samsung IDCS500 analog telephone exchange worked in parallel and, in general, was the main communication system in the company, ip telephony worked only for the sales department. And everything would have boiled on and on, but one fine day a decree was given to transfer everyone to IP telephony, dates were agreed, equipment was purchased and a plan to transfer the enterprise to the 21st century began to be implemented.

The first thing that starts to bother in this situation is the rapidly growing number of telephones that need to be managed somehow, the second, which was very worrying about the phone book. If Endpoint Manager (which, by the way, was cut out from the latest versions of FreePBX) could help us with the first one, then some questions arose with the book:





The task was interesting, the solution was not long in coming. Now I will give a complete listing, and then we will analyze in order.



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')
      
      





The program runs on the user's computer and works under the condition that the computer is connected to the network through the phone, since Yealink T19 does not know how to work as a gateway.



First we need to understand whether it is connected? and which mac and ip has our phone.



 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
      
      





Here we use the sniff function from scapy framers, using it we get a predefined udp package, wait 70 seconds and if we do not catch anything, exit.



 count=1, timeout=70, filter="dst host 192.168.0.3 and port 5060"
      
      





Next, we make sure that the device is indeed Yealink and return the necessary values โ€‹โ€‹(ip and mac).



Using a special request, we find out the current account on the phone. To do this, the current configuration is downloaded from the phone and parsed.



 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
      
      





We find out the password for this account. To do this, we turn to the table asterisk.sip and in it to the data field.



 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
      
      





Well, for the final stage, we connect to ldap AD and use sAMAccountName obtained through the getpass.getuser () function to take the cn of the current user (which usually contains the user's name).



 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
      
      





We connect to the previously created table in the database (I created it in the same place) and add everything that we learned, namely: ip, mac, username.



 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()
      
      





We could stop at this, because we have already created a dynamic address book, you ask, but I went ahead and screwed the autoprovisioning devices here.



To do this, the template configuration is downloaded from the tftp server pre-configured, into which we make our changes and save with as mac.cfg. That is, for Yealink there are two types of configuration, one global, and the second applied to a specific phone and should be of the form mac_phone.cfg



After all the changes in the file and saving it back to the tftp server, we issue a command to the phone for provisioning and rebooting the device.



 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')
      
      





After rebooting the device, we get a full name on the phone screen + always correctly filled in address book in the face of the database, then it remains only to fasten XML and a little PHP for dynamic display of content. There are many such examples, even YEALINK itself has.



PS: For greater scalability, you can move the main settings (variables) to a separate file.



All Articles