Automatikus kiépítés Yealink T19 + dinamikus címjegyzék

Amikor ehhez a céghez kerültem, már rendelkeztem néhány IP-eszköz adatbázissal, több csillaggal jelölt szerverrel és egy FreeBPX formájú javítással. Ezenkívül párhuzamosan működött egy analóg Samsung IDCS500 alközpont, és általában ez volt a vállalat fő kommunikációs rendszere, az IP-telefónia csak az értékesítési osztály számára működött. És minden tovább főzött volna így, de egy szép napon rendelet született arról, hogy mindenkit IP-telefóniára helyezzenek át, megállapodtak a határidőkben, beszerezték a berendezéseket, és elkezdték megvalósítani a vállalkozást a 21. századba hozó tervet.
Az első dolog, ami ilyen helyzetben aggodalomra ad okot, az a gyorsan növekvő telefonkészülékek száma, amelyeket valahogy kezelni kell, a második, ami nagyon aggasztó volt, az a telefonkönyv. Ha az Endpoint Manager tudott segíteni az elsőben (amit egyébként a FreePBX legújabb verzióiból vágtak ki), akkor a könyv kapcsán felmerült néhány kérdés:

  • Először is, hogyan lehet biztosítani a pontosságát, amikor a felhasználók elhelyezkedése/fluiditása folyamatosan változik?
  • Másodszor, hogyan lehet teljesen személyteleníteni a telefonokat. És nem írja be minden alkalommal a kapcsolattartó nevét?

A probléma érdekes volt, a megoldás nem sokáig váratott magára. Most adom a teljes listát, aztán megnézzük sorban.

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

A program a felhasználó számítógépén fut, és akkor működik, ha a számítógép telefonon keresztül csatlakozik a hálózathoz, mivel a Yealink T19 nem működik átjáróként.

Először is meg kell értenünk, hogy kapcsolódik-e? és milyen mac és ip van a telefonunkon.

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

Itt a scapy frameworkből a sniff függvényt használjuk, melynek segítségével kapunk egy előre meghatározott udp csomagot, várunk 70 másodpercet és ha nem fogunk semmit, kilépünk.

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

Ezután megbizonyosodunk arról, hogy az eszköz valóban Yealink, és visszaadjuk a szükséges értékeket (ip és mac).

Külön kérésre telefonon megtudjuk a folyószámlát. Ehhez az aktuális konfigurációt letölti a telefonról, és elemzi.

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

Keresse meg a fiók jelszavát. Ehhez fordulunk az asterisk.sip táblához és a benne lévő adatmezőhöz.

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

Nos, az utolsó szakaszban csatlakozunk az ldap AD-hez, és a függvényen keresztül kapott sAMAccountName-et használjuk getpass.getuser() vegyük az aktuális felhasználó cn-jét (amely általában a felhasználó teljes nevét tartalmazza).

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

Csatlakozunk az adatbázisban egy előre elkészített táblához (ott hoztam létre), és beírunk mindent, amit tanultunk, nevezetesen: ip, mac, felhasználónév.

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

Itt meg is állhatnánk, mert már létrehoztunk egy dinamikus címjegyzéket, kérdezhetik, de tovább mentem, és hozzáadtam az eszközök automatikus kiépítését.

Ehhez egy előre konfigurált tftp szerverről letöltünk egy sablon konfigurációt, amibe elvégezzük a változtatásainkat és elmentjük mac.cfg néven. Vagyis a Yealink esetében kétféle konfiguráció létezik, az egyik globális, a második pedig egy adott telefonra vonatkozik, és a mac_phone.cfg formátumú.

A fájlban végrehajtott összes módosítás és a tftp szerverre való visszamentés után parancsot adunk a telefonnak az eszköz üzembe helyezésére és újraindítására.

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

A készülék újraindítása után a telefon képernyőjén megkapjuk a teljes nevünket + egy mindig helyesen kitöltött címjegyzéket adatbázis formájában, majd csak XML-t és egy kis PHP-t kell hozzáadni a tartalom dinamikus megjelenítéséhez. Rengeteg ilyen példa van, még magában a YEALINK-ben is vannak ilyenek.

PS: A nagyobb méretezhetőség érdekében a fő beállításokat (változókat) áthelyezheti egy külön fájlba.

Forrás: will.com

Hozzászólás