Provisionnement automatique Yealink T19 + Carnet d'adresses dynamique

Quand je suis venu travailler dans cette entreprise, j'avais déjà une base sur des appareils ip, plusieurs serveurs avec un astérisque et un splash sous la forme de FreeBPX. De plus, le central téléphonique analogique Samsung IDCS500 fonctionnait en parallèle et, en général, était le principal système de communication de l'entreprise, la téléphonie IP fonctionnait uniquement pour le service commercial. Et tout se serait passé comme ça, mais un beau jour un décret a été donné pour transférer tout le monde à la téléphonie IP, des dates ont été convenues, des équipements ont été achetés et un plan de transfert de l'entreprise au 21e siècle a commencé à être mis en œuvre.
La première chose qui commence à déranger dans cette situation est le nombre croissant de téléphones qui doivent être gérés d'une manière ou d'une autre, et la deuxième chose qui était très inquiétante était l'annuaire téléphonique. Si Endpoint Manager (qui, soit dit en passant, a été supprimé des dernières versions de FreePBX) pourrait nous aider avec la première, alors quelques questions se sont posées avec le livre:

  • Tout d'abord, comment assurer sa précision avec un changement constant de dislocation / rotation des utilisateurs?
  • Deuxièmement, comment dépersonnaliser complètement les téléphones. Et ne remplissez pas le nom du contact à chaque fois?

La tâche était intéressante, la solution ne tarda pas à venir. Je vais maintenant donner une liste complète, puis nous analyserons dans l'ordre.

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

Le programme s'exécute sur l'ordinateur de l'utilisateur et fonctionne à condition que l'ordinateur soit connecté au réseau via le téléphone, car le Yealink T19 ne peut pas fonctionner comme une passerelle.

Nous devons d'abord comprendre s'il est connecté? et quel mac et ip a notre télé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 

Ici, nous utilisons la fonction sniff du framework scapy, en l'utilisant nous obtenons un paquet udp prédéfini, attendez 70 secondes et si nous n'attrapons rien, quittez.

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

Ensuite, nous nous assurons que l'appareil est bien Yealink et retournons les valeurs nécessaires (ip et mac).

À l'aide d'une demande spéciale, nous découvrons le compte courant par téléphone. Pour ce faire, la configuration actuelle est téléchargée à partir du téléphone et analysée.

 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 

Nous découvrons le mot de passe de ce compte. Pour ce faire, nous nous tournons vers la table asterisk.sip et dans le champ de données.

 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 

Eh bien, pour l'étape finale, nous nous connectons à ldap AD et utilisons sAMAccountName obtenu via la fonction getpass.getuser () pour prendre le cn de l'utilisateur actuel (qui contient généralement le nom de l'utilisateur).

 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 

Nous nous connectons à la table précédemment créée dans la base de données (je l'ai créée au même endroit) et ajoutons tout ce que nous avons appris, à savoir: ip, mac, nom d'utilisateur.

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

Nous pourrions nous arrêter à cela, car nous avons déjà créé un carnet d'adresses dynamique, demandez-vous, mais je suis allé plus loin et j'ai vissé les dispositifs d'autoprovisioning ici.

Pour ce faire, la configuration du modèle est téléchargée à partir du serveur tftp préconfiguré, dans lequel nous apportons nos modifications et l'enregistrons sous mac.cfg. Autrement dit, pour Yealink, il existe deux types de configuration, un global et le second appliqué à un téléphone spécifique et doit être de la forme mac_phone.cfg

Après toutes les modifications dans le fichier et la sauvegarde sur le serveur tftp, nous donnons la commande au téléphone pour l'approvisionnement et le redémarrage de l'appareil.

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

Après le redémarrage de l'appareil, nous obtenons un nom complet sur l'écran du téléphone + toujours correctement rempli dans le carnet d'adresses face à la base de données, puis il ne reste plus qu'à fixer le XML et un peu de PHP pour l'affichage dynamique du contenu. Il existe de nombreux exemples, même YEALINK lui-même.

PS: pour une plus grande évolutivité, vous pouvez déplacer les paramètres principaux (variables) vers un fichier séparé.

Source: https://habr.com/ru/post/fr465779/


All Articles