Automatische Bereitstellung von Yealink T19 + dynamischem Adressbuch

Als ich für dieses Unternehmen anfing, hatte ich bereits eine Datenbank mit IP-Geräten, mehrere Server mit Sternchen und einen Patch in Form von FreeBPX. Darüber hinaus arbeitete parallel eine analoge Telefonanlage Samsung IDCS500, die im Allgemeinen das Hauptkommunikationssystem im Unternehmen war; IP-Telefonie funktionierte nur für die Vertriebsabteilung. Und alles hätte so weitergekocht, aber eines schönen Tages wurde ein Dekret erlassen, alle auf IP-Telefonie umzustellen, Fristen wurden vereinbart, Ausrüstung wurde gekauft und der Plan, das Unternehmen ins 21. Jahrhundert zu überführen, begann mit der Umsetzung.
Das erste, was in einer solchen Situation besorgniserregend ist, ist die schnell wachsende Zahl von Telefonapparaten, die irgendwie verwaltet werden müssen, das zweite, was sehr besorgniserregend ist, ist das Telefonbuch. Wenn Endpoint Manager uns beim ersten helfen konnte (der übrigens aus den neuesten Versionen von FreePBX herausgeschnitten wurde), dann stellten sich bei dem Buch einige Fragen:

  • Erstens: Wie kann die Genauigkeit sichergestellt werden, wenn sich der Standort/die Fluidität der Benutzer ständig ändert?
  • Zweitens, wie man Telefone vollständig entpersonalisiert. Und nicht jedes Mal den Kontaktnamen eingeben?

Das Problem war interessant, die Lösung ließ nicht lange auf sich warten. Jetzt werde ich die vollständige Auflistung geben, und dann werden wir sie der Reihe nach betrachten.

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

Das Programm läuft auf dem Computer des Benutzers und funktioniert, sofern der Computer über ein Telefon mit dem Netzwerk verbunden ist, da Yealink T19 nicht als Gateway fungieren kann.

Zuerst müssen wir verstehen, ob es einen Zusammenhang gibt? und welchen Mac und welche IP unser Telefon hat.

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

Hier verwenden wir die Sniff-Funktion des Scapy-Frameworks, mit deren Hilfe wir ein vorgegebenes UDP-Paket erhalten, 70 Sekunden warten und wenn wir nichts fangen, beenden wir den Vorgang.

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

Als nächstes stellen wir sicher, dass es sich bei dem Gerät tatsächlich um Yealink handelt und geben die notwendigen Werte (IP und Mac) zurück.

Auf besonderen Wunsch ermitteln wir telefonisch den aktuellen Kontostand. Dazu wird die aktuelle Konfiguration vom Telefon heruntergeladen und analysiert.

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

Finden Sie das Passwort für dieses Konto heraus. Dazu greifen wir auf die Tabelle asterisk.sip und das darin enthaltene Datenfeld zurück.

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

Nun, für die letzte Phase stellen wir eine Verbindung zu LDAP AD her und verwenden den über die Funktion erhaltenen sAMAccountName getpass.getuser() Nehmen Sie den CN des aktuellen Benutzers (der normalerweise den vollständigen Namen des Benutzers enthält).

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

Wir verbinden uns mit einer vorab erstellten Tabelle in der Datenbank (ich habe sie dort erstellt) und geben alles ein, was wir gelernt haben, nämlich: IP, Mac, Benutzername.

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

Wir könnten hier aufhören, weil wir bereits ein dynamisches Adressbuch erstellt haben, fragen Sie sich vielleicht, aber ich bin noch einen Schritt weiter gegangen und habe hier die automatische Bereitstellung von Geräten hinzugefügt.

Dazu wird von einem vorkonfigurierten TFTP-Server eine Template-Konfiguration heruntergeladen, in die wir unsere Änderungen vornehmen und als mac.cfg speichern. Das heißt, für Yealink gibt es zwei Arten der Konfiguration: eine ist global und die zweite gilt für ein bestimmtes Telefon und sollte die Form mac_phone.cfg haben

Nachdem wir alle Änderungen in der Datei vorgenommen und sie wieder auf dem TFTP-Server gespeichert haben, geben wir dem Telefon den Befehl, das Gerät bereitzustellen und neu zu starten.

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

Nach dem Neustart des Geräts sehen wir unseren vollständigen Namen auf dem Telefonbildschirm + ein immer korrekt ausgefülltes Adressbuch in Form einer Datenbank. Anschließend müssen wir nur noch XML und ein wenig PHP hinzufügen, um den Inhalt dynamisch anzuzeigen. Es gibt viele solcher Beispiele, sogar YEALINK selbst hat sie.

PS: Für eine bessere Skalierbarkeit können Sie die Haupteinstellungen (Variablen) in eine separate Datei verschieben.

Source: habr.com

Kommentar hinzufügen