Approvisionnement automatique Yealink T19 + carnet d'adresses dynamique

Lorsque je suis arrivé à travailler pour cette entreprise, j'avais déjà une base de données d'appareils IP, plusieurs serveurs avec un astérisque et un patch sous la forme de FreeBPX. De plus, un PBX analogique Samsung IDCS500 fonctionnait en parallèle et, en général, constituait le principal système de communication de l'entreprise ; la téléphonie IP ne fonctionnait que pour le service commercial. Et tout aurait continué à cuire ainsi, mais un beau jour, un décret a été pris pour transférer tout le monde à la téléphonie IP, des délais ont été convenus, du matériel a été acheté et le plan de transfert de l'entreprise dans le 21e siècle a commencé à être mis en œuvre.
La première chose qui commence à s'inquiéter dans une telle situation est le nombre rapidement croissant de postes téléphoniques qui doivent être gérés d'une manière ou d'une autre, la deuxième chose qui était très inquiétante était l'annuaire téléphonique. Si Endpoint Manager pouvait nous aider avec le premier (qui, soit dit en passant, a été supprimé des dernières versions de FreePBX), alors certaines questions se sont posées avec le livre :

  • Premièrement, comment garantir son exactitude lorsque la localisation/fluidité des utilisateurs est en constante évolution ?
  • Deuxièmement, comment dépersonnaliser complètement les téléphones. Et ne pas remplir le nom du contact à chaque fois ?

Le problème était intéressant, la solution ne tarda pas à arriver. Je vais maintenant donner la liste complète, puis nous l'examinerons 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("~") + "" + "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')

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

Tout d’abord, nous devons comprendre si c’est connecté ? et quels sont le Mac et l'IP de 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, avec son aide nous recevons un paquet udp prédéterminé, attendons 70 secondes et si nous n'attrapons rien, nous quittons.

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 renvoyons les valeurs nécessaires (ip et mac).

A l'aide d'une demande spéciale, nous connaissons le compte courant par téléphone. Pour ce faire, la configuration actuelle est téléchargée depuis le 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

Découvrez le mot de passe de ce compte. Pour ce faire, nous nous tournons vers la table asterisk.sip et le champ de données qu'elle contient.

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 la dernière étape, nous nous connectons à ldap AD et utilisons sAMAccountName obtenu via la fonction getpass.getuser() prenez le cn de l’utilisateur actuel (qui contient généralement le nom complet 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

On se connecte à une table pré-créée dans la base de données (je l'ai créée là-bas) et entrons tout ce que l'on a 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 ici, car nous avons déjà créé un carnet d'adresses dynamique, demanderez-vous peut-être, mais je suis allé plus loin et j'ai ajouté ici le provisionnement automatique des appareils.

Pour ce faire, un modèle de configuration est téléchargé à partir d'un 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, l'une est globale et la seconde s'applique à un téléphone spécifique et doit être de la forme mac_phone.cfg

Après toutes les modifications apportées au fichier et sa sauvegarde sur le serveur TFTP, nous donnons l'ordre au téléphone de provisionner et de redémarrer 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 redémarrage de l'appareil, on obtient notre nom complet sur l'écran du téléphone + un carnet d'adresses toujours correctement rempli sous forme de base de données, il ne reste plus qu'à ajouter du XML et un peu de PHP pour afficher dynamiquement le contenu. Il existe de nombreux exemples de ce type, même YEALINK lui-même en possède.

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

Source: habr.com

Ajouter un commentaire