Aŭtomata provizado Yealink T19 + dinamika adresaro

Kiam mi venis labori por ĉi tiu kompanio, mi jam havis iun datumbazon de IP-aparatoj, plurajn servilojn kun asterisko kaj flikaĵo en la formo de FreeBPX. Krome, analoga PBX Samsung IDCS500 funkciis paralele kaj, ĝenerale, estis la ĉefa komunika sistemo en la firmao; IP-telefonio funkciis nur por la venda fako. Kaj ĉio daŭrus kuiri tiel, sed unu belan tagon oni donis dekreton por translokigi ĉiujn al IP-telefonio, limdatoj estis interkonsentitaj, ekipaĵo estis aĉetita, kaj la plano translokigi la entreprenon en la 21-an jarcenton komencis esti efektivigita.
La unua afero, kiu komencas zorgi en tia situacio, estas la rapide kreskanta nombro da telefonaparatoj, kiujn oni devas iel administri, la dua afero, kiu estis tre maltrankvila, estis la telefonlibro. Se Endpoint Manager povus helpi nin kun la unua (kiu, cetere, estis eltranĉita el la plej novaj versioj de FreePBX), tiam kelkaj demandoj aperis kun la libro:

  • Unue, kiel certigi ĝian precizecon kiam la loko/flueco de uzantoj konstante ŝanĝiĝas?
  • Due, kiel tute malpersonigi telefonojn. Kaj ne ĉiufoje plenigu la kontaktan nomon?

La problemo estis interesa, la solvo ne daŭris longe por alveni. Nun mi donos la plenan liston, kaj tiam ni rigardos ĝin en ordo.

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

La programo funkcias per la komputilo de la uzanto kaj funkcias kondiĉe ke la komputilo estas konektita al la reto per telefono, ĉar Yealink T19 ne povas funkcii kiel enirejo.

Unue, ni devas kompreni ĉu ĝi estas konektita? kaj kion mac kaj ip havas nia telefono.

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

Ĉi tie ni uzas la sniff-funkcion de la scapy-kadro, kun ĝia helpo ni ricevas antaŭdeterminitan udp-pakon, atendas 70 sekundojn kaj se ni kaptas nenion, ni eliras.

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

Poste, ni certigas, ke la aparato ja estas Yealink kaj redonas la necesajn valorojn (ip kaj mac).

Uzante specialan peton, ni ekscias la nunan konton ĉe la telefono. Por fari tion, la nuna agordo estas elŝutita de la telefono kaj analizita.

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

Eltrovu la pasvorton por ĉi tiu konto. Por fari tion, ni turnas nin al la tabelo asterisk.sip kaj la datumkampo en ĝi.

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

Nu, por la fina etapo ni konektas al ldap AD kaj uzante sAMAccountName ricevita per la funkcio getpass.getuser() prenu la cn de la nuna uzanto (kiu kutime enhavas la plenan nomon de la uzanto).

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

Ni konektas al antaŭkreita tabelo en la datumbazo (mi kreis ĝin tie) kaj enmetas ĉion, kion ni lernis, nome: ip, mac, uzantnomo.

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

Ni povus halti ĉi tie, ĉar ni jam kreis dinamikan adreslibron, vi eble demandos, sed mi iris plu kaj aldonis ĉi tie aŭtomatan provizadon de aparatoj.

Por fari tion, ŝablona agordo estas elŝutita de antaŭ-agordita tftp-servilo, en kiu ni faras niajn ŝanĝojn kaj konservas ĝin kiel mac.cfg. Tio estas, por Yealink ekzistas du specoj de agordo, unu estas tutmonda, kaj la dua validas por specifa telefono kaj devus esti de la formo mac_phone.cfg.

Post ĉiuj ŝanĝoj en la dosiero kaj konservi ĝin reen al la tftp-servilo, ni donas la komandon al la telefono por provizi kaj rekomenci la aparaton.

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

Post rekomenco de la aparato, ni ricevas nian plenan nomon sur la telefona ekrano + ĉiam ĝuste plenigitan adreslibron en formo de datumbazo, tiam restas nur aldoni XML kaj iom PHP por dinamike montri la enhavon. Estas multaj tiaj ekzemploj, eĉ YEALINK mem havas ilin.

PS: Por pli granda skaleblo, vi povas movi la ĉefajn agordojn (variablojn) en apartan dosieron.

fonto: www.habr.com

Aldoni komenton